#  File tests/testthat/test-constrain-degrees-edges.R in package ergm, part of the
#  Statnet suite of packages for network analysis, https://statnet.org .
#
#  This software is distributed under the GPL-3 license.  It is free,
#  open source, and has the attribution requirements (GPL Section 7) at
#  https://statnet.org/attribution .
#
#  Copyright 2003-2025 Statnet Commons
################################################################################


###### Directed
nsim <- 100
n <- 50
m <- 10
d <- 0.1

od <- function(nw) apply(as.matrix(nw, matrix.type="adjacency"), 1, sum)
id <- function(nw) apply(as.matrix(nw, matrix.type="adjacency"), 2, sum)
e <- function(nw) network.edgecount(nw)

y0 <- as.network(n, density=d, directed=TRUE)

test_that("is.dyad.independent(ergm) accessor granularity", {
  fit <- suppressWarnings(ergm(y0~sender(nodes=TRUE)+receiver(nodes=TRUE), constraints=~odegrees, estimate="CD",
                               control=control.ergm(CD.maxit=2, CD.nsteps=1, CD.samplesize.per_theta = 10)))
  expect_true(is.dyad.independent(fit, "terms"))
  expect_false(is.dyad.independent(fit, "space"))
  expect_false(is.dyad.independent(fit))
  expect_false(is.na(fit))
  expect_false(anyNA(fit))
})

test_that("constraint = outdegree on a directed network", {
  ys <- simulate(y0~sender(nodes=TRUE)+receiver(nodes=TRUE), constraints=~odegrees, coef=rep(0,n*2), nsim=nsim, output="stats")
  expect_true(all(sweep(ys[,1:n], 2, od(y0))==0))
  expect_true(any(sweep(ys[,-(1:n)], 2, id(y0))!=0))
})

test_that("constraint = indegree on a directed network", {
  ys <- simulate(y0~receiver(nodes=TRUE)+sender(nodes=TRUE), constraints=~idegrees, coef=rep(0,n*2), nsim=nsim, output="stats")
  expect_true(all(sweep(ys[,1:n], 2, id(y0))==0))
  expect_true(any(sweep(ys[,-(1:n)], 2, od(y0))!=0))
})

test_that("both in- and outdegree constraint on a directed network", {
  ys <- simulate(y0~sender(nodes=TRUE)+receiver(nodes=TRUE), constraints=~degrees, coef=rep(0,n*2), nsim=nsim, output="stats")
  expect_true(all(sweep(ys, 2, c(od(y0),id(y0)))==0))

  ys <- simulate(y0~sender(nodes=TRUE)+receiver(nodes=TRUE), constraints=~odegrees+idegrees, coef=rep(0,n*2), nsim=nsim, output="stats")
  expect_true(all(sweep(ys, 2, c(od(y0),id(y0)))==0))
})

test_that("constraint = edges on a directed network", {
  ys <- simulate(y0~sender(nodes=TRUE)+receiver(nodes=TRUE), constraints=~edges, coef=rep(0,n*2), nsim=nsim, output="stats")
  # Edges shouldn't vary, but in- and out-degrees should.
  expect_true(all(e(y0)==rowSums(ys[,1:n])))
  expect_true(all(e(y0)==rowSums(ys[,-(1:n)])))
  expect_true(any(sweep(ys[,1:n], 2, od(y0))!=0))
  expect_true(any(sweep(ys[,-(1:n)], 2, id(y0))!=0))
})


test_that("constraint = edges + dyadic constraint on a directed network", {
  y1 <- simulate(y0~edges, coef=0)
  rmat <- matrix(runif(n*n),n,n)
  ys <- simulate(y0~edgecov(!y1) + edgecov(rmat) + sender(nodes=TRUE) + receiver(nodes=TRUE), constraints=~edges + fixallbut(y1), coef=rep(0,n*2+2), nsim=nsim, output="stats")
  # Edges shouldn't vary, but in- and out-degrees should.
  expect_true(all(e(y0)==rowSums(ys[,1:n+2])))
  expect_true(all(e(y0)==rowSums(ys[,1:n+2+n])))
  expect_true(any(sweep(ys[,1:n+2], 2, od(y0))!=0))
  expect_true(any(sweep(ys[,1:n+2+n], 2, id(y0))!=0))
  expect_true(all(ys[,1]==e(y0 & !y1))) # Constrained edges shouldn't vary.
  expect_gt(var(ys[,2]),0) # Unconstrained edges should.
})

###### Undirected
y0 <- as.network(n, density=d, directed=FALSE)

test_that("constraint = degree on an undirected network", {
  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~degrees, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(sweep(ys, 2, od(y0))==0))
})

test_that("constraint = edges on an undirected network", {
  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~edges, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(e(y0)==rowSums(ys)/2)) # Edges shouldn't vary, but degrees should.
  expect_true(any(sweep(ys, 2, od(y0))!=0))
})

test_that("constraint = edges + dyadic constraint on an undirected network", {
  y1 <- simulate(y0~edges, coef=0)
  rmat <- matrix(runif(n*n),n,n)
  ys <- simulate(y0~edgecov(!y1) + edgecov(rmat) + sociality(nodes=TRUE), constraints=~edges + fixallbut(y1), coef=rep(0,n+2), nsim=nsim, output="stats")
  expect_true(all(e(y0)==rowSums(ys[,-(1:2)])/2)) # Edges shouldn't vary.
  expect_true(any(sweep(ys[,-(1:2)], 2, od(y0))!=0)) # Degrees should vary.
  expect_true(all(ys[,1]==e(y0 & !y1))) # Constrained edges shouldn't vary.
  expect_gt(var(ys[,2]),0) # Unconstrained edges should.
})

###### Bipartite undirected
y0 <- as.network(n, density=d, directed=FALSE, bipartite=m)

test_that("constraint = b1degrees on a bipartite undirected network", {
  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~b1degrees, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(sweep(ys[,1:m], 2, od(y0))==0))
  expect_true(any(sweep(ys[,-(1:m)], 2, id(y0))!=0))
})

test_that("constraint = b2degrees on a bipartite undirected network", {
  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~b2degrees, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(sweep(ys[,-(1:m)], 2, id(y0))==0))
  expect_true(any(sweep(ys[,1:m], 2, od(y0))!=0))
})

test_that("both b1 and b2 degrees constraint on a bipartite undirected network", {
  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~degrees, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(sweep(ys, 2, c(od(y0),id(y0)))==0))

  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~b1degrees+b2degrees, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(sweep(ys, 2, c(od(y0),id(y0)))==0))
})

test_that("constraint = edges on a bipartite undirected network", {
  ys <- simulate(y0~sociality(nodes=TRUE), constraints=~edges, coef=rep(0,n), nsim=nsim, output="stats")
  expect_true(all(e(y0)==rowSums(ys)/2))
  expect_true(any(sweep(ys, 2, c(od(y0),id(y0)))!=0)) # Edges shouldn't vary, but degrees should.
})
