#  File tests/testthat/test-term-mm.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
################################################################################

# a bipartite nw
set.seed(143)
b1 <- floor(runif(60, 1,100))
b2 <- floor(runif(60, 101, 130))
exbip.el <- cbind(b1,b2)
bipnw <- as.network(exbip.el, matrix.type="edgelist", bipartite=100, directed=FALSE)
bipnw %v% "Letter" <- letters[1:3]
bipnw %v% "Cost" <- c(3,2,1)
                          

# another bipartite nw with more ties and 2 attributes
set.seed(258)
b1 <- floor(runif(150, 1,200))
b2 <- floor(runif(150, 201, 400))
exbip.el <- cbind(b1,b2)
bipnw2 <- as.network(exbip.el, matrix.type="edgelist", bipartite=100, directed=FALSE)
bipnw2 %v% "Letter" <- letters[1:2]
color <- rbinom(400, 1, .4)
color[color ==1] <- "Purple"
color[color ==0] <- "Gold"
bipnw2 %v% "Color" <- color


# a directed nw
data(sampson)
set.seed(42)
set.edge.attribute(samplike, "YearsTrusted", rbinom(88, 4, .5))
set.seed(296)
set.vertex.attribute(samplike, "YearsServed", rbinom(18, 10, .5))
samplike %v% "Trinity" <- c("F", "S", "H")


# an undirected nw
data(faux.mesa.high)
fmh <- faux.mesa.high
set.seed(7)
set.edge.attribute(fmh, "GradeMet", rbinom(203, 6, .5))


# a small undirected nw w/ lots o' triangles
set.seed(20)
t<-trunc(runif(160, 1, 20))
set.seed(21)
h<-trunc(runif(160, 1, 20))
el <- cbind(t,h)
bad <- which(el[,2]==el[,1])
el[bad,2] = el[bad,2]+1
unnw <- network(el, directed=FALSE)
unnw %v% "Pet" <- c("dog", "cat")

test_that("Undirected mm() summary", {
  s.a <- summary(fmh ~ mm("Grade"))
  expect_equal(s.a, c(`mm[Grade=7,Grade=8]` = 0, `mm[Grade=8,Grade=8]` = 33,
                      `mm[Grade=7,Grade=9]` = 0, `mm[Grade=8,Grade=9]` = 2,
                      `mm[Grade=9,Grade=9]` = 23, `mm[Grade=7,Grade=10]` = 1,
                      `mm[Grade=8,Grade=10]` = 4, `mm[Grade=9,Grade=10]` = 7,
                      `mm[Grade=10,Grade=10]` = 9, `mm[Grade=7,Grade=11]` = 1,
                      `mm[Grade=8,Grade=11]` = 2, `mm[Grade=9,Grade=11]` = 6,
                      `mm[Grade=10,Grade=11]` = 1, `mm[Grade=11,Grade=11]` = 17,
                      `mm[Grade=7,Grade=12]` = 1, `mm[Grade=8,Grade=12]` = 1,
                      `mm[Grade=9,Grade=12]` = 4, `mm[Grade=10,Grade=12]` = 5,
                      `mm[Grade=11,Grade=12]` = 5, `mm[Grade=12,Grade=12]` = 6))
})


test_that("Undirected mm() summary with expression, levels, and levels2 selectors", {
  s.a <- summary(fmh ~ mm(~Grade >= 10, levels=-1, levels2=NULL))
  expect_equal(s.a, c(`mm[Grade>=10=TRUE,Grade>=10=TRUE]` = 43))
})

test_that("Undirected mm() summary with expression, levels selector, no terms", {
  s.a <- summary(fmh ~ mm(~Grade >= 10, levels=-1))
  expect_equal(s.a, numeric(0))
})


test_that("Directed mm() ERGM", {
  e.a <- ergm(samplike ~ mm("group", levels2=TRUE))
  expect_equal(coef(e.a),
               c(0.191055236762708, -3.29583686600359,
                 -2.17475172140943, -2.5649493574587,
                 1.6094379124341, -3.29583686600359,
                 -1.49165487677766, -1.09861228866811,
                 0.916290731874155), ignore_attr=TRUE)
})

