# Test file for comprehensive coverage of cluster-metrics.R # Targets uncovered code paths including edge cases and error handling skip_on_cran() # ============================================================================== # Setup Test Data # ============================================================================== # Create test matrices with node names set.seed(123) n <- 8 mat <- matrix(runif(n * n), n, n) diag(mat) <- 0 rownames(mat) <- colnames(mat) <- paste0("N", 1:n) # Clusters as list clusters_list <- list( "Group1" = c("N1", "N2"), "Group2" = c("N3", "N4", "N5"), "Group3" = c("N6", "N7", "N8") ) # Clusters as numeric vector clusters_vec <- c(1, 1, 2, 2, 2, 3, 3, 3) # Clusters as factor clusters_factor <- factor(c("A", "A", "B", "B", "B", "C", "C", "C")) # Clusters as character vector clusters_char <- c("A", "A", "B", "B", "B", "C", "C", "C") # Sparse matrix for edge cases sparse_mat <- matrix(0, 5, 5) sparse_mat[1, 2] <- 1 sparse_mat[2, 3] <- 0.5 rownames(sparse_mat) <- colnames(sparse_mat) <- paste0("S", 1:5) # ============================================================================== # Tests for aggregate_weights # ============================================================================== test_that("aggregate_weights handles density method with n_possible = 0", { w <- c(1, 2, 3) # When n_possible is 0 or NULL, falls back to length(w) result <- aggregate_weights(w, "density", n_possible = 0) expect_equal(result, sum(w) / length(w)) }) test_that("aggregate_weights density with n_possible specified", { w <- c(1, 2, 3) result <- aggregate_weights(w, "density", n_possible = 6) expect_equal(result, sum(w) / 6) }) test_that("aggregate_weights density with NULL n_possible", { w <- c(1, 2, 3) result <- aggregate_weights(w, "density", n_possible = NULL) expect_equal(result, sum(w) / length(w)) }) test_that("aggregate_weights geomean with zeros returns 0", { w <- c(0, 0, 0) result <- aggregate_weights(w, "geomean") expect_equal(result, 0) }) test_that("aggregate_weights geomean with negative values ignores them", { w <- c(-1, -2, 3, 4) result <- aggregate_weights(w, "geomean") # Only positive values used: exp(mean(log(c(3, 4)))) expected <- exp(mean(log(c(3, 4)))) expect_equal(result, expected, tolerance = 1e-10) }) test_that("aggregate_weights geomean with all negative returns 0", { w <- c(-1, -2, -3) result <- aggregate_weights(w, "geomean") expect_equal(result, 0) }) test_that("aggregate_weights throws error for unknown method", { expect_error( aggregate_weights(c(1, 2, 3), "unknown_method"), "Unknown method" ) }) test_that("wagg is an alias for aggregate_weights", { w <- c(1, 2, 3, 4, 5) expect_identical(wagg(w, "sum"), aggregate_weights(w, "sum")) expect_identical(wagg(w, "mean"), aggregate_weights(w, "mean")) }) test_that("aggregate_weights handles single value", { expect_equal(aggregate_weights(5, "sum"), 5) expect_equal(aggregate_weights(5, "mean"), 5) expect_equal(aggregate_weights(5, "median"), 5) expect_equal(aggregate_weights(5, "max"), 5) expect_equal(aggregate_weights(5, "min"), 5) expect_equal(aggregate_weights(5, "prod"), 5) expect_equal(aggregate_weights(5, "geomean"), 5) }) # ============================================================================== # Tests for .normalize_clusters # ============================================================================== test_that(".normalize_clusters handles factor input", { node_names <- paste0("N", 1:8) result <- cograph:::.normalize_clusters(clusters_factor, node_names) expect_type(result, "list") expect_equal(length(result), 3) expect_true(all(unlist(result) %in% node_names)) }) test_that(".normalize_clusters handles character input", { node_names <- paste0("N", 1:8) result <- cograph:::.normalize_clusters(clusters_char, node_names) expect_type(result, "list") expect_equal(length(result), 3) }) test_that(".normalize_clusters errors on invalid list nodes", { node_names <- paste0("N", 1:5) bad_clusters <- list(A = c("N1", "N2", "INVALID")) expect_error( cograph:::.normalize_clusters(bad_clusters, node_names), "Unknown nodes" ) }) test_that(".normalize_clusters errors on wrong length membership", { node_names <- paste0("N", 1:5) wrong_length <- c(1, 2, 3) expect_error( cograph:::.normalize_clusters(wrong_length, node_names), "must equal number of nodes" ) }) test_that(".normalize_clusters errors on wrong length factor", { node_names <- paste0("N", 1:5) wrong_factor <- factor(c("A", "B", "C")) expect_error( cograph:::.normalize_clusters(wrong_factor, node_names), "must equal number of nodes" ) }) test_that(".normalize_clusters errors on invalid type", { node_names <- paste0("N", 1:5) # Use a matrix which is neither a list, nor a vector, nor factor invalid <- matrix(1:5, nrow = 1) expect_error( cograph:::.normalize_clusters(invalid, node_names), "must be a list" ) }) # ============================================================================== # Tests for cluster_summary - NEW STRUCTURE # ============================================================================== test_that("cluster_summary validates input is numeric matrix", { expect_error( cluster_summary("not a matrix", clusters_list), "must be a cograph_network, tna object, or numeric matrix" ) expect_error( cluster_summary(data.frame(a = 1:5), clusters_list), "must be a cograph_network, tna object, or numeric matrix" ) }) test_that("cluster_summary validates square matrix", { rect_mat <- matrix(1:12, 3, 4) expect_error( cluster_summary(rect_mat, list(A = 1:2, B = 3)), "must be a square matrix" ) }) test_that("cluster_summary works with unnamed matrix", { unnamed_mat <- matrix(runif(25), 5, 5) diag(unnamed_mat) <- 0 clusters <- c(1, 1, 2, 2, 3) result <- cluster_summary(unnamed_mat, clusters) expect_s3_class(result, "cluster_summary") }) test_that("cluster_summary works with factor clusters", { result <- cluster_summary(mat, clusters_factor) expect_s3_class(result, "cluster_summary") expect_equal(dim(result$macro$weights), c(3, 3)) }) test_that("cluster_summary directed = FALSE", { result <- cluster_summary(mat, clusters_list, directed = FALSE) expect_s3_class(result, "cluster_summary") expect_false(result$meta$directed) }) test_that("cluster_summary method median", { result <- cluster_summary(mat, clusters_list, method = "median") expect_equal(result$meta$method, "median") }) test_that("cluster_summary method min", { result <- cluster_summary(mat, clusters_list, method = "min") expect_equal(result$meta$method, "min") }) test_that("cluster_summary method density", { result <- cluster_summary(mat, clusters_list, method = "density") expect_equal(result$meta$method, "density") }) test_that("cluster_summary method geomean", { result <- cluster_summary(mat, clusters_list, method = "geomean") expect_equal(result$meta$method, "geomean") }) test_that("cluster_summary with unnamed clusters list", { unnamed_clusters <- list(c("N1", "N2"), c("N3", "N4"), c("N5", "N6", "N7", "N8")) result <- cluster_summary(mat, unnamed_clusters) expect_s3_class(result, "cluster_summary") }) test_that("csum is an alias for cluster_summary", { result1 <- cluster_summary(mat, clusters_list, method = "sum") result2 <- csum(mat, clusters_list, method = "sum") expect_equal(result1$macro$weights, result2$macro$weights) }) test_that("cluster_summary works with cograph_network input", { # Create cograph_network from matrix net <- as_cograph(mat) # Should produce same results as with matrix directly result_mat <- cluster_summary(mat, clusters_list, method = "sum") result_net <- cluster_summary(net, clusters_list, method = "sum") expect_s3_class(result_net, "cluster_summary") expect_equal(result_mat$macro$weights, result_net$macro$weights) }) # ============================================================================== # Tests for cluster_summary NEW STRUCTURE fields # ============================================================================== test_that("cluster_summary returns new structure with macro/clusters/meta", { result <- cluster_summary(mat, clusters_list) # Check top-level structure expect_true("macro" %in% names(result)) expect_true("clusters" %in% names(result)) expect_true("meta" %in% names(result)) # Check macro structure expect_true("weights" %in% names(result$macro)) expect_true("inits" %in% names(result$macro)) expect_equal(dim(result$macro$weights), c(3, 3)) expect_equal(length(result$macro$inits), 3) # Check clusters structure (list of per-cluster data) expect_type(result$clusters, "list") expect_equal(length(result$clusters), 3) expect_equal(names(result$clusters), c("Group1", "Group2", "Group3")) # Each cluster element has weights and inits for (cl_name in names(result$clusters)) { expect_true("weights" %in% names(result$clusters[[cl_name]])) expect_true("inits" %in% names(result$clusters[[cl_name]])) } # Check meta structure expect_equal(result$meta$type, "tna") expect_equal(result$meta$method, "sum") expect_true(result$meta$directed) expect_equal(result$meta$n_nodes, 8) expect_equal(result$meta$n_clusters, 3) }) test_that("cluster_summary macro$weights rows sum to 1 (type = 'tna')", { result <- cluster_summary(mat, clusters_list, type = "tna") # Each row should sum to 1 (row-normalized) row_sums <- rowSums(result$macro$weights) expect_true(all(abs(row_sums - 1) < 1e-10)) }) test_that("cluster_summary macro$inits sums to 1", { result <- cluster_summary(mat, clusters_list) expect_equal(sum(result$macro$inits), 1, tolerance = 1e-10) }) test_that("cluster_summary clusters contain correct cluster subsets", { result <- cluster_summary(mat, clusters_list) # Group1 = N1, N2; Group2 = N3, N4, N5; Group3 = N6, N7, N8 expect_equal(rownames(result$clusters$Group1$weights), c("N1", "N2")) expect_equal(rownames(result$clusters$Group2$weights), c("N3", "N4", "N5")) expect_equal(rownames(result$clusters$Group3$weights), c("N6", "N7", "N8")) expect_equal(dim(result$clusters$Group1$weights), c(2, 2)) expect_equal(dim(result$clusters$Group2$weights), c(3, 3)) expect_equal(dim(result$clusters$Group3$weights), c(3, 3)) }) test_that("cluster_summary clusters weights rows sum to 1 or 0 (type = 'tna')", { result <- cluster_summary(mat, clusters_list, type = "tna") # Each row should sum to 1 (for nodes with outgoing edges) or 0 (isolated) for (cl_name in names(result$clusters)) { cl_mat <- result$clusters[[cl_name]]$weights row_sums <- rowSums(cl_mat) expect_true(all(row_sums == 0 | abs(row_sums - 1) < 1e-10), info = paste("Cluster", cl_name)) } }) test_that("cluster_summary clusters inits sums to 1 per cluster", { result <- cluster_summary(mat, clusters_list) # Each cluster's inits should sum to 1 for (cl_name in names(result$clusters)) { expect_equal(sum(result$clusters[[cl_name]]$inits), 1, tolerance = 1e-10, info = paste("Cluster", cl_name)) } }) test_that("cluster_summary type = 'raw' does not row-normalize", { result <- cluster_summary(mat, clusters_list, type = "raw") # Rows should NOT necessarily sum to 1 row_sums <- rowSums(result$macro$weights) # With random data, unlikely all rows sum to exactly 1 expect_equal(result$meta$type, "raw") }) test_that("cluster_summary type = 'cooccurrence' symmetrizes", { result <- cluster_summary(mat, clusters_list, type = "cooccurrence") # Matrix should be symmetric expect_equal(result$macro$weights, t(result$macro$weights)) }) test_that("cluster_summary compute_within = FALSE skips clusters computation", { result <- cluster_summary(mat, clusters_list, compute_within = FALSE) expect_null(result$clusters) # macro should still exist expect_true(!is.null(result$macro)) expect_equal(dim(result$macro$weights), c(3, 3)) }) # ============================================================================== # Tests for as_tna # ============================================================================== test_that("as_tna.cluster_summary works", { skip_if_not_installed("tna") cs <- cluster_summary(mat, clusters_list, type = "tna") tna_obj <- as_tna(cs) # Returns group_tna with $macro and cluster elements expect_s3_class(tna_obj, "group_tna") expect_s3_class(tna_obj$macro, "tna") # Macro tna has correct structure expect_equal(nrow(tna_obj$macro$weights), length(cs$clusters)) expect_equal(length(tna_obj$macro$inits), length(cs$clusters)) }) test_that("as_tna.default returns tna as-is", { # Create a mock tna object mock_tna <- structure( list(weights = matrix(1, 3, 3), inits = c(0.33, 0.33, 0.34), labels = c("A", "B", "C")), class = "tna" ) result <- as_tna(mock_tna) expect_identical(result, mock_tna) }) test_that("as_tna.default errors on non-tna", { expect_error( as_tna(list(a = 1)), "Cannot convert" ) }) # ============================================================================== # Tests for cluster_quality # ============================================================================== test_that("cluster_quality validates numeric matrix", { expect_error( cluster_quality("not a matrix", clusters_list), "must be a numeric matrix" ) }) test_that("cluster_quality weighted = FALSE", { result <- cluster_quality(mat, clusters_list, weighted = FALSE) expect_s3_class(result, "cluster_quality") }) test_that("cluster_quality directed = FALSE", { result <- cluster_quality(mat, clusters_list, directed = FALSE) expect_s3_class(result, "cluster_quality") }) test_that("cluster_quality handles unnamed matrix", { unnamed_mat <- matrix(runif(25), 5, 5) diag(unnamed_mat) <- 0 clusters <- c(1, 1, 2, 2, 3) result <- cluster_quality(unnamed_mat, clusters) expect_s3_class(result, "cluster_quality") }) test_that("cluster_quality returns valid per_cluster metrics", { result <- cluster_quality(mat, clusters_list) expect_true("cluster_name" %in% names(result$per_cluster)) expect_true("internal_edges" %in% names(result$per_cluster)) expect_true("cut_edges" %in% names(result$per_cluster)) expect_true("expansion" %in% names(result$per_cluster)) expect_true("cut_ratio" %in% names(result$per_cluster)) }) test_that("cluster_quality handles empty cluster gracefully", { # Create a matrix where one cluster has no internal or external edges zero_mat <- matrix(0, 5, 5) zero_mat[1, 2] <- 1 zero_mat[2, 1] <- 1 rownames(zero_mat) <- colnames(zero_mat) <- paste0("N", 1:5) clusters <- list(A = c("N1", "N2"), B = c("N3", "N4", "N5")) result <- cluster_quality(zero_mat, clusters) expect_s3_class(result, "cluster_quality") }) test_that("cqual is an alias for cluster_quality", { result1 <- cluster_quality(mat, clusters_list) result2 <- cqual(mat, clusters_list) expect_equal(result1$global$modularity, result2$global$modularity) }) # ============================================================================== # Tests for .compute_modularity # ============================================================================== test_that(".compute_modularity handles zero edge sum", { zero_mat <- matrix(0, 5, 5) membership <- c(1, 1, 2, 2, 2) result <- cograph:::.compute_modularity(zero_mat, membership, directed = TRUE) expect_true(is.na(result)) }) test_that(".compute_modularity undirected", { membership <- c(1, 1, 2, 2, 2, 3, 3, 3) result <- cograph:::.compute_modularity(mat, membership, directed = FALSE) expect_true(is.numeric(result)) }) # ============================================================================== # Tests for cluster_significance # ============================================================================== test_that("cluster_significance works with matrix input", { skip_if_not_installed("igraph") # Create a simple matrix test_mat <- matrix(runif(64), 8, 8) diag(test_mat) <- 0 # Simple membership mem <- c(1, 1, 1, 1, 2, 2, 2, 2) result <- cluster_significance(test_mat, mem, n_random = 10, seed = 42) expect_s3_class(result, "cograph_cluster_significance") expect_true(!is.na(result$observed_modularity)) expect_equal(length(result$null_values), 10) }) test_that("cluster_significance gnm method", { skip_if_not_installed("igraph") g <- igraph::make_graph("Zachary") comm <- igraph::cluster_louvain(g) result <- cluster_significance(g, comm, method = "gnm", n_random = 10, seed = 42) expect_equal(result$method, "gnm") expect_s3_class(result, "cograph_cluster_significance") }) test_that("cluster_significance errors on invalid communities", { skip_if_not_installed("igraph") g <- igraph::make_graph("Zachary") expect_error( cluster_significance(g, "invalid_communities"), "communities must be" ) }) test_that("csig is an alias for cluster_significance", { skip_if_not_installed("igraph") g <- igraph::make_graph("Zachary") comm <- igraph::cluster_louvain(g) set.seed(42) result1 <- cluster_significance(g, comm, n_random = 5) set.seed(42) result2 <- csig(g, comm, n_random = 5) expect_equal(result1$observed_modularity, result2$observed_modularity) }) test_that("cluster_significance with cograph_network input", { skip_if_not_installed("igraph") # Create a cograph_network net <- cograph(mat) comm <- c(1, 1, 1, 1, 2, 2, 2, 2) result <- cluster_significance(net, comm, n_random = 5, seed = 42) expect_s3_class(result, "cograph_cluster_significance") }) # ============================================================================== # Tests for print.cograph_cluster_significance # ============================================================================== test_that("print.cograph_cluster_significance various p-values", { skip_if_not_installed("igraph") g <- igraph::make_graph("Zachary") comm <- igraph::cluster_louvain(g) # Get a result result <- cluster_significance(g, comm, n_random = 10, seed = 42) # Test print output expect_output(print(result), "Cluster Significance Test") expect_output(print(result), "Null model") }) test_that("print.cograph_cluster_significance with NA p-value", { # Create mock object with NA p-value mock_result <- list( observed_modularity = 0.5, null_mean = 0.3, null_sd = 0, z_score = NA_real_, p_value = NA_real_, null_values = rep(0.3, 10), method = "configuration", n_random = 10 ) class(mock_result) <- "cograph_cluster_significance" expect_output(print(mock_result), "Cluster Significance Test") }) test_that("print.cograph_cluster_significance low p-value", { # Create mock object with very low p-value mock_result <- list( observed_modularity = 0.8, null_mean = 0.2, null_sd = 0.05, z_score = 12, p_value = 1e-5, null_values = rnorm(100, 0.2, 0.05), method = "configuration", n_random = 100 ) class(mock_result) <- "cograph_cluster_significance" expect_output(print(mock_result), "Highly significant") }) test_that("print.cograph_cluster_significance medium p-value", { mock_result <- list( observed_modularity = 0.5, null_mean = 0.3, null_sd = 0.05, z_score = 2.5, p_value = 0.006, null_values = rnorm(100, 0.3, 0.05), method = "configuration", n_random = 100 ) class(mock_result) <- "cograph_cluster_significance" expect_output(print(mock_result), "Very significant") }) test_that("print.cograph_cluster_significance borderline p-value", { mock_result <- list( observed_modularity = 0.35, null_mean = 0.3, null_sd = 0.03, z_score = 1.67, p_value = 0.048, null_values = rnorm(100, 0.3, 0.03), method = "configuration", n_random = 100 ) class(mock_result) <- "cograph_cluster_significance" expect_output(print(mock_result), "Significant community structure") }) test_that("print.cograph_cluster_significance non-significant", { mock_result <- list( observed_modularity = 0.31, null_mean = 0.3, null_sd = 0.05, z_score = 0.2, p_value = 0.42, null_values = rnorm(100, 0.3, 0.05), method = "gnm", n_random = 100 ) class(mock_result) <- "cograph_cluster_significance" expect_output(print(mock_result), "No significant") }) # ============================================================================== # Tests for plot.cograph_cluster_significance # ============================================================================== test_that("plot.cograph_cluster_significance works", { skip_if_not_installed("igraph") g <- igraph::make_graph("Zachary") comm <- igraph::cluster_louvain(g) result <- cluster_significance(g, comm, n_random = 10, seed = 42) # Capture plot in temp file tmp <- tempfile(fileext = ".png") on.exit(unlink(tmp), add = TRUE) grDevices::png(tmp) plot_result <- plot(result) grDevices::dev.off() expect_true(file.exists(tmp)) expect_s3_class(plot_result, "cograph_cluster_significance") }) # ============================================================================== # Tests for layer_similarity # ============================================================================== test_that("layer_similarity errors on dimension mismatch", { A1 <- matrix(1, 4, 4) A2 <- matrix(1, 5, 5) expect_error( layer_similarity(A1, A2, "jaccard"), "identical dimensions" ) }) test_that("layer_similarity jaccard with no edges", { A1 <- matrix(0, 4, 4) A2 <- matrix(0, 4, 4) result <- layer_similarity(A1, A2, "jaccard") expect_true(is.na(result)) }) test_that("layer_similarity overlap with no edges", { A1 <- matrix(0, 4, 4) A2 <- matrix(0, 4, 4) result <- layer_similarity(A1, A2, "overlap") expect_true(is.na(result)) }) test_that("layer_similarity cosine with zero norm", { A1 <- matrix(0, 4, 4) A2 <- matrix(1, 4, 4) result <- layer_similarity(A1, A2, "cosine") expect_true(is.na(result)) }) test_that("layer_similarity pearson works", { A1 <- matrix(runif(16), 4, 4) A2 <- matrix(runif(16), 4, 4) result <- layer_similarity(A1, A2, "pearson") expect_true(result >= -1 && result <= 1) }) test_that("layer_similarity overlap works", { A1 <- matrix(c(0,1,1,0, 1,0,0,1, 1,0,0,1, 0,1,1,0), 4, 4) A2 <- matrix(c(0,1,0,0, 1,0,1,0, 0,1,0,1, 0,0,1,0), 4, 4) result <- layer_similarity(A1, A2, "overlap") expect_true(result >= 0 && result <= 1) }) test_that("lsim is an alias for layer_similarity", { A1 <- mat A2 <- mat * 0.5 expect_equal( lsim(A1, A2, "jaccard"), layer_similarity(A1, A2, "jaccard") ) }) # ============================================================================== # Tests for layer_similarity_matrix # ============================================================================== test_that("layer_similarity_matrix errors with < 2 layers", { expect_error( layer_similarity_matrix(list(mat)), "at least 2 layers" ) }) test_that("layer_similarity_matrix with unnamed layers", { layers <- list(mat, mat * 0.5, mat * 0.25) result <- layer_similarity_matrix(layers, method = "jaccard") expect_equal(dim(result), c(3, 3)) expect_equal(rownames(result), c("Layer1", "Layer2", "Layer3")) }) test_that("layer_similarity_matrix pearson method", { layers <- list(L1 = mat, L2 = mat * 2) result <- layer_similarity_matrix(layers, method = "pearson") expect_equal(dim(result), c(2, 2)) expect_equal(result[1, 1], 1) }) test_that("layer_similarity_matrix overlap method", { layers <- list(L1 = mat, L2 = mat) result <- layer_similarity_matrix(layers, method = "overlap") expect_equal(result[1, 2], 1) }) test_that("lsim_matrix is an alias", { layers <- list(L1 = mat, L2 = mat * 0.5) expect_equal( lsim_matrix(layers, "cosine"), layer_similarity_matrix(layers, "cosine") ) }) # ============================================================================== # Tests for layer_degree_correlation # ============================================================================== test_that("layer_degree_correlation in mode", { layers <- list(L1 = mat, L2 = mat * 2, L3 = mat^2) result <- layer_degree_correlation(layers, mode = "in") expect_equal(dim(result), c(3, 3)) }) test_that("layer_degree_correlation out mode", { layers <- list(L1 = mat, L2 = mat * 2) result <- layer_degree_correlation(layers, mode = "out") expect_equal(dim(result), c(2, 2)) }) test_that("layer_degree_correlation with unnamed layers", { layers <- list(mat, mat * 2) result <- layer_degree_correlation(layers, mode = "total") expect_equal(colnames(result), c("Layer1", "Layer2")) }) test_that("ldegcor is an alias", { layers <- list(L1 = mat, L2 = mat * 2) expect_equal( ldegcor(layers, "total"), layer_degree_correlation(layers, "total") ) }) # ============================================================================== # Tests for supra_adjacency # ============================================================================== test_that("supra_adjacency errors on empty layers", { expect_error( supra_adjacency(list()), "at least 1 layer" ) }) test_that("supra_adjacency errors on mismatched dimensions", { layers <- list( L1 = matrix(1, 3, 3), L2 = matrix(1, 4, 4) ) expect_error( supra_adjacency(layers), "identical dimensions" ) }) test_that("supra_adjacency with single layer", { layers <- list(L1 = mat) result <- supra_adjacency(layers, omega = 1) expect_equal(dim(result), c(8, 8)) expect_equal(attr(result, "n_layers"), 1) }) test_that("supra_adjacency with omega matrix", { layers <- list(L1 = mat, L2 = mat * 2) omega_mat <- matrix(c(0, 0.5, 0.5, 0), 2, 2) result <- supra_adjacency(layers, omega = omega_mat, coupling = "diagonal") expect_s3_class(result, "supra_adjacency") }) test_that("supra_adjacency errors on wrong omega matrix dimensions", { layers <- list(L1 = mat, L2 = mat * 2) wrong_omega <- matrix(1, 3, 3) expect_error( supra_adjacency(layers, omega = wrong_omega), "omega matrix must be L x L" ) }) test_that("supra_adjacency full coupling", { layers <- list(L1 = mat, L2 = mat) result <- supra_adjacency(layers, omega = 0.5, coupling = "full") # Extract interlayer and check all entries are 0.5 interlayer <- supra_interlayer(result, 1, 2) expect_true(all(interlayer == 0.5)) }) test_that("supra_adjacency custom coupling without matrices", { layers <- list(L1 = mat, L2 = mat) expect_error( supra_adjacency(layers, coupling = "custom"), "interlayer_matrices required" ) }) test_that("supra_adjacency custom coupling with matrices", { layers <- list(L1 = mat, L2 = mat) custom_inter <- diag(nrow(mat)) * 0.3 result <- supra_adjacency( layers, omega = 1, coupling = "custom", interlayer_matrices = list(custom_inter) ) expect_s3_class(result, "supra_adjacency") }) test_that("supra_adjacency with unnamed layers", { unnamed_mat <- matrix(runif(16), 4, 4) layers <- list(unnamed_mat, unnamed_mat * 2) result <- supra_adjacency(layers, omega = 0.5) expect_true(grepl("L1", rownames(result)[1])) }) test_that("supra is an alias", { layers <- list(L1 = mat, L2 = mat * 2) result1 <- supra_adjacency(layers, omega = 0.5) result2 <- supra(layers, omega = 0.5) expect_equal(dim(result1), dim(result2)) }) # ============================================================================== # Tests for supra_layer # ============================================================================== test_that("supra_layer errors on invalid layer index", { layers <- list(L1 = mat, L2 = mat * 2) s <- supra_adjacency(layers, omega = 0.5) expect_error(supra_layer(s, 0), "must be between") expect_error(supra_layer(s, 3), "must be between") }) test_that("extract_layer is an alias for supra_layer", { layers <- list(L1 = mat, L2 = mat * 2) s <- supra_adjacency(layers, omega = 0.5) expect_equal( extract_layer(s, 1), supra_layer(s, 1) ) }) # ============================================================================== # Tests for supra_interlayer # ============================================================================== test_that("supra_interlayer errors on invalid layer indices", { layers <- list(L1 = mat, L2 = mat * 2) s <- supra_adjacency(layers, omega = 0.5) expect_error(supra_interlayer(s, 0, 1), "must be between") expect_error(supra_interlayer(s, 1, 3), "must be between") }) test_that("extract_interlayer is an alias", { layers <- list(L1 = mat, L2 = mat * 2) s <- supra_adjacency(layers, omega = 0.5) expect_equal( extract_interlayer(s, 1, 2), supra_interlayer(s, 1, 2) ) }) # ============================================================================== # Tests for aggregate_layers # ============================================================================== test_that("aggregate_layers errors on empty list", { expect_error( aggregate_layers(list()), "at least 1 layer" ) }) test_that("aggregate_layers returns single layer unchanged", { result <- aggregate_layers(list(mat), method = "sum") expect_equal(result, mat) }) test_that("aggregate_layers weighted sum errors on wrong weights length", { layers <- list(L1 = mat, L2 = mat * 2) expect_error( aggregate_layers(layers, method = "sum", weights = c(1, 2, 3)), "weights must have length" ) }) test_that("aggregate_layers min method", { layers <- list(L1 = mat, L2 = mat * 2, L3 = mat * 3) result <- aggregate_layers(layers, method = "min") expect_equal(result, mat) }) test_that("aggregate_layers preserves dimnames", { layers <- list(L1 = mat, L2 = mat * 2) result <- aggregate_layers(layers, method = "sum") expect_equal(rownames(result), rownames(mat)) expect_equal(colnames(result), colnames(mat)) }) test_that("lagg is an alias", { layers <- list(L1 = mat, L2 = mat * 2) expect_equal( lagg(layers, "mean"), aggregate_layers(layers, "mean") ) }) # ============================================================================== # Tests for verify_with_igraph # ============================================================================== test_that("verify_with_igraph works with igraph installed", { skip_if_not_installed("igraph") result <- verify_with_igraph(mat, clusters_list, method = "sum") expect_type(result, "list") expect_true("matches" %in% names(result)) expect_true("our_result" %in% names(result)) expect_true("igraph_result" %in% names(result)) }) test_that("verify_with_igraph with unnamed matrix", { skip_if_not_installed("igraph") unnamed_mat <- matrix(runif(25), 5, 5) diag(unnamed_mat) <- 0 clusters <- c(1, 1, 2, 2, 3) result <- verify_with_igraph(unnamed_mat, clusters, method = "sum") expect_type(result, "list") }) test_that("verify_igraph is an alias", { skip_if_not_installed("igraph") result1 <- verify_with_igraph(mat, clusters_list, method = "sum") result2 <- verify_igraph(mat, clusters_list, method = "sum") expect_equal(result1$matches, result2$matches) }) # ============================================================================== # Tests for print.cluster_summary # ============================================================================== test_that("print.cluster_summary works", { result <- cluster_summary(mat, clusters_list) expect_output(print(result), "Cluster Summary") expect_output(print(result), "Clusters:") expect_output(print(result), "Type:") expect_output(print(result), "Macro") expect_output(print(result), "Per-cluster weights") expect_output(print(result), "Inits:") }) test_that("print.cluster_summary handles compute_within = FALSE", { result <- cluster_summary(mat, clusters_list, compute_within = FALSE) expect_output(print(result), "not computed") }) # ============================================================================== # Tests for print.cluster_quality # ============================================================================== test_that("print.cluster_quality works", { result <- cluster_quality(mat, clusters_list) expect_output(print(result), "Cluster Quality Metrics") expect_output(print(result), "Global metrics") expect_output(print(result), "Modularity") expect_output(print(result), "Per-cluster metrics") }) # ============================================================================== # Edge Cases and Complex Scenarios # ============================================================================== test_that("cluster_summary with all zeros in one cluster pair", { # Create matrix where one cluster pair has no edges special_mat <- mat special_mat[1:2, 6:8] <- 0 special_mat[6:8, 1:2] <- 0 result <- cluster_summary(special_mat, clusters_list, method = "sum") expect_equal(result$macro$weights["Group1", "Group3"], 0, tolerance = 1e-10) }) test_that("cluster_quality with single-node cluster", { single_clusters <- list( A = "N1", B = c("N2", "N3", "N4", "N5", "N6", "N7", "N8") ) result <- cluster_quality(mat, single_clusters) expect_s3_class(result, "cluster_quality") }) test_that("layer_similarity with identical sparse matrices", { result <- layer_similarity(sparse_mat, sparse_mat, "jaccard") expect_equal(result, 1) }) test_that("supra_adjacency with three layers", { layers <- list(L1 = mat, L2 = mat * 2, L3 = mat * 0.5) result <- supra_adjacency(layers, omega = 0.3) expect_equal(dim(result), c(24, 24)) expect_equal(attr(result, "n_layers"), 3) }) test_that("aggregate_layers intersection with fully connected", { full1 <- matrix(1, 4, 4) diag(full1) <- 0 full2 <- matrix(1, 4, 4) diag(full2) <- 0 result <- aggregate_layers(list(full1, full2), method = "intersection") expected <- full1 expect_equal(result, expected) }) test_that("aggregate_layers union with sparse matrices", { m1 <- matrix(0, 4, 4) m1[1, 2] <- 1 m2 <- matrix(0, 4, 4) m2[3, 4] <- 1 result <- aggregate_layers(list(m1, m2), method = "union") expect_equal(sum(result), 2) }) # ============================================================================== # Additional Modularity Tests # ============================================================================== test_that(".compute_modularity directed graph", { membership <- c(1, 1, 2, 2, 2, 3, 3, 3) result_directed <- cograph:::.compute_modularity(mat, membership, directed = TRUE) result_undirected <- cograph:::.compute_modularity(mat, membership, directed = FALSE) # Both should be numeric expect_true(is.numeric(result_directed)) expect_true(is.numeric(result_undirected)) }) # ============================================================================== # Cluster Significance with Different Inputs # ============================================================================== test_that("cluster_significance handles configuration model failures gracefully", { skip_if_not_installed("igraph") # Create a simple network g <- igraph::make_graph("Zachary") comm <- igraph::cluster_louvain(g) # This should handle any internal fallbacks result <- cluster_significance(g, comm, method = "configuration", n_random = 5, seed = 42) expect_s3_class(result, "cograph_cluster_significance") }) test_that("cluster_significance computes z-score correctly", { skip_if_not_installed("igraph") g <- igraph::make_graph("Zachary") comm <- igraph::cluster_louvain(g) result <- cluster_significance(g, comm, n_random = 20, seed = 42) # Verify z-score calculation expected_z <- (result$observed_modularity - result$null_mean) / result$null_sd expect_equal(result$z_score, expected_z, tolerance = 1e-10) }) # ============================================================================== # Tests for single-node clusters # ============================================================================== test_that("cluster_summary handles single-node clusters", { single_clusters <- list( A = "N1", B = c("N2", "N3", "N4"), C = c("N5", "N6", "N7", "N8") ) result <- cluster_summary(mat, single_clusters) # Single-node cluster should have 1x1 zero matrix expect_equal(dim(result$clusters$A$weights), c(1, 1)) expect_equal(result$clusters$A$weights[1, 1], 0) # Single-node cluster inits should be 1 expect_equal(result$clusters$A$inits, c(N1 = 1)) }) # ============================================================================== # Tests for zero-edge clusters # ============================================================================== test_that("cluster_summary handles zero-edge clusters in within", { zero_cluster_mat <- mat zero_cluster_mat[1:2, 1:2] <- 0 # Group1 has no internal edges result <- cluster_summary(zero_cluster_mat, clusters_list) # Group1 should have zero weights expect_equal(sum(result$clusters$Group1$weights), 0) # Inits should be uniform for zero-edge cluster expect_equal(result$clusters$Group1$inits, c(N1 = 0.5, N2 = 0.5)) }) test_that("cluster_summary handles zero edge matrix for inits", { zero_mat <- matrix(0, 6, 6) rownames(zero_mat) <- colnames(zero_mat) <- paste0("N", 1:6) clusters <- list(A = c("N1", "N2"), B = c("N3", "N4"), C = c("N5", "N6")) result <- cluster_summary(zero_mat, clusters) # With no edges, inits should be uniform expect_equal(result$macro$inits, c(A = 1/3, B = 1/3, C = 1/3), tolerance = 1e-10) }) # ============================================================================== # Tests for sparse matrices # ============================================================================== test_that("cluster_summary handles sparse matrix", { sparse <- matrix(0, 5, 5) sparse[1, 2] <- 1 sparse[3, 4] <- 0.5 rownames(sparse) <- colnames(sparse) <- paste0("S", 1:5) clusters <- list(A = c("S1", "S2"), B = c("S3", "S4", "S5")) result <- cluster_summary(sparse, clusters) expect_type(result$clusters, "list") expect_equal(names(result$clusters), c("A", "B")) }) # ============================================================================== # Tests for NAs in matrix # ============================================================================== test_that("cluster_summary handles NAs in matrix", { mat_na <- mat mat_na[1, 2] <- NA mat_na[3, 4] <- NA result <- cluster_summary(mat_na, clusters_list) # Should not error, NAs handled expect_true("clusters" %in% names(result)) # Weights should not have NAs (after processing) expect_false(any(is.na(result$macro$weights))) }) # ============================================================================== # Summary # ============================================================================== cat("\n=== Cluster Metrics Coverage Tests Complete ===\n")