Nothing
.make_grid_adj <- function(nx, ny) {
stopifnot(nx >= 1, ny >= 1)
if (nx * ny == 1) return(Matrix::Matrix(0, 1, 1, sparse = TRUE))
Tx <- Matrix::bandSparse(
nx,
k = c(-1, 1),
diag = list(
rep.int(1, nx - 1),
rep.int(1, nx - 1)
)
)
Ty <- Matrix::bandSparse(
ny,
k = c(-1, 1),
diag = list(
rep.int(1, ny - 1),
rep.int(1, ny - 1)
)
)
W <- Matrix::Diagonal(ny) %x% Tx + Ty %x% Matrix::Diagonal(nx)
W
}
.make_disconnected_lattices <- function(
components = list(c(20, 20)),
n_isolates = 0
) {
stopifnot(
all(vapply(components, length, 1L) == 2L),
n_isolates >= 0
)
# Build block matrix from adjacency matrices
blocks <- lapply(components, function(x) .make_grid_adj(x[1], x[2]))
W <- Matrix::bdiag(blocks)
iso_block <- Matrix::Matrix(0, n_isolates, n_isolates, sparse = TRUE)
W <- Matrix::bdiag(W, iso_block)
# Ensure symmetric numeric sparse (dsCMatrix) and consistent dimnames
W <- Matrix::forceSymmetric(W, uplo = "U")
N <- nrow(W)
lab <- sprintf("Z%04d", seq_len(N))
dimnames(W) <- list(lab, lab)
# Strict upper-triangle edge list (sparse-safe; no dense conversion)
Up <- Matrix::triu(W, k = 1)
E <- Matrix::summary(Up)
list(
W = W,
labels = lab,
node1 = as.integer(E$i),
node2 = as.integer(E$j)
)
}
# Split a global basis R into per-component blocks R_c
.split_basis_by_component <- function(basis) {
R <- basis$R
comp <- basis$comp
N <- nrow(R)
stopifnot(length(comp$comp_id) == N)
N_comps <- comp$N_comps
# Number of nodes in each component
n_c <- tabulate(comp$comp_id, nbins = N_comps)
# Number of basis columns per component (0 for singletons)
p_c <- pmax(n_c - 1L, 0L)
if (ncol(R) != sum(p_c)) {
stop(
"ncol(R) = ", ncol(R),
" but sum(n_c - 1) = ", sum(p_c),
"; basis dimensions don't match component sizes."
)
}
# Column index ranges for each component
# Example for p_c = c(399,399,0,0,0,0,0):
# col_start = c(1, 400, 799, 799, 799, 799, 799)
col_start <- cumsum(c(1L, head(p_c, -1L)))
col_end <- col_start + p_c - 1L
out <- vector("list", N_comps)
for (c in seq_len(N_comps)) {
if (p_c[c] == 0L) {
# singleton or degenerate component: no basis columns
out[[c]] <- NULL
} else {
row_idx <- which(comp$comp_id == c)
col_idx <- col_start[c]:col_end[c]
out[[c]] <- R[row_idx, col_idx, drop = FALSE]
}
}
out
}
# Check that each component block R_c has columns summing to (approx) zero
.check_basis_sum_to_zero <- function(basis, tol = 1e-6) {
Rc_list <- .split_basis_by_component(basis)
res <- data.frame(
component = integer(0),
max_abs_colsum = numeric(0)
)
# NOTE: use seq_along(Rc_list) to avoid any mismatch with comp$N_comps
for (c in seq_along(Rc_list)) {
Rc <- Rc_list[[c]]
if (is.null(Rc)) next # singleton: no columns, nothing to check
col_sums <- colSums(Rc)
max_abs <- max(abs(col_sums))
res <- rbind(res, data.frame(
component = c,
max_abs_colsum = max_abs
))
}
attr(res, "ok") <- if (nrow(res) == 0L) TRUE else all(res$max_abs_colsum < tol)
res
}
# Check that columns of R_c are (approximately) orthogonal
.check_basis_orthogonality <- function(basis, tol_offdiag = 1e-6) {
Rc_list <- .split_basis_by_component(basis)
res <- data.frame(
component = integer(0),
max_abs_offdiag = numeric(0)
)
for (c in seq_along(Rc_list)) {
Rc <- Rc_list[[c]]
if (is.null(Rc)) next # singleton or no basis columns
G <- crossprod(Rc) # p_c x p_c
diag(G) <- 0 # ignore diagonal
max_off <- if (length(G)) max(abs(G)) else 0
res <- rbind(res, data.frame(
component = c,
max_abs_offdiag = max_off
))
}
attr(res, "ok") <- if (nrow(res) == 0L) TRUE else all(res$max_abs_offdiag < tol_offdiag)
res
}
# Check BYM2 scaling: GM(diag(R_c R_c^T)) ≈ 1 for each component
check_basis_scaling <- function(basis, tol_rel = 0.05) {
Rc_list <- .split_basis_by_component(basis)
res <- data.frame(
component = integer(0),
gm_diag_RRt = numeric(0)
)
for (c in seq_along(Rc_list)) {
Rc <- Rc_list[[c]]
if (is.null(Rc)) next # singleton: no basis, no scaling to check
RRt_diag <- rowSums(Rc^2)
gm <- exp(mean(log(RRt_diag)))
res <- rbind(res, data.frame(
component = c,
gm_diag_RRt = gm
))
}
attr(res, "ok") <- if (nrow(res) == 0L) TRUE else all(abs(res$gm_diag_RRt - 1) <= tol_rel)
res
}
# Check that R is consistent with the Laplacian Q:
# t(R_c) %*% Q_c %*% R_c should be approximately diagonal.
.check_basis_against_laplacian <- function(basis, node1, node2, N,
tol_offdiag = 1e-6) {
if (!requireNamespace("Matrix", quietly = TRUE)) {
stop("Please install the 'Matrix' package.")
}
R <- basis$R
comp <- basis$comp
stopifnot(nrow(R) == N, length(comp$comp_id) == N)
# Build Laplacian Q
Wg <- Matrix::sparseMatrix(
i = c(node1, node2),
j = c(node2, node1),
x = 1,
dims = c(N, N)
)
W <- Matrix::forceSymmetric(Wg, uplo = "U")
d <- as.numeric(Matrix::rowSums(W))
Q <- Matrix::Diagonal(x = d) - W
Rc_list <- .split_basis_by_component(basis)
res <- data.frame(
component = integer(0),
max_abs_offdiag = numeric(0)
)
for (c in seq_along(Rc_list)) {
Rc <- Rc_list[[c]]
if (is.null(Rc)) next # singleton or no basis
idx <- which(comp$comp_id == c)
Qc <- as.matrix(Q[idx, idx, drop = FALSE])
M <- crossprod(Rc, Qc %*% Rc) # p_c x p_c
diag(M) <- 0
max_off <- if (length(M)) max(abs(M)) else 0
res <- rbind(res, data.frame(
component = c,
max_abs_offdiag = max_off
))
}
attr(res, "ok") <- if (nrow(res) == 0L) TRUE else all(res$max_abs_offdiag < tol_offdiag)
res
}
test_that("R construction works", {
# connected graph
adj <- .make_disconnected_lattices(
components = list(c(3, 3)),
n_isolates = 0
)
basis <- .compute_icar_basis(
node1 = adj$node1,
node2 = adj$node2,
n_nodes = nrow(adj$W)
)
expect_true(attr(.check_basis_sum_to_zero(basis), "ok"))
expect_true(attr(.check_basis_orthogonality(basis), "ok"))
expect_true(attr(check_basis_scaling(basis), "ok"))
expect_true(attr(.check_basis_against_laplacian(
basis,
node1 = adj$node1,
node2 = adj$node2,
N = nrow(adj$W)
), "ok"))
# disconnected graph
adj <- .make_disconnected_lattices(
components = list(c(3, 3), c(2, 2)),
n_isolates = 0
)
basis <- .compute_icar_basis(
node1 = adj$node1,
node2 = adj$node2,
n_nodes = nrow(adj$W)
)
expect_true(attr(.check_basis_sum_to_zero(basis), "ok"))
expect_true(attr(.check_basis_orthogonality(basis), "ok"))
expect_true(attr(check_basis_scaling(basis), "ok"))
expect_true(attr(.check_basis_against_laplacian(
basis,
node1 = adj$node1,
node2 = adj$node2,
N = nrow(adj$W)
), "ok"))
# disconnected graph with isolates
adj <- .make_disconnected_lattices(
components = list(c(3, 3)),
n_isolates = 2
)
basis <- .compute_icar_basis(
node1 = adj$node1,
node2 = adj$node2,
n_nodes = nrow(adj$W)
)
expect_true(attr(.check_basis_sum_to_zero(basis), "ok"))
expect_true(attr(.check_basis_orthogonality(basis), "ok"))
expect_true(attr(check_basis_scaling(basis), "ok"))
expect_true(attr(.check_basis_against_laplacian(
basis,
node1 = adj$node1,
node2 = adj$node2,
N = nrow(adj$W)
), "ok"))
})
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.