test_that("Bipartite mm() summary", {
  s.ab <- summary(bipnw ~ mm("Letter"))
  expect_equal(s.ab, c(`mm[Letter=b,Letter=a]` = 8, `mm[Letter=c,Letter=a]` = 8,
                       `mm[Letter=a,Letter=b]` = 7, `mm[Letter=b,Letter=b]` = 7,
                       `mm[Letter=c,Letter=b]` = 5, `mm[Letter=a,Letter=c]` = 4,
                       `mm[Letter=b,Letter=c]` = 6, `mm[Letter=c,Letter=c]` = 6))
})


test_that("Bipartite mm() ERGM with level2 filter", {
  e.ab <- ergm(bipnw ~ mm("Letter", levels2=-(2:6)))
  expect_equal(coef(e.ab),
               c(-3.49650756126405, -4.43081679479004,
                 -3.98898404450688, -3.98898404450688), ignore_attr=TRUE)
})

fmh.mm.Race <-
  c(`mm[Race=Black,Race=Black]` = 0, `mm[Race=Black,Race=Hisp]` = 8,
    `mm[Race=Hisp,Race=Hisp]` = 53, `mm[Race=Black,Race=NatAm]` = 13,
    `mm[Race=Hisp,Race=NatAm]` = 41, `mm[Race=NatAm,Race=NatAm]` = 46,
    `mm[Race=Black,Race=Other]` = 0, `mm[Race=Hisp,Race=Other]` = 1,
    `mm[Race=NatAm,Race=Other]` = 0, `mm[Race=Other,Race=Other]` = 0,
    `mm[Race=Black,Race=White]` = 5, `mm[Race=Hisp,Race=White]` = 22,
    `mm[Race=NatAm,Race=White]` = 10, `mm[Race=Other,Race=White]` = 0,
    `mm[Race=White,Race=White]` = 4)


test_that("Undirected mm() summary with level2 filter", {
  s.ab2 <- summary(fmh ~ mm("Race", levels2=-1))
  expect_equal(s.ab2, fmh.mm.Race[-1], ignore_attr=TRUE)
})

test_that("Undirected mm() summary with level2 filter by logical matrix", {
  M <- matrix(TRUE, 5, 5)
  M[1,1] <- M[3,2] <- M[2,3] <- FALSE
  s.ab2 <- summary(fmh ~ mm("Race", levels2=M))
  expect_equal(s.ab2, fmh.mm.Race[M[upper.tri(M, TRUE)]], ignore_attr=TRUE)
})

test_that("Undirected mm() summary with level2 filter by numeric matrix", {
  M <- cbind(2,3)
  s.ab2 <- summary(fmh ~ mm("Race", levels2=M))
  expect_equal(s.ab2, fmh.mm.Race[5], ignore_attr=TRUE)
})

test_that("Undirected mm() summary with level2 AsIs", {
  s <- summary(fmh~mm("Sex",
                      levels2 = I(list(list(row="M",col="M"),
                                       list(row="F",col="M")))))
  expect_equal(s, c(`mm[Sex=M,Sex=M]` = 50, `mm[Sex=F,Sex=M]` = 71))

  expect_warning(
    s <- summary(fmh~mm("Sex",
                        levels2 = I(list(list(row="M",col="M"),
                                         list(row="M",col="F"), # Invalid level
                                         list(row="F",col="M"))))),
    "In term 'mm' in package 'ergm': Selected cells '\\[M,F\\]' are redundant \\(below the diagonal\\) in the mixing matrix and will have count 0\\.")
  expect_equal(s, c(`mm[Sex=M,Sex=M]` = 50, `mm[Sex=M,Sex=F]` = 0, `mm[Sex=F,Sex=M]` = 71))
})

