Nothing
# =============================================================================
# Numerical Equivalence: disparity_filter() vs independent reimplementation
#
# Validates cograph's disparity filter (Serrano et al., 2009) against a
# loop-based reference that computes p-values cell-by-cell.
#
# 100 networks per scenario (undirected x 3 levels + directed x 1 level).
# Delta stats: mean, median, p95, max on raw p-value matrices.
# Reports to: tmp/disparity_equivalence_report.csv
# + CVS inbox (vitest JSON) if validation system is present.
# =============================================================================
skip_on_cran()
skip_coverage_tests()
skip_if_not_installed("igraph")
# ---------------------------------------------------------------------------
# Report infrastructure
# ---------------------------------------------------------------------------
.equiv_log <- new.env(parent = emptyenv())
.equiv_log$rows <- list()
.log_result <- function(func, config, n_checked, n_passed, n_failed,
max_abs_err = NA_real_, mean_abs_err = NA_real_,
median_abs_err = NA_real_, p95_abs_err = NA_real_,
reference_package = "manual_loop", notes = "") {
.equiv_log$rows[[length(.equiv_log$rows) + 1L]] <- data.frame(
function_name = func, n_nodes = config$n, density = config$density,
seed = config$seed, directed = isTRUE(config$directed),
level = config$level,
values_checked = n_checked, values_passed = n_passed,
values_failed = n_failed,
max_abs_error = max_abs_err, mean_abs_error = mean_abs_err,
median_abs_error = median_abs_err, p95_abs_error = p95_abs_err,
reference_package = reference_package,
notes = notes, stringsAsFactors = FALSE)
}
.write_report <- function() {
if (length(.equiv_log$rows) == 0L) return(invisible(NULL))
df <- do.call(rbind, .equiv_log$rows)
utils::write.csv(df, file.path(tempdir(), "disparity_equivalence_report.csv"),
row.names = FALSE)
cat(sprintf(
paste0("\n=== DISPARITY FILTER EQUIVALENCE REPORT ===\n",
"Functions: %d | Configs: %d | Checked: %s | Passed: %s | Failed: %s\n",
"Max delta: %.2e | Mean delta: %.2e | Median delta: %.2e\n",
"Report: %s\n"),
length(unique(df$function_name)), nrow(df),
format(sum(df$values_checked), big.mark = ","),
format(sum(df$values_passed), big.mark = ","),
format(sum(df$values_failed), big.mark = ","),
max(df$max_abs_error, na.rm = TRUE),
mean(df$mean_abs_error, na.rm = TRUE),
stats::median(df$median_abs_error, na.rm = TRUE),
file.path(tempdir(), "disparity_equivalence_report.csv")
))
invisible(df)
}
.write_cvs_report <- function() {
if (length(.equiv_log$rows) == 0L) return(invisible(NULL))
if (!requireNamespace("jsonlite", quietly = TRUE)) return(invisible(NULL))
df <- do.call(rbind, .equiv_log$rows)
assertions <- lapply(seq_len(nrow(df)), function(i) {
r <- df[i, ]
status <- if (r$values_failed == 0) "passed" else "failed"
list(
ancestorTitles = list("disparity_filter equivalence"),
title = sprintf("%s: n=%d d=%.2f seed=%d level=%.2f delta=%.2e",
r$function_name, r$n_nodes, r$density,
r$seed, r$level, r$max_abs_error),
fullName = sprintf("disparity_filter equivalence > %s: n=%d seed=%d",
r$function_name, r$n_nodes, r$seed),
status = status,
duration = 0L,
failureMessages = if (status == "failed")
list(sprintf("max_abs_error=%.2e, %d/%d values failed",
r$max_abs_error, r$values_failed, r$values_checked))
else list(),
`_cvs` = list(
delta = r$max_abs_error,
tolerance = TOL,
rFunction = r$function_name,
rPackage = r$reference_package,
module = "disparity"
)
)
})
result <- list(
numTotalTestSuites = 1L,
numPassedTestSuites = as.integer(sum(df$values_failed) == 0),
numFailedTestSuites = as.integer(sum(df$values_failed) > 0),
numTotalTests = nrow(df),
numPassedTests = sum(df$values_failed == 0),
numFailedTests = sum(df$values_failed > 0),
testResults = list(list(
name = "tests/testthat/test-equiv-disparity.R",
assertionResults = assertions
)),
`_cvs` = list(target = "cograph")
)
inbox <- file.path("..", "..", "validation", "data", "inbox")
if (!dir.exists(inbox)) inbox <- file.path("..", "..", "..", "validation", "data", "inbox")
if (dir.exists(inbox)) {
fname <- sprintf("cograph-disparity-%s.json",
format(Sys.time(), "%Y%m%dT%H%M%S"))
jsonlite::write_json(result, file.path(inbox, fname),
auto_unbox = TRUE, pretty = TRUE)
cat(sprintf(" CVS report written: %s\n", fname))
}
}
# ---------------------------------------------------------------------------
# Independent reference implementation (explicit loops)
# ---------------------------------------------------------------------------
# Compute the full p-value matrix using explicit loops.
# For undirected networks, strength/degree use both directions (row and col).
# For directed networks, each edge (i,j) uses outgoing stats of i only.
#
# Returns a matrix of p-values (same dims as mat), with diag = 1.
.ref_pvalue_matrix <- function(mat, directed = FALSE) {
n <- nrow(mat)
pvals <- matrix(1, n, n)
if (directed) {
# Directed: edge (i,j) uses outgoing strength/degree of node i
for (i in seq_len(n)) {
s_i <- 0
k_i <- 0L
for (jj in seq_len(n)) {
if (mat[i, jj] > 0) {
s_i <- s_i + mat[i, jj]
k_i <- k_i + 1L
}
}
if (s_i == 0 || k_i <= 1L) next
for (j in seq_len(n)) {
if (i == j) next
if (mat[i, j] <= 0) next
p_ij <- mat[i, j] / s_i
pvals[i, j] <- (1 - p_ij)^(k_i - 1L)
}
}
# For directed networks, cograph also tests the incoming side.
# The implementation uses pmin(out_pval, in_pval) where in_pval
# treats columns as the "incoming" perspective.
# Compute incoming p-values separately.
in_pvals <- matrix(1, n, n)
for (j in seq_len(n)) {
s_j <- 0
k_j <- 0L
for (ii in seq_len(n)) {
if (mat[ii, j] > 0) {
s_j <- s_j + mat[ii, j]
k_j <- k_j + 1L
}
}
if (s_j == 0 || k_j <= 1L) next
for (i in seq_len(n)) {
if (i == j) next
if (mat[i, j] <= 0) next
p_ij <- mat[i, j] / s_j
in_pvals[i, j] <- (1 - p_ij)^(k_j - 1L)
}
}
# Combine: significant if either outgoing OR incoming test says so
pvals <- pmin(pvals, in_pvals)
} else {
# Undirected: edge (i,j) tested from both endpoints i and j.
# Strength/degree computed from the full row (symmetric matrix).
for (i in seq_len(n)) {
for (j in seq_len(n)) {
if (i == j) next
if (mat[i, j] <= 0) next
# From endpoint i
s_i <- 0
k_i <- 0L
for (kk in seq_len(n)) {
if (mat[i, kk] > 0) {
s_i <- s_i + mat[i, kk]
k_i <- k_i + 1L
}
}
if (s_i > 0 && k_i > 1L) {
p_ij <- mat[i, j] / s_i
pval_i <- (1 - p_ij)^(k_i - 1L)
} else {
pval_i <- 1
}
# From endpoint j
s_j <- 0
k_j <- 0L
for (kk in seq_len(n)) {
if (mat[j, kk] > 0) {
s_j <- s_j + mat[j, kk]
k_j <- k_j + 1L
}
}
if (s_j > 0 && k_j > 1L) {
p_ji <- mat[j, i] / s_j
pval_j <- (1 - p_ji)^(k_j - 1L)
} else {
pval_j <- 1
}
# Significant if EITHER endpoint says so
pvals[i, j] <- min(pval_i, pval_j)
}
}
}
diag(pvals) <- 1
pvals
}
# Build binary backbone from p-value matrix at given significance level
.ref_backbone <- function(pvals, level) {
sig <- 1L * (pvals < level)
diag(sig) <- 0L
sig
}
# ---------------------------------------------------------------------------
# Compare helper: p-value matrices element by element
# ---------------------------------------------------------------------------
.compare_pval_matrices <- function(co_sig, ref_pvals, ref_sig, mat,
func_name, cfg, tol = TOL) {
# --- Binary backbone comparison ---
n <- nrow(mat)
mask <- which(mat != 0 & row(mat) != col(mat), arr.ind = TRUE)
n_edges <- nrow(mask)
if (n_edges == 0L) {
.log_result(func_name, cfg, 0L, 0L, 0L, 0, 0, 0, 0,
notes = "no edges")
return(TRUE)
}
binary_match <- vapply(seq_len(n_edges), function(idx) {
i <- mask[idx, 1L]
j <- mask[idx, 2L]
co_sig[i, j] == ref_sig[i, j]
}, logical(1))
n_checked <- n_edges
n_passed <- sum(binary_match)
n_failed <- n_checked - n_passed
# --- Delta on the raw p-value matrix ---
# Reconstruct cograph p-values by inverting the binary test
# We cannot extract exact cograph p-values from the binary output alone.
# Instead, compare the binary outputs and report any mismatches.
# The ref p-values serve as the source of truth for delta stats.
# Compute deltas between ref p-values at mismatch positions
# For the report, gather the ref p-values at all edge positions
ref_edge_pvals <- vapply(seq_len(n_edges), function(idx) {
ref_pvals[mask[idx, 1L], mask[idx, 2L]]
}, numeric(1))
# Delta stats: distance of mismatched ref p-values from the threshold
if (n_failed > 0L) {
fail_idx <- which(!binary_match)
fail_pvals <- ref_edge_pvals[fail_idx]
deltas <- abs(fail_pvals - cfg$level)
max_d <- max(deltas)
mean_d <- mean(deltas)
median_d <- stats::median(deltas)
p95_d <- unname(stats::quantile(deltas, 0.95))
} else {
max_d <- 0
mean_d <- 0
median_d <- 0
p95_d <- 0
}
.log_result(func_name, cfg, n_checked, n_passed, n_failed,
max_d, mean_d, median_d, p95_d)
n_failed == 0L
}
# ---------------------------------------------------------------------------
# Compare helper: raw p-value matrix deltas (cograph internals vs reference)
# ---------------------------------------------------------------------------
.compare_pval_deltas <- function(mat, cfg, tol = TOL, func_name = "pvalue_matrix") {
# Access the internal function to get the raw p-value data
# Reconstruct cograph's p-value computation using the same vectorized logic
d <- nrow(mat)
idx_mat <- 1L * (mat > 0)
# Outgoing
row_sums <- .rowSums(mat, m = d, n = d)
row_sums[row_sums == 0] <- 1
out_edges <- mat / row_sums
out_degree <- .rowSums(idx_mat, m = d, n = d)
out_p_values <- (1 - out_edges)^(out_degree - 1)
# Incoming
col_sums <- .colSums(mat, m = d, n = d)
col_sums[col_sums == 0] <- 1
in_edges <- t(t(mat) / col_sums)
in_degree <- .colSums(idx_mat, m = d, n = d)
in_p_values <- t((1 - t(in_edges))^(in_degree - 1))
co_pvals <- pmin(out_p_values, in_p_values)
diag(co_pvals) <- 1
# Reference p-value matrix (loop-based)
is_sym <- isSymmetric(mat)
ref_pvals <- .ref_pvalue_matrix(mat, directed = !is_sym)
# Compare element-by-element on non-zero edges
mask <- which(mat != 0 & row(mat) != col(mat), arr.ind = TRUE)
n_edges <- nrow(mask)
if (n_edges == 0L) {
.log_result(func_name, cfg, 0L, 0L, 0L, 0, 0, 0, 0,
notes = "no edges")
return(TRUE)
}
deltas <- vapply(seq_len(n_edges), function(idx) {
i <- mask[idx, 1L]
j <- mask[idx, 2L]
abs(co_pvals[i, j] - ref_pvals[i, j])
}, numeric(1))
n_checked <- n_edges
n_passed <- sum(deltas <= tol)
n_failed <- n_checked - n_passed
max_d <- max(deltas)
mean_d <- mean(deltas)
median_d <- stats::median(deltas)
p95_d <- unname(stats::quantile(deltas, 0.95))
.log_result(func_name, cfg, n_checked, n_passed, n_failed,
max_d, mean_d, median_d, p95_d)
n_failed == 0L
}
# ---------------------------------------------------------------------------
# Matrix generator: weighted, all edges > 0
# ---------------------------------------------------------------------------
.make_weighted_matrix <- function(n, density, seed, symmetric = TRUE) {
mat <- create_test_matrix(n = n, density = density, weighted = TRUE,
symmetric = symmetric, seed = seed)
# Ensure all non-zero entries are strictly positive
mat[mat != 0 & mat < 0] <- abs(mat[mat != 0 & mat < 0])
mat[mat != 0] <- mat[mat != 0] + 0.01
# Restore symmetry after abs() adjustment (floating point safety)
if (symmetric) {
mat[lower.tri(mat)] <- t(mat)[lower.tri(mat)]
}
node_names <- paste0("N", seq_len(n))
dimnames(mat) <- list(node_names, node_names)
mat
}
# ---------------------------------------------------------------------------
# 100 network configurations
# ---------------------------------------------------------------------------
set.seed(2026)
N <- 100L
TOL <- 1e-10
sizes <- sample(c(6, 8, 10, 12, 15, 20, 25), N, replace = TRUE)
densities <- runif(N, 0.2, 0.6)
seeds <- sample.int(100000, N)
cat("\n")
cat("================================================================\n")
cat(" DISPARITY FILTER EQUIVALENCE REPORT\n")
cat(sprintf(" %d random weighted networks per scenario\n", N))
cat(sprintf(" Sizes: %s | Densities: 0.2-0.6\n",
paste(sort(unique(sizes)), collapse = ", ")))
cat(sprintf(" Tolerance: %.0e\n", TOL))
cat("================================================================\n\n")
# =============================================================================
# 1. Undirected p-value matrix: loop vs vectorized (100 networks)
# =============================================================================
test_that("disparity_filter undirected: p-value matrix matches loop reference (100 networks)", {
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = FALSE, level = 0.05)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = TRUE)
.compare_pval_deltas(mat, cfg, tol = TOL,
func_name = "pvalue_undirected")
invisible(NULL)
})
df <- do.call(rbind, .equiv_log$rows)
sub <- df[df$function_name == "pvalue_undirected", ]
n_fail <- sum(sub$values_failed)
expect_equal(n_fail, 0L,
info = sprintf("Undirected p-values: %d values failed across %d configs",
n_fail, nrow(sub)))
})
# =============================================================================
# 2. Undirected backbone at level=0.05 (100 networks)
# =============================================================================
test_that("disparity_filter undirected: backbone matches at level=0.05 (100 networks)", {
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = FALSE, level = 0.05)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = TRUE)
co_sig <- disparity_filter(mat, level = 0.05)
ref_pvals <- .ref_pvalue_matrix(mat, directed = FALSE)
ref_sig <- .ref_backbone(ref_pvals, level = 0.05)
.compare_pval_matrices(co_sig, ref_pvals, ref_sig, mat,
"backbone_undirected_0.05", cfg, tol = TOL)
invisible(NULL)
})
df <- do.call(rbind, .equiv_log$rows)
sub <- df[df$function_name == "backbone_undirected_0.05", ]
n_fail <- sum(sub$values_failed)
expect_equal(n_fail, 0L,
info = sprintf("Undirected backbone (0.05): %d values failed across %d configs",
n_fail, nrow(sub)))
})
# =============================================================================
# 3. Undirected backbone at level=0.1 (100 networks)
# =============================================================================
test_that("disparity_filter undirected: backbone matches at level=0.1 (100 networks)", {
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = FALSE, level = 0.1)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = TRUE)
co_sig <- disparity_filter(mat, level = 0.1)
ref_pvals <- .ref_pvalue_matrix(mat, directed = FALSE)
ref_sig <- .ref_backbone(ref_pvals, level = 0.1)
.compare_pval_matrices(co_sig, ref_pvals, ref_sig, mat,
"backbone_undirected_0.10", cfg, tol = TOL)
invisible(NULL)
})
df <- do.call(rbind, .equiv_log$rows)
sub <- df[df$function_name == "backbone_undirected_0.10", ]
n_fail <- sum(sub$values_failed)
expect_equal(n_fail, 0L,
info = sprintf("Undirected backbone (0.10): %d values failed across %d configs",
n_fail, nrow(sub)))
})
# =============================================================================
# 4. Undirected backbone at level=0.5 (100 networks)
# =============================================================================
test_that("disparity_filter undirected: backbone matches at level=0.5 (100 networks)", {
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = FALSE, level = 0.5)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = TRUE)
co_sig <- disparity_filter(mat, level = 0.5)
ref_pvals <- .ref_pvalue_matrix(mat, directed = FALSE)
ref_sig <- .ref_backbone(ref_pvals, level = 0.5)
.compare_pval_matrices(co_sig, ref_pvals, ref_sig, mat,
"backbone_undirected_0.50", cfg, tol = TOL)
invisible(NULL)
})
df <- do.call(rbind, .equiv_log$rows)
sub <- df[df$function_name == "backbone_undirected_0.50", ]
n_fail <- sum(sub$values_failed)
expect_equal(n_fail, 0L,
info = sprintf("Undirected backbone (0.50): %d values failed across %d configs",
n_fail, nrow(sub)))
})
# =============================================================================
# 5. Directed p-value matrix: loop vs vectorized (100 networks)
# =============================================================================
test_that("disparity_filter directed: p-value matrix matches loop reference (100 networks)", {
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = TRUE, level = 0.05)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = FALSE)
.compare_pval_deltas(mat, cfg, tol = TOL,
func_name = "pvalue_directed")
invisible(NULL)
})
df <- do.call(rbind, .equiv_log$rows)
sub <- df[df$function_name == "pvalue_directed", ]
n_fail <- sum(sub$values_failed)
expect_equal(n_fail, 0L,
info = sprintf("Directed p-values: %d values failed across %d configs",
n_fail, nrow(sub)))
})
# =============================================================================
# 6. Directed backbone at level=0.05 (100 networks)
# =============================================================================
test_that("disparity_filter directed: backbone matches at level=0.05 (100 networks)", {
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = TRUE, level = 0.05)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = FALSE)
co_sig <- disparity_filter(mat, level = 0.05)
ref_pvals <- .ref_pvalue_matrix(mat, directed = TRUE)
ref_sig <- .ref_backbone(ref_pvals, level = 0.05)
.compare_pval_matrices(co_sig, ref_pvals, ref_sig, mat,
"backbone_directed_0.05", cfg, tol = TOL)
invisible(NULL)
})
df <- do.call(rbind, .equiv_log$rows)
sub <- df[df$function_name == "backbone_directed_0.05", ]
n_fail <- sum(sub$values_failed)
expect_equal(n_fail, 0L,
info = sprintf("Directed backbone (0.05): %d values failed across %d configs",
n_fail, nrow(sub)))
})
# =============================================================================
# 7. Degree-1 nodes: always non-significant (edge case)
# =============================================================================
test_that("disparity_filter: degree-1 nodes have local pval=1", {
# Star graph: center has degree n-1, leaves have degree 1.
# A degree-1 node has p_ij = w / s = 1, so local pval = (1-1)^(1-1) = 0^0 = 1.
# The COMBINED p-value (pmin of both endpoints) may still be < 1 because
# the center endpoint can have a low p-value. We verify:
# (a) leaf's LOCAL pval = 1 (from leaf's perspective only)
# (b) combined backbone matches between cograph and reference
set.seed(9999)
lapply(c(4, 6, 10, 15, 20), function(n) {
mat <- matrix(0, n, n)
node_names <- paste0("N", seq_len(n))
dimnames(mat) <- list(node_names, node_names)
# Node 1 is center, nodes 2..n are leaves
weights <- runif(n - 1L, 0.1, 2.0)
mat[1, 2:n] <- weights
mat[2:n, 1] <- weights # symmetric
# (a) Verify each leaf's LOCAL p-value is exactly 1
vapply(2:n, function(leaf) {
s_leaf <- sum(mat[leaf, ])
k_leaf <- sum(mat[leaf, ] > 0)
expect_equal(k_leaf, 1L,
info = sprintf("n=%d leaf=%d should have degree 1", n, leaf))
p_leaf <- mat[leaf, 1] / s_leaf # = 1 (only one edge)
pval_leaf <- (1 - p_leaf)^(k_leaf - 1L) # = 0^0 = 1
expect_equal(pval_leaf, 1,
info = sprintf("n=%d leaf=%d local pval should be 1", n, leaf))
pval_leaf
}, numeric(1))
# (b) Backbone: cograph vs loop reference must agree
ref_pvals <- .ref_pvalue_matrix(mat, directed = FALSE)
co_sig <- disparity_filter(mat, level = 0.05)
ref_sig <- .ref_backbone(ref_pvals, level = 0.05)
dimnames(ref_sig) <- dimnames(mat)
expect_identical(co_sig, ref_sig,
info = sprintf("Star n=%d: backbone mismatch", n))
})
})
# =============================================================================
# 8. Monotonicity: stricter level -> fewer or equal edges
# =============================================================================
test_that("disparity_filter: stricter level retains fewer or equal edges", {
lapply(seq_len(min(N, 50L)), function(i) {
mat <- .make_weighted_matrix(sizes[i], densities[i], seeds[i],
symmetric = TRUE)
sig_50 <- disparity_filter(mat, level = 0.50)
sig_10 <- disparity_filter(mat, level = 0.10)
sig_05 <- disparity_filter(mat, level = 0.05)
sig_01 <- disparity_filter(mat, level = 0.01)
expect_true(sum(sig_01) <= sum(sig_05),
info = sprintf("seed=%d: level 0.01 (%d) > level 0.05 (%d)",
seeds[i], sum(sig_01), sum(sig_05)))
expect_true(sum(sig_05) <= sum(sig_10),
info = sprintf("seed=%d: level 0.05 (%d) > level 0.10 (%d)",
seeds[i], sum(sig_05), sum(sig_10)))
expect_true(sum(sig_10) <= sum(sig_50),
info = sprintf("seed=%d: level 0.10 (%d) > level 0.50 (%d)",
seeds[i], sum(sig_10), sum(sig_50)))
})
})
# =============================================================================
# 9. Structural invariants: diagonal always zero, dimnames preserved
# =============================================================================
test_that("disparity_filter: diagonal zero and dimnames preserved", {
lapply(seq_len(min(N, 30L)), function(i) {
mat <- .make_weighted_matrix(sizes[i], densities[i], seeds[i],
symmetric = TRUE)
sig <- disparity_filter(mat, level = 0.05)
expect_true(all(diag(sig) == 0),
info = sprintf("seed=%d: diagonal not zero", seeds[i]))
expect_identical(dimnames(sig), dimnames(mat),
info = sprintf("seed=%d: dimnames lost", seeds[i]))
expect_true(all(sig %in% c(0L, 1L)),
info = sprintf("seed=%d: non-binary values", seeds[i]))
})
})
# =============================================================================
# Print per-function summary with delta stats
# =============================================================================
test_that("disparity_filter equivalence: per-function delta report", {
df <- do.call(rbind, .equiv_log$rows)
if (is.null(df) || nrow(df) == 0L) {
skip("No results to report")
}
fns <- unique(df$function_name)
cat("\n")
lapply(fns, function(fn) {
sub <- df[df$function_name == fn, ]
status <- if (all(sub$values_failed == 0)) "PASS" else "FAIL"
cat(sprintf(" %-35s %s mean_d=%.2e median_d=%.2e max_d=%.2e p95_d=%.2e\n",
paste0(fn, ":"), status,
mean(sub$max_abs_error, na.rm = TRUE),
stats::median(sub$max_abs_error, na.rm = TRUE),
max(sub$max_abs_error, na.rm = TRUE),
stats::quantile(sub$max_abs_error, 0.95, na.rm = TRUE)))
})
expect_true(TRUE) # Report-only test
})
# =============================================================================
# Cross-package: backbone::disparity() (Serrano et al. peer-reviewed impl)
# =============================================================================
test_that("disparity_filter: matches backbone::disparity() (100 undirected networks)", {
skip_if_not_installed("backbone")
lapply(seq_len(N), function(i) {
cfg <- list(n = sizes[i], density = densities[i], seed = seeds[i],
directed = FALSE, level = 0.05)
mat <- .make_weighted_matrix(cfg$n, cfg$density, cfg$seed, symmetric = TRUE)
# cograph: returns binary significance matrix
co_bb <- disparity_filter(mat, level = 0.05)
# backbone v3: use backbone_from_weighted(model = "disparity")
bb_result <- tryCatch(
backbone::backbone_from_weighted(mat, model = "disparity",
alpha = 0.05, narrative = FALSE),
error = function(e) NULL
)
if (is.null(bb_result)) {
.log_result("backbone_disparity", cfg, 0L, 0L, 0L, NA_real_,
NA_real_, NA_real_, NA_real_, "backbone", "backbone error")
return(invisible(NULL))
}
# backbone returns a signed backbone matrix (+1/-1/0)
ref_bb <- as.matrix(bb_result)
# Ensure same dimension and ordering
if (!identical(dim(co_bb), dim(ref_bb))) {
.log_result("backbone_disparity", cfg, 0L, 0L, 0L, NA_real_,
NA_real_, NA_real_, NA_real_, "backbone", "dim mismatch")
return(invisible(NULL))
}
# Compare binary backbone matrices
n_cells <- length(co_bb)
# backbone may binarize differently (1 vs weight) — compare sign pattern
co_binary <- (co_bb > 0) * 1L
ref_binary <- (ref_bb > 0) * 1L
n_match <- sum(co_binary == ref_binary)
n_fail <- n_cells - n_match
match_rate <- n_match / n_cells
.log_result("backbone_disparity", cfg, n_cells, n_match, n_fail,
1 - match_rate, 1 - match_rate, 1 - match_rate,
1 - match_rate, "backbone",
sprintf("match=%.1f%%", match_rate * 100))
expect_true(match_rate >= 0.95,
info = sprintf("n=%d seed=%d match=%.1f%%", cfg$n, cfg$seed, match_rate * 100))
})
})
# =============================================================================
# Final: write reports and assert zero failures
# =============================================================================
test_that("disparity_filter equivalence: zero total failures + reports written", {
report <- .write_report()
.write_cvs_report()
expect_true(is.data.frame(report))
expect_equal(sum(report$values_failed), 0L,
info = sprintf("Failed %d values across %d configs",
sum(report$values_failed), nrow(report)))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.