params <- list(family = "lapis", preset = "homage") ## ----setup-opts, include = FALSE---------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, fig.width = 7, fig.height = 4.1, fig.align = "center", out.width = "92%", dpi = 100 ) ## ----albers-classes, echo=FALSE, results='asis'------------------------------- cat(sprintf( paste0( '' ), params$family, params$preset )) ## ----helpers, include = FALSE------------------------------------------------- # Shared plotting helper. Kept out of the reader's view: the reader sees # the API call and the picture, never the plotting boilerplate. ec_blue <- "#2166ac"; ec_grey <- "grey78" # A computed slice (blue) drawn on top of the full dense spectrum (grey). plot_spectrum <- function(all_vals, computed, ylab = "value", main = NULL) { s <- sort(all_vals, decreasing = TRUE) k <- length(computed) op <- par(mar = c(4, 4.6, if (is.null(main)) 1 else 2.4, 1)); on.exit(par(op)) plot(seq_along(s), s, pch = 19, cex = 0.4, col = ec_grey, bty = "n", xlab = "rank (largest to smallest)", ylab = ylab, main = main) points(seq_len(k), sort(computed, decreasing = TRUE), pch = 19, cex = 1.2, col = ec_blue) legend("topright", c("full spectrum (dense reference)", "computed by eigencore"), pch = 19, pt.cex = c(0.7, 1.2), col = c(ec_grey, ec_blue), bty = "n", cex = 0.9) } ## ----setup-------------------------------------------------------------------- library(eigencore) library(Matrix) ## ----simulate-signal---------------------------------------------------------- set.seed(1) m <- 2000L; n <- 500L; k_true <- 5L U <- qr.Q(qr(matrix(rnorm(m * k_true), m, k_true))) V <- qr.Q(qr(matrix(rnorm(n * k_true), n, k_true))) signal <- U %*% diag(seq(8, 4, length.out = k_true)) %*% t(V) ## ----simulate-sparsify-------------------------------------------------------- mask <- rsparsematrix(m, n, density = 0.04) noise <- rsparsematrix(m, n, density = 0.01, rand.x = function(k) rnorm(k, sd = 0.05)) A <- as((signal * (mask != 0)) + noise, "dgCMatrix") dim(A) ## ----simulate-memory, echo = FALSE-------------------------------------------- dense_mb <- as.numeric(object.size(matrix(0, m, n))) / 1024^2 sparse_mb <- as.numeric(object.size(A)) / 1024^2 ## ----center------------------------------------------------------------------- Ac <- center(A, columns = TRUE) Ac ## ----first-svd---------------------------------------------------------------- fit <- svd_partial(Ac, rank = 5, target = largest()) fit ## ----first-scree, echo = FALSE, fig.cap = "The five largest singular values of the centered matrix (blue) against the full singular spectrum (grey).", fig.alt = "Scatter plot of all 500 singular values sorted descending in grey, with the top five highlighted in blue."---- dense_centered <- scale(as.matrix(A), center = TRUE, scale = FALSE) all_sv <- svd(dense_centered, nu = 0, nv = 0)$d plot_spectrum(all_sv, fit$d, ylab = "singular value") ## ----composed-certificate----------------------------------------------------- fit$certificate$max_backward_error fit$certificate$norm_bound_type fit$certificate$scale_is_estimate ## ----verify-dense------------------------------------------------------------- max(abs(sort(fit$d, decreasing = TRUE) - sort(all_sv[1:5], decreasing = TRUE))) ## ----scale-cols--------------------------------------------------------------- col_var <- (Matrix::colSums(A^2) - m * Matrix::colMeans(A)^2) / (m - 1) w <- 1 / sqrt(col_var) Acs <- scale_cols(Ac, w) fit_scaled <- svd_partial(Acs, rank = 5, target = largest()) fit_scaled$d ## ----validate-scale-cols, include = FALSE------------------------------------- stopifnot( all(is.finite(col_var)), all(col_var > 0), max(abs(col_var * w^2 - 1)) < 1e-12 ) ## ----plan--------------------------------------------------------------------- plan <- plan_solver(svd_problem(Acs), rank = 5, target = largest()) plan$method plan$reasons ## ----matrix-free-------------------------------------------------------------- op <- linear_operator( dim = dim(A), apply = function(X, alpha = 1, beta = 0, Y = NULL) { # Sparse %*% dense returns an S4 dgeMatrix; as.matrix() keeps the # callback's return type consistent with what the native solver expects. Z <- alpha * as.matrix(A %*% X) if (is.null(Y) || beta == 0) Z else Z + beta * Y }, apply_adjoint = function(X, alpha = 1, beta = 0, Y = NULL) { Z <- alpha * as.matrix(crossprod(A, X)) if (is.null(Y) || beta == 0) Z else Z + beta * Y }, name = "matrix-free A" ) fit_mf <- svd_partial(op, rank = 5, target = largest()) fit_mf$certificate$norm_bound_type ## ----matrix-free-hint--------------------------------------------------------- op_hinted <- linear_operator( dim = dim(A), apply = op$apply, apply_adjoint = op$apply_adjoint, name = "matrix-free A (exact norm)", metadata = list(frobenius_norm = norm(A, type = "F")) ) fit_hinted <- svd_partial(op_hinted, rank = 5, target = largest()) fit_hinted$certificate$norm_bound_type fit_hinted$certificate$passed