test_that("Directed mm() ERGM with level2 filter", {
  e.ab2 <- ergm(samplike ~ mm("Trinity", levels2=-(3:9)))
  expect_equal(coef(e.ab2),
               c(-1.01160091056776, -0.693147180549145), ignore_attr=TRUE)
})

test_that("Directed mm() ERGM with level2 filter", {
  e.ab2 <- ergm(samplike ~ mm("Trinity", levels2=-(3:9)))
  expect_equal(coef(e.ab2),
               c(-1.01160091056776, -0.693147180549145), ignore_attr=TRUE)
})

samp.mm.Trinity <-
  c(`mm[Trinity=F,Trinity=F]` = 8, `mm[Trinity=H,Trinity=F]` = 12,
    `mm[Trinity=S,Trinity=F]` = 12, `mm[Trinity=F,Trinity=H]` = 8,
    `mm[Trinity=H,Trinity=H]` = 5, `mm[Trinity=S,Trinity=H]` = 9,
    `mm[Trinity=F,Trinity=S]` = 13, `mm[Trinity=H,Trinity=S]` = 12,
    `mm[Trinity=S,Trinity=S]` = 9)


test_that("Directed mm() summary with level2 matrix filter", {
  M <- matrix(FALSE, 3, 3)
  M[1,2] <- M[1,3] <- TRUE
  s.ab2 <- summary(samplike ~ mm("Trinity", levels2=M))
  expect_equal(s.ab2, samp.mm.Trinity[c(M)], ignore_attr=TRUE)
})

test_that("Undirected mm() marginal summary", {
  s.a <- summary(fmh ~ mm(.~Grade))
  expect_equal(s.a, c(`mm[.,Grade=8]` = 75, `mm[.,Grade=9]` = 65,
                      `mm[.,Grade=10]` = 36, `mm[.,Grade=11]` = 49,
                      `mm[.,Grade=12]` = 28))
})

test_that("Undirected mm() asymmetric two-sided summary", {
  s.a.tab <- as.matrix(fmh, matrix.type="edgelist") %>%
    rbind(.,.[,2:1,drop=FALSE]) %>%
    (function(x) data.frame(Race=factor(fmh%v%"Race")[x[,1]], Grade=factor(fmh%v%"Grade")[x[,2]])) %>%
    table()
  s.a <- summary(fmh ~ mm(Race~Grade, levels2=TRUE))
  expect_equal(s.a, c(s.a.tab), ignore_attr=TRUE)
})

test_that("Undirected mm() asymmetric two-sided summary and pipe operator on one side", {
  Grade <- fmh%v%"Grade" %>% factor()
  Race <- fmh%v%"Race" %>% replace(., . %in% c("Black","White","Other"), "BWO") %>% factor()

  s.a.tab <- as.matrix(fmh, matrix.type="edgelist") %>%
    rbind(.,.[,2:1,drop=FALSE]) %>%
    (function(x) data.frame(Grade=Grade[x[,2]], Race=Race[x[,1]])) %>%
    table()
  s.a <- summary(fmh ~ mm(Grade~(~Race) %>% COLLAPSE_SMALLEST(3,"BWO"), levels2=TRUE))
  expect_equal(s.a, c(s.a.tab), ignore_attr=TRUE)
})

test_that("Undirected mm() marginal summary with fixed levels set", {
  s.a <- summary(fmh ~ mm(.~Grade, levels=I(c(7,6,9,8)), levels2=TRUE))
  expect_equal(s.a, c(153, 0, 65, 75), ignore_attr=TRUE)
})

test_that("Undirected mm() summary with fixed levels set", {
  s.a <- summary(fmh ~ mm("Grade", levels=I(c(7,6,9,8)), levels2=TRUE))
  expect_equal(s.a, c(`mm[Grade=7,Grade=7]` = 75, `mm[Grade=7,Grade=6]` = 0,
                      `mm[Grade=6,Grade=6]` = 0, `mm[Grade=7,Grade=9]` = 0,
                      `mm[Grade=6,Grade=9]` = 0, `mm[Grade=9,Grade=9]` = 23,
                      `mm[Grade=7,Grade=8]` = 0, `mm[Grade=6,Grade=8]` = 0,
                      `mm[Grade=9,Grade=8]` = 2, `mm[Grade=8,Grade=8]` = 33))
})


test_that("Undirected valued mm() sum summary", {
  s.a <- summary(fmh ~ mm("Grade"), response="GradeMet")
  expect_equal(s.a,
               c(`mm.sum[Grade=7,Grade=8]` = 0, `mm.sum[Grade=8,Grade=8]` = 96,
                 `mm.sum[Grade=7,Grade=9]` = 0, `mm.sum[Grade=8,Grade=9]` = 6, 
                 `mm.sum[Grade=9,Grade=9]` = 75, `mm.sum[Grade=7,Grade=10]` = 3, 
                 `mm.sum[Grade=8,Grade=10]` = 15, `mm.sum[Grade=9,Grade=10]` = 21, 
                 `mm.sum[Grade=10,Grade=10]` = 24, `mm.sum[Grade=7,Grade=11]` = 3, 
                 `mm.sum[Grade=8,Grade=11]` = 6, `mm.sum[Grade=9,Grade=11]` = 19, 
                 `mm.sum[Grade=10,Grade=11]` = 4, `mm.sum[Grade=11,Grade=11]` = 54, 
                 `mm.sum[Grade=7,Grade=12]` = 2, `mm.sum[Grade=8,Grade=12]` = 5, 
                 `mm.sum[Grade=9,Grade=12]` = 11, `mm.sum[Grade=10,Grade=12]` = 17, 
                 `mm.sum[Grade=11,Grade=12]` = 18, `mm.sum[Grade=12,Grade=12]` = 16
                 ))
})

test_that("Undirected valued mm() nonzero summary", {
  fmh %ergmlhs% "response" <- "GradeMet"
  s.a <- summary(fmh ~ mm("Grade", form="nonzero"))

  expect_equal(s.a, c(`mm.nonzero[Grade=7,Grade=8]` = 0,
                      `mm.nonzero[Grade=8,Grade=8]` = 33,
                      `mm.nonzero[Grade=7,Grade=9]` = 0,
                      `mm.nonzero[Grade=8,Grade=9]` = 2,
                      `mm.nonzero[Grade=9,Grade=9]` = 22,
                      `mm.nonzero[Grade=7,Grade=10]` = 1,
                      `mm.nonzero[Grade=8,Grade=10]` = 4,
                      `mm.nonzero[Grade=9,Grade=10]` = 7,
                      `mm.nonzero[Grade=10,Grade=10]` = 8,
                      `mm.nonzero[Grade=7,Grade=11]` = 1,
                      `mm.nonzero[Grade=8,Grade=11]` = 2,
                      `mm.nonzero[Grade=9,Grade=11]` = 6,
                      `mm.nonzero[Grade=10,Grade=11]` = 1,
                      `mm.nonzero[Grade=11,Grade=11]` = 17,
                      `mm.nonzero[Grade=7,Grade=12]` = 1,
                      `mm.nonzero[Grade=8,Grade=12]` = 1,
                      `mm.nonzero[Grade=9,Grade=12]` = 4,
                      `mm.nonzero[Grade=10,Grade=12]` = 5,
                      `mm.nonzero[Grade=11,Grade=12]` = 5,
                      `mm.nonzero[Grade=12,Grade=12]` = 6))
})


test_that("Undirected mm() summary with just one unique attribute level and levels imposed exogenously", {
  nw2 <- network.initialize(2, directed = FALSE)
  nw2[1, 2] <- TRUE
  nw2 %v% "x" <- c("b", "b")

  s.a <- summary(nw2 ~ mm("x", levels = letters[1:3], levels2 = TRUE))
  expect_equal(s.a, c(`mm[x=a,x=a]` = 0, `mm[x=a,x=b]` = 0, `mm[x=b,x=b]` = 1,
                      `mm[x=a,x=c]` = 0, `mm[x=b,x=c]` = 0, `mm[x=c,x=c]` = 0))
})
