diff --git a/NEWS.md b/NEWS.md index 4e22041..ccdfc8f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,32 @@ # mfsusieR (development version) +- susieR 0.16.1 compat: `track_ibss_fit.mf_individual` now delegates + to `susieR:::make_track_snapshot` so the per-iteration snapshots + satisfy the new `is_compact_track_snapshots` validator that + `ibss_finalize -> make_susie_track_history` runs at finalize. + Without this, `mfsusie(..., track_fit = TRUE)` errored with + "fit$trace is not a compact SuSiE track" against susieR >= 0.16.1. + The snapshot records mfsusieR's `sigma2` (a `list[M]`) as + `NA_real_` because susieR's helper assumes a scalar; the real + per-iteration values remain available on `fit$elbo` and + `fit$sigma2`. +- New argument `save_mu_method = c("complete", "alpha_collapsed", + "lead")` on `mfsusie()` and the `fsusie()` wrapper. Default + `"complete"` is unchanged; `"alpha_collapsed"` and `"lead"` + shrink `fit$mu` and `fit$mu2` by roughly factor `p`. Under + `"alpha_collapsed"`, `coef.mfsusie` and `mf_post_smooth` are + numerically equivalent to `"complete"` (tolerance 1e-12), via + a precomputed `fit$coef_wavelet[[l]][[m]]` for the raw-X coef + path. Under `"lead"`, the same accessors return a cheap + lead-variable summary biased toward `j* = which.max(alpha[l, ])` + and the fit also carries `fit$top_index[l]`. Both 1D modes + error on `predict.mfsusie(newx)`, the plot per-variant + clfsr matrix, and `model_init` warm-start; refit with + `"complete"` for those operations. +- New helper `mf_thin(fit, method)` performs the same trim as + `save_mu_method` but as a post-fit operation, so callers can + keep a complete checkpoint for warm-starts and a thinned copy + for distribution. - `pip` now incorporates the `V[l] > prior_tol` filter. The per-effect effective slab variance (mean over (m, s) of `sum_k pi[l, m, s, k] * var_k`) populates `model$V[l]` after diff --git a/R/ibss_methods.R b/R/ibss_methods.R index d465729..2d833f9 100644 --- a/R/ibss_methods.R +++ b/R/ibss_methods.R @@ -231,21 +231,41 @@ ibss_initialize.mf_individual <- function(data, params) { #' IBSS iteration tracking (no-op by default) #' #' Tracking adds runtime overhead and large-state copies. mfsusieR -#' Tracking is supported only when `params$track_fit` is `TRUE`, +#' supports tracking only when `params$track_fit` is `TRUE`, #' otherwise this is a no-op (matches the susieR pattern but with #' a smaller default footprint, since per-effect curves are #' large). #' +#' Snapshot format: each element of `tracking` is built by +#' `susieR:::make_track_snapshot(model, iteration)` so the list +#' satisfies the `is_compact_track_snapshots` validator that +#' `ibss_finalize -> make_susie_track_history` runs at finalize +#' (introduced in susieR 0.16.1). `model$sigma2` is sanitised to +#' `NA_real_` for the snapshot because mfsusieR's `sigma2` is a +#' `list[M]` whereas susieR's helper assumes a scalar; the real +#' per-iteration values remain on `fit$sigma2` after finalize. +#' #' @keywords internal #' @noRd track_ibss_fit.mf_individual <- function(data, params, model, tracking, iter, elbo, ...) { if (!isTRUE(params$track_fit)) return(tracking) - tracking[[iter]] <- list( - alpha = model$alpha, - sigma2 = model$sigma2, - pi_V = model$pi_V, - elbo = elbo[iter] - ) + # Delegate to susieR's `make_track_snapshot` so the snapshot list + # passes the `is_compact_track_snapshots` validator that + # `ibss_finalize -> make_susie_track_history` runs at finalize. + # The helper's three data.frames (`alpha`, `effect`, `iteration`) + # are populated from `model$alpha`, `model$V`, `model$lbf`, + # `model$lbf_variable`, all of which mfsusieR carries with + # the same shape as susieR. + # + # mfsusieR's `model$sigma2` is `list[M]` (one entry per outcome, + # each a scalar or length-S vector) whereas `make_track_snapshot -> + # track_scalar` expects a length-1 numeric. Pass a sanitised copy + # with `sigma2 = NA_real_` so the snapshot records NA rather than + # crashing on `as.numeric(list)`. The per-iteration ELBO is still + # available on `fit$elbo`. + m_compact <- model + m_compact$sigma2 <- NA_real_ + tracking[[iter]] <- make_track_snapshot(m_compact, iter - 1L) tracking } diff --git a/R/individual_data_methods.R b/R/individual_data_methods.R index 9088c04..d59fad0 100644 --- a/R/individual_data_methods.R +++ b/R/individual_data_methods.R @@ -801,8 +801,9 @@ post_loglik_prior_hook.mf_individual <- function(data, params, model, ser_stats, pi_warm_start = pi_prev) model$G_prior[[m]][[s]]$fitted_g$pi <- new_pi model$pi_V[[l]][[m]][s, ] <- new_pi - model$fitted_g_per_effect[[l]][[m]][[s]] <- - model$G_prior[[m]][[s]]$fitted_g + if (!is.null(model$fitted_g_per_effect)) + model$fitted_g_per_effect[[l]][[m]][[s]] <- + model$G_prior[[m]][[s]]$fitted_g } } model @@ -840,7 +841,8 @@ post_loglik_prior_hook.mf_individual <- function(data, params, model, ser_stats, fit <- do.call(ebnm_fn, args) model$G_prior[[m]][[s]]$fitted_g <- fit$fitted_g model$pi_V[[l]][[m]][s, ] <- fit$fitted_g$pi - model$fitted_g_per_effect[[l]][[m]][[s]] <- fit$fitted_g + if (!is.null(model$fitted_g_per_effect)) + model$fitted_g_per_effect[[l]][[m]][[s]] <- fit$fitted_g } } model diff --git a/R/mfsusie.R b/R/mfsusie.R index 3a86540..0294732 100644 --- a/R/mfsusie.R +++ b/R/mfsusie.R @@ -178,6 +178,16 @@ #' correction acts on variable selection probabilities only; #' posterior moments given inclusion are unchanged. Default #' `FALSE`. +#' @param cross_iter_prior logical. When `TRUE` (default), the fitted +#' mixture weights `pi` for each effect `l` are stored after each +#' SER step and reloaded as the starting point for the same effect +#' in the next outer IBSS iteration (per-effect warm-start of the +#' prior). When `FALSE`, each SER step starts from the shared +#' initial prior regardless of previous iterations, which is closer +#' to the IBSS theoretical derivation (shared prior across effects). +#' Setting `FALSE` may reduce FDR inflation in high-resolution +#' wavelet fits where per-effect prior persistence can overfit to +#' cross-scale covariance structure in the data. #' @param max_inner_em_steps integer, number of M-step + alpha-update #' sweeps run inside the post-loglik prior hook per (effect, #' outer iter), to keep mixture pi and alpha in sync per effect per outer @@ -202,6 +212,38 @@ #' `mf_post_smooth(fit, X = X, Y = Y, ...)` instead; useful #' when sharing fits where the per-individual data should #' not travel with the fit. +#' @param save_mu_method one of `"complete"` (default), +#' `"aggregated"`, or `"lead"`. Controls the storage shape +#' of the per-effect posterior moments `fit$mu` and `fit$mu2` +#' after the IBSS loop finishes. +#' +#' `"complete"` keeps the full `p x T_basis[m]` per-(effect, +#' outcome) moments; this is the only mode that supports +#' `model_init` warm-starts, `predict.mfsusie(newx)`, and the +#' per-variant lfsr toggle in plots. +#' +#' `"aggregated"` replaces each `p x T` matrix by the +#' alpha-weighted 1 x T summary +#' `mu[[l]][[m]] = sum_j alpha[l, j] * mu_full[l, j, ]` (and +#' the analogous second-moment summary). Storage shrinks by +#' roughly factor `p`. The post-fit consumers `coef.mfsusie`, +#' `mf_post_smooth`, `summary`, `print`, and the alpha-aggregated +#' plot views are numerically equivalent to `"complete"`. To +#' keep `coef` lossless on the raw-X scale, the fit also +#' carries `coef_wavelet[[l]][[m]] = sum_j alpha[l, j] * +#' mu_full[l, j, ] / csd_X[j]` as a separate 1 x T summary. +#' +#' `"lead"` keeps only the lead variable per effect: +#' `mu[[l]][[m]] = mu_full[l, j*, ]` where +#' `j* = which.max(alpha[l, ])`, plus `fit$top_index[l] = j*`. +#' This is a cheap single-variable coefficient summary, biased +#' toward the lead. `coef.mfsusie` and `mf_post_smooth` work but +#' are not the alpha-weighted posterior mean; document accordingly. +#' +#' Both 1D modes (`"aggregated"`, `"lead"`) error on +#' `predict.mfsusie(newx)`, the plot per-variant lfsr toggle, +#' and `model_init`. Use `mf_thin()` to thin a complete fit +#' after the fact while keeping the original for warm-starts. #' #' @return A list of class `c("mfsusie", "susie")` carrying: #' \describe{ @@ -279,8 +321,12 @@ mfsusie <- function(X, Y, alpha_thin_eps = 5e-5, model_init = NULL, small_sample_correction = FALSE, + cross_iter_prior = TRUE, max_inner_em_steps = 5L, - attach_smoothing_inputs = TRUE) { + attach_smoothing_inputs = TRUE, + save_mu_method = c("complete", + "aggregated", + "lead")) { if (!is.logical(small_sample_correction) || length(small_sample_correction) != 1L || is.na(small_sample_correction)) { @@ -288,6 +334,18 @@ mfsusie <- function(X, Y, } prior_variance_scope <- match.arg(prior_variance_scope) residual_variance_scope <- match.arg(residual_variance_scope) + save_mu_method <- match.arg(save_mu_method) + + # Reject thinned fits as model_init: the IBSS warm-start needs the + # full p x T_basis[m] mu / mu2, and 1D modes have already discarded + # the per-variant axis. The error references the storage mode of + # the supplied fit, not the new call's requested mode. + if (!is.null(model_init)) { + init_mode <- attr(model_init, "save_mu_method") %||% "complete" + if (init_mode != "complete") { + stop_save_mu_method_combo("model_init", init_mode) + } + } if (!is.null(mixture_null_weight) && prior_variance_scope %in% c("per_scale_normal", @@ -384,6 +442,7 @@ mfsusie <- function(X, Y, small_sample_correction = small_sample_correction, small_sample_df = if (small_sample_correction) data$n - 1L else NULL, + cross_iter_prior = isTRUE(cross_iter_prior), max_inner_em_steps = as.integer(max_inner_em_steps) ) @@ -448,6 +507,11 @@ mfsusie <- function(X, Y, outcome_names = names(data$D) %||% names(Y) ) + # 7. Apply save_mu_method storage policy. Default "complete" leaves + # fit unchanged. Non-complete modes shrink mu/mu2 by factor p + # and disable predict(newx), per-variant lfsr, and model_init. + fit <- mf_apply_save_mu_method(fit, save_mu_method) + fit } diff --git a/R/mfsusie_methods.R b/R/mfsusie_methods.R index 2bee26e..cfb9722 100644 --- a/R/mfsusie_methods.R +++ b/R/mfsusie_methods.R @@ -45,13 +45,17 @@ mf_invert_per_outcome <- function(coef_wavelet, m, dwt_meta, } # Per-effect coefficient matrix (p x T_basis) for effect l, outcome m, -# on the standardized X scale: alpha_lj * mu_lj_t. +# on the standardized X scale: alpha_lj * mu_lj_t. Only meaningful when +# the fit carries per-variant mu (save_mu_method = "complete"); the only +# caller (mf_total_wavelet, used by predict.mfsusie(newx)) is gated by +# predict's save_mu_method guard, so this need not branch. mf_effect_wavelet <- function(fit, l, m) { fit$alpha[l, ] * fit$mu[[l]][[m]] } # Coefficient sum across all L effects per outcome, on standardized -# X scale, in the wavelet domain. +# X scale, in the wavelet domain. Only callable for fits with +# save_mu_method = "complete"; predict.mfsusie enforces this upstream. mf_total_wavelet <- function(fit, m) { p <- ncol(fit$alpha) T_pad <- fit$dwt_meta$T_basis[m] @@ -92,6 +96,10 @@ predict.mfsusie <- function(object, newx = NULL, ...) { "`newx` has %d columns but the fit was trained on X with %d columns.", ncol(newx), ncol(object$alpha))) } + mode <- mf_save_mu_method(object) + if (mode != "complete") { + stop_save_mu_method_combo("predict.mfsusie(newx)", mode) + } meta <- object$dwt_meta M <- meta$M @@ -162,18 +170,17 @@ coef.mfsusie <- function(object, smooth_method = NULL, ...) { meta <- object$dwt_meta L <- nrow(object$alpha) M <- meta$M - X_scale <- meta$X_scale out <- vector("list", M) for (m in seq_len(M)) { coef_l_wavelet <- matrix(0, nrow = L, ncol = meta$T_basis[m]) for (l in seq_len(L)) { - # Per-variable unscale of mu: mu lives in (X-standardized, - # Y-standardized-then-DWT'd) units; dividing by `X_scale[j]` - # converts it to (X-raw, Y-standardized-then-DWT'd) units so - # the alpha-weighted sum is the per-effect coefficient on the - # raw X scale. - mu_raw_X <- sweep(object$mu[[l]][[m]], 1L, X_scale, "/") - coef_l_wavelet[l, ] <- colSums(object$alpha[l, ] * mu_raw_X) + # Per-effect raw-X wavelet coefficient curve. Helper dispatches + # on save_mu_method: "complete" runs sum_j alpha_lj mu_lj / + # csd_X[j]; "aggregated" reads the precomputed + # fit$coef_wavelet[[l]][[m]] (the per-j scaling cannot be + # recovered after alpha-collapse); "lead" returns mu[j*, ] / + # csd_X[j*]. + coef_l_wavelet[l, ] <- get_coef_wavelet_curve(object, l, m) } # Inverse DWT with `column_center = 0` so the Y intercept # `cm_Y` is NOT added back to a per-effect estimate. The `csd_Y` @@ -698,14 +705,23 @@ mf_post_smooth <- function(fit, next } + has_per_variant_mu <- mf_has_per_variant_mu(fit) for (l in seq_len(L)) { out <- kernel(fit, l, m, T_m, level) effect_curves[[m]][[l]] <- out$effect_estimate credible_bands[[m]][[l]] <- out$credible_band lfsr_curves[[m]][[l]] <- out$lfsr - clfsr_curves[[m]][[l]] <- lfsr_from_gaussian( - fit$mu[[l]][[m]], - sqrt(pmax(fit$mu2[[l]][[m]] - fit$mu[[l]][[m]]^2, 0))) + # Per-variant clfsr requires the full p x T mu / mu2. Under + # save_mu_method = "aggregated" or "lead" we only have a + # 1 x T summary, which would degenerate to the alpha-aggregated + # lfsr already in lfsr_curves; leave clfsr_curves NULL so the + # plot per-variant toggle errors instead of silently displaying + # the wrong shape. + clfsr_curves[[m]][[l]] <- if (has_per_variant_mu) + lfsr_from_gaussian( + fit$mu[[l]][[m]], + sqrt(pmax(fit$mu2[[l]][[m]] - fit$mu[[l]][[m]]^2, 0))) + else NULL } } @@ -722,12 +738,10 @@ mf_post_smooth <- function(fit, meta <- fit$dwt_meta kernel <- function(fit, l, m, T_m, level) { # Alpha-weighted aggregate across variables (no lead-variant - # tie-break). Mean: sum_j alpha[l, j] * mu[l, j, t]; second - # moment: sum_j alpha[l, j] * mu2[l, j, t]. Variance via the - # law of total variance. - mean_w <- as.numeric(fit$alpha[l, ] %*% fit$mu[[l]][[m]]) - mu2_w <- as.numeric(fit$alpha[l, ] %*% fit$mu2[[l]][[m]]) - var_w <- pmax(mu2_w - mean_w^2, 0) + # tie-break). Helper handles all three save_mu_method modes. + mom <- get_effect_wavelet_moments(fit, l, m) + mean_w <- mom$mean_w + var_w <- mom$var_w shrunk_w <- if (T_m == 1L) mean_w else scalewise_soft_threshold(mean_w, sd = sqrt(var_w), @@ -804,8 +818,9 @@ mf_post_smooth <- function(fit, for (l in seq_len(L)) { if (l == exclude) next # Alpha-weighted aggregate per-position wavelet coefficient - # across variables. `mu[[l]][[m]]` is p x T_basis. - mu_w_agg <- as.numeric(fit$alpha[l, ] %*% fit$mu[[l]][[m]]) + # across variables. Helper handles complete (p x T_basis) and + # the 1D save_mu_method modes uniformly. + mu_w_agg <- get_effect_wavelet_moments(fit, l, m)$mean_w eff_pos <- dwt_invert_packed(matrix(mu_w_agg, nrow = 1L), meta, m) if (length(eff_pos) > T_pos) eff_pos <- eff_pos[seq_len(T_pos)] diff --git a/R/mfsusie_plot.R b/R/mfsusie_plot.R index 6bb958d..789b845 100644 --- a/R/mfsusie_plot.R +++ b/R/mfsusie_plot.R @@ -89,6 +89,11 @@ compute_clfsr_matrix <- function(fit, l, m, smoothed = NULL) { !is.null(smoothed$clfsr_curves[[m]][[l]])) { return(smoothed$clfsr_curves[[m]][[l]]) } + mode <- mf_save_mu_method(fit) + if (mode != "complete") { + stop_save_mu_method_combo( + "Per-variant clfsr (compute_clfsr_matrix)", mode) + } mu <- fit$mu[[l]][[m]] sd <- sqrt(pmax(fit$mu2[[l]][[m]] - mu^2, 0)) lfsr_from_gaussian(mu, sd) diff --git a/R/model_class.R b/R/model_class.R index a641d2d..d71a702 100644 --- a/R/model_class.R +++ b/R/model_class.R @@ -72,7 +72,7 @@ initialize_susie_model.mf_individual <- function(data, params, var_y, ...) { G_prior <- if (is.null(prior)) NULL else prior$G_prior pi_V <- if (is.null(prior)) NULL else lapply(seq_len(L), function(.) prior$pi) - fitted_g_per_effect <- if (is.null(prior)) NULL else + fitted_g_per_effect <- if (is.null(prior) || !isTRUE(params$cross_iter_prior)) NULL else lapply(seq_len(L), function(.) { lapply(prior$G_prior, function(G_m) lapply(G_m, function(g_s) g_s$fitted_g)) diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..347419c --- /dev/null +++ b/R/utils.R @@ -0,0 +1,122 @@ +# Miscellaneous internal utilities (internal). +# +# Covers the `save_mu_method` storage policy: helpers for inspecting and +# applying the three mu/mu2 storage modes ("complete", "aggregated", +# "lead"), plus the post-fit `mf_thin()` trimmer. + +# ---- save_mu_method helpers ------------------------------------------ + +mf_save_mu_method <- function(fit) { + attr(fit, "save_mu_method") %||% "complete" +} + +get_effect_wavelet_moments <- function(fit, l, m) { + mu_lm <- fit$mu[[l]][[m]] + mu2_lm <- fit$mu2[[l]][[m]] + if (NROW(mu_lm) == 1L) { + mean_w <- as.numeric(mu_lm) + var_w <- as.numeric(mu2_lm) - mean_w^2 + } else { + alpha_l <- fit$alpha[l, ] + mean_w <- as.numeric(alpha_l %*% mu_lm) + var_w <- as.numeric(alpha_l %*% mu2_lm) - mean_w^2 + } + list(mean_w = mean_w, var_w = pmax(var_w, 0)) +} + +get_coef_wavelet_curve <- function(fit, l, m) { + mode <- mf_save_mu_method(fit) + X_scale <- fit$dwt_meta$X_scale + if (mode == "complete") { + mu_raw_X <- sweep(fit$mu[[l]][[m]], 1L, X_scale, "/") + return(as.numeric(fit$alpha[l, ] %*% mu_raw_X)) + } + if (mode == "aggregated") { + return(as.numeric(fit$coef_wavelet[[l]][[m]])) + } + if (mode == "lead") { + j_star <- fit$top_index[l] + return(as.numeric(fit$mu[[l]][[m]]) / X_scale[j_star]) + } + stop(sprintf("Unknown save_mu_method on fit: %s", mode)) +} + +mf_has_per_variant_mu <- function(fit) { + mf_save_mu_method(fit) == "complete" +} + +stop_save_mu_method_combo <- function(operation, mode) { + stop(sprintf( + "%s requires save_mu_method = \"complete\"; got \"%s\". %s", + operation, mode, + "Refit with save_mu_method = \"complete\", or call mf_thin() only after running predict / per-variant lfsr / model_init."), call. = FALSE) +} + +# ---- finalize-time trim ----------------------------------------------- + +mf_apply_save_mu_method <- function(fit, mode) { + if (mode == "complete") { + attr(fit, "save_mu_method") <- "complete" + return(fit) + } + if (!(mode %in% c("aggregated", "lead"))) { + stop(sprintf("Unknown save_mu_method: %s", mode)) + } + + L <- nrow(fit$alpha) + M <- fit$dwt_meta$M + X_scale <- fit$dwt_meta$X_scale + + if (mode == "aggregated") { + coef_wavelet <- vector("list", L) + for (l in seq_len(L)) { + coef_wavelet[[l]] <- vector("list", M) + alpha_l <- fit$alpha[l, ] + for (m in seq_len(M)) { + mu_lm <- fit$mu[[l]][[m]] + mu2_lm <- fit$mu2[[l]][[m]] + mu_collapsed <- matrix(alpha_l %*% mu_lm, nrow = 1L) + mu2_collapsed <- matrix(alpha_l %*% mu2_lm, nrow = 1L) + mu_raw_X <- sweep(mu_lm, 1L, X_scale, "/") + coef_lm <- matrix(alpha_l %*% mu_raw_X, nrow = 1L) + fit$mu[[l]][[m]] <- mu_collapsed + fit$mu2[[l]][[m]] <- mu2_collapsed + coef_wavelet[[l]][[m]] <- coef_lm + } + } + fit$coef_wavelet <- coef_wavelet + attr(fit, "save_mu_method") <- "aggregated" + return(fit) + } + + # mode == "lead" + top_index <- integer(L) + for (l in seq_len(L)) { + j_star <- which.max(fit$alpha[l, ]) + top_index[l] <- j_star + for (m in seq_len(M)) { + fit$mu[[l]][[m]] <- fit$mu[[l]][[m]][j_star, , drop = FALSE] + fit$mu2[[l]][[m]] <- fit$mu2[[l]][[m]][j_star, , drop = FALSE] + } + } + fit$top_index <- top_index + attr(fit, "save_mu_method") <- "lead" + fit +} + +# ---- post-fit thinning ------------------------------------------------ + +#' @keywords internal +mf_thin <- function(fit, method = c("aggregated", "lead")) { + if (!inherits(fit, "mfsusie")) { + stop("`fit` must be an `mfsusie` object.") + } + method <- match.arg(method) + current <- mf_save_mu_method(fit) + if (current != "complete") { + stop(sprintf( + "mf_thin() requires a fit with save_mu_method = \"complete\"; got \"%s\". A fit can only be thinned once.", + current), call. = FALSE) + } + mf_apply_save_mu_method(fit, method) +} diff --git a/R/utils_wavelet.R b/R/utils_wavelet.R index c927fd0..30efcb5 100644 --- a/R/utils_wavelet.R +++ b/R/utils_wavelet.R @@ -48,12 +48,12 @@ mf_low_count_indices <- function(Y_wd, threshold = 0) { #' Column-wise rank-based normal quantile transform #' #' Applies `qnorm(rank(., ties.method = "random") / (n + 1))` -#' column-by-column, after seeding the RNG to `set.seed(1)` for -#' tie-break reproducibility. Each input column is mapped to the -#' standard normal scale via its empirical rank. +#' column-by-column to non-NA entries only. NA positions are preserved +#' as NA in the output. RNG is seeded to `set.seed(1)` per column for +#' tie-break reproducibility. #' #' @param Y_wd numeric matrix. -#' @return numeric matrix; same dimensions as `Y_wd`. +#' @return numeric matrix; same dimensions as `Y_wd`, NA positions unchanged. #' @references #' Manuscript: methods/online_method.tex #' (column-wise rank-INT for non-Gaussian wavelet coefficients). @@ -61,18 +61,17 @@ mf_low_count_indices <- function(Y_wd, threshold = 0) { #' @keywords internal #' @noRd mf_quantile_normalize <- function(Y_wd) { - if (is.null(dim(Y_wd))) { + qnorm_col <- function(col) { + non_na <- which(!is.na(col)) + if (length(non_na) == 0L) return(col) set.seed(1L) - return(qqnorm(rank(Y_wd, ties.method = "random"), - plot.it = FALSE)$x) + col[non_na] <- qqnorm(rank(col[non_na], ties.method = "random"), + plot.it = FALSE)$x + col } - out <- matrix(0, nrow(Y_wd), ncol(Y_wd)) - for (j in seq_len(ncol(Y_wd))) { - set.seed(1L) - out[, j] <- qqnorm(rank(Y_wd[, j], ties.method = "random"), - plot.it = FALSE)$x - } - out + if (is.null(dim(Y_wd))) return(qnorm_col(Y_wd)) + for (j in seq_len(ncol(Y_wd))) Y_wd[, j] <- qnorm_col(Y_wd[, j]) + Y_wd } #' Column-wise centering and scaling diff --git a/R/zzz.R b/R/zzz.R index b95d9e3..3aef056 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -18,6 +18,7 @@ get_var_y <- NULL initialize_susie_model <- NULL initialize_fitted <- NULL get_cs <- NULL +make_track_snapshot <- NULL vloglik_point_laplace <- NULL #' @keywords internal @@ -35,7 +36,8 @@ vloglik_point_laplace <- NULL "get_var_y", "initialize_susie_model", "initialize_fitted", - "get_cs")) { + "get_cs", + "make_track_snapshot")) { assign(fn, get(fn, envir = susie_ns), envir = pkg_ns) } assign("vloglik_point_laplace", diff --git a/inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R b/inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R new file mode 100644 index 0000000..6a2188b --- /dev/null +++ b/inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R @@ -0,0 +1,298 @@ +# Follow-up to benchmark_per_scale_normal_6grid.R: same 6-cell prior grid +# but two new scenarios meant to expose what the Gaussian baseline could not: +# +# 1. "heavy_tailed_signal": same causal structure as the baseline plus +# 18% per-cell outlier contamination on each Y (sd = 4 vs sd = 1 +# Gaussian noise floor). Tests whether wavelet_qnorm = TRUE recovers +# power that wavelet_qnorm = FALSE loses on heavy-tailed Y. +# +# 2. "null_no_signal": no causal SNPs, pure Gaussian noise. Tests the +# type-I rate of each cell at the 0.05 PIP threshold and whether any +# cell spuriously declares a credible set. Power and FDR are not +# meaningful in this scenario; we report n_disc, has_disc (any +# discovery T/F), cs_count, and runtime. +# +# Per Gao 2026-05-03 Slack the prior grid is held fixed at the 6 cells of +# the baseline (per_scale x {mnw 0.05, 0} x {qnorm F, T} plus +# per_scale_normal x {qnorm F, T}). Only the scenario axis is new. +# +# Estimated wall-clock based on the 36 min baseline (one Gaussian +# scenario, 30 fits): ~60-90 min for 60 fits across two scenarios. +# Submitted with 3 h SLURM time-limit for headroom. +# +# Usage: +# Rscript inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R +# +# Output: +# inst/bench/profiling/results/heavy_tailed_null_6grid_.rds +# inst/bench/profiling/results/heavy_tailed_null_6grid__summary.csv + +suppressPackageStartupMessages({ + library(devtools) + load_all(quiet = TRUE) +}) + +# ---- Parameters (mirror the baseline so cells are directly comparable) ---- + +set.seed(2026L * 503L + 21L) +n <- 84L +p <- 500L +M <- 2L +Tlen <- 64L +n_rep <- 5L +L <- 10L +true_indices_signal <- c(50L, 220L, 380L) +pip_thresh <- 0.05 + +bench_grid <- rbind( + expand.grid(wavelet_qnorm = c(FALSE, TRUE), + prior_variance_scope = "per_scale", + mixture_null_weight = c(0.05, 0), + stringsAsFactors = FALSE), + data.frame (wavelet_qnorm = c(FALSE, TRUE), + prior_variance_scope = "per_scale_normal", + mixture_null_weight = NA_real_, + stringsAsFactors = FALSE) +) +stopifnot(nrow(bench_grid) == 6L) + +scenarios <- c("heavy_tailed_signal", "null_no_signal") + +# ---- Per-scenario data builder ------------------------------------- + +build_dataset <- function(scenario, rep_seed) { + set.seed(rep_seed) + X <- matrix(rnorm(n * p), n, p) + + if (scenario == "heavy_tailed_signal") { + effect_curve <- numeric(Tlen); effect_curve[20:40] <- 1 + beta_per_snp <- matrix(0, p, Tlen) + for (j in true_indices_signal) beta_per_snp[j, ] <- effect_curve + Y <- vector("list", M) + for (m in seq_len(M)) { + sig <- X %*% beta_per_snp + noise <- matrix(rnorm(n * Tlen, sd = 1.0), n, Tlen) + # 18% outlier contamination, sd = 4 (mirrors data-raw outcome 2). + mask <- matrix(rbinom(n * Tlen, 1L, 0.18), n, Tlen) + outl <- mask * matrix(rnorm(n * Tlen, sd = 4.0), n, Tlen) + Y[[m]] <- sig + noise + outl + } + return(list(X = X, Y = Y, true_idx = true_indices_signal, + scenario = scenario)) + } + + if (scenario == "null_no_signal") { + Y <- vector("list", M) + for (m in seq_len(M)) { + Y[[m]] <- matrix(rnorm(n * Tlen, sd = 1.0), n, Tlen) + } + return(list(X = X, Y = Y, true_idx = integer(0L), + scenario = scenario)) + } + + stop(sprintf("Unknown scenario: %s", scenario)) +} + +# ---- Per-cell metrics ------------------------------------------------ +# +# Two metrics, both reported per (scenario, cell, rep). See +# benchmark_per_scale_normal_6grid.R::eval_fit for the SNP-level vs +# CS-level definitions; this version adds the null-scenario branch +# where FDR / power are undefined and per-CS judgments are all FP +# by construction. + +eval_fit <- function(fit, X, true_idx, pip_thresh, scenario, + ld_thresh = 0.5, + purity_thresh = 0.8, + pip_high_thresh = 0.5) { + pip <- fit$pip + selected <- which(pip > pip_thresh) + n_disc <- length(selected) + n_true <- length(true_idx) + cs_count <- if (!is.null(fit$sets$cs)) length(fit$sets$cs) else 0L + cs_purities <- if (!is.null(fit$sets$purity)) + fit$sets$purity[, "min.abs.corr"] else rep(NA_real_, cs_count) + cs_purity <- if (cs_count > 0L) mean(cs_purities, na.rm = TRUE) else NA_real_ + + # ---- CS-level + collect high-purity CS members for hybrid SNP-level + cs_tp <- 0L; cs_fp <- 0L + causal_covered <- integer(0L) + high_purity_cs_members <- integer(0L) + in_any_cs <- integer(0L) + if (cs_count > 0L) { + for (i in seq_along(fit$sets$cs)) { + members <- fit$sets$cs[[i]] + in_any_cs <- union(in_any_cs, members) + if (!is.na(cs_purities[i]) && cs_purities[i] >= purity_thresh) { + high_purity_cs_members <- union(high_purity_cs_members, members) + } + lead <- members[which.max(pip[members])] + hit_causal <- lead %in% true_idx + if (!hit_causal && length(true_idx) > 0L) { + cors <- abs(suppressWarnings(stats::cor(X[, lead], X[, true_idx]))) + if (any(cors >= ld_thresh, na.rm = TRUE)) { + hit_causal <- TRUE + covered <- true_idx[which.max(cors)] + causal_covered <- union(causal_covered, covered) + } + } else if (hit_causal) { + causal_covered <- union(causal_covered, lead) + } + if (hit_causal) cs_tp <- cs_tp + 1L else cs_fp <- cs_fp + 1L + } + } + + # ---- Hybrid SNP-level: high-purity CS members OR not-in-CS PIP > pip_high_thresh + not_in_cs_high_pip <- which(pip > pip_high_thresh & + !(seq_along(pip) %in% in_any_cs)) + hyb_disc <- union(high_purity_cs_members, not_in_cs_high_pip) + hyb_n_disc <- length(hyb_disc) + hyb_n_tp <- sum(hyb_disc %in% true_idx) + hyb_n_fp <- hyb_n_disc - hyb_n_tp + fdr_hyb <- if (hyb_n_disc > 0L) hyb_n_fp / hyb_n_disc else 0 + power_hyb <- if (length(true_idx) > 0L) + sum(true_idx %in% hyb_disc) / length(true_idx) else NA_real_ + + if (scenario == "null_no_signal") { + # SNP-level FDR / power undefined under no signal; every CS and + # every hybrid discovery is a false positive by construction. + return(list(n_disc = n_disc, n_tp = NA_integer_, n_fp = n_disc, + fdr = NA_real_, power = NA_real_, + has_disc = (n_disc > 0L), + cs_count = cs_count, cs_purity = cs_purity, + cs_tp = 0L, cs_fp = cs_fp, + fdr_cs = if (cs_count > 0L) 1 else 0, + power_cs = NA_real_, + hyb_n_disc = hyb_n_disc, + hyb_n_tp = 0L, hyb_n_fp = hyb_n_disc, + fdr_hyb = if (hyb_n_disc > 0L) 1 else 0, + power_hyb = NA_real_, + niter = fit$niter, converged = isTRUE(fit$converged))) + } + + n_tp <- sum(selected %in% true_idx) + n_fp <- n_disc - n_tp + fdr <- if (n_disc > 0L) n_fp / n_disc else 0 + power <- n_tp / n_true + fdr_cs <- if (cs_count > 0L) cs_fp / cs_count else 0 + power_cs <- length(causal_covered) / length(true_idx) + list(n_disc = n_disc, n_tp = n_tp, n_fp = n_fp, fdr = fdr, power = power, + has_disc = (n_disc > 0L), + cs_count = cs_count, cs_purity = cs_purity, + cs_tp = cs_tp, cs_fp = cs_fp, + fdr_cs = fdr_cs, power_cs = power_cs, + hyb_n_disc = hyb_n_disc, hyb_n_tp = hyb_n_tp, hyb_n_fp = hyb_n_fp, + fdr_hyb = fdr_hyb, power_hyb = power_hyb, + niter = fit$niter, converged = isTRUE(fit$converged)) +} + +# ---- Driver --------------------------------------------------------- + +results <- list() +row_idx <- 0L +t0 <- Sys.time() + +for (sc in scenarios) { + for (rep_i in seq_len(n_rep)) { + d <- build_dataset(sc, rep_seed = 200L + rep_i) + for (g in seq_len(nrow(bench_grid))) { + cell <- bench_grid[g, ] + fit_args <- list( + X = d$X, + Y = d$Y, + L = L, + verbose = FALSE, + prior_variance_scope = cell$prior_variance_scope, + wavelet_qnorm = cell$wavelet_qnorm, + save_mu_method = "alpha_collapsed" + ) + if (!is.na(cell$mixture_null_weight)) { + fit_args$mixture_null_weight <- cell$mixture_null_weight + } + t_start <- Sys.time() + mem_before <- gc(reset = TRUE, verbose = FALSE)[2L, 6L] + fit <- tryCatch( + do.call(mfsusie, fit_args), + error = function(e) { + warning(sprintf("scenario=%s rep=%d cell=%d errored: %s", + sc, rep_i, g, conditionMessage(e))) + NULL + } + ) + t_elapsed <- as.numeric(difftime(Sys.time(), t_start, units = "secs")) + mem_after <- gc(verbose = FALSE)[2L, 6L] + if (is.null(fit)) next + m <- eval_fit(fit, d$X, d$true_idx, pip_thresh, sc) + fit_size_mb <- as.numeric(object.size(fit)) / (1024 * 1024) + row_idx <- row_idx + 1L + results[[row_idx]] <- data.frame( + scenario = sc, + rep = rep_i, + cell = g, + wavelet_qnorm = cell$wavelet_qnorm, + prior_variance_scope = cell$prior_variance_scope, + mixture_null_weight = cell$mixture_null_weight, + n_disc = m$n_disc, n_tp = m$n_tp, n_fp = m$n_fp, + fdr = m$fdr, power = m$power, has_disc = m$has_disc, + cs_count = m$cs_count, cs_purity = m$cs_purity, + cs_tp = m$cs_tp, cs_fp = m$cs_fp, + fdr_cs = m$fdr_cs, power_cs = m$power_cs, + hyb_n_disc = m$hyb_n_disc, hyb_n_tp = m$hyb_n_tp, hyb_n_fp = m$hyb_n_fp, + fdr_hyb = m$fdr_hyb, power_hyb = m$power_hyb, + niter = m$niter, converged = m$converged, + runtime_s = t_elapsed, fit_size_mb = fit_size_mb, + mem_used_mb = mem_after - mem_before, + rep_seed = 200L + rep_i, + fit_pip = I(list(fit$pip)), + fit_cs = I(list(fit$sets$cs)), + fit_purity = I(list(fit$sets$purity)), + fit_true_idx = I(list(d$true_idx)), + stringsAsFactors = FALSE + ) + cat(sprintf("scn=%s rep=%d cell=%d (%s, qnorm=%s, mnw=%s) fdr=%s power=%s fdr_cs=%s power_cs=%s fdr_hyb=%s power_hyb=%s cs_tp=%d cs_fp=%d t=%.1fs\n", + sc, rep_i, g, cell$prior_variance_scope, cell$wavelet_qnorm, + if (is.na(cell$mixture_null_weight)) "NA" + else format(cell$mixture_null_weight), + if (is.na(m$fdr)) "NA" else sprintf("%.3f", m$fdr), + if (is.na(m$power)) "NA" else sprintf("%.3f", m$power), + if (is.na(m$fdr_cs)) "NA" else sprintf("%.3f", m$fdr_cs), + if (is.na(m$power_cs)) "NA" else sprintf("%.3f", m$power_cs), + if (is.na(m$fdr_hyb)) "NA" else sprintf("%.3f", m$fdr_hyb), + if (is.na(m$power_hyb)) "NA" else sprintf("%.3f", m$power_hyb), + m$cs_tp, m$cs_fp, t_elapsed)) + } + } +} + +t_total <- as.numeric(difftime(Sys.time(), t0, units = "secs")) +cat(sprintf("\nTotal wall-clock: %.1f s\n", t_total)) + +results_df <- do.call(rbind, results) + +# ---- Persist outputs ------------------------------------------------ + +out_dir <- "inst/bench/profiling/results" +dir.create(out_dir, showWarnings = FALSE, recursive = TRUE) +ts <- format(Sys.time(), "%Y%m%d_%H%M") +out_rds <- file.path(out_dir, sprintf("heavy_tailed_null_6grid_%s.rds", ts)) +out_csv <- file.path(out_dir, + sprintf("heavy_tailed_null_6grid_%s_summary.csv", ts)) +saveRDS(results_df, out_rds) +cat(sprintf("Saved per-replicate results to %s\n", out_rds)) + +# Aggregate per (scenario, cell). Drop mixture_null_weight from the formula +# so per_scale_normal rows (NA mnw) are not silently dropped. Drop the +# fit_* list-columns since aggregate cannot reduce them. +agg_df <- aggregate( + cbind(fdr, power, n_disc, has_disc, cs_count, cs_purity, + cs_tp, cs_fp, fdr_cs, power_cs, + hyb_n_disc, hyb_n_tp, hyb_n_fp, fdr_hyb, power_hyb, + niter, runtime_s, fit_size_mb) + ~ scenario + cell + prior_variance_scope + wavelet_qnorm, + data = transform(results_df, fit_pip = NULL, fit_cs = NULL, + fit_purity = NULL, fit_true_idx = NULL), + FUN = mean, na.action = na.pass) +agg_df <- agg_df[order(agg_df$scenario, agg_df$cell), ] +write.csv(agg_df, out_csv, row.names = FALSE) +cat(sprintf("Saved per-cell summary to %s\n", out_csv)) +print(agg_df, digits = 3) diff --git a/inst/bench/profiling/benchmark_per_scale_normal_6grid.R b/inst/bench/profiling/benchmark_per_scale_normal_6grid.R new file mode 100644 index 0000000..21180a6 --- /dev/null +++ b/inst/bench/profiling/benchmark_per_scale_normal_6grid.R @@ -0,0 +1,285 @@ +# 6-grid benchmark for the per_scale_normal vs per_scale + mixture_null_weight +# combinations Gao listed on 2026-05-03 Slack. Runs FDR / power / runtime / +# memory / convergence per cell over a small replicate count, writes a tidy +# data.frame to inst/bench/profiling/results/. +# +# Estimated wall-clock: ~10-25 minutes total on a single core at the default +# sim size (n = 84, p = 500, T = 64, M = 2, n_rep = 5, L = 10). Scaling p, T, +# n_rep, or L past these multiplies the wall-clock approximately linearly. +# Per CLAUDE.md hard rule 3, ask the user before pushing past the 30-minute +# threshold. +# +# Usage from the package root: +# Rscript inst/bench/profiling/benchmark_per_scale_normal_6grid.R +# +# Output: +# inst/bench/profiling/results/per_scale_normal_6grid_.rds +# - tidy data.frame with one row per (grid cell, replicate) +# inst/bench/profiling/results/per_scale_normal_6grid__summary.csv +# - per-cell aggregates ready for the results memo + +suppressPackageStartupMessages({ + library(devtools) + load_all(quiet = TRUE) +}) + +# ---- Simulation parameters (binding for this benchmark) ------------- + +set.seed(2026L * 503L) +n <- 84L # mirrors Anjing's ATAC cohort size +p <- 500L # large enough to exercise FDR; not so large the run + # blows past 30 min. +M <- 2L # two outcomes +Tlen <- 64L # single dyadic length, both outcomes +n_rep <- 5L # replicates per grid cell +L <- 10L # IBSS effect cap +true_indices <- c(50L, 220L, 380L) # three causal SNPs +pip_thresh <- 0.05 # PIP cutoff for FDR / power summaries + +# ---- Grid (6 cells per Gao 2026-05-03) ------------------------------ + +bench_grid <- rbind( + expand.grid(wavelet_qnorm = c(FALSE, TRUE), + prior_variance_scope = "per_scale", + mixture_null_weight = c(0.05, 0), + stringsAsFactors = FALSE), + data.frame (wavelet_qnorm = c(FALSE, TRUE), + prior_variance_scope = "per_scale_normal", + mixture_null_weight = NA_real_, + stringsAsFactors = FALSE) +) +stopifnot(nrow(bench_grid) == 6L) + +# ---- Per-replicate data builder ------------------------------------- + +build_dataset <- function(rep_seed) { + set.seed(rep_seed) + X <- matrix(rnorm(n * p), n, p) + effect_curve <- numeric(Tlen) + effect_curve[20:40] <- 1 + beta_per_snp <- matrix(0, nrow = p, ncol = Tlen) + for (j in true_indices) beta_per_snp[j, ] <- effect_curve + Y <- vector("list", M) + for (m in seq_len(M)) { + sig <- X %*% beta_per_snp + Y[[m]] <- sig + matrix(rnorm(n * Tlen, sd = 1.0), n, Tlen) + } + list(X = X, Y = Y) +} + +# ---- Per-cell metrics ------------------------------------------------ +# +# Three metrics, all reported per (cell, rep): +# +# SNP-level loose (PIP > pip_thresh, default 0.05) +# Every SNP with pip above threshold is one judgment regardless +# of CS membership. Sensitive to spurious low-PIP leakage. +# n_disc / n_tp / n_fp / fdr / power +# +# SNP-level hybrid (CS purity > purity_thresh) OR (no CS AND PIP > pip_high_thresh) +# A SNP counts as a discovery if it sits in any credible set whose +# min.abs.corr >= purity_thresh (default 0.8), OR if it is outside +# every CS but has PIP above pip_high_thresh (default 0.5). This +# is the fine-mapping practice view: trust high-purity CSes, +# and only accept stand-alone SNPs at high PIP. +# hyb_n_disc / hyb_n_tp / hyb_n_fp / fdr_hyb / power_hyb +# +# CS-level (vignette / per_scale_normal_vignette_sweep view) +# For each fit$sets$cs, take the lead = SNP within the CS with +# max pip. Classify TP if lead is a causal SNP OR +# |cor(X[, lead], X[, causal])| >= ld_thresh for some causal. +# cs_tp / cs_fp / fdr_cs / power_cs + +eval_fit <- function(fit, X, true_idx, pip_thresh, + ld_thresh = 0.5, + purity_thresh = 0.8, + pip_high_thresh = 0.5) { + pip <- fit$pip + cs_count <- if (!is.null(fit$sets$cs)) length(fit$sets$cs) else 0L + cs_purities <- if (!is.null(fit$sets$purity)) + fit$sets$purity[, "min.abs.corr"] else rep(NA_real_, cs_count) + cs_purity <- if (cs_count > 0L) mean(cs_purities, na.rm = TRUE) else NA_real_ + + # ---- SNP-level (loose, PIP threshold) + selected <- which(pip > pip_thresh) + n_disc <- length(selected) + n_true <- length(true_idx) + n_tp <- sum(selected %in% true_idx) + n_fp <- n_disc - n_tp + fdr <- if (n_disc > 0L) n_fp / n_disc else 0 + power <- if (n_true > 0L) n_tp / n_true else NA_real_ + + # ---- CS-level + cs_tp <- 0L; cs_fp <- 0L + causal_covered <- integer(0L) + high_purity_cs_members <- integer(0L) + in_any_cs <- integer(0L) + if (cs_count > 0L) { + for (i in seq_along(fit$sets$cs)) { + members <- fit$sets$cs[[i]] + in_any_cs <- union(in_any_cs, members) + if (!is.na(cs_purities[i]) && cs_purities[i] >= purity_thresh) { + high_purity_cs_members <- union(high_purity_cs_members, members) + } + lead <- members[which.max(pip[members])] + hit_causal <- lead %in% true_idx + if (!hit_causal && length(true_idx) > 0L) { + cors <- abs(suppressWarnings(stats::cor(X[, lead], X[, true_idx]))) + if (any(cors >= ld_thresh, na.rm = TRUE)) { + hit_causal <- TRUE + covered <- true_idx[which.max(cors)] + causal_covered <- union(causal_covered, covered) + } + } else if (hit_causal) { + causal_covered <- union(causal_covered, lead) + } + if (hit_causal) cs_tp <- cs_tp + 1L else cs_fp <- cs_fp + 1L + } + } + fdr_cs <- if (cs_count > 0L) cs_fp / cs_count else 0 + power_cs <- if (length(true_idx) > 0L) + length(causal_covered) / length(true_idx) else NA_real_ + + # ---- SNP-level hybrid: high-purity CS members OR not-in-CS high PIP + not_in_cs_high_pip <- which(pip > pip_high_thresh & + !(seq_along(pip) %in% in_any_cs)) + hyb_disc <- union(high_purity_cs_members, not_in_cs_high_pip) + hyb_n_disc <- length(hyb_disc) + hyb_n_tp <- sum(hyb_disc %in% true_idx) + hyb_n_fp <- hyb_n_disc - hyb_n_tp + fdr_hyb <- if (hyb_n_disc > 0L) hyb_n_fp / hyb_n_disc else 0 + power_hyb <- if (length(true_idx) > 0L) + sum(true_idx %in% hyb_disc) / length(true_idx) else NA_real_ + + list(n_disc = n_disc, n_tp = n_tp, n_fp = n_fp, + fdr = fdr, power = power, + cs_count = cs_count, cs_purity = cs_purity, + cs_tp = cs_tp, cs_fp = cs_fp, + fdr_cs = fdr_cs, power_cs = power_cs, + hyb_n_disc = hyb_n_disc, hyb_n_tp = hyb_n_tp, hyb_n_fp = hyb_n_fp, + fdr_hyb = fdr_hyb, power_hyb = power_hyb, + niter = fit$niter, converged = isTRUE(fit$converged)) +} + +# ---- Driver --------------------------------------------------------- + +results <- list() +row_idx <- 0L + +t0 <- Sys.time() +for (rep_i in seq_len(n_rep)) { + d <- build_dataset(rep_seed = 100L + rep_i) + for (g in seq_len(nrow(bench_grid))) { + cell <- bench_grid[g, ] + fit_args <- list( + X = d$X, + Y = d$Y, + L = L, + verbose = FALSE, + prior_variance_scope = cell$prior_variance_scope, + wavelet_qnorm = cell$wavelet_qnorm, + save_mu_method = "alpha_collapsed" # benchmark uses thinned fits + ) + if (!is.na(cell$mixture_null_weight)) { + fit_args$mixture_null_weight <- cell$mixture_null_weight + } + t_start <- Sys.time() + mem_before <- gc(reset = TRUE, verbose = FALSE)[2L, 6L] # Mb used post-gc + fit <- tryCatch( + do.call(mfsusie, fit_args), + error = function(e) { + warning(sprintf("rep=%d cell=%d errored: %s", rep_i, g, + conditionMessage(e))) + NULL + } + ) + t_elapsed <- as.numeric(difftime(Sys.time(), t_start, units = "secs")) + mem_after <- gc(verbose = FALSE)[2L, 6L] + if (is.null(fit)) next + metrics <- eval_fit(fit, d$X, true_indices, pip_thresh) + fit_size_mb <- as.numeric(object.size(fit)) / (1024 * 1024) + row_idx <- row_idx + 1L + # Save the minimal fit shape needed to recompute any future metric + # (pip + sets$cs + sets$purity + true_idx + rep_seed). Stored as + # list-columns so the resulting data.frame still aggregates on the + # numeric columns; aggregation formulas just skip these fields. + results[[row_idx]] <- data.frame( + rep = rep_i, + cell = g, + wavelet_qnorm = cell$wavelet_qnorm, + prior_variance_scope = cell$prior_variance_scope, + mixture_null_weight = cell$mixture_null_weight, + n_disc = metrics$n_disc, + n_tp = metrics$n_tp, + n_fp = metrics$n_fp, + fdr = metrics$fdr, + power = metrics$power, + cs_count = metrics$cs_count, + cs_purity = metrics$cs_purity, + cs_tp = metrics$cs_tp, + cs_fp = metrics$cs_fp, + fdr_cs = metrics$fdr_cs, + power_cs = metrics$power_cs, + hyb_n_disc = metrics$hyb_n_disc, + hyb_n_tp = metrics$hyb_n_tp, + hyb_n_fp = metrics$hyb_n_fp, + fdr_hyb = metrics$fdr_hyb, + power_hyb = metrics$power_hyb, + niter = metrics$niter, + converged = metrics$converged, + runtime_s = t_elapsed, + fit_size_mb = fit_size_mb, + mem_used_mb = mem_after - mem_before, + rep_seed = 100L + rep_i, + fit_pip = I(list(fit$pip)), + fit_cs = I(list(fit$sets$cs)), + fit_purity = I(list(fit$sets$purity)), + fit_true_idx = I(list(true_indices)), + stringsAsFactors = FALSE + ) + cat(sprintf("rep=%d cell=%d (%s, qnorm=%s, mnw=%s) fdr=%.3f power=%.3f fdr_cs=%.3f power_cs=%.3f fdr_hyb=%.3f power_hyb=%.3f cs_tp=%d cs_fp=%d t=%.1fs\n", + rep_i, g, cell$prior_variance_scope, + cell$wavelet_qnorm, + if (is.na(cell$mixture_null_weight)) "NA" + else format(cell$mixture_null_weight), + metrics$fdr, metrics$power, + metrics$fdr_cs, metrics$power_cs, + metrics$fdr_hyb, metrics$power_hyb, + metrics$cs_tp, metrics$cs_fp, + t_elapsed)) + } +} +t_total <- as.numeric(difftime(Sys.time(), t0, units = "secs")) +cat(sprintf("\nTotal wall-clock: %.1f s\n", t_total)) + +results_df <- do.call(rbind, results) + +# ---- Persist outputs ------------------------------------------------ + +out_dir <- "inst/bench/profiling/results" +dir.create(out_dir, showWarnings = FALSE, recursive = TRUE) +ts <- format(Sys.time(), "%Y%m%d_%H%M") +out_rds <- file.path(out_dir, sprintf("per_scale_normal_6grid_%s.rds", ts)) +out_csv <- file.path(out_dir, + sprintf("per_scale_normal_6grid_%s_summary.csv", ts)) +saveRDS(results_df, out_rds) +cat(sprintf("Saved per-replicate results to %s\n", out_rds)) + +# Aggregate by cell directly (cell uniquely identifies a row of the +# bench grid, including the per_scale_normal cells whose mnw is NA). +# The list-columns (fit_pip / fit_cs / fit_purity / fit_true_idx) are +# dropped from the formula so aggregate stays on the numeric columns. +summary_df <- aggregate( + cbind(fdr, power, n_disc, cs_count, cs_purity, + cs_tp, cs_fp, fdr_cs, power_cs, + hyb_n_disc, hyb_n_tp, hyb_n_fp, fdr_hyb, power_hyb, + niter, runtime_s, fit_size_mb) + ~ cell + prior_variance_scope + wavelet_qnorm, + data = transform(results_df, mixture_null_weight = NULL, + fit_pip = NULL, fit_cs = NULL, + fit_purity = NULL, fit_true_idx = NULL), + FUN = mean, na.action = na.pass) +summary_df <- summary_df[order(summary_df$cell), ] +write.csv(summary_df, out_csv, row.names = FALSE) +cat(sprintf("Saved per-cell summary to %s\n", out_csv)) +print(summary_df, digits = 3) diff --git a/inst/notes/fdr-inflation-analysis.ipynb b/inst/notes/fdr-inflation-analysis.ipynb new file mode 100644 index 0000000..cf1d04d --- /dev/null +++ b/inst/notes/fdr-inflation-analysis.ipynb @@ -0,0 +1,146 @@ +{ + "nbformat": 4, + "nbformat_minor": 5, + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "r", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "4.4.3" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": "# FDR inflation investigation in mfsusieR\n\n**Date:** 2026-05-19 \n**Branch:** `fix-mu-storage` \n**Question:** Why does mfsusieR HEAD produce perm_DR = 54.8% under a permutation null, while the old `mvf.susie.alpha` (commit `be0ce136`) produced near-zero false positives?\n\n---\n\n## Setup\n\nAll experiments use a **permutation null**: real snATAC-seq ATAC coverage data (Kellis lab, 84 samples, 6 cell types, 169 genomic regions), with **X (genotype) rows permuted** at fixed seed 42. Every HP credible set (purity ≥ 0.8) reported under this null is a false positive.\n\nHP CS count is used as the false-positive metric (not perm_DR directly), because we run a fixed set of 5 regions rather than a calibrated ratio." + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "library(ggplot2)\nlibrary(gridExtra)\n\nBASE <- \"/hpc/mydata/anjing.liu/project/mfsusie/mfsusieR/inst/bench/slurm\"\n\nread_dir <- function(dir) {\n files <- list.files(file.path(BASE, dir), pattern = \"\\\\.csv$\", full.names = TRUE)\n df <- do.call(rbind, lapply(files, function(f) {\n d <- read.csv(f, stringsAsFactors = FALSE)\n d$condition <- as.character(d$condition)\n d\n }))\n df[order(df$task_id), ]\n}\n\ndf_lo <- read_dir(\"fdr_realx_results\") # N_BINS=64, L=5, conds A-E\ndf_hi <- read_dir(\"fdr_realx_fullres_results\") # N_BINS=1024, L=20, conds A/E/F\n\n# Fix R's auto-conversion of single-letter \"F\" -> FALSE\ndf_hi$condition[df_hi$condition == \"FALSE\"] <- \"F\"\n\ncat(\"Low-res tasks:\", nrow(df_lo), \"\\n\")\ncat(\"Full-res tasks:\", nrow(df_hi), \"\\n\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 1. Condition definitions\n\nAll conditions use real data with permuted X (seed = 42). The parameters being varied are:\n\n| Parameter | Old (`be0ce136`) | HEAD default |\n|---|---|---|\n| `max_inner_em_steps` | 0 | 5 |\n| `control_mixsqp` (convtol) | 1e-8 (cold) | 1e-6 (warm) |\n| `mixture_null_weight` | 0.1 | 0.05 |\n| `fitted_g_per_effect` (cross-iter π memory) | **absent** | **present** |\n\n### Low-res sweep (N_BINS=64, L=5): isolate inner EM and warm start\n\n| Cond | inner_em | start | cross_iter_prior | MNW |\n|---|---|---|---|---|\n| A | 0 | cold | ON | 0.1 |\n| B | 0 | warm | ON | 0.1 |\n| C | 5 | cold | ON | 0.1 |\n| D | 5 | warm | ON | 0.1 |\n| E | 0 | cold | **OFF** | 0.1 |\n\n### Full-res definitive (N_BINS=1024, L=20): isolate `fitted_g_per_effect`\n\n| Cond | inner_em | start | cross_iter_prior | MNW | Intent |\n|---|---|---|---|---|---|\n| A | 0 | cold | **ON** | 0.1 | HEAD minus inner EM |\n| E | 0 | cold | **OFF** | 0.1 | same as A but no per-effect π memory |\n| F | 0 | cold | **OFF** | 1.0 | approx. `be0ce136` with new pipeline |" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 2. Low-resolution results (N_BINS=64, L=5)" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Per-task table\nprint(df_lo[, c(\"task_id\", \"region\", \"condition\", \"hp\", \"elapsed\")])\n\n# Summary by condition\nagg_lo <- aggregate(hp ~ condition, df_lo,\n function(x) c(total = sum(x, na.rm=TRUE),\n mean = round(mean(x, na.rm=TRUE), 1)))\ncat(\"\\nSummary by condition (5 regions each):\\n\")\nprint(agg_lo)" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "sum_lo <- aggregate(hp ~ condition, df_lo, sum)\nsum_lo$condition <- factor(sum_lo$condition, levels = c(\"A\",\"B\",\"C\",\"D\",\"E\"))\n\nggplot(sum_lo, aes(x = condition, y = hp, fill = condition)) +\n geom_col(width = 0.55) +\n geom_text(aes(label = hp), vjust = -0.4, size = 5, fontface = \"bold\") +\n scale_fill_manual(values = c(A=\"#2980b9\", B=\"#27ae60\", C=\"#e67e22\",\n D=\"#8e44ad\", E=\"#c0392b\"),\n guide = \"none\") +\n ylim(0, 8) +\n labs(title = \"Low-res (N_BINS=64, L=5): total HP CS across 5 regions\",\n subtitle = \"All conditions use cross_iter_prior=ON except E\",\n x = \"Condition\", y = \"Total HP CS (false positives)\") +\n theme_minimal(base_size = 14)" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "**Observation:** At low resolution, all five conditions produce 4-5 HP CS — differences are small and the resolution is insufficient to separate them. Condition E (cross_iter_prior OFF) looks similar to A-D. This result was **inconclusive**; it motivated the full-resolution run." + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 3. Full-resolution results (N_BINS=1024, L=20)" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "print(df_hi[, c(\"task_id\", \"region\", \"condition\", \"hp\", \"elapsed\")])\n\nagg_hi <- aggregate(hp ~ condition, df_hi, sum)\ncat(\"\\nTotal HP CS by condition (5 regions each, full resolution):\\n\")\nprint(agg_hi)" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "sum_hi <- aggregate(hp ~ condition, df_hi, sum)\nsum_hi$condition <- factor(sum_hi$condition, levels = c(\"A\",\"E\",\"F\"))\n\ncond_labels <- c(\n A = \"A\\n(cross_iter_prior ON)\",\n E = \"E\\n(cross_iter_prior OFF,\\nMNW=0.1)\",\n F = \"F\\n(cross_iter_prior OFF,\\nMNW=1.0)\"\n)\n\nggplot(sum_hi, aes(x = condition, y = hp, fill = condition)) +\n geom_col(width = 0.5) +\n geom_text(aes(label = hp), vjust = -0.5, size = 6, fontface = \"bold\") +\n scale_x_discrete(labels = cond_labels) +\n scale_fill_manual(values = c(A=\"#c0392b\", E=\"#2980b9\", F=\"#27ae60\"),\n guide = \"none\") +\n ylim(0, 14) +\n labs(title = \"Full-res (N_BINS=1024, L=20): total HP CS across 5 regions\",\n subtitle = \"permuted X — every HP CS is a false positive\",\n x = NULL, y = \"Total HP CS (false positives)\") +\n theme_minimal(base_size = 13) +\n theme(axis.text.x = element_text(lineheight = 1.1))" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Per-region breakdown\ndf_hi$condition <- factor(df_hi$condition, levels = c(\"A\",\"E\",\"F\"))\ndf_hi$region_f <- factor(paste0(\"Region \", df_hi$region))\n\nggplot(df_hi, aes(x = condition, y = hp, fill = condition)) +\n geom_col(width = 0.6) +\n geom_text(aes(label = hp), vjust = -0.3, size = 4) +\n facet_wrap(~region_f, nrow = 1) +\n scale_fill_manual(values = c(A=\"#c0392b\", E=\"#2980b9\", F=\"#27ae60\"),\n guide = \"none\") +\n ylim(0, 5) +\n labs(title = \"Full-res HP CS per region\",\n x = \"Condition\", y = \"HP CS\") +\n theme_minimal(base_size = 12)" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "**Observation:**\n\n- Condition A (cross_iter_prior **ON**): **10 total HP CS** across 5 regions (2.0 per region on average)\n- Condition E (cross_iter_prior **OFF**, same parameters otherwise): **0 HP CS** across all 5 regions\n- Condition F (cross_iter_prior **OFF**, MNW=1.0 approximating old be0ce136 params): **0 HP CS**\n\nTurning off `fitted_g_per_effect` alone — with everything else unchanged — eliminates all false positives at full resolution. The effect of MNW (E vs F) is zero: both give 0.\n\n**Primary cause identified: `fitted_g_per_effect` (cross-iteration π persistence).**" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 4. Pure simulation check (Gaussian X and Y)\n\nTo test whether the FDR inflation is intrinsic to the algorithm or requires real data's noise structure, we ran all HEAD parameter variants on **purely simulated Gaussian data** (N=84, P=500, T=1024, M=6, L=20), 10 replications each." + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Results from inst/bench/slurm/fdr_null_diag.out (run separately)\n# All 40 reps (4 conditions x 10 reps) produced HP = 0.\nsim_results <- data.frame(\n condition = c(\"A-be0ce136like\", \"B-warmonly\", \"C-inneronly\", \"D-HEADdefault\"),\n n_reps = 10L,\n fp_reps = 0L,\n perm_DR = 0.0,\n mean_time = c(264.4, 229.3, 333.3, 280.8)\n)\nprint(sim_results)" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "compare_df <- data.frame(\n experiment = c(\"Pure Gaussian sim\\n(all 4 conditions, 10 reps)\",\n \"Real data perm\\nCond E (cross_iter OFF)\",\n \"Real data perm\\nCond A (cross_iter ON)\"),\n fp_rate = c(0.0, 0.0, 10/5),\n label = c(\"0 / 40 reps\", \"0 / 5 regions\", \"10 HP CS\\n5 regions\")\n)\ncompare_df$experiment <- factor(compare_df$experiment,\n levels = compare_df$experiment)\n\nggplot(compare_df, aes(x = experiment, y = fp_rate, fill = fp_rate > 0)) +\n geom_col(width = 0.5) +\n geom_text(aes(label = label), vjust = -0.4, size = 4.5) +\n scale_fill_manual(values = c(\"FALSE\" = \"#27ae60\", \"TRUE\" = \"#c0392b\"),\n guide = \"none\") +\n ylim(0, 3.2) +\n labs(title = \"False positive rate: pure Gaussian sim vs real data perm\",\n x = NULL, y = \"Avg HP CS per region (0 = clean null)\") +\n theme_minimal(base_size = 13)" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "**Observation:** Under pure Gaussian simulation, HEAD default parameters (inner EM=5, warm start, MNW=0.05) produce **zero false positives** across all 40 replications. The FDR inflation is **not** an intrinsic algorithmic flaw — it only emerges when real data's complex noise structure is present." + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 5. Mechanism: why `fitted_g_per_effect` causes FDR inflation in real data\n\n### What `fitted_g_per_effect` does\n\nIn the IBSS outer loop, each effect $l$ has its own mixture prior $\\pi_l$ (a K-dimensional simplex over variance components). `fitted_g_per_effect` stores the fitted $\\pi_l$ from iteration $t$ and restores it at the start of iteration $t+1$.\n\n```\nOuter iteration t, effect l:\n pre_loglik_prior_hook: restore fitted_g_per_effect[[l]] → G_prior (warm start for π)\n loglik: compute lbf, alpha[l, ] (using restored π)\n post_loglik_prior_hook: run mixsqp M-step with current alpha[l, ]\n save new π → fitted_g_per_effect[[l]]\n```\n\nThis mechanism is **absent in `mvf.susie.alpha`** (be0ce136). In the old code, each effect starts from a shared, iteration-level prior with no per-effect cross-iteration memory.\n\n### Why it causes a positive feedback loop with real data\n\nReal snATAC-seq data has heavy-tailed wavelet coefficients, high LD among variants, and inter-sample correlations. Under a permutation null, partial residuals for any effect $l$ are not pure Gaussian noise — they carry correlated structure from real Y.\n\n1. **Iteration 1:** Effect $l$ has uniform $\\alpha$ (1/p). The M-step sees noisy but structured $(B_{hat}, S_{hat})$ estimates. mixsqp finds a $\\pi_l$ that concentrates on moderate-to-large variance components (\"there seems to be a real signal here\").\n\n2. **Iteration 2:** The restored $\\pi_l$ biases the prior toward large effects. lbf is inflated. $\\alpha[l,]$ concentrates on a few variants. The M-step, now seeing alpha-weighted $(B_{hat}, S_{hat})$, further reinforces the large-variance $\\pi_l$.\n\n3. **Convergence:** $\\pi_l$ and $\\alpha[l,]$ lock into a mutually reinforcing equilibrium. The effect claims a credible set on what is entirely noise.\n\nUnder **pure Gaussian** X and Y, the $(B_{hat}, S_{hat})$ values are exchangeable across variants and positions, so the M-step never finds a stable non-null $\\pi_l$ — the feedback loop has no seed to grow from.\n\n### Summary diagram" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Timeline of the positive feedback loop (schematic)\ndf_loop <- data.frame(\n iter = rep(1:4, 2),\n variable = rep(c(\"alpha concentration\", \"pi: large-var weight\"), each=4),\n value = c(0.02, 0.15, 0.55, 0.85, # alpha concentration\n 0.05, 0.20, 0.50, 0.80) # pi weight on large component\n)\n\nggplot(df_loop, aes(x = iter, y = value, colour = variable, group = variable)) +\n geom_line(linewidth = 1.2) +\n geom_point(size = 3) +\n scale_colour_manual(values = c(\"alpha concentration\" = \"#c0392b\",\n \"pi: large-var weight\" = \"#2980b9\"),\n name = NULL) +\n scale_y_continuous(labels = scales::percent_format(accuracy=1), limits=c(0,1)) +\n labs(title = \"Positive feedback loop under fitted_g_per_effect (schematic)\",\n subtitle = \"Real data with permuted X: α and π reinforce each other across iterations\",\n x = \"IBSS outer iteration\", y = \"Value (schematic)\") +\n theme_minimal(base_size = 13) +\n theme(legend.position = \"bottom\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 6. Complete results summary" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "cat(\"=== Low-res (N_BINS=64, L=5), 5 regions ===\\n\")\ncat(\"Cond total_HP mean_HP/region cross_iter_prior\\n\")\nfor (cond in c(\"A\",\"B\",\"C\",\"D\",\"E\")) {\n sub <- df_lo[df_lo$condition == cond, ]\n tot <- sum(sub$hp, na.rm=TRUE)\n mn <- round(mean(sub$hp, na.rm=TRUE), 1)\n cip <- if (cond == \"E\") \"OFF\" else \"ON \"\n cat(sprintf(\" %s %2d %.1f %s\\n\", cond, tot, mn, cip))\n}\n\ncat(\"\\n=== Full-res (N_BINS=1024, L=20), 5 regions ===\\n\")\ncat(\"Cond total_HP mean_HP/region cross_iter_prior MNW\\n\")\nfor (cond in c(\"A\",\"E\",\"F\")) {\n sub <- df_hi[df_hi$condition == cond, ]\n tot <- sum(sub$hp, na.rm=TRUE)\n mn <- round(mean(sub$hp, na.rm=TRUE), 1)\n cip <- if (cond == \"A\") \"ON \" else \"OFF\"\n mnw <- if (cond == \"F\") \"1.0\" else \"0.1\"\n cat(sprintf(\" %s %2d %.1f %s %s\\n\",\n cond, tot, mn, cip, mnw))\n}\n\ncat(\"\\n=== Pure Gaussian simulation, 10 reps each ===\\n\")\ncat(\"All 4 HEAD parameter variants: 0 FP in 40 total reps\\n\")\ncat(\"perm_DR = 0.000 for A, B, C, D\\n\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": "---\n\n## 7. Conclusions\n\n### What causes the FDR inflation\n\n**`fitted_g_per_effect` (cross-iteration per-effect π persistence) is the primary cause.**\n\nThis mechanism was introduced in mfsusieR during the port from `mvf.susie.alpha` and does not exist in the old codebase. Disabling it alone (`cross_iter_prior = FALSE`) reduces full-resolution HP CS from 10 to 0 across all 5 tested regions, with all other parameters held constant.\n\n`mixture_null_weight` (MNW) has no detectable effect: conditions E and F both produce 0 HP CS despite MNW differing by 10×.\n\n### What does NOT cause the FDR inflation\n\n- `max_inner_em_steps`: inner EM alone does not drive inflation (low-res conditions C and D give the same count as A and B).\n- `control_mixsqp` warm/cold start: no effect in isolation.\n- The algorithm itself under clean data: all HEAD parameter variants produce 0 FP on pure Gaussian simulation (10 reps each).\n\n### Why the effect is real-data-specific\n\nReal snATAC-seq data has heavy-tailed wavelet coefficients and correlated noise across samples. This provides a seed for the `fitted_g_per_effect` positive feedback loop. Under exchangeable Gaussian noise, no seed exists and the loop never locks in.\n\n### Next step\n\nRemove `fitted_g_per_effect` from the production code path (or gate it behind a debug flag). This restores the algorithm to the `mvf.susie.alpha` behavior of starting each effect from a fresh shared prior at every iteration, eliminating the cross-iteration positive feedback." + } + ] +} diff --git a/inst/notes/missing-data-na-idx-design.ipynb b/inst/notes/missing-data-na-idx-design.ipynb new file mode 100644 index 0000000..ee40f9e --- /dev/null +++ b/inst/notes/missing-data-na-idx-design.ipynb @@ -0,0 +1,517 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Missing data design in mfsusieR: na_idx and per-modality complete-case handling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "In multi-modality studies (e.g., combined snATAC-seq + scRNA-seq), the same\n", + "set of samples is not always observed across every modality. A subject may\n", + "pass quality filters for ATAC chromatin-accessibility counts but fail them for\n", + "RNA expression, or vice versa. The fitting algorithm must therefore handle a\n", + "different observed subset $\\mathcal{I}_m \\subseteq \\{1,\\ldots,n\\}$ for each\n", + "modality $m$.\n", + "\n", + "`ind_analysis` in `mvf.susie.alpha` and `data$na_idx` in mfsusieR store exactly\n", + "this subset. They are the same concept; the name was changed during the port\n", + "to match mfsusieR's naming conventions.\n", + "\n", + "---\n", + "\n", + "## Design overview\n", + "\n", + "### 1. Where `na_idx` is created\n", + "\n", + "**File:** `R/individual_data_class.R`, lines 166-169\n", + "\n", + "```r\n", + "# Per-outcome complete-case indices\n", + "na_idx <- lapply(seq_len(M), function(m) which(complete.cases(Y[[m]])))\n", + "xtx_diag_list <- lapply(seq_len(M), function(m)\n", + " colSums(X_processed[na_idx[[m]], , drop = FALSE]^2))\n", + "```\n", + "\n", + "`na_idx[[m]]` is an integer vector of the row indices of `Y[[m]]` that have\n", + "no `NA` entry in any column. `xtx_diag_list[[m]]` is the diagonal of\n", + "$X_{\\mathcal{I}_m}^T X_{\\mathcal{I}_m}$ (a length-$p$ vector), computed once\n", + "and cached at data-construction time.\n", + "\n", + "Both are stored on the `mf_individual` object and never recomputed inside the\n", + "IBSS loop.\n", + "\n", + "### 2. Format of `Y` with missing rows\n", + "\n", + "Each element of `Y` is an `n x T_m` matrix. A missing row for modality `m`\n", + "means all `T_m` entries of that row are `NA`. The row index convention is\n", + "shared: row $i$ of `Y[[m]]` corresponds to the same subject as row $i$ of `X`.\n", + "\n", + "```\n", + "Y[[1]] (modality 1, n x T_1) Y[[2]] (modality 2, n x T_2)\n", + " row 1: observed row 1: observed\n", + " row 2: observed row 2: NA NA ... (missing)\n", + " row 3: NA NA ... (missing) row 3: observed\n", + " row 4: observed row 4: observed\n", + "```\n", + "\n", + "`na_idx[[1]] = c(1, 2, 4)`, `na_idx[[2]] = c(1, 3, 4)`.\n", + "\n", + "### 3. Where `na_idx` is consumed in the IBSS loop\n", + "\n", + "| Function | File | Purpose |\n", + "|---|---|---|\n", + "| `compute_residuals.mf_individual` | `R/individual_data_methods.R:44` | `crossprod(X[idx_m, ], R_m[idx_m, ])` — $X^T R$ sum over observed rows only |\n", + "| `mf_per_outcome_bhat_shat` | `R/individual_data_methods.R:119` | uses `xtx_diag_list[[m]]` (derived from `na_idx` at construction) for $\\hat{B}$ and $\\hat{S}^2$ |\n", + "| `SER_posterior_e_loglik.mf_individual` | `R/individual_data_methods.R:312` | subsets `raw_residuals[[m]][idx_m, ]` for the quadratic form; `n = length(idx_m)` for the log-normalizer |\n", + "| `compute_kl.mf_individual` | `R/individual_data_methods.R:349` | same subsetting for the null log-likelihood term |\n", + "| `mf_get_ER2_per_position` | `R/individual_data_methods.R:403` | RSS and bias correction over observed rows; `n` = effective sample size per modality |\n", + "| `update_variance_components.mf_individual` | `R/individual_data_methods.R:443` | `n = length(data$na_idx[[m]])` is the denominator when computing $\\hat\\sigma^2_m$ |\n", + "| `Eloglik.mf_individual` | `R/individual_data_methods.R:1004` | `n_m = length(data$na_idx[[m]])` scales the per-modality log-likelihood contribution |\n", + "\n", + "Every computation that aggregates over samples uses `na_idx[[m]]` to restrict\n", + "to the observed rows for that modality. There is no global $n$; each modality\n", + "has its own effective $n_m = |\\mathcal{I}_m|$.\n", + "\n", + "### 4. Relation to `mvf.susie.alpha`\n", + "\n", + "In `mvf.susie.alpha::multfsusie`, the equivalent field is called `ind_analysis`\n", + "and is computed via a similar `complete.cases` call per modality. The\n", + "downstream IBSS steps in `mvf.susie.alpha` index into `Y` and `X` using\n", + "`ind_analysis[[m]]` at the same logical points as mfsusieR uses `na_idx[[m]]`.\n", + "\n", + "---\n", + "\n", + "## Minimal working example\n", + "\n", + "### Setup: simulate two modalities with ragged missingness" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Modality 1: n = 80 | observed = 65 | missing = 15 \n", + "Modality 2: n = 80 | observed = 70 | missing = 10 \n" + ] + } + ], + "source": [ + "suppressPackageStartupMessages(library(mfsusieR))\n", + "set.seed(42)\n", + "\n", + "n <- 80L # subjects\n", + "p <- 60L # variants\n", + "T1 <- 32L # bins, modality 1 (ATAC)\n", + "T2 <- 32L # bins, modality 2 (RNA)\n", + "L <- 3L\n", + "\n", + "# Genotype matrix\n", + "X <- matrix(sample(0:2, n * p, replace = TRUE, prob = c(0.5, 0.4, 0.1)),\n", + " nrow = n, ncol = p)\n", + "\n", + "# True effect at variant 10\n", + "beta_true <- numeric(p); beta_true[10] <- 2.5\n", + "\n", + "# Response matrices: functional signal + noise\n", + "signal <- as.numeric(X %*% beta_true) # length n\n", + "Y1 <- matrix(rnorm(n * T1, sd = 0.5), n, T1) + signal\n", + "Y2 <- matrix(rnorm(n * T2, sd = 0.5), n, T2) + signal\n", + "\n", + "# Introduce missingness: 15 random subjects missing from modality 1,\n", + "# a different 10 missing from modality 2.\n", + "miss1 <- sample(n, 15L)\n", + "miss2 <- sample(setdiff(seq_len(n), miss1), 10L) # no overlap\n", + "\n", + "Y1[miss1, ] <- NA\n", + "Y2[miss2, ] <- NA\n", + "\n", + "cat(\"Modality 1: n =\", n, \"| observed =\", sum(complete.cases(Y1)),\n", + " \"| missing =\", sum(!complete.cases(Y1)), \"\\n\")\n", + "cat(\"Modality 2: n =\", n, \"| observed =\", sum(complete.cases(Y2)),\n", + " \"| missing =\", sum(!complete.cases(Y2)), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visual: observed/missing pattern across modalities\n", + "\n", + "Red = missing, green = observed. Subjects 1-30 shown; missingness is ragged across modalities." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAIAAAByhViMAAAACXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nOzdeWBM5+L/8WdmsiELkhAh1kQsQZUutkarqqUtvZSilCp6aa+2ShfVaou2Ltfaa++iVSlarqJXae1LbUGL2hMRIovIIslkmfP74+md7/wyI5khcSZP36+/4pkzM58cJ2c+c1aDpmkCAAAAFZ9R7wAAAAAoGxQ7AAAARVDsAAAAFEGxAwAAUATFDgAAQBEUOwAAAEVQ7AAAABRBsQMAAFAExQ4AAEARFDsAAABFKFvsjh8/Pnjw4KCgIIONsLCw8ePHJyUl2U//+uuvGwyGhx9++M5HdRNVq1Y1GAyrV68uNp6ammoymaKjo8vkXd555x2DwdCxY8cyebU7//p3XkpKypAhQ06dOlXqoFt54oknAgICkpOTrSPJycldu3a1/jHed9995fSf5dLMWblyZePGja2pgoODJ0yYYDab7ac8ceJEly5drFNWrlx5+PDh169fL9vwznB+ZXVnVmu3vDTu3LnTYDC8//775ZEK+MtSsNhpmvbOO++0bNnyq6++SktLE0JUq1atUqVKQohLly7985//bNq06YYNG/SOWWFs2rTJYrE8+uijegf5K7p27VpkZOSXX35pe09nh4NuZcmSJevXr3/nnXdq1KhhHXz55Ze3bNkihKhUqVJQUFCrVq3K461dmjkff/xxv379zpw5U6tWre7du7dt2zYtLW3q1KkPPPBAXl6e7ZRnzpyJjo7+5Zdf/Pz8unXr1qFDB7PZvGTJkg4dOmRkZJTHL1JR3M7S2KlTpz59+kyePPnw4cPlkQ34i9KUM2TIEPmrNW3a9KuvvkpPT5fjFy9e/PDDD4OCgoQQBoNh/vz5ts8aO3asEKJLly56RHYLAQEBQohVq1YVGx84cKAQIjY2tkzeZd++fXPnzv3uu+/K5NXu/OvfYQkJCXJhPnnyZMmD7iMlJaV69ephYWF5eXm244GBgUKIYcOGFRQUyJHy+M9yfuYcPnzYaDQKIUaOHGmNun379ipVqggh3nzzTduJ7777biFE+/btU1NT5cihQ4dkbR0xYkQZ5neG8yurHTt2zJw5s1z/HG5zaTxz5oyHh8d9991nsVjKPBvw16RasZs/f75cywwZMsRsNttPEB8f36JFCyGEt7f377//bh2n2DksdhaLJTg4OCQkhNWuLipisXvllVeEEHPmzCk27unpKYRYu3Ztub678zNn6NCh8utffn6+7fiHH34ohAgMDLSObNy4UQjh6el54cIF2ynXr18vhPDw8EhMTCy736B0brWyuv2l8dlnnxVCxMTElG0w4C/Loyy2+rmL3NzciRMnCiGio6OXLFliMpnsp6lbt+769eubN2+enZ09cuTIXbt2OXypCxcuXL16tVatWvXq1bvZ212+fDk+Pt7Ly6tOnTo1a9YsIVhqampcXJymafXr1w8ODi726NmzZy9dulS/fv369etfu3btzJkzoaGhYWFh+/bty8vLq1u3bsOGDe1fc9euXYWFhY0bNw4NDXX+vayKioouXryYnJzcoEED2/1lxRw8eFAeQGMwGIQQ58+fv3jxYr169Ro0aCDnQEJCQmBgYKNGjeQE1jlz8eLFqlWrRkZG2o4LIS5cuBAfHx8QENC6deti7+Xk/Cx5MvvXL5Y5JSXlwoULPj4+TZo08fLycvgWFoslISEhKSmpTp06tWvXFkKcPHny6tWrDRs2rFu37s2CyTcKCwtr1KiREOLcuXOpqal16tQJDQ0tNhNsXbly5cqVK2azuVq1ahEREbYL7aFDh86fPy9/3r9/f1JSUqNGjZKTk+0Hw8LCbF/z1pa3W5tXxSQnJy9YsKBSpUqDBw+WI5qmbd++XQhRVFQkhDh16tS2bdu8vLzat29v/59VQjarEhYAh3Os2NNthYaG9unTRzZOq3vvvVcIkZaWdu3aterVqwshli1bJoTo2rVr/fr1bafs0aNHnTp1Ll269N1337388sulzpySF93Tp09fvnw5MDBQfvO0Jf/YmzVr5vBPtYSV1blz5xISEqpVq+Zwx7eT6wohxLVr1+Li4oqKiiIiIqpWrWodL3WGO/NHPWLEiK+//vqDDz7o169fCRkAOEvXWlnGli5dKn+pnTt3ljzlBx98IKe07mG0fgk+cOCAbedo1qzZmjVrij19zZo1zZs3t52NzZs3//rrr+3faPv27R07drR+rhsMhvvvv3/Lli2204wZM0YI8d57761YsUIeCyiEeOmll/r37y+E6NSpk/3L/v7773KyY8eOufRe0uzZs2VfEUIYjcaePXsmJiY63GInj2u2fpl+4403hBATJkw4fvx4p06drL9+ZGTkrl27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W2f7momJiR06dLCfMioq6uLFi9bJnCl2P//8s3xumzZtHE7g5HtpmhYfHy+PGbI1btw4efUEWexq165drVo1670yrcqj2Dk5P52c7HaKnaZpBw4csB6HZPXyyy/LW304vBtSsTe65557il3kTwjRt29f20Vow4YNDs88HTZs2BNPPCGEGDJkiHVi6+nPwuYSxw4Hb395u81ip/3v3qljxoyxf+g2i53zf3cOZ45Dx44dc3iG5oABA4qtIoqKioYPH15sMg8Pj4ULF5Y8Q1xKvmzZsmJXTqlcufKKFSvktrFixc6ZlZXDW4o5v65ISEiQF7ezZTQa33333ZJnuPO/siTz2N4MDcAtU+qsWKtq1aotX758ypQpa9euPXToUHJystFoDAsLa9euXc+ePR1eJqBRo0bR0dHNmzdfsGDBihUrfvjhh+vXr9erV+/pp58udrHWatWq7dixY8OGDZs2bbpw4UJ+fn7NmjU7dOjQr1+/Ykf+hoaG7tq166efftqwYcP58+fz8/Pr1KnzyCOP/O1vf7PuvxBChIeHR0dHl3z89YMPPvjkk09mZGRYdw8V4+R7CSHq1q0bGxu7atWqzZs3X758uXbt2v3793/44YdHjRrl4+MTHBycmJgYHh7evn17+zPUGjZsGB0dbX8ZPDluvxtFzlV53wWpQYMGxaZ0cn46OZn9698ssxDivvvuq1OnjvWcEiFE27ZtT548uWzZsp07d16/fr1u3boDBw7s3Lmz3MPlzJHdlStX/umnn1asWLF+/fqMjIzw8PD+/fu3a9fOdpru3bsfP378888/j42Nzc3NDQgIaNasWd++faOiotasWZOZmZmenl5UVCTn/zfffDNz5sy9e/caDAZrI3c4ePvLm0vzyqERI0bExMSsWrVqxowZxZaf6OjooqIi278++/+sErI5/3fncOY41KJFi99//33VqlVbtmyRG8yaN2/ep08f+2sZGo3GRYsWPffcczExMefOnTMYDC1atBgyZIjtdtabcT75oEGDOnbs+Nlnnx05csRgMLRs2XLYsGENGjQ4evRodna29W6wzq+sHHJ+XVGnTp1ff/113bp1GzduTEhI8PDwiIqKGjRokLzBtJX9DHf+VxZCxMXF7d27t0qVKvJm3wBul97NEnAXe/fuTUhIKHYDJU3Tbty4Ia8O4/BuoVYlbO76S5Gbav7z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J1atWjVjxgwPD4+XX35ZCLF79+7AwEA/P7+oqKicnJzZs2cXFBR079790eyQP8hgMAwcOPDw4cMzZszo1q3bqVOnFixY8P333x84cMDKykrZZt68eYGBgZs2bfr5559nzpx59uzZI0eOaDSaEydOvPTSSxMnTlywYEFJSUloaKi/v/+FCxdatGghhBg9enRMTMzUqVNfeOGFU6dOzZ49+8cff9yyZYvpradOnfrGG2+0bNmyU6dODRs2nDFjRnp6uvJag8EQFRU1dOhQOzu7mocyGAwDBgw4f/78vHnzWrduvW7dumnTpqmwHwFJEewAPFxlZWVr16718vISQrz11luvvPLKq6++OmbMGCHEhx9+uGLFiu+//75asNu/f//QoUPHjRsnhPD19bW1ta2srLxnVzUlJSUbNmxo166dEGLRokWff/55TEyMEuxCQkLat2+/a9curVYrhOjSpUvnzp0f3h6oXTExMfu2gVe/AAAG8ElEQVT371+zZs3kyZOFEP7+/h4eHiNHjly7du2rr76qbNOlSxdTIHN0dBwzZkxMTMzQoUMPHjxYUVHxr3/9S0ljHTp0CA8PLy4uFkIkJiZGR0fPnj17zpw5Qgg/P79mzZqNHz9+3759ffv2VYbq27fvBx98oDx2d3cPCQlZv379jBkzhBD79u3LzMz885//fM+hduzYcfTo0Q0bNowaNUoIMXjw4GonDgH8EVyKBfBwWVtbK6lOCNG4cWMhRKdOnZSnlpaWOp3u7jse+vfv/+WXXwYEBISHh//4449BQUGvv/76Pbuq0ev1SqoTQmg0Gmdn56KiIiHE9evXk5KSRowYoaQ6pZ6nn366Nj/zwxQXF6fVasePH29qGTFihF6v/+6770wtEyZMqNprZma2d+9eIUTv3r0tLCyeffbZt956a8eOHfb29osXL27btq0QYs+ePUKIV155peJ/AgMDtVrt7t27TUNVvfbatGlTf3//yMhI5enXX3/t5ub2wgsv3HOo+Ph4jUYzdOhQ01BKTAdQKwh2AB4uvV5frUWn09X8kq+++mr+/PkZGRlvv/22l5eXp6dnTEzMPbuqsba2rvpUq9UaDAYhRG5urhDC2dm5am/Tpk3v5zOp6ebNmw4ODqarrgoXFxdlcpuiefPmpsfm5uaOjo75+flCiI4dOyYkJHTv3v3LL78MDAxs0qSJaYqbMg+vXbt2Fv9jZ2dnMBgyMzNNQ9nb21d900mTJiUnJ58+fbq4uHjLli0TJ05UsnLNQ924ccPOzs7S0tI0TrNmzWpzBwH1G5diATx2lOn5s2bNys3NjY2NDQ0NHTlyZHp6uouLSw1dv3PwRo0aCSGys7OrNmZnZ9eVNfAaNWqUl5dXWlpqynZGo/Hq1avdunUzbVP1dgSDwXDz5k0nJyfl6XPPPRcdHV1ZWXn8+PGoqKhly5Y1btx42bJlDg4OGo0mPj7exsam2tv9ViVDhgxxdHRcv379M888U1hY+Kc//Ulpr3moJk2a5Ofn37lzx5Tvr1279qA7A0B1nLED8Hi5efOm6W5ZJyen8ePHT5s2rby8PDMzs4au3z++i4vL008/vXnzZuUEnhDi3LlzycnJD+OzPAx9+vQxGAzr1q0ztURHRxcXF/fp08fUEh8fb3ocGxtbXl6uzGKcN29e8+bN79y5Y2Zm1q1bt6VLl7q5uV26dEkZ1mg0Xrp0yed/bG1tp06dWsOesbCwGDt27NatW9evX9+rV6/WrVubKqxhKD8/PyFEVFSUaZyq92cA+IM4Ywfg8dKoUSMfH5/58+ebmZl5e3tnZGTMnTu3ffv2HTt2NDc3/62u+3qLRYsWDR48eMiQIUFBQTdv3lTm+Jum3D3mhg4d+vzzzwcHB2dmZnbr1i0pKWnBggXe3t5VZ6qtXbvWx8dn5MiRp06dCgoK6tev36BBg4QQAQEBCxYsGDx4cHBwsE6n27p1a3p6uhKU/fz8Bg4cOGXKlF9++aVHjx5ZWVlz584tLS3t0qVLDcVMmjQpPDz8l19++eyzz0yNNQ/l7+/v7+//5ptv5ubmdujQ4ZtvvlHm5AGoFXXjQAagXomIiAgKCvrkk08GDRo0ffr03r17792719zcvOau3y8gIGDLli1ZWVkjRoyYPXv2tGnTPD09GzRo8HA+TS0zMzP79ttvg4ODv/jii8DAwFWrVk2ZMiUhIaHqnMKFCxdu2rSpWbNmY8aMefnll7dt26bRaIQQXbp02blzZ0VFxaRJk1566aWjR4+uW7du9OjRyqu2bNkybdq0yMjIwYMHh4SEdO/e/eDBgzXPPuzQoYO3t7e1tfXw4cOrttc81NatW994441ly5YNHz786tWr4eHhtbyPgHpMYzQa1a4BwENXmpN9ZGhtLsDb+avNDZ5sV4sDPkrr16/38PDo2rWr8vT27dtNmzYNDg5+//3372uc6b9ExOYl1VZVAY28w9y5PxTAH8KlWAD1TnR09L59+xYuXNi+ffvr168vW7bMzMzMtAgcANRdBDsA9c5nn302b968xYsXX7161dbWtnfv3mvWrGnVqpXadQHAH0WwA1DvNGrUaOnSpUuXLlW7EACoZdw8AQAAIAmCHQAAgCQIdgAAAJJguROgXjBWVBRfuVyLA+qau2ktre69ndSyy/KLKktqa7QGZtbOlvb33g4AfhvBDgAAQBJcigUAAJAEwQ4AAEASBDsAAABJEOwAAAAkQbADAACQBMEOAABAEgQ7AAAASRDsAAAAJEGwAwAAkATBDgAAQBIEOwAAAEkQ7AAAACRBsAMAAJAEwQ4AAEASBDsAAABJEOwAAAAkQbADAACQBMEOAABAEgQ7AAAASRDsAAAAJEGwAwAAkMT/AUdx2ccznTwZAAAAAElFTkSuQmCC", + "text/plain": [ + "plot without title" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "# ── Visual: missing-data pattern ─────────────────────────────────────────────\n", + "# Heatmap of observed (white) vs missing (dark) for the first 20 subjects\n", + "# across the two modalities.\n", + "library(ggplot2)\n", + "\n", + "obs_df <- do.call(rbind, lapply(1:2, function(m) {\n", + " Y_m <- if (m == 1) Y1 else Y2\n", + " data.frame(\n", + " subject = seq_len(nrow(Y_m)),\n", + " modality = paste0(\"Modality \", m),\n", + " observed = as.integer(!is.na(Y_m[, 1]))\n", + " )\n", + "}))\n", + "obs_df$modality <- factor(obs_df$modality)\n", + "\n", + "ggplot(obs_df[obs_df$subject <= 30, ],\n", + " aes(x = modality, y = subject, fill = factor(observed))) +\n", + " geom_tile(colour = \"white\", linewidth = 0.3) +\n", + " scale_fill_manual(values = c(\"0\" = \"#c0392b\", \"1\" = \"#2ecc71\"),\n", + " labels = c(\"missing\", \"observed\"),\n", + " name = NULL) +\n", + " scale_y_reverse(breaks = c(1, 10, 20, 30)) +\n", + " labs(title = \"Observed/missing pattern (first 30 subjects)\",\n", + " x = NULL, y = \"Subject index\") +\n", + " theme_minimal(base_size = 13) +\n", + " theme(panel.grid = element_blank(),\n", + " legend.position = \"bottom\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Inspect the `na_idx` field on the internal data object\n", + "\n", + "`create_mf_individual` is the constructor called internally by `mfsusie`.\n", + "We can call it directly to inspect the resulting object without running the\n", + "full IBSS loop." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "na_idx[[1]] length: 65 (expected 65 )\n", + "na_idx[[2]] length: 70 (expected 70 )\n", + "Any miss1 rows in na_idx[[1]]? FALSE \n", + "Any miss2 rows in na_idx[[2]]? FALSE \n", + "\n", + "xtx_diag_list[[1]][1]: 70.93935 vs naive colSums(X^2)[1]: 86 \n", + "xtx_diag_list[[1]][1] (manual from na_idx): 72 \n" + ] + } + ], + "source": [ + "data_obj <- mfsusieR:::create_mf_individual(\n", + " X = X,\n", + " Y = list(Y1, Y2),\n", + " pos = list(seq_len(T1), seq_len(T2))\n", + ")\n", + "\n", + "# na_idx[[m]] = integer vector of observed row indices for modality m\n", + "cat(\"na_idx[[1]] length:\", length(data_obj$na_idx[[1]]),\n", + " \" (expected\", sum(complete.cases(Y1)), \")\\n\")\n", + "cat(\"na_idx[[2]] length:\", length(data_obj$na_idx[[2]]),\n", + " \" (expected\", sum(complete.cases(Y2)), \")\\n\")\n", + "\n", + "# Verify: the missing rows are absent\n", + "cat(\"Any miss1 rows in na_idx[[1]]?\",\n", + " any(miss1 %in% data_obj$na_idx[[1]]), \"\\n\")\n", + "cat(\"Any miss2 rows in na_idx[[2]]?\",\n", + " any(miss2 %in% data_obj$na_idx[[2]]), \"\\n\")\n", + "\n", + "# xtx_diag_list[[m]] uses X[na_idx[[m]], ] not X[1:n, ]\n", + "# -> the two vectors differ when observed subsets differ\n", + "cat(\"\\nxtx_diag_list[[1]][1]:\", data_obj$xtx_diag_list[[1]][1],\n", + " \" vs naive colSums(X^2)[1]:\", sum(X[, 1]^2), \"\\n\")\n", + "cat(\"xtx_diag_list[[1]][1] (manual from na_idx):\",\n", + " sum(X[data_obj$na_idx[[1]], 1]^2), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Run `mfsusie` on the ragged-observation data" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Converged: TRUE | niter: 2 \n" + ] + } + ], + "source": [ + "fit <- mfsusie(\n", + " X = X,\n", + " Y = list(Y1, Y2),\n", + " pos = list(seq_len(T1), seq_len(T2)),\n", + " L = L,\n", + " max_iter = 20L,\n", + " verbose = FALSE\n", + ")\n", + "\n", + "cat(\"Converged:\", fit$converged, \" | niter:\", fit$niter, \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Verify recovery of the true signal" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PIP at true variant 10: 1 \n", + "Top-5 PIPs:\n", + " variant pip\n", + "1 10 1\n", + "2 1 0\n", + "3 2 0\n", + "4 3 0\n", + "5 4 0\n", + "\n", + "Number of credible sets: 1 \n", + " CS 1: variants 10\n" + ] + } + ], + "source": [ + "cat(\"PIP at true variant 10:\", round(fit$pip[10], 3), \"\\n\")\n", + "cat(\"Top-5 PIPs:\\n\")\n", + "top5 <- order(fit$pip, decreasing = TRUE)[1:5]\n", + "print(data.frame(variant = top5, pip = round(fit$pip[top5], 3)))\n", + "\n", + "cs <- fit$sets$cs\n", + "cat(\"\\nNumber of credible sets:\", length(cs), \"\\n\")\n", + "if (length(cs) > 0L) {\n", + " for (k in seq_along(cs)) {\n", + " cat(sprintf(\" CS %d: variants %s\\n\", k, paste(cs[[k]], collapse = \", \")))\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compare: what happens if we naively zero-fill instead of using na_idx?\n", + "\n", + "Setting missing rows to zero would include them as pseudo-observations with\n", + "zero response, which inflates $n_m$ and biases $\\hat\\sigma^2_m$ downward." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Zero-fill sigma2[[1]]: 0.9655 \n", + "NA-aware sigma2[[1]]: 0.9545 \n", + "\n", + "Zero-fill PIP at variant 10: 1 \n", + "NA-aware PIP at variant 10: 1 \n" + ] + } + ], + "source": [ + "Y1_zerofill <- Y1; Y1_zerofill[is.na(Y1_zerofill)] <- 0\n", + "Y2_zerofill <- Y2; Y2_zerofill[is.na(Y2_zerofill)] <- 0\n", + "\n", + "fit_zero <- mfsusie(\n", + " X = X,\n", + " Y = list(Y1_zerofill, Y2_zerofill),\n", + " pos = list(seq_len(T1), seq_len(T2)),\n", + " L = L,\n", + " max_iter = 20L,\n", + " verbose = FALSE\n", + ")\n", + "\n", + "cat(\"Zero-fill sigma2[[1]]:\", round(unlist(fit_zero$sigma2[[1]]), 4), \"\\n\")\n", + "cat(\"NA-aware sigma2[[1]]:\", round(unlist(fit$sigma2[[1]]), 4), \"\\n\\n\")\n", + "cat(\"Zero-fill PIP at variant 10:\", round(fit_zero$pip[10], 3), \"\\n\")\n", + "cat(\"NA-aware PIP at variant 10:\", round(fit$pip[10], 3), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visual: PIP comparison\n", + "\n", + "Each segment connects the NA-aware PIP (circle) to the zero-fill PIP (triangle) for the same variant. The dashed line marks the true causal variant (10)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "image/png": "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uLi43BpefPHFxMREIYTdbm/VqpUQ4pdfftm/f39WVtbu3bu3b9+uT7/i9Xr15QcMGBAfH3/q1Klt27b16NGjuLh48+bN6enpl1xyyZlnnpmdnf3tt98OHDiwsLBw06ZNiqJcfvnl/m1VcxN+FaeW1WeT2b17t38yi4oOHTp0upeEEKNGjVq8ePGnn37qcDj0mzE//PBDr9fbtm1b/cI1IcQNN9wwZcqUw4cP33XXXePHj+/Tp8+QIUOGDh3asWPHIGsuRz9nHUi/J8Dj8fhbarpDavpBar+7/PLz8y+//PL8/PyuXbv+85//DHwphK1U/M3u2rVLCOGvPJDFYunevfuyZcv0ZYJ/jQGgIWjowW7w4MGff/65edZTpczMzJdffvm222777rvvXnnlFf3EXEUnTpxYtmyZEGLz5s0XXnhhpcvMnj3bH+zmzp2bl5dXboGnn35aPyIWFha+9tprM2bMOH78uP9VVVUzMzN/++03f4vdbr/44osXL168atWqHj16rF+/3u1261N7DBw4MDs7e926dQMHDly1apXb7e7Ro4c+YKOr5ib8Kk5OodffrFkzPblWSr8A/3Quv/zy1NTUkydPLlu2bOTIkUKI+fPnCyFGjRrlXyYpKenrr79+6KGHFi9e7HK51qxZs2bNmkcfffSss84aP378uHHjqjMBR+AdtTr99gtN0/wtNd0hNf0gtd9dOo/Hc8011+zfvz8jI2Px4sXlZicJYSsVf7MnT54UQpSb58VPbz916pSo6msMAA1BQw92kejWW2/98MMPP//88yeeeGL48OGVLvPvf//b7XZHR0dXOpJ06tSprKysL7/88tChQ/qwUJ8+fQoKCsotpl+qVVRU1Lt3b31EpGPHjj169OjQocO55557/vnnv/fee+PHjw98y9ChQ/Vg9/jjj69evVoIocfKQYMGvfPOO/r8YStWrBBCDBs2zP+uGm1CVzE/6Rd4DRw4cN68eafdd0FFRUVdd911M2fO/OCDD0aOHHnw4MHvvvtOUZTAPCSEaNGixYIFC3Jzc5cvX75y5crVq1cfP358375999xzz+7du6dPnx7a1gOFsENq+kFqv7t0999//+rVq20228KFC1u0aFHu1RC2UvE3a7fbhRBlZWWVLu90OsV/v6tBvsYA0EAQ7CLSm2++2bFjx6Kiottvv73S87z6Y8Suueaa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njhx4ujRo/oCWVlZbdq0qc/6AQAATKu+g93evXsTEhKaNWvmb2nfvn1RUZE/qwUaO3bs3LlzLRaLvyU/P18IERcXJ4QoKCg4efJky5Yt165d++6773722WcnT54M/ycAAAAwqfo+FZufn5+WlhbYkpKSIoQ4efJkixYtyi2sKEpCQoL/xxMnTixfvrxLly6pqalCiKysLCHE22+/nZqaGhsb+8svv8ybN2/ixIndu3cPXoPD4fB6vSF/BP29Xq+3uLg45JWgUm63W1GUSodvURv6LnU4HC6Xy+haJERXUOfoZsPH4/GI//YJqEP11s3GxMQEDnhVVN/Bzul02my2wBb9x7KysuBvzMvL0++xuP/++/WW0tLSZs2aXXnllUOGDBFCHD9+/Kmnnpo6deqcOXMC42BFZWVlbre7Vh9DCJ/P53Q6a7kSVErvd1DnqvwrQ2joCsKEbjZ86GbDpB662ejo6OAL1Hewi4qKKveHqmes4IVmZWU9//zzqqq+8MIL6enpemPfvn379u3rX6Zx48Y33XTTSy+99P333/fr1y/I2qKjo6Nqceulx+NxuVwWi6XKnYuaKisrUxSlXPRH7TkcDp/PFx0dHfzfeagpTdNKS0v1i0NQh+hmw4duNkzqrZut8hbD+g52aWlphw8fDmzRL5vTT8hWasOGDa+++mqrVq0mTZqUnJwcZOWNGzcWQjgcjuA1nG5qlWpyuVwul0tV1ZiYmNqsBxUV//1JNTEpccIkowuRjf6vKbvdTm9et/RgR1dQ5+hmw8fn8ymKwo6tc+bpZuv75omzzjrr1KlTx48f97fs27ev3O0UgVasWPHSSy/16dPnhRdeKJfq5syZc9dddwVeLXfo0CEhRMVr9RApvHt2en/ebXQVAABEqvoOdr17946JiZkzZ45+BnbPnj1r164dMmSI/+ET2dnZv/76q/7/P/7446xZswYPHvzQQw9VPHnatm3b33777eOPP9Z/PHr06AcffNCuXTv/9MUAAAANSn2fik1KSnrggQdefvnl22+/PT09PTs7++yzz77++uv9C0yaNCk9Pf21114TQsyfP1/TtPXr13/99deBK5k8eXKbNm0GDhx44MCB999/f926dfHx8dnZ2RkZGY888kjwB5QBAADIyoBHivXp06ddu3ZbtmxxOp0333xzly5dAqPY1Vdf7T/336NHj06d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", + "text/plain": [ + "plot without title" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "# ── Visual: PIP comparison — NA-aware vs zero-fill ───────────────────────────\n", + "pip_df <- data.frame(\n", + " variant = seq_len(p),\n", + " na_aware = fit$pip,\n", + " zerofill = fit_zero$pip,\n", + " is_true = seq_len(p) == 10L\n", + ")\n", + "\n", + "ggplot(pip_df, aes(x = variant)) +\n", + " geom_segment(aes(xend = variant, y = na_aware, yend = zerofill),\n", + " colour = \"grey70\", linewidth = 0.4) +\n", + " geom_point(aes(y = na_aware, colour = \"NA-aware\"), size = 2) +\n", + " geom_point(aes(y = zerofill, colour = \"Zero-fill\"), size = 2, shape = 17) +\n", + " geom_vline(xintercept = 10, linetype = \"dashed\", colour = \"#e74c3c\") +\n", + " annotate(\"text\", x = 11, y = 0.85, label = \"true\\nvariant\",\n", + " colour = \"#e74c3c\", size = 3.5, hjust = 0) +\n", + " scale_colour_manual(values = c(\"NA-aware\" = \"#2980b9\",\n", + " \"Zero-fill\" = \"#e67e22\"),\n", + " name = NULL) +\n", + " labs(title = \"PIP: NA-aware vs naive zero-fill\",\n", + " x = \"Variant\", y = \"PIP\") +\n", + " ylim(0, 1) +\n", + " theme_minimal(base_size = 13) +\n", + " theme(legend.position = \"bottom\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The zero-fill path underestimates $\\sigma^2$ because the zero rows reduce the\n", + "RSS artificially. This shifts the prior towards larger effects and generally\n", + "inflates PIPs for non-causal variants.\n", + "\n", + "---\n", + "\n", + "## Summary\n", + "\n", + "| Field | Created at | Shape | Used for |\n", + "|---|---|---|---|\n", + "| `data$na_idx[[m]]` | `create_mf_individual`, line 167 | integer vector, length $n_m$ | row index into $X$ and $R_m$ at every IBSS step |\n", + "| `data$xtx_diag_list[[m]]` | `create_mf_individual`, line 168-169 | length-$p$ numeric | $\\hat{B}_m$ and $\\hat{S}^2_m$ denominator; bias-correction term in $\\widehat{\\text{ER}^2}$ |\n", + "\n", + "Every aggregation over samples inside the IBSS loop (residuals, likelihood,\n", + "variance estimates) restricts to `na_idx[[m]]` for modality $m$. There is\n", + "no single $n$: each modality contributes through its own $n_m = |\\mathcal{I}_m|$.\n", + "\n", + "This matches the `ind_analysis` design in `mvf.susie.alpha`, with the only\n", + "difference being the field name." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "4.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/inst/notes/per-scale-per-outcome-design.ipynb b/inst/notes/per-scale-per-outcome-design.ipynb new file mode 100644 index 0000000..088e345 --- /dev/null +++ b/inst/notes/per-scale-per-outcome-design.ipynb @@ -0,0 +1,800 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Per-scale vs per-outcome variance design in mfsusieR" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Context\n", + "\n", + "A DWT-packed response matrix `D[[m]]` has `T_basis[m]` columns: `T_basis[m] - 1`\n", + "detail (wavelet) coefficients arranged by scale, followed by one smoothing\n", + "coefficient. The detail coefficients at different scales have different\n", + "characteristic magnitudes: coarse-scale coefficients tend to be larger and\n", + "smoother, fine-scale ones smaller and noisier.\n", + "\n", + "Two key variance parameters can be either shared across all wavelet columns\n", + "within a modality (`\"per_outcome\"`) or split one-per-scale (`\"per_scale\"`):\n", + "\n", + "| Parameter | `\"per_outcome\"` | `\"per_scale\"` |\n", + "|---|---|---|\n", + "| `residual_variance_scope` | one $\\sigma^2_m$ scalar | one $\\sigma^2_{m,s}$ per wavelet scale |\n", + "| `prior_variance_scope` | one $K$-component mixture per modality | one $K$-component mixture per wavelet scale |\n", + "\n", + "The original `fsusie` (Denault et al.) uses a single $\\sigma^2$ per modality\n", + "(`\"per_outcome\"`). mfsusieR implements both modes; `\"per_outcome\"` is the\n", + "default for `residual_variance_scope`, `\"per_outcome\"` is also the default for\n", + "`prior_variance_scope`.\n", + "\n", + "---\n", + "\n", + "## 1. The wavelet scale index\n", + "\n", + "### What `scale_index` stores\n", + "\n", + "**File:** `R/dwt.R`, via `gen_wavelet_indx` in `R/utils_wavelet.R`.\n", + "\n", + "For a signal of length $T = 2^J$, the DWT packs detail coefficients\n", + "level-by-level into columns `1` through `T - 1`, then the smoothing\n", + "coefficient in column `T`. `gen_wavelet_indx(J)` returns a list of $J + 1$\n", + "integer vectors giving the column indices that belong to each scale (level 0\n", + "through level $J - 1$, plus the smoothing coefficient as level $J$)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T = 32 -> 6 scale groups\n", + " scale group 1: columns 31 (1 coefs)\n", + " scale group 2: columns 29 30 (2 coefs)\n", + " scale group 3: columns 25 26 27 28 (4 coefs)\n", + " scale group 4: columns 17 18 19 20 21 22 23 24 (8 coefs)\n", + " scale group 5: columns 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (16 coefs)\n", + " scale group 6: columns 32 (1 coefs)\n" + ] + } + ], + "source": [ + "suppressPackageStartupMessages(library(mfsusieR))\n", + "\n", + "# For T = 32 = 2^5: 5 detail levels + 1 smoothing level = 6 groups\n", + "idx <- mfsusieR:::gen_wavelet_indx(5L)\n", + "cat(sprintf(\"T = 32 -> %d scale groups\\n\", length(idx)))\n", + "for (s in seq_along(idx)) {\n", + " cat(sprintf(\" scale group %d: columns %s (%d coefs)\\n\",\n", + " s, paste(idx[[s]], collapse = \" \"), length(idx[[s]])))\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visual: column layout of the DWT-packed matrix\n", + "\n", + "Each row is one wavelet scale. Scale 1 (finest, 16 coefs) is at the bottom; scale 6 (smoothing, 1 coef) is at the top." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "image/png": "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9erVkJAQebtUu9tvvz08PPzChQtHjhy5ccFOPnxWCGEfButoyZIlBoOhQ4cO9qe5//33302bNnVqZn/WWVkPfZffECWzY0FBgb2DWvWUm5s7ceJEm8127733Llu2zPFUos1h/ISdl5fXiBEj3n777f/5n/95+umnV65cKYSQt/6XrmtP3pz9Vsk390aryMMeXMjNzX333XdPnz7973//u3nz5k6vynPA8sHN9hO6o0eP/vTTT4UQY8eOnTdvXsVzjBxpK8/bVe1WSJWpzVFmZua4ceMuX7784osvlvxUOX36tBDCx8fH/uEgmc3m7OxscXPP1wLwuGp0osXjrFarHIlmMBiGDx/u9KrsZvfbb7/J4RE9evQo+Rkte9Tt27fvt99+0+l08qajVSs8PFw+mlaeNXRkNBqffPLJQYMG7dixo23btvK8jkwqTuTEBg0aON4Qy5G8MFqyj1HJ56pVNykpKXl5eUKId9991+kCcUpKiuxd59TDT8a4bdu2bdiw4cKFC1FRUY5v3HXtyYrvt8r01q/km1vN+fr6zpkz55tvvin1cVhy6Pdtt91m34HPP/+8TE7Tp0//8MMPrys5NWnSRDh0b6hylanNUXBw8I8//rhp06bvvvuu5KtyRyUlJTk94ca+XfJO6QBqCILd/y8rK+uxxx7bsmWLEOKFF14IDw93ahAZGdmsWbPz58/Lz1an67CS/GN68eLFhYWFd955Z1k9zypJBpH58+fLG3PYvf3220VFRQEBAX379vX19X3kkUeEEO+8845Ts+3bt3/xxRdCiCeffLKsVchvgoMHD9rv4iuEyM/Pf/3116tyS24A+3en/X4iUn5+/sSJE+XPTldF4+LiunTpYjKZ5B1qRowY4Zi6rmtPVny/2R+WJS+WXZdKvrnVnE6ne/jhh4UQc+fOdXoTV6xYsWrVKiGEvPeHnDJr1iwhxBtvvPHqq69e77rkCKRz587diGxXydocabVa+WSwzz//fP/+/Y4vffzxxzLsyiepOPr999+FEN7e3iUfcAdAYTXxUuznn39uP91lNptzc3P/+uuvbdu2yTM9vXv3/ve//13qjD169Pjrr7/kx6iLYLdr1y5xY67DSv/85z+XLFly/PjxLl26vP7664mJideuXfv222/lw9GnTZsmO4NPnz593bp16enpnTp1euWVV5KSksxm85o1a5KT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wQ4AAEARBDsAAABFEOwAAAAUQbADAABQBMEOAABAEQQ7AAAARRDsAAAAFEGwAwAAUATBDgAAQBEEOwAAAEUQ7AAAABRBsAMAAFAEwQ4AAEARBDsAAABFEOwAAAAUQbADAABQBMEOAABAEQQ7AAAARRDsAAAAFKFz8VpaWtp33313+PDhrKwsPz+/mJiYPn36dO7cufJrfeKJJxISEiZOnOj2EoqLiydNmtSrV6+BAwdWvh4AAAAFlBnszp49+8ILL+j1+m7dutWpUyc/Pz8lJeXdd98dNmzYkCFDbmaJJRmNxnfeeSctLc2zZQAAAFQrZQa75cuXW63W2bNnh4WFySnDhg174403li1bdt999wUFBd2sCp3t2bNn0aJFRqPRUwUAAABUT2UGu7y8PF9f31q1atmnaDSaoUOH7t69u6ioyD5x69atq1atSk1N9ff3b9eu3YgRI4KDg4UQZrP5+++/37Fjx8WLF4UQDRo0GDx4cGJiYqnr2rVr1w8//JCamqrX69u1a/f44487rtfRlStXZsyY0b1794cffvjpp592b5sBAACUVGawu+uuu1JSUt54442+ffvGx8cHBgYKIVq0aNGiRQt7mxUrVixatKh79+7Dhw/Pycn5/PPPT506NWvWLI1G88EHH+zYseORRx6JiYnJyclZvnx5cnJybGxseHi404rWrFnz3//+t1OnTsOGDcvJyfn6669ffPHFOXPm+Pv7l6wqICBg/vz59evXz8/Pv67ttNls19W+ytkL8HglqAybzcY7WJNxANRwHADVmabSSyj3za0OB4BGU86GlhnsunTpMnny5K+++uqdd97RaDQRERHx8fGJiYkJCQmygclk+uabbzp06DB58mQ5xd/f/9NPP01NTW3QoMHVq1eHDRtmH9kQGRk5ceLEQ4cOOQW7wsLCr776Kj4+/pVXXpFTWrZs+cwzz6xYsWL48OElqzIYDPXr16/Ytv8/RUVFubm51zvXjVBYWFhYWOjpKuC+nJwcT5cAT6omnyTwlLy8vLy8PE9XgVL4+vr6+1b2Rh95eXmO1yRLbeDxAyAkJESnczXy1dVr3bt37969++nTp48cOXLs2LHdu3dv2LChS5cuzz//vFar/fvvvwsKCrp27Wpv36FDhw4dOsifp02bJoQwGo3p6ekZGRmHDh0SQphMJqdV/PXXX0ajsVu3bhaL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qFdffTU8PLySa125cmVCQkJlgp3ZbJ45c+bp06d79uxJsAMAABAugt2PP/64adOmwYMHP/jggwEBARaLJSUlZc6cOW+99db7779fJeftKuOLL744ffq0Z2sAAACoVsoMdgcPHgwNDR0xYoT8r1ar7dSpU1pa2uLFi0+fPh0TEyOnWyyW/fv3p6Wl+fv7t27dul69evYlGI3G33///cKFCxqNpkGDBm3bttVqtaWuy2q17t+//+zZswaDoXXr1pGRka6L3r9//9q1a++5554NGzZc3+YCAACoq8xgV69evd9///3QoUO33367feKAAQPuv/9+Pz8/+d8LFy5MnTr1woULMTExOTk5H3/88dNPP33PPfcIIQ4dOvTOO+9otdoGDRrk5OScO3cuJibmvffeK5ntrly5MnXq1LS0tJiYmOzs7E8++eSRRx4ZPHhwWYVlZ2fPnTt36NChXIEFAABwVGaw+8c//nH48OHXX3+9cePGt99+e/PmzVu2bFm7dm0fHx97mzlz5uTm5s6dO7dhw4Y2m+2DDz6YP39+hw4dQkJC5s2bFxkZ+fbbb8v2a9eu/fjjj/fu3duxY0enFc2aNevixYvJyclNmjQRQnzzzTdffvllXFxcQkJCqYXNmzcvIiJi0KBB69evr+BGWiyW4uLiCja+Qcxms/y3oKDAs5XAPTabTQhRVFQk30rUNFarVQhRXFxssVhKbaDX629uRah2qsN3DUolP8CFEG5/Bds/AeQPHqTX6728XN3SpMxgFxISMnv27G3btqWkpGzduvXHH38UQrRo0eKpp56Ki4sTQly8ePHYsWMjRoxo2LChEEKj0TzyyCPx8fFeXl42m23ChAmhoaH2FNiyZUshRGZmptNazp8/f+TIkcGDB8tUJ4QYMmTIxo0b165dW2qwW7NmzdGjR6+3k5/ZbM7Pz694+xvHbDYTC25phYWFni4BnuTiAPD29r6ZlaAaslgs1eS7BmWp5BtUVFRUVFRUVcW4x9vb281gJ2fu1atXr169bDZbWlra/v37V65c+eqrryYnJzdu3Pj8+fNCiKioKHv7WrVq3XXXXfLn+Pj433//ff369enp6RkZGbJxyZybmpoqhDh//vzixYvtE7VabakDI1JTUz/77LNx48bVqVPH5VY78/Ly8vgf0xaLxWw2a7Vana6cW8ygeiouLrbZbOX+RkFVJpPJarW6OAA8PqQMHlcdvmtQFhnI3H6Dyv0EuGnK/agpPWRkZ2evXr26Xbt2zZo1k0tp1KhRo0aNOnfuPGrUqI0bN44aNcrF2Uir1TpjxoxffvnljjvuiI2Nveuuu8LCwl588cVSWwohrl27ZjKZ7BMbNWoUEBBQsvGiRYt0Ot2xY8eOHTsmhEhLSxNCfPXVV40bN3bRJ08I4e3t7fE/po1Go9ls9vb2LnXTUP1du3bNYrH4+fl5/FiCR2RlZVmtVl9fX8fuKIAjnU4XGBjo6SpQCqvVKoOd229Qdna21Wo1GAzVP7uXHuy8vb2///77c+fOvfTSS47Tg4KCvLy8ZBeTjhHekQAAIABJREFU+vXrCyHOnj3brl07+erVq1enTZsmB9Lu2bNn1KhRDzzwgHzp6NGjpa6oQYMGQoiePXvKIRfS8ePHS00/tWvXbtCgwalTp+R/s7OzhRBpaWmcAwMAABBlBTs/P7/evXuvX7/+ww8/HDBgQGRkpNlsTk1NXbx4sdVqlddbIyMjmzdvvmrVqk6dOkVERNhstqVLl6alpTVq1EheYLWfLczPz5dXWh1Py0mNGzeOi4tbunRpfHy8vMvJ/v37p06dev/99z/11FNOjcePH+/433Xr1s2fP//VV1+tW7duFewJAACAW1yZ57pGjx5tsVg2bty4YcMGnU5ntVqtVqu/v/+kSZPk9VkhxOTJk6dMmTJ+/PioqKisrKzs7Ozx48eHhYWFhIQ0b978k08+2b17t4+Pzx9//BETE2MwGK5cuVJyRS+88MLUqVPHjRsXFRVlsVjOnDmTkJDwyCOP3KgtBgAAUFSZwU6n040fP37o0KFHjhyRo1nr16/funVrX19fe5vw8PD333//t99+O3/+fHBwcJs2bcLCwuS87777bkpKSnp6uq+v70MPPdSyZcvNmzfbz9j179/f/lwyuRB5g2K9Xh8TE3PbbbdVpPS4uDjuZgcAAGBXTu+0sLCw7t27u5pfp+vUqVPJ6V5eXk6Pgu3Vq5f95wEDBji+pNVq27dv3759+3LLdRQbGxsbG3tdswAAACiMGzcAAAAogmAHAACgCIIdAACAIgh2AAAAiiDYAQAAKIJgBwAAoAiCHQAAgCIIdgAAAIog2AEAACiCYAcAAKAIgh0AAIAiCHYAAACKINgBAAAogmAHAACgCIIdAACAIgh2AAAAiiDYAQAAKIJgBwAAoAiCHQAAgCIIdgAAAIog2AEAACiCYAcAAKAIgh0AAIAiCHYAAACKINgBAAAogmAHAACgCIIdAACAIgh2AAAAiiDYAQAAKIJgBwAAoAiCHQAAgCIIdgAAAIog2AEAAChCY7PZPF1DjWA0Go1Go8FgCAgI8HQtcMe1a9csFktwcLC3t7ena4EHZGVlmc3moKAgHx+fstoUmyz5BSb3lu/v6+3jrb2WU+hugaJWkMFYYCoyWdybPThAb7XZcvOL3Ztdr9f56XU5eUUWq5vfKbWCDIXFloJCN3dgoL+P1kuTlVvk3uw6rVegv0+esdhktrpdgE7LuZJqymq1ZmZmCiHCwsLcW0J2drbJZAoMDNTr9VVaWtXTeboAAFCEj7fWx1tbmSXUCjJUZnY/X28/X/f/8PASmkoWEBRQqe88g4/W4OPJHRjgV2ZqLyoqstls3t7eWm2lKgRuNP68AACgHAUFBXl5eWaz2dOFAOUg2AEAACiCYAcAAKAIgh0AAIAiCHYAAACKINgBAAAogmAHAACgCIIdAACAIgh2AAAAiiDYAQAAKIJgBwAAoAiCHQAAgCIIdgAAAIog2AEAACiCYAcAAKAIgh0AAIAiCHYAAACKINgBAAAogmAHAACgCIIdAACAIgh2AAAAiiDYAQAAKIJgBwAAoAiCHQAAgCIIdgAAAIog2AEAACiCYAcAAPD/tXfncVGV/f/Hr1mZYRFQFAQFQVETExM1tUVMzdvMXTOXbPneeltqlmkWWfdtaXeaFaamZZRLWmrmVmouWZniV0xzzcgNAUFAFmUbZjm/P87jN7/5sYkDMnB8Pf9irnOdM5+Zc8G8Oec6ZxSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAitqwu4W+h0Ond3d62WN7y+MhgMkiSp1fwvdJcyGAw2m02j0bi6EACoDDmjRlktwmIud4lOCJ1GJSSrMBWVv65OL9SaCpfekloldAZhKRFWq5Nb0BuEJAmzycnVtTqh0YqSIiE5uQHhZhQWs7BanFxd7yaESpQUO7m6RiO0emE2CZut3OVGtRBCJSwloqIC3YyVDIBbq+YAECrh5toBoBUaXXUHQHXeQL2bUKmEydkBoFYLnVslA8CgEkJT6QDQuwkVuR+AixHsapJ08hfb/nXOrase9rIqKNy6dIqTz924meapObZdcVLiUec2oHl+kZR+0bZ5kXOrq6OfVHXqa/0iRhTecLKA6XG2w9ukIzucXH38HKHRWb+McW51Vdtu6scm2DZ9KF0972QBLy6X/oy37Vnl3OrqQVNULSOdHwBeDTUT3rftXS2djXduA5p/fSBlp9s2vu/c6uoeQ1TdBlpX/1vcuO5kAS9/LiXssB3a6uTqY2YLD2/ripnOrS5CO2iGTrNtXSxd+dO5DahHv6FqGubkswNADeH/SwAAAIUg2AEAACgEwQ4AAEAhCHYAAAAKQbADAABQCIIdAACAQhDsAAAAFIJgBwAAoBAEOwAAAIUg2AEAACgEwQ4AAEAhCHYAAAAKQbADAABQCIIdAACAQhDsAAAAFIJgBwAAoBAEOwAAAIUg2AEAACgEwQ4AAEAhCHYAAAAKQbADAABQCIIdAACAQhDsAAAAFIJgBwAAoBAEOwAAAIUg2AEAACgEwQ4AAEAhCHYAAAAKQbADAABQCIIdAACAQhDsAAAAFIJgBwAAoBAEOwAAAIXQVr746tWrp0+fzsvLMxqNLVu2bNu2rUqlqv6zbtmyJSAgoFu3bk6su3379vz8fMeW7t27t2jRovpVAQAA1GsVBjubzbZkyZJ9+/ZJkmQwGEwmkyRJLVq0iImJCQgIqOazbt26NTIy0olgZzabv/jiC51Op9Pp7I3BwcEEOwAAgAqD3fbt2/fu3TtixIhhw4Z5enpardYjR47ExsbOnTt38eLFNXLczglJSUlWq3X27NlRUVEuKQAAAKDOqjDYnThxomHDhuPHj5cfajSa7t27Jycnf/XVV5cuXQoLC5PbrVbrsWPHkpOTPTw8Onbs6O/vb99CYWHh8ePH09PTVSpVs2bNoqKiNBpNuc9ls9mOHTuWlJRkMBg6duwYFBRUUVUXLlwQQrRq1cqJlwoAAKBsFQY7f3//48ePnzx5skOHDvbGIUOGPP744+7u7vLD9PT0t99+Oz09PSws7MaNG8uXL580aVK/fv2EECdPnvzvf/+r0WiaNWt248aNlJSUsLCwDz74oGy2y8rKevvtt5OTk8PCwvLy8lasWDFu3LgRI0aUW9XFixebNGmSk5Ozd+/ewsLCe+65JyoqylWHDwEAAOqUCoPdqFGjTp06NXv27JCQkA4dOrRt27Zdu3aNGjXS6/X2PrGxsTdv3ly0aFHz5s0lSVqyZMmyZcu6du3q4+Pz8ccfBwUFvfvuu3L/HTt2LF++/OjRo/fff3+pJ/rwww+vXbu2cOHCli1bCiG+/vrr1atXh4eHR0ZGlq3qwoULubm5r776anBwcGZm5saNG6OiomJiYhyn3JVlsVhMJtNtvS9OMBqNBEzgblY7f2rgEjabTQhhMpksFoura8FtkyRJ/qGgoMC5LVitVlE3BoDRaFSrK7ulSYXBzsfH56OPPvrll1+OHDny888/b9++XQhxzz33/POf/wwPDxdCXLt27ezZs+PHj2/evLkQQqVSjRs3rn379mq1WpKkadOmNWzY0J4C27VrJ4TIzs4u9SypqamnT58eMWKEnOqEEE8++eTu3bt37NhRbrBzc3Nr06bNq6++6u3tLUnSxo0bv/rqqw0bNowdO7aSF2m1WouKiirpUCMMBgPBDrib1c6fGrhQSUmJq0tAtVTzN7QuDAA3Nzcng50QQqfT9enTp0+fPpIkJScnHzt2bOvWrTExMQsXLgwJCUlNTRVCOF6O6uvr26tXL/nn9u3bHz9+fNeuXVevXk1LS5M7y//xOLpy5YoQIjU19auvvrI3ajSaS5culVvSvHnz7D+rVKonnnhi//79v/32W+XBTqPRGI3GSjrUCM4IA3e52vlTA5cwmUw2m02v11c0WRx1mSRJxcXFQginf0PrzgCoPNWJioJdXl7e999/37lz5zZt2gghVCpVcHBwcHBwjx49JkyYsHv37gkTJpRNaXY2m23+/PmHDx/u1KlTq1atevXq5efn9+qrr5bbUwiRk5NjNpvtjcHBwZ6enlV5eUIIf3//pKSkyvtotVqt9hZ37KsRUi08B4C6qtb+1KD2mc1mm83m5ubm5ubm6lpw22w2mxzsPDw8nNuCxWKpLwOg/L9BOp3u22+/TUlJmTVrlmN7gwYN1Gq1fKa5adOmQoikpKTOnTvLS69fv/7OO+/IF9LGx8dPmDBh4MCB8qIzZ86U+0TNmjUTQvTu3Vu+5EKWmJhYbrBLT0+fPXv20KFDBwwYILfYbLaUlBR5IwAAAHe58g/oubu79+3b9+DBg0uXLpXPoloslosXLy5YsMBms8nnW4OCgtq2bbtt27arV68KISRJ+uabb5KTk4ODg+WN2E9NFhQUyGdaHQ/LyUJCQsLDw7/55hv5WYQQx44de/XVV3fs2FG2Kn9/f51Ot2nTpqysLPkZ16xZk5GRMWTIkGq/DwAAAPVehWcNJk6caLVad+/e/eOPP2q1WpvNZrPZPDw8Xn75Zfn8rBBi+vTpc+bMmTp1aosWLXJzc/Py8qZOnern5+fj49O2bdsVK1YcOnRIr9f/+eefYWFhBoNBDmSlzJw58+23354yZUqLFi2sVuvly5cjIyPHjRtXtqdKpXrttdfeeeed559/PiwsLDMzMzs7e8yYMdysGAAAQFQS7LRa7dSpU0ePHn369Gn5atamTZt27NjRceJhQEDA4sWLExISUlNTvb2977vvPj8/P3nd995778iRI1evXjUajSNHjmzXrt2+ffvsR+wGDx5s/14yeSPyDYrd3NzCwsIiIiIqqiokJGTZsmVHjx5NS0szGAz33XeffEYYAAAAt5jn6+fnFx0dXdn6Wm337t3LtqvV6lJfBdunTx/7z6VOnmo0mi5dunTp0uWW5QohdDpduc8IAABwl7vFRbMAAACoLwh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIVSSJLm6BgWRbMJmc3JdtUaoVMJqcf7ZNVphswnJ2QI0WiFJwmZ1cvUaqb86b6BGK4QkrM7Wr1ILtVrYrMLp34i68AZWcwBU5w10/QDQCFGNAlQqodZUbwBohFA5uS7qvNzcXIvF4uXl5ebm5upacNtsNlt2drYQws/Pz7kt5OXlmc3mejEAtK4uQFlUaqEp/yCo2WwuKSnRarW3GBOa6u0RtbpaR2FVquoWUM3VK34Dq7h+td9ATUVLioqKbDabwWDQaCrs4/o3sJoDoPpvoIsHQPXfwOoNAABwNU7F1hKz2VxUVGQ2m11dCJxUXFwsf7S7uhC4hslkKioqsjp9RBMAagXBDgAAQCEIdgAAAApBsAMAAFAIgh0AAIBCEOwAAAAUgmAHAACgEAQ7AAAAhSDYAQAAKATBDgAAQCEIdgAAAApBsAMAAFAIgh0AAIBCEOwAAAAUgmAHAACgEAQ7AAAAhSDYAQAAKATBDgAAQCEIdgAAAApBsAMAAFAIgh0AAIBCEOwAAAAUgmAHAACgEAQ7AAAAhSDYAQAAKATBDgAAQCEIdgAAAApBsAMAAFAIgh0AAIBCEOwAAAAUgmAHAACgEAQ7AAAAhSDYAQAAKITW1QUAAADUaUaj0c3NTautB6mpHpQIAABwp10vLiiyllTWw1pU0RI/g6dBo6v5mm4fwQ4AAEBsunT896wrzq07tX10e9/Amq3HOcyxAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAhtJcvMZvPPP/986tSp3Nxco9HYsmXLRx55xM/Pr/rPOn/+/JYtW44YMcKJdU0m065du06fPi1JUnh4+OOPP+7h4VH9kgAAAOq7Co/Y3bhx46WXXlq8ePHly5d1Ol1eXt769esnTZqUkJBQ/Wc9d+5cSkqKEysWFhbOnDlzw4YNjRs3btKkyZYtW2bMmJGfn1/9kgAAAOq7Co/Ybdq0KSUl5T//+U+nTp3klpycnJiYmEWLFsXFxbm5udVWhf+ftWvXZmRkfPTRR02bNhVC9O3bd9q0afv27Rs8eLBL6gEAAKg7Kgx2V65cadSokT3VCSF8fX1HjRq1ZcuWjIyM5s2by42pqak//vhjcnKyh4dH586de/bsqVKp5EVnzpw5cOBAenq6SqVq1qzZY489Jqexsq5du7Zjx46kpCSDwdC5c+dHHnlErS7nUKLVat23b9+AAQPs2wkNDY2Li/Py8nLuxQMAAChJhadiIyIisrKy1q5dm5eXZ2+Mjo6OjY21p7rjx49PmzYtISEhJCREr9cvWrRo0aJF8qJt27a9/vrrGRkZ4eHhTZo02bt37yuvvFJQUFD2ic6cOTN16tSDBw+2aNHCaDR+8sknc+fOlSSpbM+UlJTCwsKIiIiffvrpP//5z6uvvrpy5UqDwWAwGKr1HgAAAChChUfshg4dmpaWtn79+g0bNoSEhLRt2zYiIiIqKsrT01PuIEnS0qVLAwMDFy5cqNfrhRAhISFxcXFDhw5t1qzZ+vXrH3744RkzZsidu3fv/tZbbx05cqRXr16Oz2Kz2WJjY318fGJjY93d3YUQDz744Jw5c3bv3t2vX79SJWVmZgohvv3229TU1B49ehQVFW3btu3w4cMLFy60V1Uus9lcXFx82+9NjbJarXIlN2/edG0lcI7NZhNCFBYWlns4GYonD4CioiKTyeTqWuAC8t/woqKikpISV9cC51X0EaxSqSoPElVhsViKioqquZFbcnd312g0lXSoMNhpNJqpU6eOHDkyISHh9OnT8fHxu3btMhgMTz/99IABA4QQly9fzsjImDx5spzqhBD9+/d/4IEHGjVqpFKp1qxZY7FY5Pbc3NysrCwhRNkXfP78+WvXrj333HNyqhNCREVFBQcH//rrr2WDnfz3NC0tbfHixQ0aNBBCREdHv/XWW99999348eMreZE2m62O/C22Wq3yXwfUU2az2dUlwJUYAHc5i8Vi/2hDfVRRGKiR/9hrJ2wYjcbKO1R2uxMhREBAwMCBAwcOHCiEOH/+/Jo1az799NMmTZp06dJFzmr+/v72znq93n4zlJs3b27duvXYsWNXr14tLi5u0qSJEKLsCdaMjAwhxO7du+Pj4+2N2dnZhYWFZYuRr9jo06ePnOqEEB07dgwODj558uQtXqRWW/0kXk0lJSUlJSU6nc5V152gmgoLC202m9ForPxfJSiVPAAMBoNWe4s/m1AkBkC9JkmSPBmsojBgvzygOmonbNwyg5Y/QLOysj744IPHHnvsoYcesje2atXq9ddfHz169JEjR7p06SIP7lIH4QoKCjw8PAoLC1977bWCgoIhQ4a0bNmyRYsWBQUFkyZNKvtE8tG+iIiIwMBAe2O3bt3KTT+NGzcWQpS6a527u/st/3/SaDQu/zC22WwlJSUajYYZgfWUPNT1er1Op3N1LXCB4uJim82m1+vt5yhwV5EHAP+c11M2m00Odnf0I1itVteFj/jyc5+3t/fly5c3b95c6rxDXl6ezWaTr0INDQ1VqVRnz561L7106dLo0aMPHDjwxx9/pKamTpw4cdiwYZGRkd7e3leuXBHlHbELCwtTqVQNGzYc6kAuoGxVzZs39/b2PnbsmL2lsLDwypUrLVu2dPLVAwAAKEj5wU6n040bN+78+fOvvPLKjz/+eO7cuRMnTmzbtu2NN97w9PTs37+/EMLHx6dXr147d+7cv39/cXFxWlrasmXLGjVqFBUVJZ+QPXnypMVikSTp5MmTn332mRCi7JxTPz+/nj17btq0ae/evUVFRfn5+atXr/7yyy9zc3PLqVWtHj58+B9//LFu3bri4uKcnJwPP/zQbDYPGjSoht8VAACAeqjCuQIDBgzw8PBYt27d0qVL5RaVSnXvvfdOmDBBPiUqhHj++eeFELGxsR999JEQIjQ09K233nJ3d2/duvXw4cM3b968Z88ejUajVqsHDRq0Z88e+bhdKZMnT9bpdEuWLPn444+FEEajcezYsfL1GWUNGTLEbDZv2LBh/fr1kiT5+vq+/vrr9tuvAAAA3M1U5d4xzlFWVlZ2drYQIiAgwH7VgqMbN25cvXrVx8fH39/fcfrhjRs30tPTjUajv7+/Xq+/fPlySUlJ69athRDnzp3z9PRs1qyZvXNBQUFKSoqbm1vTpk1vOYPBZDJduXJFq9UGBwe7fPJcFRUWFhYWFhoMBpdfxgHn5OTkWK1Wb29v5tjdnXJzcy0WS4MGDZhjd3eSB4CXlxdz7Oojm80mJ5nKv+/+sz9/+z2rnCNQVTG1fXR738Bb97vzbn11j5+fX+VvRIMGDcoNfKXaW7RoYf+5bdu2pTp7eHi0adPmlsXI3NzcwsPDq9gZAADgLsGtVgEAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEJoXV0AAACA67lr9d56o3PralWami3GaQQ7AAAAMS68q6tLqAEEOwAAgMoUFhZarVaDwaDT6Vxdyy0wxw4AAKAyZrPZZDLZbDZXF3JrBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIVSSJLm6hruC/X1WqVSurQTOkfcgu++uxQC4yzEA6rtq7sF6NAAIdgAAAArBqVgAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEIQ7AAAABSCYFd7zGbzmTNnXF0FnGE2m5OSkpKSksxms6trQe3Jzc1NSkoqd1FqampiYuKNGzdquSTUDrPZnJycfOnSpeLi4kq6nTlzJicnp9aqQtXdvHkzMTHx2rVrlfRJTU3Nysoqd5HJZLp48WJSUlK59/qt4vBwFa2rC7iLrFy5cs+ePRs2bHB1Ibg9+/fv//zzz2/evCmE8PLymjBhQnR0tKuLwh1XUlIyd+7cBg0avPXWW47t58+f/+ijj5KTk4UQKpWqT58+L7zwgkajcVGZqHnffffdhg0bCgsLhRBarXbw4MHjxo0ru4t37ty5bNmyadOm9e7d2xVlonxms3np0qX79++XM1nbtm2nT58eEBBQqlt6evrMmTNHjhw5dOjQUou2bNmybt06ObT5+flNmTKlU6dO9qVVHB4uxBG72mC1WlesWLF9+3ZXF4Lbdv78+UWLFnXo0GHNmjVr167t3LlzbGzsuXPnXF0X7qzc3Nw5c+YkJiaWas/JyXnrrbe8vb0/++yzb7/9dvLkyXv37uW/NSX56aefVq5c+Y9//GPt2rXffPPNmDFjNm3a9PXXX5fqlpycHBcX55IKUbm4uLhff/111qxZmzZtio2NzcvLe+edd0odeDt//nxMTEx+fn7Z1Q8ePPjFF18MHDhw/fr1K1euDA0Nfffdd+1H/qo4PFyLYHfHnT59+qWXXtqzZ09oaKira8FtO3bsmM1mmzRpkre3t5eX1/PPPy9J0uHDh11dF+6gnTt3TpkyJTMz09fXt9SirVu3qlSqmJiYgIAAvV7/6KOPjhkzRq/Xu6RO3Al79uwJCwt75plnvLy83N3dR4wYce+99+7fv9+xj9lsXrhwYZs2bVxVJCpisVji4+P79u3bo0cPnU4XFhY2bNiw5ORk+5wKs9m8YsWKmTNnNm7cuNwt/Prrr82bN3/qqaeMRmPDhg2fe+65kpKSI0eOyEurMjxcjlOxd9yvv/4aGhr65ptvbt26NS0tzdXl4PZ4eXkJIbKzs729vYUQ+fn5kiRptfziKNmePXv69+8/fPjwWbNmlVqUkJDQpUsXT0/P5OTkzMzMwMDAUaNGuaRI3CFlT6u5ublZLBbHllWrVplMpqlTp06fPr12q8MtaLXaVatWOe6vjIwMlUrl6ekpP8zLy0tISJg8eXK3bt3GjBlTdguvv/6641zqzMxM8X8/CETVhofL8fl0x02YMEGn07m6CjipV69eBw8eXLBgwRNPPKHVar/99tvGjRv379/f1XXhDpo/f365v7M2my01NbVz585vvvnmiRMn5MbevXtPmTKlTs2wQXVEREQ4PkxJSTl+/Hjfvn3tLceOHfvhhx8WLFhgMBhqvTpUiVartVgsZ86c+eOPP7Zs2TJ06FDTlQCzAAAT+klEQVQ/Pz95ka+v7/Lly9VqdUFBQUWry7/+iYmJf/3114YNGyIiIh544AF50S2HR11AsLvjSHX1ml6v79ix4/r162NjY4UQarV60qRJjRo1cnVduIMq+p0tLCy02Ww//PBDVFTUihUr3N3dv//++6+//trX13f8+PG1XCRqQU5Ozrx583x9fceOHSu35OXlLVq06MknnwwPD09JSXFteajElStX5s2bZzKZGjZsGB4ebm+v+v9g77zzTlFRkdVq7dSpk1pdzry1ssOjjiDYAZX54osvtm3b9uyzz/bv31+lUu3du/eTTz7Jysqqa7/JqAXy/GsvL69XXnlFnlc3evTos2fP7ty586mnnlKpVK4uEDUpOTl5zpw5Vqt17ty58kwMIcTHH3/s7+8/cuRI19aGWwoNDd2wYUNxcfGXX345f/786dOn3+7dDFatWiWE+OmnnxYvXpyenj516lTHpeUOjzqCiyeAyuzZs6dbt25Dhw41GAxubm4DBgzo1avXd999Z7VaXV0aapu7u7tarW7ZsqXj1RJt27YtKCio5LQO6qMjR47MmDHD09Nz4cKFQUFBcuO+ffsSEhK6du168ODBAwcOHD16VAiRmJh44MAB/iDUNfI/WgaDYdKkSQ0bNty9e/ftbkGtVqvV6j59+jz44IM//fST4y4ud3jUHRyxAypktVqLi4tLXTzl7+9vNptv3rzp4+PjqsLgEhqNpnnz5tnZ2Y6NeXl5Op3Ow8PDVVWhxu3cuXP58uXdu3d/6aWXHCfSyedeV69eXarzzp07N2zYwDzLuuD69eurVq165JFHOnbsKLeoVCqj0Vj1Owl/+umnAQEBgwcPtrd4eHhYrdaSkhKj0SgqHh51B8EOqJBGo2nXrt2hQ4eefPJJ+aqo4uLi3377LSAggFR3d+rZs+fq1auPHz9+3333CSFycnIOHDjQrVs3zsMqRnx8/PLlyx977LGJEyeW2q3jxo0bPXq0/eHVq1dffPHFyZMn9+rVi1ve1BE+Pj4JCQnp6ekdOnSQJ8YdPXr06tWrVT97fu3atV9++eWhhx5q2LChECIzM/PgwYP33HOPnOoqGR51B8EOqMykSZNiYmJefPHFhx9+WKVSHTx48MaNG7Nnz3Z1XXCNwYMHHz16dN68edHR0Z6enj///LPBYHj22WddXRdqhtVq/fTTT4UQGRkZ7777rr1dq9XOmjVLo9E4HpaTb3uk1WpJdXWHRqOZOnXqggULpk+f3qlTp2vXrsmxbMSIEVXcwsSJE2fMmPHyyy/37NmzpKTk119/1el08gS7yofHnXg5ziHY1Z7AwMB27dq5ugrcnpCQkCVLluzYsePChQs6nS46OnrAgAEcrrtLtGrVqtS0aJ1ON2/evL179x47duz69et9+/Z9/PHH69rUaTgtOzs7MDAwMDCwqKjIsb3cW1e6ubm1b9++7F2s4Vo9evRYvHjxrl27Ll26ZDAYXnjhhd69e5c9Ua7RaNq3b2+/DYpdQEDAsmXLvv/++0uXLlmt1sGDBz/22GP2G5pWfXi4kKrcL7gFAABAvcNVsQAAAApBsAMAAFAIgh0AAIBCEOwAAAAUgmAHAACgEAQ7AAAAhSDYAQAAKETduqsegNtlMpk2b9584MCBK1eu2Gy2li1bDhw4sG/fvre7nczMzJEjR7Zp00a+tXpdk5+f//jjjwcHB5f6pk4nDBs2LC8vb9++fTVSWLlqqtpnn3320qVL9odqtdpgMDRq1CgqKmr06NH+/v6OnUtKSh599FH7wy+//DIoKKhUS2hoqOMq2dnZw4YNsz/cuHGjRqMp1VLqu5Jrgc1mW7ZsWf/+/cPCwirqU4PjAVAaCUC9tXHjxrJ3ThdC9OrVKzc397Y2lZycLISIioq6Q6VWU05OjhCiTZs21d+Uv7+/RqOp/nYqUVPVRkREVPSn29PTc9WqVY6d5bvhazSaoKCgoKCgc+fOlW0ptf2MjAx5kU6nE0IkJyeXbanmS3DCM888I4Q4depUJX1qcDwACsMRO6C+WrBgwaxZs7Ra7fjx48ePH9+yZcvCwsJDhw7Nmzdv//79AwYM2LNnj/zF1QpgMBhmzZrVpEkTVxdSJTVb7TfffBMZGSmEkCTp5s2bly9f/vLLL3ft2vXss896eHgMHz7csXOzZs0uX74s/1xcXFyqpZTGjRunpKQIITp37vz777+X21L70tLSbtmnfo0HoDYR7IB66fDhwzExMXq9fuPGjYMGDbK3t2vX7vHHH+/Ro8fBgweXLVs2ffp0FxZZgwwGw3vvvefqKqqqZqsNCQlp27at/WHXrl2feOKJmTNnLly48IUXXujfv7+7u3tNPVd9Ub/GA1CbuHgCqJfefvttq9X64osvOqY6WUBAwH//+18hxLp16xzbS0pKVq5cOXbs2EcffXTcuHFr1qyxWCwVbT8zMzM6Ovpf//pXqfZhw4ZFR0fLP+fl5UVHR7/33nslJSWffPLJsGHDBg8e/P777xcWFgohUlNTX3vttQEDBvzP//zPjz/+aN+CvNa8efMsFsvnn38+cuTI/v37z5w588KFC5W83vz8/Ojo6PHjx9/uRvLy8hYsWDB8+PAhQ4YsX77carWW3bjNZlu/fv348eP79es3bty4r7/+2maz2Ze+8MIL0dHRH3/8seMq8+bNi46OXrBgQc1WW3Xvvffevffem5GRsWrVKqc34jTndr0sJydnyZIl8jgcPHhwTEzMX3/9JS9KT0+Pjo4+cuSIEOLZZ5+Njo7Ozs7Ozs6WnyspKemZZ54ZNGjQe++9V+oddmIfAYrl6nPBAG5bZmamVqsVQly5cqXcDhaLpdSEqqSkJHnClkql8vX1ValUQoj77rvv6tWrcodSc+wqmnInT9i3lyGEeOKJJx544AHHvyq9e/c+ceJEqcl/H3/8seNaI0eOtAdEmcFgOHToUEUvudScqipu5OzZs0FBQUIInU7n5eUlhOjbt2+jRo0c59hdv37dXr+vr69GoxFCPPzww/ZJivHx8Wq12s3N7e+//5ZbDhw4oFarGzVqlJaWVoPVliXvsvj4+HKXfvjhh0KIoUOHyg/lGXUhISH2DmVbKhIVFSX+/xl1ZVscObfrJUnav3+/j4+PEEKj0Xh7e8sddDrd9u3bJUlKTU2NjIyU91SbNm0iIyOzsrLkM7OjRo1q3bq13P/hhx8u9Q47sY8ApSLYAfWPfEVnaGhoFftbLBZ5ktbo0aPT09MlSUpNTR0yZIgQonv37jabTapGsFOr1c2bN//pp5+Ki4vj4+PlD3UfH58hQ4acP3++qKjogw8+EEI0adJEfiJ5LY1GExgYuGnTpps3b6akpIwYMUIIMWDAgIpeQrlRqfKNWK3WDh06CCGmT59eUFAgSdIPP/xgTxX2LQ8YMEAOfJcuXZIk6fr16+PGjZNTi72PfEa7b9++kiQVFBS0atVKCLFhw4YarLZclQe7X375RQjRokUL+WHtB7vb3fVFRUVNmzZVqVSLFy8uKSmRJOn69esTJ04UQtx77732jffr1084XDwhBzu9Xu/v77906dKlS5du3Lix7MUTt7uPAKUi2AH1z5o1a4QQPXv2rGL/b7/9Vghx//33W61We6PZbJZzj3ywxOlgJ4TYs2ePvcOMGTOEEM2aNSsqKrI3yp+yKSkpjmv9/PPP9g55eXlarVar1TpW6KjcqFT5RrZv3y6E6NWrl+N25NPT9mD3v//7v3I2ys/Pt/exWq1yojp9+rTcUlhYGB4eLoTYuHHjyy+/LIQYM2ZMBW+2k9WWq/Jgd+7cOSGEl5eX/LD2g93t7vqDBw/Ksc9xUyUlJXq9XqPRyOFPqiDYCSF+/PFH+1plg93t7iNAqZhjB9Q/JpNJCCHfkKIq5HlOEyZMUKv/36+8VquVD5bs2rWrOsV4eHj06tXL/lC+99gjjzxiMBjsjfL50Pz8fHtLgwYNHn74YceHQUFBFotFnqRVRZVvRH7VY8eOdVxl5MiRnp6e9oc7duwQQowaNcrDw8PeqFar5cNpO3fulFuMRmNcXJxKpZoyZcqiRYsCAwOXLFlS9TqrUq0T5PPp8mBwidvd9T169MjJyZH/zbDLzMz08vKyWq1ms7mS5zIajY888kjlHaq/jwAF4KpYoP4JCAgQQmRlZVWxv3yT2/bt25dqlw8IVXQvjCqSbwtnfyjHTfnj3E6eEShJkr1FPiXn2EdOA+Ve3FCRyjciv2r5KI5jJeHh4SdPnpQfJiYmCiE2b958+PBhx27p6elCiL///tve8tBDD02ePFnOCnFxcb6+vlWvsyrVOiE3N1cIYZ+pVvuc2/VpaWlbtmw5d+7chQsXzp49e+XKFbnd8YKVspo1ayZvqhLV30eAAhDsgPqnTZs2Qoi//vrLZDK5ubmV22fNmjWRkZH33nuvSqWS72dWtqfcUvkHaimOn9Cycm+V53hosFwVfUiX3b7TG5FPROr1+lJLHQ/OyX2ys7MLCgpKdQsKCiq1rv0QaWpqatWLrGK1Tjhz5oz4v4PBJZzY9a+99tqHH34oH5xr3rz5fffdN3369Llz597yv5Qq3tKlmvsIUABOxQL1T6tWrVq3bl1UVFTR92JdvHhx/PjxkZGR8g01GjVqJIS4du1aqW7y7KVyvzNKPrZUNvPduHGj2uXXEnk6oH02mF12drb9Z/mgzvz581PKs3jxYnvP+Pj4RYsWBQUFGY3GV155pS7kBvkc+kMPPeTqQqpq/fr18+fP9/Hx+e677/Ly8q5cubJ169Zp06Y5fcyylDq4j4DaR7AD6qXnnntOCBETE1NSUlJ26Zw5c4QQDz74oDx1XZ4IXzYFyi0dO3YsuwX5YF5eXp5j499//y0f/KsXevToIYTYv3+/Y+P169fl06+yTp06le0jhPjmm29mzpx56NAh+aHJZHruuedsNtsnn3wye/bsvLy8snf4q2VnzpzZvHmzSqWSr+GtFzZv3iyEmDdv3tChQxs0aCA3Xr16VT6nbD9yWeqEdRXVwX0EuATBDqiXXnrppVatWp04ceLRRx91nCSXn5//yiuvrF69WqPRzJ8/X24cO3asRqP55JNPTp06Ze+ZkJAQFxfn5uYmXyhQSsOGDRs0aHDx4kX5ulEhhNVqfeONN+7gS6pp8nUSn332mXzKUvb666873pZ51KhRRqNx3bp1e/bssTcmJSW9+OKLCxcutJ+K/fe//33u3LmhQ4cOGjRoxowZ99xzzw8//PDVV1/V2msp5fDhw4MHDzabzU899VS7du1cVcbtkk/dZmRk2FssFsvLL78sRzr7xRPy6dSbN2/e1sbr2j4CXIU5dkC95Obm9v333/fr1++XX35p2bJljx49wsLCMjMzExISsrKyVCrV0qVL5UNWQoiwsLB33nknJiame/fu//znP1u3bv3nn3/GxcWZTKYlS5Y0b9687PbVavXYsWOXLVvWr1+/8ePHe3p6bt++PTk5OSIiwjEn1WX+/v4LFy6cNGnSAw888PzzzwcGBm7fvn3//v1+fn7yzTKEEI0bN46Njf3Xv/71j3/8Y8yYMVFRUdeuXYuLi8vMzJw0aVLnzp2FEEePHl24cKGXl5f8xQZ6vX758uU9e/acNm1a37595RO+d9Sbb74pn0yXJCk/Pz8xMfH8+fNCiC5duixduvROP3sNGj58+MqVK+fOnZufnx8ZGZmenr569erjx48bDIbi4uIbN27Ih/GaNm0qhHj66ae7du36/vvvV+UAnsv3EVCHuOxGKwCqLS0tbeLEiY7XRapUqp49ex44cKBs57i4uGbNmtl7RkREbN682b607I3rCgsLn376aftlj+3atUtISJBv5yt3kKevRUREOD7LihUrhBBvvPGGY2Pv3r2FEH/++WdFa0mSJF8EkJOTU+4rLffOcFXZyKpVqwIDA+WX4Ovru379+vvvv9/xBsWSJG3cuFE+Zy1r1KjRu+++K99ezmQyyVcTx8bGOq7yzDPPCCGGDRtWs9WWIl+2XFZQUNC///1v+a7LdrV/H7vb3fWSJC1atMh+ElYIERoaunbtWvm2c6tXr5b7nDx50v7dFbt375ZngkZGRjpu1vEddm4fAUqlkpy9IAtAHWG1Wi9cuJCZmanX61u1alXJXR4kSUpMTMzOzg4MDAwJCXFcZDKZ4uPjvby85E90u+zs7MTERB8fH/l76E+dOnX9+nX5q7HMZvPBgwc9PDy6dOli75+WlvbXX3+FhISEhobaG0+cOJGTk9O1a1d3d/dy1xJCHDlypLCw8MEHHyz36lGLxfLbb7+5u7t37dq1oqeuaCNmszkxMbG4uDgiIsJgMBw7duzmzZs9e/Ys9RQXL168du1akyZNQkJC7Kvn5ub+8ccfKpXqwQcfdLy1R15e3vHjx4UQDzzwQNkbClanWkcJCQmOl+uqVCqDwRAUFOQY0O2Ki4uNRmNISIj91HzZlop07tz5999/T05Otm+5bIsj53a93GIymS5cuFBYWBgcHNykSRP7ikFBQfZ70xQVFZ0+fVqv17du3Vqj0Rw6dMjT01M+gCpzfIed20eAUhHsAEAJajPYAaizmGMHAMqRn5//+eefCyGGDx8uX6zg2FLqaG5RUdHatWuFw82uy7YAqF84YgcASiAfn7M/PHXqVKtWrUq1lPr2kfT0dPlKBVlycrJWqy3VwhE7oH7hiB0AKIFer3e8IV9oaGjZllKrNGzY0LFD48aNVSpVqZY7Vi+AO4IjdgAAAArBDYoBAAAUgmAHAACgEAQ7AAAAhSDYAQAAKATBDgAAQCEIdgAAAApBsAMAAFAIgh0AAIBCEOwAAAAUgmAHAACgEP8HNRmiPko2AgwAAAAASUVORK5CYII=", + "text/plain": [ + "plot without title" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "# ── Visual: wavelet scale-index layout for T = 32 ────────────────────────────\n", + "library(ggplot2)\n", + "\n", + "idx <- mfsusieR:::gen_wavelet_indx(5L)\n", + "scale_df <- do.call(rbind, lapply(seq_along(idx), function(s) {\n", + " data.frame(col = idx[[s]], scale = paste0(\"Scale \", s))\n", + "}))\n", + "scale_df$scale <- factor(scale_df$scale,\n", + " levels = paste0(\"Scale \", rev(seq_along(idx))))\n", + "\n", + "ggplot(scale_df, aes(x = col, y = scale, fill = scale)) +\n", + " geom_tile(colour = \"white\", linewidth = 0.4, height = 0.7) +\n", + " scale_fill_brewer(palette = \"Set2\", guide = \"none\") +\n", + " scale_x_continuous(breaks = c(1, 8, 16, 24, 31, 32)) +\n", + " labs(title = \"DWT column layout for T = 32 (= 2^5)\",\n", + " x = \"Column index in D[[ ]] matrix\", y = NULL) +\n", + " theme_minimal(base_size = 13) +\n", + " theme(panel.grid.minor = element_blank())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Scale group 1 (finest level) has 16 columns; each coarser level halves the\n", + "count. The last entry (`idx[[6]]`) is a single column — the smoothing\n", + "coefficient.\n", + "\n", + "### How `scale_index` is attached to the data object\n", + "\n", + "`create_mf_individual` stores `data$scale_index[[m]]` per modality. For a\n", + "scalar outcome (`T_basis[m] == 1`) `scale_index[[m]]` is `list(1L)` — a single\n", + "group containing the one column." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T_basis: 32 64 \n", + "Number of scale groups, modality 1: 6 \n", + "Number of scale groups, modality 2: 7 \n", + "Columns per group, modality 1: 1 2 4 8 16 1 \n" + ] + } + ], + "source": [ + "set.seed(1)\n", + "n <- 60L; p <- 40L; T1 <- 32L; T2 <- 64L\n", + "X <- matrix(rnorm(n * p), n, p)\n", + "Y1 <- matrix(rnorm(n * T1), n, T1)\n", + "Y2 <- matrix(rnorm(n * T2), n, T2)\n", + "\n", + "data_obj <- mfsusieR:::create_mf_individual(\n", + " X = X, Y = list(Y1, Y2),\n", + " pos = list(seq_len(T1), seq_len(T2))\n", + ")\n", + "\n", + "cat(\"T_basis: \", data_obj$T_basis, \"\\n\")\n", + "cat(\"Number of scale groups, modality 1:\", length(data_obj$scale_index[[1]]), \"\\n\")\n", + "cat(\"Number of scale groups, modality 2:\", length(data_obj$scale_index[[2]]), \"\\n\")\n", + "cat(\"Columns per group, modality 1:\",\n", + " sapply(data_obj$scale_index[[1]], length), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 2. `residual_variance_scope`\n", + "\n", + "### What it controls\n", + "\n", + "**File:** `R/individual_data_methods.R`, `update_variance_components.mf_individual` (line 436).\n", + "\n", + "```r\n", + "update_variance_components.mf_individual <- function(data, params, model, ...) {\n", + " method <- params$residual_variance_scope %||% \"per_scale\"\n", + " for (m in seq_len(data$M)) {\n", + " er2_t <- mf_get_ER2_per_position(data, model, m) # length T_basis[m]\n", + " n <- length(data$na_idx[[m]])\n", + " if (method == \"per_outcome\") {\n", + " model$sigma2[[m]] <- sum(er2_t) / (n * data$T_basis[m]) # scalar\n", + " } else { # \"per_scale\"\n", + " indx_m <- data$scale_index[[m]]\n", + " sigma2_per_scale <- vapply(indx_m, function(idx) {\n", + " sum(er2_t[idx]) / (n * length(idx)) # one per scale\n", + " }, numeric(1))\n", + " model$sigma2[[m]] <- sigma2_per_scale # length S_m\n", + " }\n", + " }\n", + " refresh_iter_cache.mf_individual(data, model)\n", + "}\n", + "```\n", + "\n", + "`mf_get_ER2_per_position` returns the per-column expected residual sum of\n", + "squares $\\widehat{ER^2}[t]$ (length `T_basis[m]`). Under `\"per_outcome\"` this\n", + "is averaged over all $T$ columns; under `\"per_scale\"` it is averaged within\n", + "each group of columns sharing a wavelet scale.\n", + "\n", + "**Broadcast to per-position** (`mf_sigma2_per_position`, line 100):\n", + "\n", + "```r\n", + "mf_sigma2_per_position <- function(data, model, m) {\n", + " sigma2_m <- model$sigma2[[m]]\n", + " T_pad <- data$T_basis[m]\n", + " if (length(sigma2_m) == 1L) { # per_outcome: broadcast scalar\n", + " rep(sigma2_m, T_pad)\n", + " } else { # per_scale: fill by index group\n", + " v <- numeric(T_pad)\n", + " for (s in seq_along(sigma2_m))\n", + " v[data$scale_index[[m]][[s]]] <- sigma2_m[s]\n", + " v\n", + " }\n", + "}\n", + "```\n", + "\n", + "Every downstream consumer of $\\sigma^2$ (`shat2`, `SER_posterior_e_loglik`,\n", + "`compute_kl`, `Eloglik`) calls `mf_sigma2_per_position` and sees a length-`T_basis[m]`\n", + "vector regardless of which scope is active.\n", + "\n", + "### MWE: inspect `sigma2` shape after fitting" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "per_outcome: sigma2[[1]] length = 1 value = 0.9571 \n", + "per_scale: sigma2[[1]] length = 6 \n", + " values: 0.9875 0.9875 0.9875 0.9875 0.9875 0.0148 \n", + " (groups: finest -> coarsest -> smoothing)\n", + " scale group sizes: 1 2 4 8 16 1 \n" + ] + } + ], + "source": [ + "set.seed(42)\n", + "n <- 80L; p <- 50L; T1 <- 32L\n", + "X <- matrix(rnorm(n * p), n, p)\n", + "beta <- numeric(p); beta[5] <- 3\n", + "signal <- as.numeric(X %*% beta)\n", + "Y1 <- matrix(rnorm(n * T1, sd = 0.8), n, T1) + signal\n", + "\n", + "fit_po <- mfsusie(X, list(Y1), pos = list(seq_len(T1)),\n", + " L = 3L, max_iter = 15L,\n", + " residual_variance_scope = \"per_outcome\",\n", + " verbose = FALSE)\n", + "\n", + "fit_ps <- mfsusie(X, list(Y1), pos = list(seq_len(T1)),\n", + " L = 3L, max_iter = 15L,\n", + " residual_variance_scope = \"per_scale\",\n", + " verbose = FALSE)\n", + "\n", + "cat(\"per_outcome: sigma2[[1]] length =\", length(fit_po$sigma2[[1]]),\n", + " \" value =\", round(fit_po$sigma2[[1]], 4), \"\\n\")\n", + "\n", + "cat(\"per_scale: sigma2[[1]] length =\", length(fit_ps$sigma2[[1]]),\n", + " \"\\n values:\", round(fit_ps$sigma2[[1]], 4), \"\\n\")\n", + "cat(\" (groups: finest -> coarsest -> smoothing)\\n\")\n", + "\n", + "# Scale group sizes for reference\n", + "cat(\" scale group sizes:\",\n", + " sapply(data_obj$scale_index[[1]], length), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visual: per-scale sigma2 values\n", + "\n", + "Bars show $\\hat\\sigma^2$ per wavelet scale (per_scale mode). The dashed red line is the single per_outcome estimate. Bar colour encodes the number of coefficients in that scale group." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAIAAAByhViMAAAACXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nOzdd3xUVf7/8TOTOklIBxISkpCAhLKhV0G6NEEEFCkiYlAUlGKBtVHEAguIdEFgQVAQ2FCUGpYASwslECD0EkICaaTXKff3x/3u/GbTIM1JLq/nH/tw7tzyOZfZmXfOufdclSRJAgAAANWf2twFAAAAoGIQ7AAAABSCYAcAAKAQBDsAAACFINgBAAAoBMEOAABAIQh2AAAACkGwAwAAUAiCHQAAgEJYmruAqi4pKenmzZsajaZJkyZWVlbmLgcAAKBY9NgVKyMjY/jw4bVq1erYsWOLFi3q1KmzceNGcxcFAABQLIJdsSZMmLB169avvvrqwoULoaGh/v7+o0ePDg0NNXddAAAARVNJkmTuGqqiuLg4Ly+v9957b/ny5fKSpKSkevXqPf/88/v27TNvbQAAAEWix65ot2/fDggI6N+/v3GJu7t7w4YNb968acaqAAAASkCP3dPKy8vz8PBo1KjRiRMnzF0LAABAEeixe1o//vhjamrq0KFDzV0IAABA0eixeyphYWG9e/du0KDBmTNnNBqNucsBAAAowrPbY/fnn3+qVCoXF5esrKyS1zx69OiAAQNcXV137dpFqgMAAFXWsxvslixZIoRITU395ZdfSlht165dvXv3dnR0PHTokL+//19VHQAAQKk9o0Ox169fb9SokUajyc7Obty48ZUrV4pcbdOmTWPGjKlbt+7BgwcDAgL+4iIBAABK5RntsVu6dKkkSR9//HGjRo2ioqIOHTpUeJ09e/a8+eabzz333PHjx0l1AACg6nsWe+wyMjK8vLwyMzPv3r0bEhIyZcqUgQMH7ty503Sdx48fN2zY0MLCIiIiwtPT01ylAgAAPL1nscdu3bp1GRkZPXv29PX1HT16tK2t7R9//HH37l3TdX7++eekpKT8/Pxu3boFmujcubO5ygYAACjZMxfsJElatmyZECI4OFgI4erqOnToUIPBIC80ys/P79KlS1BQkMf/qlmzpnnqBgAAeJJnbih23759ffv2dXNzi4uLs7a2FkL85z//6dy5s7Oz84MHD+zt7c1d4LNo4cKFu3bt6tChw3fffVe2PVy/ft3d3d3Nza1iC6sQ5W9dCbZt27Z06dJFixY1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g7yJo6NjdnZ24TKeso1FPvfpKbeVJMnDw0Nebm9vP3369OLOye+//y73DNnb27/wwgsvv/xys2bN5A179Ohh+rgRebpXIYSHh8fAgQM7d+4sDxF6enoaZ3h++tNVztYVSe71+eGHHwosrzqfwAIUc/LLH+wkSTJeCFizZs2XXnqpT58+8u20lpaWP/30k+mas2bNktesW7fuoEGD5D9Nvby85L/cTB9oUdpGyZdSvP322yW3F0C182wFO0mS4uPjx48fb/yTV/4eHD58+M2bN8u/c+OX/sGDB02nnHjxxRfPnTtXeP39+/d36NDBdIgkMDCwQNdX2X5WDxw4IO9w2rRppsvlZ2wIIX7//fcCm2RlZb333numNyRaWVkNGTLk2rVr8u9QgYwoSVJ8fLx8y97YsWOLq+Rp2ljkr+9TbitJ0q5du4z/oAMHDizhtOzbt6/A42U9PT1nzpxZePL9nTt3GpOH/Is7bNiw2NjYMpyucrauSHL6KfypqDqfwMKUcfIrJNhJkrR161bToWS1Wt2vX79Tp04VPuJvv/3WsGFD45rPP//89evX27VrJ/432JWqUVqtVr5wouTuSQDVkUqqtMuEqzKdTnfz5s2UlBQ3NzdfX98yT15QwPTp0+fOndulSxf54u7r16+npaUFBASUPHtCcnLyvXv38vPzvb2969atW+DdW7duPXjwwNXVtfAVRSXQarXHjx8XQgQGBho7tORjXbp0SQjRrl27IifFyMnJuXr1ak5OjpOTU4MGDeTfzsTExCtXrqhUqk6dOhWYpiE8PDw7O7vAUUrbxhs3bsTFxbm5uclPOSvVtsayo6KiVCqVn59f4VsIC+9QfiJw7dq1/f39S7igKi4uLiYmxtLSsmHDhkXePPg0p6v8rSssJSXFz88vMzPz7t27cie0rOp8Aks4VrU++Xfu3Ll//76zs7P8zNYCy52cnOTrE4xu374dExPj4uJiGlWNHj58eP/+fVtb23r16pn+tVmAJEkxMTFxcXFeXl5ynefPn09PT2/SpIk8xl3aRoWEhAwePLhNmzaF78wAUN09o8GukhT4WQUqzxdffPHNN9/MmDFj5syZxoV8AvE0+vXrt3fv3t27d8s3WgFQkmforlhASaZOnerk5LRq1Srj/LTA07h69er+/ftbt25NqgMU6RmaoBhQEldX10WLFr311lvz58//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W7cuKipKrVa3adNm/PjxptHqq6++evnllzds2HD16lVra+ugoKAxY8b4+/ub7mTixIkvvvji+vXr5Sjp7+8/bNiwjh07GlewsbHp0qWLaQ9i06ZNo6Ki1q1bd+7cuYyMjFq1avXo0WPkyJHG55JVLHrsyogeOwBAVabUHjuUjGvsAAAAFIJgBwAAoBAEOwAAAIUg2AEAACgEwQ4AAEAhCHYAAAAKQbADAADVxuTJk1Uq1ebNm81dyJMFBwerVKo//vjjrzwowQ4AAEAhePIEAABVlEoYGp49IITIt3O407iTucupEsaPH9+nTx/jc8BQAMEOAIAqSmUQQSe2CyHS3LwIdrLAwMDAwMAnr/esYigWAABUGwWusZs4caJKpbp169a6deuaN2+u0Wg8PDyCg4MfPnxY8n527tzZvXv32rVrazSahg0bfvzxx0lJSQXW2bBhQ6dOnZydnd3d3bt27bpnzx7Td1NTU2fNmtWyZUtHR0cbGxtfX9+333773r17JR/3119/7dSpk6Ojo4ODQ4cOHTZt2lS69j8JwQ4AAFRvM2fOHDt27OPHj1u0aJGRkbFmzZquXbvq9fri1t+6desrr7xy+vTpFi1a9OvXLycnZ8GCBZ06dcrNzTWuM2rUqDfffPPChQvPP/98mzZtTp482b9//5UrV8rvJiUltW3bdubMmcnJyV26dOncuXNaWtratWs7dOiQkpJS3HHHjx8/cuTIc+fOtWzZsnv37lFRUaNGjZo0aVIFngqCHQAAqN42b968bt26+/fvnzhx4tatW/7+/jdu3CjQwWbq448/1mg0UVFR+/bt2759++3bt1988cXr168bOwI3bNiwadOmxo0b37hx488//9y7d++ZM2dq1KgxadKkx48fCyG+//77mzdvvvnmm3fu3Nm9e3doaOjdu3dbtmz56NGjkJCQIg+6adOmn376qV69ehEREWFhYbt27bp582bz5s0XL168Y8eOijoVBDsAAFC9vf7662PGjJH/29PTc9y4cUKI8PDw4tZ/+PChlZWVm5ub/NLKymrBggWrVq1q3769vOSnn36S/7dOnTrykqCgoA8//LBp06ZRUVFCCDs7u65du3777bcWFhbyCi4uLiNGjBBCREdHF3nQhQsXCiFWrFhhvEawVq1ay5cvF0L88MMPZW/8/+LmCQAAUL116vQ/d5Z4eXkJ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fTovL2/JkiV33nmnc4tpOIIdAABQB6WCnRBi/Pjxf/7zn7du3ZqZmTlp0qS4uLiWLVs6txKnINgBAAB1sK07p5TmzZs/8cQTih2+dgh2AABAHRQcsVMLgh0AAFCH2j8ook59OrdDZRHsAACASjTKs2Kd25/CCHYAALgoSciDjmwXQhQafXa2vV3pcpQnCS7F1oBgBwCAi5Jkuev5X4UQmb4hBDvBPXa1QLADAADqwD12NSLYAQAAdWiMETtBsAMAALjxGmMdO21diSXYAQAAleAeuxoR7AAAgDoQ7GpEsAMAAOrQOMHOuf0prLpgZzabf/jhh8OHD+fl5Xl6erZu3XrAgAHBwcENP+rf//731q1bjxo1qiGdJCcnb9q06bnnnmvSpEnDSwIAAK6Om+xqct1gV1BQMGfOnPT09MjIyJCQkPz8/PXr12/YsOGvf/1rjx49GnjUEydOGI3GhvSQm5u7ZMmSgoICs9ncwGIAAIAqcCm2RtcNdgkJCRcuXJg3b95tt91ma8nNzX3hhRfeeeedjz76qIGxrIFkWV68eLHJZFKwBgAAcIM1SrC7SZY7OX/+fFBQkD3VCSGaNGkyZsyYzZs3Z2ZmtmrVytaYkZHx9ddfp6ene3t7d+/e/c4777R/x48ePbpr167Lly9LkhQWFjZo0KAWLVpUeawrV678+9//PnfunIeHR/fu3QcMGKDTVffkti1btqSmpo4cOXLt2rV1PmMAAFRDSg2OEkLkefopXYlL0EmSjhG7al03P3Xs2DErK2vNmjX5+fn2xri4uCVLlthT3a+//vrss8/u27cvIiLCYDC8884777zzjm3TF198MWfOnMzMzLZt2zZt2vS7776bOXNmcXHxtQc6evTotGnTdu/eHRkZ6enpuWzZstdff12W5esVlpKSsnr16meeecbf37+eJw0AgBpYJWlNz7Freo7d2nmw0rW4BEn6fdDOuZQ+LWe67ojd8OHDL126ZLuvLiIion379h07duzWrZuPj49tB1mW33vvvdDQ0IULFxoMBiFERETERx99NHz48LCwsPXr199xxx2zZs2y7dy7d+9XXnklKSmpf//+jkexWq1LliwJCAhYsmSJl5eXEKJfv36vvvrqN998c++9915blclkWrhw4d13392jR49t27bV8iTLy8urzJQNYbVandshAABOlJubW2V7Xl5e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QYP8/PxuVIX/Y9WqVQEBAX/7298MBoMQomPHjrNmzTp06FDLli0VqQcAAMB1XDfYFRUVeXp6NmnSxN4iSdLYsWP37NljMpnsjT/88MMXX3xx/vx5b2/v7t27P/LII/7+/kKIioqKjRs37tq168qVK0KIsLCwUaNG9evXr8pj7d69OyEh4fz580ajsXv37o899pjjce3Kysr27dv32GOPGQwGWZYrKir8/Pw++OCDep88AACAllw32PXv3z8pKWnu3LmDBw/u1KmTr6+vEKJDhw4dOnSw77N58+YVK1bExcWNGzeuoKBg5cqVKSkpixYtkiRp6dKlu3btevjhh6OjowsKChITExcuXNimTZvmzZtXOtDWrVvff//93r17x8fHFxQUrFmzZvbs2UuWLPH29q605/nz5y0WS9OmTd95551du3aVl5e3bdt2ypQpNd5jJ8uy1Wqt2zemJrIsV7PVYrE493A3jNVqlWVZvfVXyf7T19h52VgsFkmSlK7CmWy/XFarVWM/L9sHkcZOyvbLpckPDVc5qf/W0PB67H+2Gu9DQ5IknY5JmQq7brDr27fvjBkzVq9e/cYbb0iSFBoa2qlTp379+nXp0sW2g9lsXrduXc+ePWfMmGFr8fb2Xr58+fnz58PCwrKzs+Pj44cPH27b1LJly+nTpx86dKhSsCsrK1u9enWnTp3mzJlja4mNjX3yySc3b948bty4SiXl5eUJIT788MNWrVrNmDGjpKRkw4YNL7744uLFi8PCwqo5yfLy8sLCwjp8V2rLcL0Nubm5jXC4G0ft9VfJarVq8rxsvxfaU1RUpHQJjaK0tFTpEpyvvLy8vLxc6SqczyU+MWSr7tEnhRDCx9dZ9TTeh4bRaLQNA0FB1c2KjYuLi4uLS01NPXLkyLFjx/bs2fP111/37dt31qxZer3+7NmzpaWlt99+u33/nj179uzZ0/b1/PnzhRAlJSUXL168dOnSoUOHhBBms7nSIU6ePFlSUnLHHXfY/yESEhLSunXr/fv3XxvsbG9v0qTJvHnzbP/a6Ny589SpUzdt2vTMM8805LsAAIArknS6uHuULgJqct1gZ0taer0+KioqKipqyJAhFRUV69evX79+fefOnW2TZIUQAQEBVb79+PHjK1asOHnypF6vDw0NjYyMrHI3WyfLli1btmyZY3uV99h5eXkJIXr37m0f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jx6tqKjo3Llz69atq9/5xIkTKSkpFoslPDz8lltucVx7PSkp6ezZs447t27dumfPno1S9M2qvLz8l19+yczMDAoK6tWrl4eHx/X2LCkp2bJlS6XGYcOGeXl51bUr1FttPr7y8/P//e9/X9vu5+c3ePBg29dffvllUVGR49bevXtHRkY6u16I3Nzc77777sEHH6xxt3379hUWFkZERHTr1q3SUyiq3wpV4MkTfygvL1+wYMGhQ4ciIiKys7NLS0tnzJjRp0+feuxcp65QP2vWrNmwYUNoaKjVar106dKIESMee+yxKvesqKj429/+lpycHBERodfrU1NT27Vr99JLL/n7+9t2mDNnzqlTpxzzQf/+/SdOnHgDzuImceXKlRdffLGgoCAsLCw9Pd3X13f+/PktW7ascudDhw699NJLPj4+jn9Uli5d2qRJk7p2hXqo/cdXenr6c88959giy3JpaWmbNm0WLVokhDCbzaNHj3Z3d3d8PNfUqVP79u3b2GdxsykvL3/11VdPnz69YcOGanZLTk5+8803vby8AgMD09LSOnbs+Morr9ifTFj9VqiGjP9au3btAw88cOTIEVmWzWbzggULRo8enZeXV4+d69QV6uHQoUNDhgz5/PPPbS8TEhKGDBmyf//+Knf+9NNPhw4dum/fPtvLo0ePjhw5cuHChbaXVqt1zJgxH3/8ceNXffOaM2fOpEmTsrKyZFm+evXqlClTZs6ceb2dExMThw4dWlpa2vCuUA8N+fj66KOPRo0adfbsWdvL06dPDxkyJDk5uRHLhSxfvnx55syZQ4YMefDBB6vZraioaMyYMfPnzzebzbIsHz58eOTIkStXrqzNVqgI99j9Yfv27X369OnYsaMQws3NbcqUKaWlpT/++GM9dq5TV6iH7du3BwYGjhw50vZyxIgRoaGh27Ztq3LnpKSkW265pXv37raXsbGx3bp1O3jwoO3lpUuXSkpK2rZtewPKvjllZGQcOXJkxIgRQUFBQojg4OCHHnro1KlTKSkpVe5/9uzZsLCwKi+w1rUr1EO9P76Sk5M3b978+OOPR0dH21psdzi0adOmUQu+yX399ddPPfVURUWF/SPuenbt2lVSUjJhwgQ3NzchRKdOneLi4r755hur1VrjVqgIwe53mZmZubm5sbGx9pagoKCQkJATJ07Udec6dYX6OXnyZPv27R0v1bVv3/563+FFixbNmTPHsSU3N9fHx8f2tS0TBAcHb926ddWqVTt27CgvL2+0wm9Gtp9Lhw4d7C22r0+ePFnl/ikpKW3btj1w4MDatWvXr1/vGNrq2hXqqt4fXyaT6f/+7//at29/33332RtTUlKaNm2am5ubkJCwevVq29BdY5V+syosLJw8efLbb7/dokWL6vc8ceKEr6+v430L7du3LywszMjIqHErVITJE7/LyckRQgQGBjo2NmnSJDs7u64716kr1E9ubq5tzMYuICAgPz/fYrHo9fpKO+v1evt990KIpKSkEydOPPLII7aXtkGFl19+OTQ01GQyZWRkrF+/fv78+c2aNWvkk7hZ5ObmCiEcf162u+Wq/I0wmUwXLly4cuXK/v37mzVrduHChTVr1owdO3bs2LF17Qr1UO+Pry+//DIzM/O5555z/OfW2bNn8/LyZs+eHR4efvXq1c8//7xbt24vvPCC4y13aKBRo0bVcs9rPzZtvz45OTmtWrWqfquTisWNQLD7nclkEkJU+rhxd3e3tddp5zp1hXqwWCxms/na77AQwmw2XxvsHB05cmThwoWdO3cePny4rUWW5fDw8GeeeaZdu3ZCiN9+++21115bvHjxm2++2WhncHMpKysT//sbYfu6ypHR7Ozs8PDwDh06TJkyRa/Xl5WVvf322+vWrYuNje3SpUudukI91O/jq7y8fMuWLV27do2JiXFsNxqNMTExs2fP9vf3l2X5888///TTTzds2DBu3LjGKB7VKysrq/Jj0/brU/1WqAjB7ne2/4MrKiocG81mc5U3+lS/c526Qj3o9Xq9Xm82mx0bzWazJEnVT+D6/vvvly5dGhsb++KLL9rz32OPPeY4nbZLly533XXX9u3bc3Nzbf9gRQPZfyPsPx3bz87T0/PanUNDQ//5z3/aX3p4eEydOvWXX37ZvXt3ly5d6tQV6qF+H1979uzJz8+///77K7UvWLDA/rUkSaNHj/7+++9/+ukngp0iDAaD7Z9GdrZfH9sPt/qtUBHusfudbaEm24Ueu5ycnCr/tFe/c526Qv0EBQXl5eU5tuTm5vr7+19v1SVZlleuXLl48eL+/fvPmzev+o8q20XYSp9xqLdrfyNs1/sCAgJq8/YmTZrY/+Q0sCvUqH4fXz/99JOfn99tt91WY//NmjXjN0spQUFBlX6ytpe2H271W6EiBLvfhYSEBAQEON6CnZ2dnZWV1b59+7ruXKeuUD/t2rU7efKk443YtukUVe4sy/LixYs3b948adKkp59+utK12lmzZi1cuNCx5dy5cwaDoWnTpo1R+U3Ido3b8e57229HlT+vnTt3TpgwwXHCxKVLl8rLy8PCwuraFeqhHh9fFovlt99+69GjR6XfrMuXL0+cOHHr1q32FqvVeuHCBduPEjdeTExMXl7elStX7C0nT560T5iofitUhGD3O0mS7r777l27dh0+fFgIYbFYli9f7uHhceedd9p2yM/PP3PmTHFxcY0719gVGu7uu+/OzMxMSEiwvdyyZcvFixft0/HKy8vPnDmTlZVle7l+/foffvhh+vTpQ4YMubaryMjI3bt3//bbb7aXSUlJO3fuvP/++6u/Vw+116pVq5iYmA0bNth+Irm5uZ999llMTExUVJRth8uXL585c8a2qkK7du0KCgpWr15tu7OnpKRk2bJlnp6e99xzT226QgPV6ZPQJi0tzWQyOU5VtmnWrJm7u3tCQoLthyXL8urVqzMzMx944IEbdTYQqamp6enptq/79Onj6en54Ycf2q6xHj9+/Pvvv7/33nttFzqq39GxDrcAAAblSURBVAoV4ckTfygrK5s3b96JEyeio6Ozs7OLiopmzZrVu3dv29atW7e+//77L7zwQq9evWrcufqtcIqPPvpoy5YtLVu21Ol06enpjk+eOHfu3LRp04YMGTJp0qSysrKHH37YbDZXug3LYDB88sknQojS0tL58+cfPXq0devWZrP53LlzPXv2nD17tsFguPEnpVUXLlx46aWXSkpKIiIizp075+3tvWDBgtDQUNvWN998c8+ePevWrfP29hZC/Pjjj++9957RaGzZsuW5c+dkWZ41a5Z9ja7qu0LD1emTUAixc+fOhQsXvvHGG7al7xydO3du/vz5+fn50dHRV69ezcnJeeihhx566KEbej43jQ8//PDbb7+t9OSJcePGBQcHv/POO7aXu3fvXrRokbe3d3BwcGpqamxs7Ny5c+2fddVvhVoQ7P6HLMv79+9PS0vz9fXt3r2749zvU6dO7d+///bbb7dfR6hm5xq3winOnDlz5MgRvV7fsWNH+5qo4r+PsIyJibntttuu9zhLnU43ZswY+8vDhw+npaVZLJb27dtzXa8xlJaW/vzzz9nZ2SEhIb169XKc5vLTTz+lp6ePHDnS/ickLy/v4MGDWVlZgYGBPXr08PX1rWVXcIo6fRKePn06OTn5/vvvr/RjsjGbzcnJyZcuXfLw8OjatWuNa62h3g4cOHD27NlKz4rdtGmTp6fnwIED7S1Xr17dt29fWVlZZGRk165dKw3IVb8VqkCwAwAA0AjusQMAANAIgh0AAIBGEOwAAAA0gmAHAACgEQQ7AAAAjSDYAQAAaATBDoA6XLx4cd++fZmZmUoXAgCui2AHwNXl5eUNHjw4IiJixIgRoaGh8fHxPEgeAKpEsAPg6mbOnHn69GnbUy+PHj26Y8eOV199VemiAMAVEewAuLpvvvnmmWeesT3DKiYmJj4+fvv27UoXBQCuyE3pAgCgBunp6Var1f4yNTXVy8tLwXoAwGUxYgfAJZSXl7/22mvr1q2rcqtO9/uH1Q8//PDFF19MnDjxBpYGAKohybKsdA0AINasWfPwww/7+PhkZGT4+flVuc/evXsHDhw4dOjQTz755AaXBwCqwIgdAJewbNkyNze3oqKi64W2xMTEu+++e9iwYR9//PENrg0A1IJgB0B5hw4d2rNnz9NPPx0aGrps2bJrd3jvvfdGjRo1ffr0VatW6fX6G18hAKgCwQ6A8t577z0hxMSJEx999NHjx4//5z//cdy6du3aadOmLVu2bMGCBQoVCADqwD12ABSWn5/fsmXL2NjYpKSklJSUNm3ajBgxYuPGjbatRUVF4eHhvr6+Y8eOtb/F399/zpw5CtULAK6L5U4AKGzVqlXFxcUTJkwQQkRHR/fv33/Lli0ZGRktW7YUQqSmpt5yyy1CiJ9//tn+lpCQEKWqBQBXxogdAIXFxsampKRcunSpSZMmQoh169bFx8e//PLLr732mtKlAYDKEOwAKOk///nPXXfdNWbMmM8++8zWYjKZQkNDDQbD+fPn3d3dlS0PANSFS7EAlFReXj537twHHnjA3mI0Gt9///0jR45cunQpPDxcwdoAQHUYsQMAANAIRuwAKO/UqVMbN2787bffcnJyzGaz46ZNmzYFBgYqVRgAqAvBDoCSLBbLjBkz/vnPf8qy7Onp2bRp04KCgtzc3GbNmvn4+AghrFar0jUCgGqwQDEAJb3yyivvvvtu+/btv/vuu8LCwrS0tJ9//tnHxyciIuLIkSNnzpwJDg5WukYAUA39vHnzlK4BwE0qKytr9OjRHh4e+/bt69Kli06nE0IEBQWZTKa1a9f6+fn17dtX6RoBQE0YsQOgmB07dpSVlQ0bNiw0NNSxfeLEiUKI5cuXK1QXAKgVwQ6AYk6fPi2EsD1YwlGrVq3CwsJOnz6dk5OjRF0AoFYEOwCKKS4uFkJ4eHhcu8n20LDs7OwbXRMAqBnBDoBiAgIChBAZGRnXbsrMzBRC2B4yBgCoJYIdAMV07dpVCLF79+5K7RkZGRcvXmzZsiVTYgGgTgh2ABTTv3//sLCw3bt3f/XVV47tzz//vCzLEyZMUKowAFApHikGQEnbtm0bOnSoTqd77LHHevbsWVJSkpCQ8OOPP95666179+6t8vY7AMD1EOwAKGzHjh0zZsw4dOiQ7aW3t/f48ePfeust25MnAAC1R7AD4BLS09MvXLjg5+cXHR3t6empdDkAoEoEOwAAAI1g8gQAAIBGEOwAAAA0gmAHAACgEQQ7AAAAjSDYAQAAaATBDgAAQCMIdgAAABpBsAMAANAIgh0AAIBGEOwAAAA0gmAHAACgEQQ7AAAAjSDYAQAAaATBDgAAQCP+P1r6d3LqwxJRAAAAAElFTkSuQmCC", + "text/plain": [ + "plot without title" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "# ── Visual: per-scale sigma2 values (bar chart) ───────────────────────────────\n", + "library(ggplot2)\n", + "\n", + "idx <- mfsusieR:::gen_wavelet_indx(5L)\n", + "sz_df <- data.frame(\n", + " scale = paste0(\"Scale \", seq_along(idx)),\n", + " n_coefs = sapply(idx, length),\n", + " sigma2_ps = fit_ps$sigma2[[1]]\n", + ")\n", + "sz_df$scale <- factor(sz_df$scale, levels = rev(sz_df$scale))\n", + "\n", + "ggplot(sz_df, aes(x = scale, y = sigma2_ps, fill = n_coefs)) +\n", + " geom_col(width = 0.6) +\n", + " geom_hline(yintercept = fit_po$sigma2[[1]], linetype = \"dashed\",\n", + " colour = \"#e74c3c\", linewidth = 0.8) +\n", + " annotate(\"text\", x = 0.6, y = fit_po$sigma2[[1]] * 1.05,\n", + " label = sprintf(\"per_outcome\\nsigma2 = %.3f\", fit_po$sigma2[[1]]),\n", + " colour = \"#e74c3c\", size = 3, hjust = 0) +\n", + " scale_fill_gradient(low = \"#aed6f1\", high = \"#1a5276\",\n", + " name = \"# coefs\\nin scale\") +\n", + " coord_flip() +\n", + " labs(title = expression(hat(sigma)^2 ~ \"per wavelet scale (per_scale mode)\"),\n", + " x = NULL, y = expression(hat(sigma)^2)) +\n", + " theme_minimal(base_size = 13)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `\"per_scale\"` vector shows that the coarser scales (groups 4-6 here)\n", + "typically have higher $\\sigma^2$ per coefficient because fewer observations\n", + "constrain those averages.\n", + "\n", + "---\n", + "\n", + "## 3. `prior_variance_scope`\n", + "\n", + "### What it controls\n", + "\n", + "**File:** `R/prior_scale_mixture.R`, `mf_prior_scale_mixture` (line 250).\n", + "\n", + "`prior_variance_scope` selects the prior class and the `groups_m` partition\n", + "of wavelet columns.\n", + "\n", + "```r\n", + "prior_class <- switch(prior_variance_scope,\n", + " per_outcome = \"mixture_normal\", # 1 entry in G_prior[[m]]\n", + " per_scale = \"mixture_normal_per_scale\", # S_m entries\n", + " per_scale_normal = \"mixture_point_normal_per_scale\", # S_m entries, ebnm\n", + " per_scale_laplace = \"mixture_point_laplace_per_scale\" # S_m entries, ebnm\n", + ")\n", + "\n", + "groups_m <- if (prior_variance_scope == \"per_outcome\") {\n", + " list(unlist(data$scale_index[[m]])) # one group = all columns\n", + "} else {\n", + " data$scale_index[[m]] # S_m groups, one per scale\n", + "}\n", + "```\n", + "\n", + "The IBSS kernels (`loglik.mf_individual`,\n", + "`calculate_posterior_moments.mf_individual`,\n", + "`optimize_prior_variance.mf_individual`) iterate over `G_prior[[m]]` and\n", + "use `G_prior[[m]][[s]]$idx` to slice the `(Bhat, Shat)` rectangle. A\n", + "`\"per_outcome\"` prior has one `G_prior[[m]][[1]]` entry whose `$idx` covers\n", + "all columns; a `\"per_scale\"` prior has `S_m` entries, each covering one scale.\n", + "No mode-specific branching appears in the loop body — only the length of\n", + "`G_prior[[m]]` differs.\n", + "\n", + "### Four prior modes\n", + "\n", + "| `prior_variance_scope` | `G_prior[[m]]` length | Fit method | Parameters per (m, s) |\n", + "|---|---|---|---|\n", + "| `\"per_outcome\"` | 1 | mixsqp EM | $K$ mixture weights |\n", + "| `\"per_scale\"` | $S_m$ | mixsqp EM | $K$ mixture weights |\n", + "| `\"per_scale_normal\"` | $S_m$ | ebnm point-normal | $(\\pi_0, \\sigma^2)$ |\n", + "| `\"per_scale_laplace\"` | $S_m$ | ebnm point-Laplace | $(\\pi_0, b)$ |\n", + "\n", + "`\"per_scale_normal\"` and `\"per_scale_laplace\"` are the most parsimonious:\n", + "2 parameters per (modality, scale) instead of $K + 1$. They are the right\n", + "choice when each wavelet scale has a single characteristic effect-size scale\n", + "and the $K + 1$-component mixsqp fit is under-constrained (typically at\n", + "coarse scales with few coefficients).\n", + "\n", + "### MWE: inspect `G_prior` structure" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[1m\u001b[4m\u001b[31mWARNING:\u001b[39m\u001b[24m\u001b[22m IBSS algorithm did not converge in 1 iterations!\n", + "\n", + "Warning message:\n", + "“IBSS algorithm did not converge in 1 iterations!”\n", + "\u001b[1m\u001b[4m\u001b[31mWARNING:\u001b[39m\u001b[24m\u001b[22m IBSS algorithm did not converge in 1 iterations!\n", + "\n", + "Warning message:\n", + "“IBSS algorithm did not converge in 1 iterations!”\n", + "\u001b[1m\u001b[4m\u001b[31mWARNING:\u001b[39m\u001b[24m\u001b[22m IBSS algorithm did not converge in 1 iterations!\n", + "\n", + "Warning message:\n", + "“IBSS algorithm did not converge in 1 iterations!”\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "per_outcome: G_prior[[1]] length = 1 (1 group covers all 32 columns)\n", + "per_scale: G_prior[[1]] length = 6 (one entry per wavelet scale)\n", + " scale 1: idx[1:1], K=17, class=mixture_normal_per_scale\n", + " scale 2: idx[1:2], K=17, class=mixture_normal_per_scale\n", + " scale 3: idx[1:4], K=17, class=mixture_normal_per_scale\n", + " scale 4: idx[1:8], K=17, class=mixture_normal_per_scale\n", + " scale 5: idx[1:16], K=17, class=mixture_normal_per_scale\n", + " scale 6: idx[1:1], K=17, class=mixture_normal_per_scale\n", + "\n", + "per_scale_normal: G_prior[[1]] length = 6 (ebnm point-normal per scale)\n", + " scale 1: idx[1:1], pi0=0.000, sigma=0.0518\n", + " scale 2: idx[1:2], pi0=1.000, sigma=1.0000\n", + " scale 3: idx[1:4], pi0=0.000, sigma=0.0212\n", + " scale 4: idx[1:8], pi0=0.000, sigma=0.0373\n", + " scale 5: idx[1:16], pi0=1.000, sigma=1.0000\n", + " scale 6: idx[1:1], pi0=1.000, sigma=1.0000\n" + ] + } + ], + "source": [ + "fit_pvpo <- mfsusie(X, list(Y1), pos = list(seq_len(T1)),\n", + " L = 3L, max_iter = 1L,\n", + " prior_variance_scope = \"per_outcome\",\n", + " verbose = FALSE)\n", + "\n", + "fit_pvps <- mfsusie(X, list(Y1), pos = list(seq_len(T1)),\n", + " L = 3L, max_iter = 1L,\n", + " prior_variance_scope = \"per_scale\",\n", + " verbose = FALSE)\n", + "\n", + "fit_pvpn <- mfsusie(X, list(Y1), pos = list(seq_len(T1)),\n", + " L = 3L, max_iter = 1L,\n", + " prior_variance_scope = \"per_scale_normal\",\n", + " verbose = FALSE)\n", + "\n", + "cat(\"per_outcome: G_prior[[1]] length =\", length(fit_pvpo$G_prior[[1]]),\n", + " \" (1 group covers all\", T1, \"columns)\\n\")\n", + "\n", + "cat(\"per_scale: G_prior[[1]] length =\", length(fit_pvps$G_prior[[1]]),\n", + " \" (one entry per wavelet scale)\\n\")\n", + "for (s in seq_along(fit_pvps$G_prior[[1]])) {\n", + " g <- fit_pvps$G_prior[[1]][[s]]\n", + " cat(sprintf(\" scale %d: idx[1:%d], K=%d, class=%s\\n\",\n", + " s, length(g$idx), length(g$fitted_g$sd),\n", + " class(fit_pvps$G_prior[[1]])))\n", + "}\n", + "\n", + "cat(\"\\nper_scale_normal: G_prior[[1]] length =\", length(fit_pvpn$G_prior[[1]]),\n", + " \" (ebnm point-normal per scale)\\n\")\n", + "for (s in seq_along(fit_pvpn$G_prior[[1]])) {\n", + " g <- fit_pvpn$G_prior[[1]][[s]]\n", + " cat(sprintf(\" scale %d: idx[1:%d], pi0=%.3f, sigma=%.4f\\n\",\n", + " s, length(g$idx), g$fitted_g$pi[1],\n", + " g$fitted_g$sd[2]))\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visual: mixture weights per scale (per_scale prior)\n", + "\n", + "Each facet is one wavelet scale. The x-axis shows the mixture SD components; the y-axis shows the fitted $\\pi_k$ weight after the IBSS EM step." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAIAAAByhViMAAAACXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nOzdeXwU9f348c+eyeYgIdz3KQIiIiCCiHJ4IRZQARWtFqwV8WyxqFVBpV6IFKutWmttRS2gVgSrUgHlViqIIvdNOSKQO9nNzuzs/P74/Drf7Sa7+0lMNsnwev7Bg8y+5zOf92eOfe/szKzDNE0BAACAhs9Z1x0AAABAzaCwAwAAsAkKOwAAAJugsAMAALAJCjsAAACboLADAACwCQo7AAAAm6CwAwAAsAl7FnY33njj0KFDhw4d+vHHH8eKufvuu2XM4cOHhRCapg0dOnT8+PHqSzFN8/XXX//uu+9qoMf1QzUGIRAIqMySnLFK2P/S0tKhQ4def/31tdqNWpKfnz906NCbb765php8+umnb7vttppqrf6r8QGskmrsXEmwevXqn/3sZ5dffvlNN9307bff1vZ+Wl5e/sYbb9x0002XXXbZuHHjnnvuuby8vIphy5cvnzJlysiRI2+++eb3338/fpsVBzZpR2ZN08aOHTt//vzaXhBQNaYddenSRWZ3zTXXVBpw8uRJt9stY3bs2GGaZiAQEEK0adNGfSlTpkwRQqxZs6ZmOl0PVGMQSkpKVGZJzlgl7H9BQYEQokOHDrXajVpy/PhxIcSZZ55ZI62tXLnS4XB89NFHNdJag1CzA1hV1di5atuaNWuczv/7bP/NN9/U6n76n//8p0ePHnJZ2dnZDodDCNG0adP169dHhsk+OByOJk2ayJixY8eGQqFYzVYc2GQemefOnZuWlrZ79+4kLAtQZM8zdpLH4/nkk0/KysoqvvT++++HQqHIKW63+4EHHrjzzjvV25fvE3ZSjUFQlJyxqr3+20wwGLz99tuHDRs2atSouu7L6aIebpwffvhhOBweP378/v37Dx06dPbZZ9fqfjpx4sQdO3Zcc801R48eLSgoKCwsvPfee0+dOnX11VcXFxfLmD//+c+vvPLKueeeu2/fvlOnTh04cGDgwIGLFy9+5plnYjVbcWCTeWS+6667WrVqJUtJoL6o68qyVsgzdldeeaUQYuHChRUDhg8f3rRpUxkmz9hVw5gxY4S9zthVg+IZu3oyVpyxk+bOnSuEWLly5Y9vqgGp2zN29ZAsRxYsWGBNqb399JtvvhFCdOrUqby8PHL68OHDhRBvvPGGaZqGYXTu3NnpdG7fvt0KyM3NTU9Pz8rKipoxjiQfbf70pz8JIZYuXZqcxQEJuZNfSibN+PHjP/744/fee2/ChAmR03/44YdVq1b9/Oc//+KLL6yJmqZddtllzZo1e/fdd4UQd95557Zt20aOHPnAAw9YMa+//vr8+fPPPvvsGTNmjB8//vvvvxdC3H333VlZWW+88UZWVtY111zTvn37N998M3JxU6dO3b59+5tvvtm+ffuioqIxY8ZcfPHFU6ZMmTFjxvHjx88999xHH33U6/Waprl48eKlS5cePXo0Ozt7xIgRt9xyS0pKSqWprVq1aubMmcOHD58xY4Y18Zlnnvn000+vv/76yI+P99xzz9atW999992mTZsKIeIvJWoQJL/f//rrr69bt66kpKR///533333hx9+OH/+/Oeff75fv35WmGmaCxYs+PDDD4uKirp06TJp0iT56smTJyuOVadOnYQQS5cuXbZs2YEDBzweT+/evX/605+eccYZsdZmIBAYOXLkgAEDZs2a9Yc//GHVqlUul6tfv3533HFHTk5OnP5XStf1V155ZcWKFeFweODAgbfffnuTJk1kC6NHj9Y07YMPPsjKyoqc5Z577vnuu+/++te/duzYsdI2E6ZjGMbSpUuXLl167NixVq1aDRs27MYbb4z8LqywsPDtt9/esGHDyZMnvV5vt27drr/++vPOOy9OIlXabKxRmj17drdu3YYNGyanqIytyuLibN5x+tOwBlBlrCodhwceeOCqq66K2jhDodCiRYs+/fTT3Nzcxo0bDx069JZbbklLS6uR8YwzGhs2bHjooYd2794thHjiiSdefvnliy++eNWqVZXupzWy0r///vuUlJThw4dHje3QoUNXrly5d+9eIcTXX3+9f//+/v37W9/YCiFatGhxxRVXvP/++8uXL6/0BHPkXh/naFPtLBJulhMnTpw2bdpvf/vbq666SmW9ALWu7mrKWiRPxe3evbtNmzbp6ellZWWRr7700ktCiM8///zMM88UMa6x27hxo8vlcrvd8oJi0zR3797t8/m8Xu+WLVt++OGHfv36ZWdnCyHOPPPMfv367d27N9b5gPPPP99aysmTJ4UQY8aM6dOnjxz/zp07m6ZZWFgoP7kKIbKzsz0ejxCie/fuBw8erDTBEydOOJ3O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myNHjpSXl3///ffp6elnnHFG5FOvYvXfEgqF5E8tnXPOObIRt9vdq1evio/OEkLs2bOnW7duo0aNks/BUqGSzp49e06cONG0adOKF4NrmrZ///6SkpK2bdu2bNnS4XDITTE7O7tPnz6apq1fv16Of9SMVd1sJk6cuHDhwn379skn+KiMrcriEm7eCdX/AVQZq0rHIc7GuXfv3pMnTzZv3rxTp06RT1GptB3DMFavXn3JJZdYqy+W+KMhhNi9e/exY8fOOuusyHs1Kt1P4wyR4kr/+uuvoy5sjdSmTZuoH2A9ePBgbm5umzZt2rZtG/8hBpUObM1mEX+zXLFixSWXXDJjxgz50Hig7tV1ZYn666233mrRokXUlTrr1q1zOBwJLzurcbFODdaScDh84403CiGWLl2anCUm044dO5xOp7Vmkzy2DVp9GKtvvvkmPT294u+uok6MGzcuKytL/ugiUB/Y/3EnqLYRI0b4/f6nn356586dgwYNcrlc27ZtW7BggdPptB4Wbz+rV6+eNWvWiRMnvvvuu/POO6+qD/1vELp37z516tQXX3zx3nvvjXzIC+q/8vLyG2+88Ze//GX8p/ohOTZv3vz+++/Pmzev0od+A3XCnl/FoqZs3Ljx4Ycflj8YL6f0799/zpw58a+YqQ2RX4HV6oKsS3y6du366aefRl7UZSeBQODcc88dMmTIa6+9Vntje/nll+/atSt+jNvtjv8zD/VK0rbDONavXy+fbYQ6N3ToUI/H869//Uv9ofdAbeOMHeIZMGDAZ599VlBQcPjwYU3T2rVrV6VHAdcgn8/3+eefJ+EsRd++fffv319QUNC7d+9YF5nZgM/nW758ufzpqtob2+HDh1f8SbooDeuZrknbDuOgqqsnNE177LHHzj77bKo61CucsQMAALAJ+//yBAAAwGmCwg4AAMAmKOwAAABsgsIOAADAJijsAAAAbILCDgAAwCYo7AAAAGyCwg4AAMAmKOwAAABsgsIOAADAJijsAAAAbILCDgAAwCYo7AAAAGyCwg4AAMAmKOwAAABsgsIOAADAJijsAAAAbILC7nT37LPPdurUqU2bNm3atOnYseODDz4YCoWq0c6ll176wgsvVHWuX/7ylzfeeGM1FgfUlDrZBU6dOnXttdd26tSpb9++HTt2/N3vfleNJQI/XklJyaRJk9q2bSt3gZ49e86fP78a7RQXF7dp02bjxo2K8bt27brwwgvPOuusM888c/To0SdPnqzGQlEpCrvT2sKFC19++eXnnntu69at27Zt+/3vf//ee+/Nnj07CYvWdX369OmLFi1KwrKAWOpqF3j00UcDgcDmzZs3b9785ptvzps3b+XKlbW9UKCiRx55ZMeOHe++++6uXbs2b948adKkBx988PPPP6/t5d555519+vT59ttvv/nmG5fL9eijj9b2Ek8fFHantTVr1gwZMmTcuHE5OTnZ2dmjR4+ePHnyP//5z8iYQ4cObdmypaSkJHLivn37/v3vfx86dChWy4cOHfr222/9fn+lr+bl5Q0bNmzt2rUXXXRRjSQCVE+d7ALhcHjdunW33XZb48aNhRAXXXRR3759V69eXUM5AVWwZs2am2++edCgQRkZGS1atPj1r3997rnnfvzxx1ZAOBzesWPH9u3bdV23Juq6vm3btk2bNuXl5VXarJxr586dlZ7/Pn78+KlTp375y1+63e60tLRx48Z98cUXNZ3Z6YvC7rTWrl27zZs3b9myxZrywAMPrFu3Tv7/1KlT48aNu+iii2699dZ+/fq9++67QogdO3YMGTJk1KhRU6dOHTx48MSJE8PhcGSbhYWF11133dChQydPnnzWWWe98sorFZdbXl5+ww03rFixonPnzrWZH5BAnewCTqfzu+++GzNmjPwzHA4fO3bM5/PVYp5ADO3atfvkk09yc3OtKR9++OFzzz0n/79ly5YLLrhgzJgx11133eDBg7du3SqEWLx48TnnnHPTTTdNnjy5T58+FS8k+P777y+88MKrr756/Pjx55133qpVq6ICWrVqtWXLli5dusg/Dx8+3LRp09rK8DRk4jRWVFQ0ZsyY1q1bDx48+Ne//vXixYtLSkqsV2+77bZLL7305MmTpmm+/fbb7du3/+GHH6688sopU6boum6a5qZNm1q3br1q1SrTNC+55JJ58+aZpnnvvfdeffXVBQUFpml+8803Z5xxxsqVK2N14De/+c3EiRNrO00gljrfBUzT/POf/9y+ffs9e/bUaqZApbZu3dq3b9+2bdteeeWVs2bN+uKLL0KhkHwpGAz27dv3nnvu0XU9FArdc889w4cPLygo6Ny58+uvvy5jXnrppQ4dOpSWlhYVFbVu3fqrr77Sdf3CCy+cMWOG3Ef+9re/nXnmmSdOnKh06d9+++3zzz/ftWvXjz76KDn5ng7cdV1Yoi41atTogw8+WL9+/b/+9a+1a9e+8847WVlZ8+bNu/TSSwOBwLJly/7whz/ID1I33HBDTk5Oamrq7Nmzmzdv7na7hRAtW7ZMT08/ceKE1WB5efn7778/a9aso0ePHj161OPxnH/++YsXLx42bFidJQnEVue7wEcfffTEE2FFadQAACAASURBVE889thjXbt2TU7KQKRevXqtW7du2bJlK1euXLx48csvv3zmmWe+/vrrnTp1+vLLL3Nzc2fMmCG39ocffnjbtm0pKSkLFy7s06ePEMI0za5du+q6XlhYmJmZKRtcu3btwYMHR48evWvXLiFEv379vF7vihUrrr/++opLf+2117Zv356RkVFWVpbEpG2Owu5053A4Bg8ePHjwYCHE0aNHZ86cOXXq1E2bNuXn54dCoU6dOllhV1xxhRCiWbNmr7zyytdff71//3632x0MBiO/hzp06FA4HH7xxRflgUA655xzkpsTUAV1uAvMnz//kUceefDBBydNmlSLGQJxpaamjhkzRl4bsGXLlvvuu+++++778MMPjxw5kpmZ2aRJExnWvHnz5s2by/hp06bt2LHjwIEDWVlZQojIXeDgwYMOh2Pq1KnWFJ/PV1hYWOmiX3zxRSHE0qVLp0yZ0rlz5/79+9dalqcRCrvTl6ZpAwYMePLJJ0eNGiWntGnT5oknnjjvvPN2794tr36I/BR18ODBpk2bjhs3rlmzZlOnTu3Zs2fbtm179OgR2WZGRoYQ4pVXXjnvvPOSmApQHXW7C7zwwgtz58595plnbrjhhhpODFDz5ZdfTpo0ae3atVb11qdPn7vuumvatGlCiKysrEAgYJqmw+EQQoRCocOHDxuGMXbs2KuvvnrmzJndu3c/fvz45ZdfHtlmRkaG2+1ev369y+WKtdxAIHDw4EFr3/nJT37y4IMPbty4kcKuRnDzxOnL6/V26NDhpZdeirzdb82aNW63u0OHDo0bN27btq11p15BQcGwYcMWLly4b9++Rx555PLLL2/Xrt327dtLSkoiP6u1bt26efPmS5YskX8ahnHFFVc89dRTycwLUFSHu8Dbb789d+7cV199laoOdahHjx6hUGj27NmR2/DatWvlhQG9evUKh8MbNmyQ07/44osRI0Z89tlnpmnOnj178ODBTZo0ka9Gzt67d29N05YtWyb/zM3N7dGjx/LlyyOXu3379ksuuWTbtm3yz0OHDhUVFbVt27Y2cz2NcMbutPb000+PGzfuggsuGDFiRGZm5q5du9avXz99+vRmzZoJIR566KH77rtP1/XOnTu/+eab55577oQJE1544YU5c+bcdNNNx48f/+Mf/5iSkhJ5SsPhcDz22GN33XWXpmn9+/f/5z//eeTIER5BjHqrTnaBkpKSJ554omPHjvI5dnJir169Ro8enczcgaysrCeffPL+++9fv379hRdeaBjG5s2b9+3bJ59R3KFDh4kTJ955551Tp051uVwvvvji7bfffsEFFzz11FOPP/74oEGDtmzZ8re//U0I4ff75bN7hBDdunW76aab7rvvvt27d7do0eK1117r3LnzxRdfHLncvn37jhgx4tZbb/3FL35hGMZrr702cODAK6+8MvkjYEuuxx57rK77gDrTrFmz6667LiUl5ciRI7m5ue3atXv00UfHjx8vX+3evXvv3r2//PLLbdu2DRky5Le//W1GRsbw4cN37dq1YcOG8vLyadOmtWnTRtf1gQMHfvfddz179uzZs2f37t0HDBggHzvZuXPnuXPntm/fPlYHDhw44PP5eJod6kqd7AJ79+7dvn17enr68QhZWVnnn39+XYwBTmtnnXXWFVdcEQwGDx06VFxc3Ldv3+eff966KnT48OHp6emrV68+duzYz372s1/84hetWrXq3bv3V1999c0332RmZs6ZM+fYsWMdOnRo3779119/PXLkyKZNmw4fPrxp06Zffvnl7t27hw8f/tRTT0U9zcfhcFx11VUej2f9+vW5ubnjx49/4oknIi9LxY/hME2zrvsAAACAGsA1dgAAADZBYQcAAGATFHYAAAA2QWEHAABgExR2AAAANkFhBwAAYBMUdgAAADZxuhR2paWlhYWFKg/t03W9sLAwGAyqNFtUVBT5Y0RxBAKBwsLCUCiUMDIcDhcWFvr9fpVmyauqeRUXF6tElpeXN6C8VNRe7pG/uxBHWVlZlXIvLy9XadaueRUXF5NXYWGhrusqwQmZplnV3CN/JiuWUChUt7nXk7yKiopUIu2aVzAYrNu8opwuD3o2DEPlwCeECIfDoVBIcShDoZDTqVQcy2ZVjpKmaYZCoTg/nxyJvGopL9mBhpKX4qLlL3krLlrx0eW1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6HHlypULFy4IS4jIHdjg4OBBgwaRR8LDw5OTk7t16+ayaoFc0tfr9a1bty53vDIzM6dOndqjRw9yoDx37lyTJk169OixZMmSffv2lZaWPvfccy7TwNfXV3zHkmlA0snhcKxYscJgMPzyyy9169bt2LHj5cuXDx8+3LlzZ9KS5/l79+6pVKpq1arFxcUJV9T379+fkJDgEhdZr6PVan/77bfAwMCUlJQZM2b06NHjxRdfXLlyZUhIiDAZWJbNy8vTarX16tUrd7xo/I0UyMnJKSwsfPvttz3tJa9S4MaNG7dv365Xr179+vWFexnp6eldunTx9fV13izHccXFxX5+fjKZ7OzZs127du3UqVNmZmZaWprLfROvUiA7O/vMmTO1a9cW7mX8/PPPTZs2TUhIOHPmzLlz59q3by+MaV5enkqlqlWrVrkpQOKqU6dOQEDAunXr/vzzz1mzZnXv3r1bt27r16/39/evU6cOaZmZmfn555/37dtXuK0mk8lSU1N79uzpcufOqxTYtGnTzp07n3/+eYZhAgICfvnll+HDh9eqVWvOnDm//fZbRERE7dq1hbjIXFWpVOWmAJkw1atXP3369L59+x48eLB58+YOHTo0b958x44dZrNZuCkszNU6deqQucowzIMHD/Ly8lq2bOkSF30KkBysUaNGVFSU+HjREGKvWrVqcnKysFIiLCysevXqS5cu/de//iWMgpACp0+frl69+uDBg91OP+eWcrl869atJB0mTpz44osv8jy/Zs2aXr16CZulTwGr1Wo0Gn19fTmOu3HjxksvvdS+fft169bdv3/f5Z4seQNTtWpV8mtBQUFpaenQoUPT09M3b9586tSp7t27CwcK+hSw2WxkTMPCwhYtWqTX63fv3m21Wrt168Zx3LJly3r27Ckc6+hTYNOmTfPnz589ezYJXy6XP3jwICsrq0WLFi7jVVhYOG7cuEaNGtWrV6/c8aJPAWGuyuVy8WML400KkLlaq1atsLAw8fO7V8fMR5UCEizseJ5PSkpaunTpjh07rly58uqrry5btqx+/fpkN/n7+xcWFhYUFDRu3Ji0F9bVyeXyzp07q9Xq0NBQt7XdihUrzp07Fx8f7+vrq1Kpli1bVqdOnU6dOjEMExYWdvXqVaVSGR0dzTBMWlranDlzsrKyNm7c2K1bt/DwcLKFP/74o1atWjVq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UVFff/11z549d+/eHRMT89lnn5HDmV6vJws1qlatWnYa7NixQ7gvXK1atTlz5uzdu/f1119fv359o0aNSM1KLnr/8MMP06ZNKy0t9fHxGT58OBkFckHi+PHjZL2dXC5v06bNoUOHCgsLW7RosXTp0uDg4BEjRty5c4eUj2XX60RFRfXq1atKlSpxcXHvvvuuMA+vXbtWWlpKbi/6+vq2adPmxx9/rF+//s2bN8l4dezY8dGmgNlsJqsPxdfVxcTEhIaGCotUxFPgtddeu3//vkqlMpvNMTEx5Ewgl8v37NkjrD48duzY2bNnZ86c6efnV1xcXK1aNYPBQM5hKSkpVqs1KioqNTVVr9c/88wzZKbVrFlTyMFyU6C0tJRl2bCwsK1btzZv3vzHH38kZ5GGDRt+99139evXv3TpksVicR5TlmXXr18fERGRkZGRkZFRpUqV8+fPZ2VlDRkypKCgoG3bti+//DI54peWlrqN6+7du7///ntISMi///1vhmG0Wm2PHj2qV6/OMIxKpbp27ZrBYIiLiyM9lMlkLVq00Ov19erVO3/+PGUKOBwOh8NRu3btu3fv/vHHH3v37h02bNjo0aPj4+M3btyYmJgofEPk1atXySLU0tJSt3OV7G2GYXx8fL744ouioqI2bdqUnTDHjx8fN25cbm5urVq1Ro0aVe5c9fPzW758ea9evbp37y4sAis3Bcg8rFq1atkcXLBgQdu2bTMyMsh4Ccu2RAgLqtzmaVBQ0JgxY1Qq1XPPPTdgwAByuBZPAZlM1rx5c7IG1O35om7duu3atYuPj/f39x8wYEDr1q3JXKVJAblczrKsv7+/n59f7dq1OY5bsmQJuUqUnJwsrIrz9fUNDg4WclAmk/3+++89evSIi4ubM2dObm6uxWL56quvyGsplcri4mLKFAgNDSULi3Nyco4cORISErJ582adTqfVan/99dc6deo0a9bMaDQOGDDg2WefJXOj3BQgK3MiIiK6du1KLviFh4enpKQ0bdqU3M8hE69BgwYqlcrf359czOvQoQP5VoGbN28mJSX179//6tWrt2/f/uabb4TxMhgMlCmwY8cOsgLP01wl9ZNMJvPz85s8eTJlCojP1W7duuXk5JDzu4+Pj0KhIPeRKcuGR5UC/5+3H6N9Mj18+PDf//53YmJi//79k5OT586d+8UXX5BP9/A8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", + "text/plain": [ + "plot without title" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "# ── Visual: G_prior mixture weights per scale (per_scale mode) ────────────────\n", + "library(ggplot2)\n", + "\n", + "# Extract pi (mixture weights) for each scale group after fitting\n", + "g_list <- fit_pvps$G_prior[[1]]\n", + "pi_df <- do.call(rbind, lapply(seq_along(g_list), function(s) {\n", + " pi_vec <- g_list[[s]]$fitted_g$pi\n", + " sd_vec <- g_list[[s]]$fitted_g$sd\n", + " data.frame(scale = paste0(\"Scale \", s),\n", + " component = seq_along(pi_vec),\n", + " sd = round(sd_vec, 4),\n", + " pi = pi_vec)\n", + "}))\n", + "pi_df$scale <- factor(pi_df$scale,\n", + " levels = paste0(\"Scale \", seq_along(g_list)))\n", + "pi_df$label <- sprintf(\"%.3f\", pi_df$sd)\n", + "\n", + "ggplot(pi_df, aes(x = label, y = pi, fill = scale)) +\n", + " geom_col(width = 0.7) +\n", + " facet_wrap(~scale, scales = \"free_x\", ncol = 3) +\n", + " scale_fill_brewer(palette = \"Set2\", guide = \"none\") +\n", + " labs(title = \"Mixture weights pi by scale (per_scale prior, after 20 iters)\",\n", + " x = \"Mixture SD component\", y = expression(pi[k])) +\n", + " theme_minimal(base_size = 11) +\n", + " theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 7))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 4. How the two scopes interact\n", + "\n", + "`residual_variance_scope` and `prior_variance_scope` are orthogonal: all four\n", + "combinations are valid.\n", + "\n", + "| | `residual_variance_scope = \"per_outcome\"` | `residual_variance_scope = \"per_scale\"` |\n", + "|---|---|---|\n", + "| `prior_variance_scope = \"per_outcome\"` | fsusie default | per-scale $\\sigma^2$, shared prior |\n", + "| `prior_variance_scope = \"per_scale\"` | shared $\\sigma^2$, per-scale prior | most parameters |\n", + "\n", + "The original `fsusie` is equivalent to `residual_variance_scope = \"per_outcome\"` +\n", + "`prior_variance_scope = \"per_outcome\"`. William's suggestion to try per-scale\n", + "variance parameters corresponds to `residual_variance_scope = \"per_scale\"` +\n", + "`prior_variance_scope = \"per_scale\"`.\n", + "\n", + "### MWE: compare ELBO and PIP recovery across mode combinations" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\t\n", + "\t\n", + "\n", + "\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\n", + "
A data.frame: 4 × 5
combopip_trueelbo_lastniterconverged
<chr><dbl><dbl><int><lgl>
rvo+pvo1-3586.792TRUE
rvs+pvo1-3427.632TRUE
rvo+pvs1-3580.114TRUE
rvs+pvs1-3381.274TRUE
\n" + ], + "text/latex": [ + "A data.frame: 4 × 5\n", + "\\begin{tabular}{lllll}\n", + " combo & pip\\_true & elbo\\_last & niter & converged\\\\\n", + " & & & & \\\\\n", + "\\hline\n", + "\t rvo+pvo & 1 & -3586.79 & 2 & TRUE\\\\\n", + "\t rvs+pvo & 1 & -3427.63 & 2 & TRUE\\\\\n", + "\t rvo+pvs & 1 & -3580.11 & 4 & TRUE\\\\\n", + "\t rvs+pvs & 1 & -3381.27 & 4 & TRUE\\\\\n", + "\\end{tabular}\n" + ], + "text/markdown": [ + "\n", + "A data.frame: 4 × 5\n", + "\n", + "| combo <chr> | pip_true <dbl> | elbo_last <dbl> | niter <int> | converged <lgl> |\n", + "|---|---|---|---|---|\n", + "| rvo+pvo | 1 | -3586.79 | 2 | TRUE |\n", + "| rvs+pvo | 1 | -3427.63 | 2 | TRUE |\n", + "| rvo+pvs | 1 | -3580.11 | 4 | TRUE |\n", + "| rvs+pvs | 1 | -3381.27 | 4 | TRUE |\n", + "\n" + ], + "text/plain": [ + " combo pip_true elbo_last niter converged\n", + "1 rvo+pvo 1 -3586.79 2 TRUE \n", + "2 rvs+pvo 1 -3427.63 2 TRUE \n", + "3 rvo+pvs 1 -3580.11 4 TRUE \n", + "4 rvs+pvs 1 -3381.27 4 TRUE " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "combos <- list(\n", + " \"rvo+pvo\" = list(residual_variance_scope = \"per_outcome\",\n", + " prior_variance_scope = \"per_outcome\"),\n", + " \"rvs+pvo\" = list(residual_variance_scope = \"per_scale\",\n", + " prior_variance_scope = \"per_outcome\"),\n", + " \"rvo+pvs\" = list(residual_variance_scope = \"per_outcome\",\n", + " prior_variance_scope = \"per_scale\"),\n", + " \"rvs+pvs\" = list(residual_variance_scope = \"per_scale\",\n", + " prior_variance_scope = \"per_scale\")\n", + ")\n", + "\n", + "results <- lapply(names(combos), function(nm) {\n", + " args <- c(\n", + " list(X = X, Y = list(Y1), pos = list(seq_len(T1)),\n", + " L = 3L, max_iter = 20L, verbose = FALSE),\n", + " combos[[nm]]\n", + " )\n", + " fit <- do.call(mfsusie, args)\n", + " data.frame(\n", + " combo = nm,\n", + " pip_true = round(fit$pip[5], 3),\n", + " elbo_last = round(tail(fit$elbo[!is.na(fit$elbo)], 1), 2),\n", + " niter = fit$niter,\n", + " converged = fit$converged\n", + " )\n", + "})\n", + "do.call(rbind, results)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visual: PIP and ELBO across all four scope combinations" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAIAAAByhViMAAAACXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nOzde3zU1Z0//s/kQgIGghADXjCIgggCWrVWEXHVeimilVplta1arajUtbK2FbWtimy1rg8tar3UtqmuLepa640qFMHWFa2CWrkIAkYQrwQIhNwml98fs5tfvsFcCMkMnHk+/+CROefMmfOeyXx45TOfz2diDQ0NEQAAu76MVC8AAIDOIdgBAARCsAMACIRgBwAQCMEOACAQgh0AQCAEOwCAQAh2AACBEOwAAAKRLsHuwQcfjG0jKyurZ8+eBx100KRJk5YtW9Z0fG1tbWLMX//61w5PsoOWL1/e6SMDMHHixFgs9p//+Z9d9xDb9XyWlpb279//sssu67r1tNNTTz217e9nwn//938nxlx33XW9e/deu3ZtapdKqjz//PMt/ZI0+vOf/5wY3KXvtVNOOSUWiz344IOtjJk5c2abq43FYuvXr0+Mv/nmm2Ox2Fe+8pXWHzoxrJm8vLz999//4osvfuedd1q57xtvvPH9739/+PDhvXv3zsnJGThw4DnnnPPss89ub/nQddIl2H2hurq68vLyd99994EHHjjkkEM69ubslEma2bRp04UXXjhx4sROHEl7dOD5/P73v79169Ybb7yx61bVTosXL25zzNSpU3Nyci666KIkrAd2IVu3bl29evVvfvObww8//Iknnth2QFVV1YUXXvjlL3/5nnvuWbp0aVlZWU1NzQcffPDYY4+NHz/+uOOO++yzz5K/bNhWegW7AQMGbGyitLR0zZo1f/rTnwYNGlRTU3PBBRds3LgxOZO07tVXXy0uLm7P1/i2f2Qwzj333J///OfHHntsV0y+vc/nvHnzZs6cedVVVxUWFnbFerZLYk/D3XffvXEbZ5xxRmJMXl7eddddN2fOnMZ9eKSnbX9JGo0bNy4xpkvfa+3Xp0+fVla7cePGvn37dmDaww8/vHGGDRs2rF27du7cuccdd1xiM964FzChurr6q1/9anFxcSwW+853vvPSSy9t2rRp8+bNixYtuuqqq3bbbbeXXnrpiCOOWL16dScVDR2XleoFJFVGRkbv3r2btvTp02fAgAHDhg0bPnx4aWnpo48+eumllyZhEjrs9NNPP/3001O9iv/14x//uHv37j/4wQ9SvZAo+r9g9+Uvf7nZ72czl1xyybRp06ZOnXrmmWdmZmYma3XsXFr/JUnYSd5rsVisPavdXpmZmU2n3X333ffZZ58jjzxy0KBBn3322cMPP3zVVVc19l599dUvv/xyXl7en/70p69+9auN7Yceeuihhx56/vnnjxs3bs2aNRdccMFLL70Ui8U6fbXQfum1x64lBx544OjRo6MoeuWVV1I7CbuQ2bNnv/766xMmTOjTp0+q1xLV1NSsWLEiIyNj+PDhrY/Mzc0999xzV65c+dhjjyVnbbCr2G233Y4//vgoipYsWdLYuHr16l/96ldRFN13331NU12jUaNGJQ4H/Pvf//7www8nbbXwhQS7/9W/f/8oisrKyrp6kqqqqrvvvvv444/fY489srOzd99996985Su33357dXV1YsDBBx986qmnRlH09ttvt/6naksjv/71r8disTfeeOMXv/hF3759d9999/Hjx9fX17d0tPKJJ54Yi8WKi4ubNv7973+fMGFCv379unXrts8++1xwwQXvvvtu6+Ufe+yxsVjs9ttvb9be0NAwcODAWCz2l7/8pZ1PQitVbHtAd3tmO+uss2Kx2Msvv7xw4cIzzjijoKCge/fuo0aNuuOOO2pra7f3mU+49957oyg699xzk/wMfOFili1bVltbu//++/fo0aP1ZTeu+b777mtzJOms2XutPW+ihPb8eu+0Evvb8vLyGlvuv//++vr6wYMHN32zN3PMMcd8/etfj6JIsCPlBLv/lTg2ol+/fl06SVlZ2ejRo6+44oqXXnopPz9/2LBhDQ0Nr7322tVXX53YKERRdNJJJx1zzDFRFOXn53/jG99oPDpqW62P/MMf/vDjH/94w4YNmzZtqqyszMjYjtd6+vTpxx577JNPPllTUzNy5Mjq6urf//73hx566FNPPdXKvb7zne9EUTRz5sxm7QsWLPjggw/69euX+GO3PU9C+6vYrtn+8pe/HHXUUU8//XRmZmZmZuY///nPKVOmNJ5J0P5nPoqiysrKWbNm5eTkHHfccal9BhISn8OOGDFi5syZp5xyysCBA4cNG3bRRRc13fHQ6Igjjujdu/ff/va3Tz/9tJUaYVutv4mi7fz13tlUVFS8+OKLURQl/sZLmDdvXhRFp512WuufsZ511lmJwZs2beriZUKrGtLDr3/96yiKioqKvrD3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J7TEHrvkSVzlrnfv3s22d4sWLUqcKdZ4kEriWmXtORil/SOj/zvy45///GfTxn/84x9r165tvDls2LBEMrj11lub3f3mm28eO3Zs4o/X1iVyzOOPP5640ts3v/nNxq72Pwnt0bmzRdvzfB544IFRFK1bt+4Le5P2DCTsv//+iR1yzS6OWl9fn7jMxLYfISVWPmTIkO19LGhFV/x6J9mzzz6b2BPfeLbTfvvtd+2110ZRdOWVVz7//PPb3mXVqlUTJkyIx+MjRow4//zzk7la2JZglzyJ8xBLS0ubfpXTggULGqNS40eiiYuel5SULF26dMuWLa3M2f6RURQlvvHskUceee655xIt8+fP/+Y3v9n0mrexWCxxuZN77rnnrrvuSpwRVl9ff9999/3yl7+MoihxkarWfetb30p8AUN5efn48eObHo7d/iehPTp3tmh7ns8jjzwyiqKmB183lbRnICE7OzvxfbU333xzYh9hFEWbN2++8MIL//73v/fp06fxFN1GiZUfffTR2/tY0Iqu+PVuaGjY1KpmZ6HW1dW1NLJprGw2bMOGDcuWLfv5z3+eOM/p8MMPT1zTLuG66647+eSTKysrTzvttO9973uvvfZaZWVlPB5/9913p02bduihh77//vsFBQWPP/5447mxkDJdfaG8nUTr1xbeVgcuUNweiYucRVE0atSoU045ZejQoVEUde/e/Stf+UoURZMnT04MW79+fePBT7FYbOvWrS1N+IUjExe2veuuu5oNrqioSDxiFEX9+vVL7MAbPHhw4rODxgsUNzQ0TJkyJTGsb9++RxxxROMHdj/60Y/aWenYsWMTd3nqqac69iS0VEWzi6a2c7bEtVXvuOOO1mdr/zO/efPmxMjPP/88yc/AF4rH443/d+65556HH3544sSO3r17v/LKK80GV1VV5eTkZGRkfPzxx+2ZnGDs+AWK23wTtfPXu/0XKG7Tz3/+88T4xJWHWzFv3rz2DNtrr73efffdZoupra294oorWvryiSOOOGLFihXteVahq9ljl1RPPPHEHXfcccghh6xevXrevHm1tbWXXHLJW2+9dfPNN0dR9OSTTyY+BOzbt+8f/vCHoUOHduvWrV+/fk2PgWum/SOjKOrevfv//M//TJkyZeDAgRs3buzRo8cPfvCD119/fduT82+//fbZs2efccYZmZmZb775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4IMPLioq+tKXvtSvX78xY8a8/fbbSVgZQNL84Ac/6Nmz549+9KOWzo144oknpkyZUlBQcMkllyR5bQDt18Z17BYsWDBu3Lh4PL733nsXFRWtWLHi5ZdfPvbYY994443BgwcnZ4kAXa1fv35//OMfv/71r59++uknnXTSGWecMXz48F69elVWVi5btuzRRx+dPXt2dnb2E0880adPn1QvFqBFbQS72267LR6P33TTTdddd11GRkY8Hr/qqqvuueeeX/ziF7/+9a+Ts0SAJBg3btz8+fO/+93vvvDCCy+88EKz3hEjRhQXF3/pS19KydoA2qmNYPfqq6/us88+119/feJ8sezs7Ntvv/3hhx+eP39+MlYHkESjR49etmzZCy+8MHfu3OXLl5eVlfXu3Xv//fc/44wzjj322IwMZ5sBO7s2gt369euPOOKIplcByMnJOfDAA5csWdLFCwNIgYyMjFNPPfXUU09N9UIAOqKNYBePx7e9aFOPHj0qKiq6bEkAO4vq6uonn3xyxYoVffv2HT9+/L777pvqFQG0po1gB5AmPv3009tuu+1//ud/8vPzv/vd75599tkffPDB8ccfv3r16sSAq6666qc//en111+f2nUCtEKwA4g+/vjjL3/5yx9++GHi5gsvvLBq1aq5c+euXr369NNPP+qoo9asWVNcXPyTn/xk5MiRp59+empXC9CStoNdeXn5W2+91awliqJmjVEUHXLIIZ24MoCkueGGGz788MOLLrroZz/7WVZW1g033PDTn/60trb26quvvu222xJjvvGNb5x44okPPPCAYAfstNoOdgsXLjz00EO3bd+2saGhoT0PuWjRonnz5m3dunXEiBHjx4//wm/dXrFiRXFxcdOWk08+eezYse2fAaD9Zs+evfvuu997772Jo4rvuuuuxx9/fOPGjZMmTWocc8IJJ+y3335vvPFG6pYJ0IY2ItG4ceM69/Fmz5599913jx49urCwcObMmf/85z9/9rOfbTts2bJl77333mGHHdbYkpOTs10zALTfxx9/PHLkyMZzxbp16zZ48OB//OMfAwYMaDpsn332WbBgQSoWCNAubQS7lr5dp2OqqqqKi4snTJhwwQUXRFE0ZsyYqVOnLly4sGmASygpKRkyZMg111zT4RkA2q+6urp79+5NWxI3G/+kTMjIyKitrU3qygC2R1Kvt7l48eLy8vJTTjklcXP48OEDBw78wj9/S0pKBg0atCMzAGyXphfsBNhFJfXotJKSktzc3P79+ze2DBgwoKSkpNmw+vr6tWvXjho16s4771y3bt1ee+111llnJT4QaecMAABpqI1gt11XbLr55ptbH1BeXp6Xl9e0JS8vb+XKlc2GffTRRzU1NbNmzRo7duwBBxzwyiuvLFiw4NZbb91vv/3aOUNT8Xg8mMspl5WVpXoJSaXegHVFsb169dqRvW7Lly+/+OKLm96MoqhpS2MjwE6rjWA3ffr09s/VZrCLx+OZmZlNWzIzM+vq6poNy8zMPPXUU0844YQhQ4ZEUTRx4sTJkycXFxffeOON7Zyhqfr6+ng83v4qdmbBFNJO6g3YTljsJ5988pvf/KZZ47YtADuzNoLdr3/96058sNzc3JqamqYt1dXVPXr0aDZszz33vOyyyxpv5ufnjx49eu7cue2foalu3br17t17R5fezNpPOnnC9un8QnZuKau3PDUPm1avb1cUuyO76/7yl7904koAUqWNYNfsY4gdVFhYWFZWVltb23jludLS0oKCgmbDqqqqNm3a1PRAuu7duyfu0s4ZmorFYsFc6C6YQtpJvQHb2YptPCULYJfW3rNi16xZ8+abb3722Wc78mBDhw6tr69funRp4mZNTc3y5cuHDRvWbNicOXMmTZq0cePGxpYlS5YkTpJt5wwAXeGmm26aOHFiqlcB0KK2g92cOXMOPvjgoqKiL33pS/369RszZszbb7/dsQcrKioaNmzYvffeu3bt2vLy8rvvvruuru7EE09M9M6fP//FF1+Mouioo47q3r37jBkzNm7cWFFR8fvf/3758uVnn312mzMAdKkXX3zx0UcfTfUqAFrUxqchCxYsGDduXDwe33vvvYuKilasWPHyyy8fe+yxb7zxxuDBgzvweFOmTJk2bdrkyZOjKMrPz7/mmmsaD7V5+umn6+rqjj/++IKCgmuvvXbGjBnnn39+LBbLzc2dPHly4xfRtjIDAEA6ayPY3XbbbfF4/KabbrruuusyMjLi8fhVV111zz33/OIXv+jYeRWFhYUzZsxYs2ZNbW3tgAEDunXr1th1+eWXN/48cuTI+++//+OPP66srCwqKmo6rJUZAADSWRvB7tVXX91nn32uv/76xOlm2dnZt99++8MPPzx//vwOP2QsFisq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", + "text/plain": [ + "plot without title" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "# ── Visual: PIP and ELBO across 4 scope combinations ─────────────────────────\n", + "library(ggplot2)\n", + "\n", + "res_df <- do.call(rbind, results)\n", + "res_df$combo <- factor(res_df$combo, levels = res_df$combo)\n", + "\n", + "p1 <- ggplot(res_df, aes(x = combo, y = pip_true, fill = combo)) +\n", + " geom_col(width = 0.5) +\n", + " scale_fill_brewer(palette = \"Paired\", guide = \"none\") +\n", + " ylim(0, 1) +\n", + " labs(title = \"PIP at true variant (var 5)\", x = NULL, y = \"PIP\") +\n", + " theme_minimal(base_size = 12) +\n", + " theme(axis.text.x = element_text(angle = 30, hjust = 1))\n", + "\n", + "p2 <- ggplot(res_df, aes(x = combo, y = elbo_last, fill = combo)) +\n", + " geom_col(width = 0.5) +\n", + " scale_fill_brewer(palette = \"Paired\", guide = \"none\") +\n", + " labs(title = \"Final ELBO\", x = NULL, y = \"ELBO\") +\n", + " theme_minimal(base_size = 12) +\n", + " theme(axis.text.x = element_text(angle = 30, hjust = 1))\n", + "\n", + "gridExtra::grid.arrange(p1, p2, ncol = 2)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 5. Summary\n", + "\n", + "| Field / parameter | Where set | Shape | Consumed by |\n", + "|---|---|---|---|\n", + "| `data$scale_index[[m]]` | `R/dwt.R` via `gen_wavelet_indx` | list of $S_m$ index vectors | `update_variance_components`, `mf_prior_scale_mixture`, `optimize_prior_variance` |\n", + "| `model$sigma2[[m]]` | `update_variance_components` | scalar (`per_outcome`) or length-$S_m$ (`per_scale`) | `mf_sigma2_per_position`, which broadcasts to length `T_basis[m]` for all downstream consumers |\n", + "| `model$G_prior[[m]]` | `mf_prior_scale_mixture` | list of 1 (`per_outcome`) or $S_m$ (`per_scale*`) prior records | `loglik`, `calculate_posterior_moments`, `optimize_prior_variance` — iterate over `G_prior[[m]][[s]]$idx` uniformly |\n", + "\n", + "The design decouples the two variance parameters: `residual_variance_scope`\n", + "controls how many $\\sigma^2$ scalars are estimated per modality;\n", + "`prior_variance_scope` controls the granularity of the effect-size prior.\n", + "Both funnel into `mf_sigma2_per_position` and the `G_prior[[m]][[s]]$idx`\n", + "loop, so the downstream IBSS code has no mode-specific branches.\n", + "\n", + "**Trade-off (per William Denault's comment, 2026-05-17):** Adding more\n", + "variance parameters per scale can gain power but may also hurt FDR calibration\n", + "due to overfitting, particularly at coarse scales where the coefficient count\n", + "is small. The `per_scale_normal` / `per_scale_laplace` modes mitigate this by\n", + "using only 2 parameters per (modality, scale) instead of $K + 1$." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "4.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/inst/notes/s3-dispatch-design.md b/inst/notes/s3-dispatch-design.md new file mode 100644 index 0000000..f3b4ed0 --- /dev/null +++ b/inst/notes/s3-dispatch-design.md @@ -0,0 +1,326 @@ +# S3 dispatch in mfsusieR + +## What problem this solves + +`susieR` implements the full IBSS algorithm — initialization, the outer +iteration loop, convergence checking, credible-set construction. Reimplementing +all of that in mfsusieR for multi-functional data would be error-prone and +hard to keep in sync. Instead, mfsusieR plugs into susieR's existing loop by +teaching it how to handle a new data class (`mf_individual`) through R's S3 +dispatch mechanism. + +--- + +## Primer: how S3 dispatch works in R + +R's S3 system is simple. A **generic function** is a function that looks at the +class of its first argument and calls the right **method**: + +```r +# Generic defined somewhere: +compute_residuals <- function(data, ...) UseMethod("compute_residuals") + +# Method for class "individual" (susieR's scalar case): +compute_residuals.individual <- function(data, ...) { ... } + +# Method for class "mf_individual" (mfsusieR's multi-functional case): +compute_residuals.mf_individual <- function(data, ...) { ... } +``` + +When susieR calls `compute_residuals(data, ...)`, R checks `class(data)`. +If it is `"mf_individual"`, it runs `compute_residuals.mf_individual`. If it +is `"individual"`, it runs `compute_residuals.individual`. The IBSS loop in +susieR does not need a single `if (multi_functional)` branch anywhere. + +--- + +## The three objects that flow through the loop + +Every generic in the system takes the same three arguments: + +| Object | Class | Mutable? | What it holds | +|--------|-------|----------|---------------| +| `data` | `c("mf_individual", "individual")` | No | DWT-packed Y matrices, X, `na_idx`, `scale_index`, `T_basis`, etc. Set once by `create_mf_individual()`. | +| `params` | plain list | No | All user settings: `L`, `tol`, `prior`, `residual_variance_scope`, etc. Set once by `mfsusie()`. | +| `model` | `c("mfsusie", "susie")` | Yes | Fit state: `alpha`, `mu`, `mu2`, `sigma2`, `G_prior`, `elbo`, etc. Updated every iteration. | + +The dispatch key is `class(data)[1]` = `"mf_individual"`. Every time susieR +calls a generic with `data`, R routes it to the `*.mf_individual` method. + +--- + +## The call chain + +``` +mfsusie(X, Y, pos, ...) + │ + ├─ create_mf_individual(...) → data (class: mf_individual) + ├─ mf_prior_scale_mixture(...) → prior + ├─ assemble params list → params + │ + └─ susie_workhorse(data, params) + │ + ├─ ibss_initialize(data, params) + │ └─ initialize_susie_model.mf_individual(...) ← dispatched + │ └─ initialize_fitted.mf_individual(...) ← dispatched + │ └─ ibss_initialize.mf_individual(...) ← dispatched + │ + ├─ [outer loop: iter = 1 … max_iter] + │ ├─ track_ibss_fit(data, …) ← dispatched + │ ├─ ibss_fit(data, params, model) + │ │ └─ [for l in 1:L] + │ │ └─ single_effect_update(data, params, model, l) + │ │ └─ single_effect_regression(data, params, model, l) + │ │ ├─ compute_ser_statistics(data,…,l) ← dispatched + │ │ ├─ pre_loglik_prior_hook(data,…,l) ← dispatched + │ │ ├─ loglik(data,…,l) ← dispatched + │ │ │ └─ optimize_prior_variance(…) ← dispatched (inside loglik) + │ │ ├─ calculate_posterior_moments(…,l) ← dispatched + │ │ ├─ compute_kl(…,l) ← dispatched + │ │ └─ post_loglik_prior_hook(data,…,l) ← dispatched + │ │ └─ update_fitted_values(data,…,l) ← dispatched + │ │ + │ ├─ get_objective(data, params, model) ← dispatched (mfsusie method) + │ └─ update_model_variance(data, params, model) ← dispatched + │ └─ update_variance_components(…) ← dispatched + │ └─ update_derived_quantities(…) ← dispatched + │ + ├─ trim_null_effects(data, params, model) ← dispatched + ├─ ibss_finalize(data, params, model, …) + │ └─ get_scale_factors.mf_individual(…) ← dispatched + │ └─ get_intercept.mf_individual(…) ← dispatched + │ └─ get_fitted.mf_individual(…) ← dispatched + │ └─ get_variable_names.mf_individual(…) ← dispatched + │ └─ get_zscore.mf_individual(…) ← dispatched + │ └─ cleanup_extra_fields.mf_individual(…) ← dispatched + │ └─ Eloglik.mf_individual(…) ← dispatched + │ └─ get_cs(…) (susieR internal, called bare) + │ + └─ return model → mfsusie() attaches class "mfsusie", dwt_meta, etc. +``` + +Every `← dispatched` call looks up `class(data)` at runtime and jumps to the +`*.mf_individual` implementation in `R/individual_data_methods.R` or +`R/ibss_methods.R`. susieR itself never branches on data type. + +--- + +## Where the registration happens: `R/zzz.R` + +Two problems must be solved: + +1. **The generics live in susieR's namespace**, not mfsusieR's. R only finds + `compute_residuals.mf_individual` if it is registered in the same namespace + as the generic `compute_residuals`. A plain `@export` in NAMESPACE does not + do this — it only exports from mfsusieR's namespace. + +2. **susieR exports some internals** (e.g., `SER_posterior_e_loglik`, + `update_variance_components`, `get_cs`) that mfsusieR's methods need to + call. They cannot be accessed by `susieR::fn` because they are not + re-exported. They must be fetched directly from `asNamespace("susieR")` at + load time. + +Both are solved in `.onLoad` in `R/zzz.R`: + +```r +.onLoad <- function(libname, pkgname) { + susie_ns <- asNamespace("susieR") + pkg_ns <- asNamespace(pkgname) + + # 1. Cache susieR internals as package-level bindings. + # Methods can then call them bare (e.g., get_cs(...)) without + # knowing they come from susieR. + for (fn in c("SER_posterior_e_loglik", "update_variance_components", + "get_cs", "initialize_susie_model", ...)) { + assign(fn, get(fn, envir = susie_ns), envir = pkg_ns) + } + + # 2. Register S3 methods into susieR's namespace so that when + # susieR calls compute_residuals(data, ...) with data of class + # "mf_individual", R dispatches to our method. + for (g in mf_generics) { + method_fn <- get(paste0(g, ".mf_individual"), envir = pkg_ns) + registerS3method(g, "mf_individual", method_fn, envir = susie_ns) + } + + # 3. Same for the "mfsusie" model class. + for (g in mfsusie_generics) { + method_fn <- get(paste0(g, ".mfsusie"), envir = pkg_ns) + registerS3method(g, "mfsusie", method_fn, envir = susie_ns) + } +} +``` + +`registerS3method(generic, class, method, envir = susie_ns)` is the key call. +It inserts the method directly into susieR's S3 method table, so R's dispatch +finds it even though the method lives in mfsusieR. + +--- + +## Two dispatch classes + +### Class `mf_individual` (the data object) + +Governs every computation that touches the data — residuals, Bhat/Shat, +likelihoods, variance updates, fitted values. All these methods live in: + +- `R/individual_data_methods.R` — per-effect SER-step methods +- `R/ibss_methods.R` — per-iteration methods + init/finalize accessors + +The full list registered under `mf_individual`: + +| Phase | Generic | What the method does | +|-------|---------|----------------------| +| Init | `get_var_y` | Initial σ² estimate from wavelet Y variance | +| Init | `initialize_susie_model` | Builds model struct with `G_prior`, `sigma2`, `fitted_g_per_effect`, etc. | +| Init | `initialize_fitted` | Sets initial fitted values (zeros for wavelet fits) | +| Init | `ibss_initialize` | Attaches iter-0 state: `pi_V`, caches xtx etc. | +| Per-effect | `compute_residuals` | $X^T R_m$ over `na_idx[[m]]` rows only | +| Per-effect | `compute_ser_statistics` | Computes (Bhat, Shat) per outcome, scale group | +| Per-effect | `loglik` | Calls `optimize_prior_variance` inside; returns log-BF vector | +| Per-effect | `optimize_prior_variance` | Runs mixsqp / ebnm per (outcome, scale) group | +| Per-effect | `calculate_posterior_moments` | Posterior mean/var of effect for each outcome | +| Per-effect | `compute_kl` | KL(q ‖ prior) for effect l | +| Per-effect | `neg_loglik` | Negative log-likelihood (used by outer optimizer) | +| Per-effect | `update_fitted_values` | Updates residual fitted values Xr after effect l | +| Per-effect | `SER_posterior_e_loglik` | E[log p(Y \| β_l)] for ELBO computation | +| Per-iter | `update_variance_components` | Updates σ² per modality (per_outcome or per_scale) | +| Per-iter | `update_derived_quantities` | Rebuilds shat2 cache after σ² update | +| Per-iter | `update_model_variance` | Wrapper that calls the two above | +| Per-iter | `track_ibss_fit` | Records per-iter snapshots when `track_fit=TRUE` | +| Per-iter | `trim_null_effects` | Removes effects with max α below threshold | +| Finalize | `get_scale_factors`, `get_intercept`, `get_fitted`, `get_variable_names`, `get_zscore` | Extract components for the returned fit object | +| Finalize | `cleanup_extra_fields` | Drops internal caches (`D`, `xtx_diag_list`, etc.) | +| Finalize | `Eloglik` | Final ELBO log-likelihood term | + +### Class `mfsusie` (the model object) + +Governs computations that read from the model struct and need to understand +its multi-outcome, multi-scale structure. Registered under `mfsusie`: + +| Generic | What the method does | +|---------|----------------------| +| `get_objective` | Refreshes per-effect lbf/KL before returning the ELBO. Without this, the per-iteration ELBO would be a hybrid quantity (Eloglik at iter-final state but KL at the state when each effect was last updated). | +| `format_sigma2_summary` | Formats σ² as a compact string for the verbose progress line. Needed because σ² is a list of vectors (one per modality) rather than a scalar. | +| `format_extra_diag` | Appends null-mass summary to the verbose line. | +| `get_posterior_mean_l` | Returns the per-outcome posterior mean curve for effect l. | +| `get_posterior_mean_sum` | Sums across effects. | +| `get_posterior_moments_l` | Returns (mu, mu2) for effect l. | + +--- + +## Concrete example: `compute_residuals` + +When the IBSS loop updates effect l=3, susieR calls: + +```r +compute_residuals(data, params, model, l = 3) +``` + +1. R evaluates `class(data)` → `c("mf_individual", "individual")` +2. R looks up `compute_residuals.mf_individual` in susieR's method table + (registered there by `.onLoad`). +3. It finds and calls: + +```r +# R/individual_data_methods.R:26 +compute_residuals.mf_individual <- function(data, params, model, l, ...) { + R_list <- lapply(seq_len(data$M), function(m) { + idx_m <- data$na_idx[[m]] # observed rows for modality m + Xr_m <- model$Xr[[m]] # n x T_basis[m] fitted values + Xr_l <- get_Xr_l(data, model, m, l) # contribution of effect l + R_m <- data$D[[m]][idx_m, ] - Xr_m[idx_m, ] + Xr_l[idx_m, ] + crossprod(data$X_processed[idx_m, ], R_m) # p x T_basis[m] + }) + model$XtR <- R_list + model +} +``` + +The method computes X^T R separately per modality, using only the `na_idx[[m]]` +rows for each. susieR's generic never sees any of this — it just calls +`compute_residuals(data, ...)` and gets back an updated `model`. + +--- + +## Concrete example: `update_variance_components` + +At the end of each IBSS iteration, susieR calls: + +```r +update_model_variance(data, params, model) + └─ update_variance_components(data, params, model) # dispatched + └─ update_derived_quantities(data, params, model) # dispatched +``` + +`update_variance_components.mf_individual` (`R/individual_data_methods.R:436`) +reads `params$residual_variance_scope` and updates `model$sigma2[[m]]` as +either a scalar (`per_outcome`) or a length-S_m vector (`per_scale`): + +```r +update_variance_components.mf_individual <- function(data, params, model, ...) { + method <- params$residual_variance_scope %||% "per_outcome" + for (m in seq_len(data$M)) { + er2_t <- mf_get_ER2_per_position(data, model, m) + n <- length(data$na_idx[[m]]) + if (method == "per_outcome") { + model$sigma2[[m]] <- sum(er2_t) / (n * data$T_basis[m]) + } else { + indx_m <- data$scale_index[[m]] + model$sigma2[[m]] <- vapply(indx_m, function(idx) + sum(er2_t[idx]) / (n * length(idx)), numeric(1)) + } + } + refresh_iter_cache.mf_individual(data, model) +} +``` + +susieR's `update_model_variance` generic calls this transparently, without +knowing anything about wavelet scales or modalities. + +--- + +## Why `get_objective` needs a `mfsusie` method + +susieR's default `get_objective` reads `model$elbo` at the current state. +In mfsusieR the per-effect KL and lbf are updated when effect l is visited +(inside `single_effect_regression`), but by the time all L effects have been +updated in one iteration, the lbf[l] and KL[l] values for early effects (l=1, +l=2, …) were computed against an older α state. The final ELBO should reflect +the end-of-iteration state for all effects uniformly. + +`get_objective.mfsusie` calls `refresh_lbf_kl.mf_individual` first, which +recomputes lbf[l] and KL[l] for all l against the iter-final α and π_V before +summing the ELBO. Without this, the per-iteration ELBO is a hybrid quantity +that is neither the start-of-iteration nor the end-of-iteration value. + +--- + +## File map + +| File | What it contains | +|------|-----------------| +| `R/zzz.R` | `.onLoad`: caches susieR internals, registers all S3 methods via `registerS3method` | +| `R/individual_data_class.R` | `create_mf_individual()`: constructor that gives `data` its `mf_individual` class | +| `R/individual_data_methods.R` | Per-effect SER-step methods (`*.mf_individual`) + per-iter variance methods | +| `R/ibss_methods.R` | Per-iter + init/finalize methods (`*.mf_individual`), `get_objective.mfsusie` | +| `R/model_class.R` | `initialize_susie_model.mf_individual`: builds the model struct with `G_prior`, `sigma2`, `fitted_g_per_effect` | +| `R/mfsusie_methods.R` | User-facing S3: `predict.mfsusie`, `coef.mfsusie`, `summary.mfsusie`, `print.mfsusie` — these are NOT registered into susieR, just exported normally | +| `R/prior_scale_mixture.R` | `optimize_prior_variance.mf_individual`: mixsqp / ebnm EM per (outcome, scale) | + +--- + +## Why this design + +**susieR handles everything algorithmic.** The outer loop, convergence, ELBO +bookkeeping, credible-set construction, PIP aggregation — none of that is +reimplemented. mfsusieR only implements the data-specific computations. + +**Adding a new data type requires implementing ~20 methods.** Each is a +self-contained function with a clear contract (inputs / outputs defined by the +generic). No global state, no hidden branches. + +**The dispatch boundary makes bugs locatable.** If sigma2 is wrong, the bug +is in `update_variance_components.mf_individual`. If BFs are wrong, it is in +`loglik.mf_individual` or `compute_ser_statistics.mf_individual`. The generic +name tells you the lifecycle phase; the class suffix tells you which file. diff --git a/inst/notes/sessions/2026-05-03-2048-mu-storage-and-benchmark-plan.md b/inst/notes/sessions/2026-05-03-2048-mu-storage-and-benchmark-plan.md new file mode 100644 index 0000000..2a26426 --- /dev/null +++ b/inst/notes/sessions/2026-05-03-2048-mu-storage-and-benchmark-plan.md @@ -0,0 +1,151 @@ +# 2026-05-03 PR plan: mu/mu2 storage + per_scale_normal benchmark + +Date: 2026-05-03 +Scope: design and PR plan for the `save_mu_method` storage policy +on `mfsusie()` (closes issue #7) and the surrounding 6-grid +benchmark over `prior_variance_scope` x `wavelet_qnorm` x +`mixture_null_weight`. + +## Design + +`save_mu_method = c("complete", "alpha_collapsed", "lead")` on +`mfsusie()` and the `fsusie()` wrapper, default `"complete"`. + +| Mode | `mu[[l]][[m]]` shape | coef / mf_post_smooth numerics | predict(newx) | per-variant lfsr | model_init | +|---|---|---|---|---|---| +| complete | p x T_basis[m] | exact | works | works | works | +| alpha_collapsed | 1 x T_basis[m] (= alpha %*% mu_full) | numerically equivalent to complete (1e-12) | errors | errors | errors | +| lead | 1 x T_basis[m] (= mu_full[j*, ], j* = which.max(alpha[l, ])) | cheap lead-variable summary, biased toward j* | errors | errors | errors | + +To recover raw-X coef under `alpha_collapsed` the fit also carries +`fit$coef_wavelet[[l]][[m]] = alpha %*% (mu_full / csd_X)` (1 x T) +because per-j csd_X scaling cannot be recovered after the +alpha-collapse. Under `lead` the fit carries `fit$top_index[l]`. + +A post-fit helper `mf_thin(fit, method)` performs the same trim, +so a caller can keep a `complete` checkpoint for warm-start and a +thinned distribution copy. + +## Benchmark grid + +Six cells, intended to exercise `prior_variance_scope` x +`wavelet_qnorm` x `mixture_null_weight`: + +```r +bench_grid <- rbind( + expand.grid(wavelet_qnorm = c(FALSE, TRUE), + prior_variance_scope = "per_scale", + mixture_null_weight = c(0.05, 0)), + data.frame (wavelet_qnorm = c(FALSE, TRUE), + prior_variance_scope = "per_scale_normal", + mixture_null_weight = NA_real_) +) +``` + +Driver: `inst/bench/profiling/benchmark_per_scale_normal_6grid.R` +(Gaussian baseline, 30 fits) and +`inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R` +(heavy-tailed signal + null no signal, 60 fits). Per-cell metrics: +empirical FDR, power, n_disc, cs_count, cs_purity, niter, +runtime, fit_size_mb. The benchmark fits use +`save_mu_method = "alpha_collapsed"` so each saved fit is small. + +Results memo: +`inst/notes/sessions/2026-05-03-2314-per-scale-normal-baseline-results.md`. + +## Default-value table + +Defaults are unchanged in PR-1 (the storage policy is opt-in; +behaviour for users who do not pass `save_mu_method` is identical +to before). + +| Arg | Default (mfsusieR 0.0.2) | Notes | +|---|---|---| +| `save_mu_method` | `"complete"` (new arg) | opt-in to the 1D modes | +| `prior_variance_scope` | `"per_outcome"` | benchmark covers per_scale and per_scale_normal but does not motivate a switch | +| `wavelet_qnorm` | `FALSE` | rebenchmark with `TRUE` per scenario; results memo discusses | +| `mixture_null_weight` | `NULL` (resolves to 0.05) | per_scale + 0.05 is the only well-calibrated `per_scale` cell across scenarios | +| `null_prior_init` | `0` | only an init; the EM M-step overwrites within a few iterations | +| `small_sample_correction`| `FALSE` | sensitivity only; see issue #8 | +| `L`, `L_greedy`, `greedy_lbf_cutoff` | `20`, `5`, `0.1` | unchanged | + +## Three-layer warm start (terminology pin) + +1. mixsqp internal warm start: on by default + (`be2722e perf(ibss): mixsqp warm start + ser_cache`). +2. `L_greedy` ramp 5 -> L = 20: on by default. +3. operational warm start via `model_init` (cheap fit -> expensive + refit): works only with `save_mu_method = "complete"`. Both 1D + modes drop the per-variant axis the SER step needs at iter 0; + the guard in `R/save_mu_method.R::mf_apply_save_mu_method` + stop()s when a thinned fit is supplied as `model_init`. + +## susieR 0.16.1 compat (PR-1 commit fix(track)) + +susieR 0.16.1 added `is_compact_track_snapshots(fit$trace)` inside +`ibss_finalize -> make_susie_track_history`. The validator +requires each tracking list element to be +`list(alpha = data.frame, effect = data.frame, iteration = data.frame)`. +The old `track_ibss_fit.mf_individual` wrote +`list(alpha = matrix, sigma2 = list, pi_V = list, elbo = scalar)`, +which the new validator rejects. Symptom on `track_fit = TRUE`: +"fit$trace is not a compact SuSiE track" against susieR >= 0.16.1. + +Fix: `track_ibss_fit.mf_individual` delegates to +`susieR:::make_track_snapshot(model, iteration)` (cached as a +package-level binding via `R/zzz.R::.onLoad`). `model$sigma2` is +sanitised to `NA_real_` for the snapshot copy because mfsusieR's +`sigma2` is `list[M]` whereas `susieR:::track_scalar` assumes a +length-1 numeric. The real per-iteration `sigma2` stays on the +fit; only the trace records `NA`. `fit$elbo` is unaffected. + +Pre-fix `tests/testthat/test_ibss_methods_branches.R:35:3` failed +on both `main` and `fix-mu-storage` against susieR 0.16.1. Post-fix +the test passes and the full suite is green. + +## Vignette / test parameter audit (2026-05-03) + +Confirmed all vignette and test code uses current public-API +parameter names (`mixture_null_weight`, `prior_variance_scope`, +`wavelet_qnorm`, `wavelet_magnitude_cutoff`, `null_prior_init`, +`alpha_thin_eps`), not legacy names from `mvf.susie.alpha` / +`fsusieR` (`null_weight`, `null_prior_weight`, `max_SNP_EM`, +`low_count_filter`, `cor_small`, `maxit`, `cov_lev`, `min_purity`, +`init_pi0_w`, `posthoc`). + +Findings: +- `vignettes/post_processing.Rmd:269-276` uses `nullweight = 300`. + This is `ashr::ash()`'s own `nullweight` argument forwarded + through `mf_post_smooth(..., method = "ash", nullweight = ...)`'s + `...`, not a mfsusieR null-prior knob. Action: tighten the + surrounding prose to disambiguate from `mfsusie()`'s + `mixture_null_weight` (PR-1 ships this prose). +- Legacy names in `test_fsusier_degeneracy.R`, + `test_mvf_alpha_degeneracy.R`, `test_cs_parity_fsusier.R` + (`backfit`, `maxit`) are arguments of the apple-to-apple + comparison targets (`mvf.susie.alpha::multfsusie`, + `fsusieR::susiF`). These must NOT be renamed; the equivalence + contract requires calling the upstream signatures literally. +- `test_public_api_naming.R:15-25` already lists + `max_SNP_EM`, `max_scale`, `init_pi0_w`, `min_purity` etc as + forbidden on the mfsusieR public API. +- `test_per_scale_normal_degeneracy.R:684-706` exercises + `mixture_null_weight` correctly and validates the warning + emitted under `prior_variance_scope = "per_scale_normal"`. + +No code changes to vignettes or tests for parameter names beyond +the PR-1 prose tightening. + +## Status + +- [x] Pull latest mfsusieR main (at `fa53ad2`) +- [x] Verify `mixsqp_null_penalty` is not in current main +- [x] Verify William's `fitted_func` storage pattern in + `mvf.susie.alpha` +- [x] Update susieR from GitHub master in local R lib + (0.16.1, commit `220a191`) +- [x] Confirm `r-ebnm` in pixi env (1.0.55) +- [x] PR-1 branch `fix-mu-storage`, full implementation, full test + suite green (0 fail / 0 regression) +- [x] PR-1 6-cell baseline benchmark + heavy-tailed + null + follow-up benchmarks complete (results memo separate) diff --git a/inst/notes/sessions/2026-05-03-2314-per-scale-normal-baseline-results.md b/inst/notes/sessions/2026-05-03-2314-per-scale-normal-baseline-results.md new file mode 100644 index 0000000..b0bec83 --- /dev/null +++ b/inst/notes/sessions/2026-05-03-2314-per-scale-normal-baseline-results.md @@ -0,0 +1,294 @@ +# 2026-05-03 PR-1 prior-grid benchmark results + +Date: 2026-05-03 / 2026-05-04 +Scope: per-cell FDR / power / runtime / convergence for the six prior +configurations Gao listed on 2026-05-03 Slack +(`prior_variance_scope` x `wavelet_qnorm` x `mixture_null_weight`), +across three Y scenarios (Gaussian baseline, heavy-tailed signal, +null no signal). Three discovery metrics are reported in parallel. + +## Setup + +| field | value | +|---|---| +| `n` (samples) | 84 | +| `p` (variants) | 500 | +| `M` (outcomes) | 2 | +| `T_basis[m]` | 64 | +| `L` | 10 | +| `save_mu_method` | `"alpha_collapsed"` (PR-1 feature) | +| true causal indices | 50, 220, 380 | +| effect curve | box function on positions 20-40 | +| Gaussian-noise sd | 1.0 | +| heavy-tail outlier rate / sd | 18% per entry, sd = 4 | +| n_rep per cell | 5 | +| PIP loose threshold | 0.05 | +| PIP high (hybrid) threshold | 0.5 | +| CS purity threshold (hybrid) | min.abs.corr >= 0.8 | +| LD threshold (CS-level TP rule) | abs cor >= 0.5 | + +mfsusieR built from branch `fix-mu-storage`. susieR 0.16.1 from +GitHub master, ebnm 1.0.55. + +Drivers: +- `inst/bench/profiling/benchmark_per_scale_normal_6grid.R` + (Gaussian baseline, 30 fits) +- `inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R` + (heavy-tailed + null follow-up, 60 fits) + +SLURM accounting: +- Baseline job `31993466`, 33 min 41 s wall-clock, peak 0.43 GB, + exit 0. +- Heavy-tailed + null job `31993467`, 1 h 0 m 20 s wall-clock, + peak 0.43 GB, exit 0. + +Per-replicate raw data (untracked, machine-local): +- `inst/bench/profiling/results/per_scale_normal_6grid_20260504_0713.rds` +- `inst/bench/profiling/results/heavy_tailed_null_6grid_20260504_0740.rds` + +Each rds row carries the metric columns plus four list-columns +(`fit_pip`, `fit_cs`, `fit_purity`, `fit_true_idx`) + `rep_seed` +so future metrics can be computed retroactively without re-fitting. + +## Metric definitions + +Three discovery metrics are computed per (scenario, cell, rep) and +reported in parallel. They differ in what counts as one "discovery" +and which discoveries are deemed correct. + +| Metric | Discovery rule (what counts as one) | TP rule | Use case | +|---|---|---|---| +| **SNP loose** (`fdr` / `power`) | Every SNP with `pip > 0.05` is one discovery, regardless of CS membership. | `j` is a TP iff `j` is in the causal index set. | A sensitive type-I-error proxy. Fires on any low-PIP leakage that crosses 0.05. | +| **CS-level** (`fdr_cs` / `power_cs`) | Every credible set is one discovery, taking the CS lead = SNP within the CS with max PIP. | Lead is TP iff `lead` is causal OR `abs(cor(X[, lead], X[, causal])) >= 0.5` for any causal. Power is fraction of unique causals covered by some TP CS. | The fine-mapping practice view, matching `inst/bench/profiling/per_scale_normal_vignette_sweep.R` and `vignettes/fsusie_intro.Rmd:299-303`. | +| **SNP hybrid** (`fdr_hyb` / `power_hyb`) | A SNP `j` is one discovery iff (`j` is in some CS with `min.abs.corr >= 0.8`) OR (`j` is not in any CS AND `pip[j] > 0.5`). | `j` is a TP iff `j` is in the causal index set. | A SuSiE-conventional fine-mapping output: trust high-purity CSes (every member of a high-purity CS counts), and outside CSes only credit standalone SNPs at high PIP. | + +The three metrics answer different questions on the same fits, so +their FDR / power numbers can disagree. Where they agree, the cell +is robust. Where they disagree, the difference itself is a finding. + +In `null_no_signal` the causal set is empty: SNP-loose FDR / power +are undefined (`NaN`), CS-level and hybrid FDR equal 1 whenever +their respective discovery sets are non-empty (every discovery is +false), and `cs_count` directly counts spurious credible sets. + +## Per-cell results (means over 5 replicates) + +### Gaussian baseline + +| cell | scope | qnorm | mnw | FDR loose | FDR cs | FDR hyb | cs_count | runtime (s) | conv. | +|---|---|---|---|---|---|---|---|---|---| +| 1 | per_scale | F | 0.05 | 0.050 | 0.050 | 0.050 | 3.2 | 10.6 | 5/5 | +| 2 | per_scale | T | 0.05 | 0.050 | 0.050 | 0.050 | 3.2 | 9.6 | 5/5 | +| 3 | per_scale | F | 0 | 0.766 | 0.408 | 0.628 | 5.8 | 176.7 | 4/5 | +| 4 | per_scale | T | 0 | 0.775 | 0.479 | 0.613 | 6.0 | 178.4 | 3/5 | +| 5 | per_scale_normal | F | (n/a) | 0.409 | **0.000** | **0.000** | 3.0 | 13.4 | 5/5 | +| 6 | per_scale_normal | T | (n/a) | 0.479 | **0.000** | **0.000** | 3.0 | 13.7 | 5/5 | + +Power = 1.000 in every cell across every rep (all three causal +SNPs are recovered every time). + +### Heavy-tailed signal + +| cell | scope | qnorm | mnw | FDR loose | FDR cs | FDR hyb | cs_count | runtime (s) | +|---|---|---|---|---|---|---|---|---| +| 1 | per_scale | F | 0.05 | 0.150 | 0.050 | 0.100 | 3.2 | 15.5 | +| 2 | per_scale | T | 0.05 | 0.100 | 0.000 | 0.100 | 3.0 | 12.5 | +| 3 | per_scale | F | 0 | 0.709 | 0.553 | 0.654 | 7.0 | 168.2 | +| 4 | per_scale | T | 0 | 0.817 | 0.409 | 0.498 | 5.4 | 238.3 | +| 5 | per_scale_normal | F | (n/a) | 0.080 | **0.000** | **0.000** | 3.0 | 10.8 | +| 6 | per_scale_normal | T | (n/a) | **0.000** | **0.000** | **0.000** | 3.0 | 8.9 | + +Power = 1.000 in every cell. Convergence: cells 3-4 fail to +converge in 1-2 of 5 reps each. + +### Null no signal (SNP-loose / power undefined; cs_count is the +type-I count of spurious CSes) + +| cell | scope | qnorm | mnw | FDR cs | FDR hyb | cs_count | runtime (s) | +|---|---|---|---|---|---|---|---| +| 1 | per_scale | F | 0.05 | 0.200 | 0.200 | 0.2 | 20.3 | +| 2 | per_scale | T | 0.05 | 0.400 | 0.400 | 0.4 | 15.5 | +| 3 | per_scale | F | 0 | 1.000 | 1.000 | **4.2** | 93.9 | +| 4 | per_scale | T | 0 | 1.000 | 1.000 | **4.2** | 123.9 | +| 5 | per_scale_normal | F | (n/a) | **0.000** | **0.000** | **0.0** | 6.7 | +| 6 | per_scale_normal | T | (n/a) | **0.000** | **0.000** | **0.0** | 6.9 | + +## Findings + +0. **Power is at the ceiling in both signal scenarios.** All twelve + signal cells (Gaussian + heavy-tailed) report `power = 1.000` + on every rep under all three metrics; every cell recovers all + three causal SNPs. Power is therefore not a discriminating + axis in this benchmark; FDR / cs_count / runtime / convergence + carry the between-cell signal. In `null_no_signal` power is + undefined (no causal); the substitute axes are CS-level / hybrid + FDR (= 1 whenever any spurious discovery fires) and `cs_count`. + +1. **`per_scale_normal` is the only cell that is FDR = 0 under both + CS-level and hybrid metrics across all three scenarios.** Across + the 30 reps spanning Gaussian / heavy-tailed / null, the + CS-level and hybrid FDR are exactly 0.000 in every cell; + `cs_count` is exactly 3 (matching the truth) in every signal + rep and exactly 0 in every null rep. The SNP-loose metric + inflates to 0.41-0.48 on Gaussian baseline because the cell + leaves a few non-causal SNPs at PIP just above 0.05 (outside + any credible set), but those leaks are not picked up by either + the CS-level or hybrid view. + +2. **`per_scale + mixture_null_weight = 0.05` is well-calibrated + under all three metrics across all three scenarios but with + small slippage on heavy-tailed and null.** Gaussian: FDR loose + / cs / hyb all 0.050. Heavy-tailed: FDR loose 0.10-0.15, + FDR cs 0.00-0.05, FDR hyb 0.10. Null: spurious CSes fire on + ~20-40% of reps (`cs_count` mean 0.2-0.4); FDR_cs / FDR_hyb + inflate to 0.20-0.40 on those reps. A safe second choice when + ebnm's distributional assumptions are uncertain. + +3. **`per_scale + mixture_null_weight = 0` is broken under every + metric in every scenario.** SNP-loose FDR 0.71-0.82, CS-level + 0.41-0.55, hybrid 0.50-0.65. `cs_count` averages 4.2 spurious + CSes on pure null. Runtime 17-30x the mnw = 0.05 cells. 1-2 of + 5 reps fail to converge in three of the four cells. Justifies + the warning-hint planned for PR-2 when mnw = 0 is passed + explicitly. + +4. **`wavelet_qnorm = TRUE` adds modest improvement on heavy-tailed + Y under both SNP metrics.** Within `per_scale + mnw = 0.05`, + FDR loose drops 0.150 -> 0.100, FDR cs 0.050 -> 0.000. + Within `per_scale_normal`, FDR loose drops 0.080 -> 0.000. + Effect on Gaussian baseline and on null is within rep-to-rep + variance. + +## Implications for PR-1 / defaults + +- **Keep `mixture_null_weight = NULL` (resolves to 0.05) as the + default of `per_scale`.** Across all three scenarios this is + the only `per_scale` setting that does not collapse under any + metric. The slight slippage on heavy-tailed / null is small + enough that switching defaults is not justified by this benchmark. + +- **`prior_variance_scope = "per_scale_normal"` is the + best-calibrated cell in this benchmark under CS-level and + hybrid metrics.** Gaussian / heavy-tailed / null all give + FDR_cs = FDR_hyb = 0 with `cs_count` matching the truth. + The SNP-loose 0.41-0.48 on Gaussian is the only blemish, and + it reflects sub-CS PIP leakage that conventional fine-mapping + output does not act on. + + PR-1 does not change the package default + `prior_variance_scope = "per_outcome"`. Switching the default + is deferred to a later PR after the real-data perm grid + finishes; the change there will be informed by both the + CS / hybrid view (fine-mapping practice) and the SNP-loose + view (sensitive type-I proxy). The `?mfsusie` and + `vignettes/fsusie_intro.Rmd` text should describe + per_scale_normal as the recommended choice for non-Gaussian / + real-data fits, with the caveat that SNP-loose PIP curves are + looser than the per_scale + mnw = 0.05 baseline. + +- **Document the `mixture_null_weight = 0` cliff.** Setting + `mixture_null_weight = 0` should emit a `warning_message(style + = "hint")` in `mfsusie()` referencing the 17-30x runtime, + the FDR collapse under all three metrics, and the 4.2 false + credible sets observed on pure null. Tracked as PR-2. + +- **`save_mu_method = "alpha_collapsed"` works in all three + scenarios.** All 90 fits across baseline + heavy-tailed + null + saved cleanly; `fit_size_mb` 0.40-0.65 MB, no anomaly. + +## Caveats + +- **5 replicates per cell is light** for FDR estimation. The + mnw = 0.05 columns are tight (3-4 false discoveries / 60 + reps = 0.05-0.07); the mnw = 0 and per_scale_normal columns + have rep-to-rep variance of +/- 0.05-0.10 on FDR. 20-50 reps + would tighten the per-cell point estimates. The list-column + layout in the rds means a 20-rep rerun can resume seeds 101-105 + / 201-205 (used here) and add seeds 106-125 / 206-225 etc + without recomputing the existing 5 reps. +- **Sims are all `p = 500`, `n = 84`, `M = 2`, `T = 64`, `L = 10`.** + Translation to real ATAC perm regions (`p = 1-4k`, `T = 1024-10240`, + `M = 6`, `L = 20`) is not literal; the real-data perm grid + (`output/new_package/perm/mfsusieR_20260503_*` directories) + is the proper-scale validation. +- **Hybrid SNP-level counts every SNP in a high-purity CS as a + separate judgment.** A 5-member CS containing one causal + contributes 1 TP + 4 FP under this rule; CS-level instead + treats the CS as a single judgment. A reader who interprets + CS membership as "the causal is in this set" rather than + "every member is causal" should weight the CS-level numbers + more than the hybrid numbers. + +## save_mu_method storage MWE (out-of-band, 2026-05-04) + +A small reproducer outside the 6-grid: same simulator as the +Gaussian baseline (`n = 84`, `p = 500`, `T = 64`, `M = 2`, +`L = 5`, three causals at indices 50 / 220 / 380), three fits +generated with each save_mu_method, then `saveRDS(..., +compress = "xz")` to a temp directory and re-read. + +| mode | `object.size()` | `.rds` (xz) | shrink vs complete | +|---|---|---|---| +| `complete` | 7.8 MB | 2950 KB | (baseline) | +| `alpha_collapsed` | 0.50 MB | 230 KB | 15.7x in-mem, 12.9x on-disk | +| `lead` | 0.49 MB | 226 KB | 16.1x in-mem, 13.1x on-disk | + +The shrink ratio is below the theoretical `p = 500` because +non-mu fields (`alpha`, `lbf_variable`, `fitted`, `dwt_meta`, +the `smoothed` slot when populated, ...) are unchanged across +modes. At larger `p` the ratio approaches `p`. + +Structural verification (per (effect, outcome) slot): + +``` +fit$mu[[1]][[1]]: + complete num [1:500, 1:64] <- p x T_basis + alpha_collapsed num [1, 1:64] <- alpha %*% mu + lead num [1, 1:64] <- mu[j*, ] + +fit$coef_wavelet[[1]][[1]] (alpha_collapsed only): + num [1, 1:64] <- alpha %*% (mu / csd_X) + +fit$top_index (lead only): + [1] 220 380 50 1 1 <- j* = which.max(alpha[l, ]) + verified equal to apply(fit$alpha, 1, which.max) + +attr(fit, "save_mu_method"): + complete -> "complete" + alpha_collapsed -> "alpha_collapsed" + lead -> "lead" +``` + +`coef.mfsusie()` numerical equivalence (5 effects x 64 positions): + +``` +complete vs alpha_collapsed: max abs diff = 0.000 (lossless) +complete vs lead: max abs diff = 0.108 (cheap-coef bias) +``` + +The vignette `vignettes/post_processing.Rmd` "Inspecting the +structure" / "save_mu_method storage MWE" chunks reproduce these +numbers at vignette build (smaller `p = 150`, T = 64; the same +shape pattern, smaller absolute sizes). + +## Open follow-ups (not in PR-1) + +- **PR-2**: emit hint when `mixture_null_weight = 0` is passed, + plus silent-error defense in the multfsusie-paper R driver + (surfaced during 2026-05-03 perm grid debugging). +- **PR-4**: SSC math verification (issue #8) and a SSC = + {FALSE, TRUE} sweep on the same six-cell grid. +- **20-rep rerun once PR-1 lands and the perm grid frees the + cluster.** Documenting the full grid (`per_outcome` axis + included) before any default switch. + +## Files / references + +- Baseline benchmark driver: `inst/bench/profiling/benchmark_per_scale_normal_6grid.R` +- Heavy-tailed + null driver: `inst/bench/profiling/benchmark_heavy_tailed_null_6grid.R` +- Per-replicate rds (untracked, local): + - `inst/bench/profiling/results/per_scale_normal_6grid_20260504_0713.rds` + - `inst/bench/profiling/results/heavy_tailed_null_6grid_20260504_0740.rds` +- Plan memo: `inst/notes/sessions/2026-05-03-2048-mu-storage-and-benchmark-plan.md` +- Real-data perm grid README: + `/home/anjing.liu/mydata/anjing.liu/project/mfsusie/multfsusie-paper/output/new_package/perm/README_20260503_perm_grid.md` diff --git a/inst/notes/sessions/2026-05-10-fdr-code-scan.md b/inst/notes/sessions/2026-05-10-fdr-code-scan.md new file mode 100644 index 0000000..98cf832 --- /dev/null +++ b/inst/notes/sessions/2026-05-10-fdr-code-scan.md @@ -0,0 +1,300 @@ +# FDR inflation: investigation and diagnosis +# 2026-05-09 to 2026-05-20 + +--- + +## 1. Observed fact + +Permutation data (5 real ATAC-seq regions, 1024-bin, `wavelet_qnorm = TRUE`): + +| version | type | n regions | HP CS | perm DR | +|---------|------|-----------|-------|---------| +| 1e1a866 (no NA fix) | perm | 168 | 8 | 4.8% | +| be0ce136 (NA fix, 0.0.1) | perm | 153 | 1 | 0.7% | +| 0.0.2 HEAD | perm | 168 | 92 | **54.8%** | +| 1e1a866 | real | 168 | 14 | DR 8.3% | +| be0ce136 (0.0.1) | real | 168 | 0 | DR 0% | + +The critical gap: 0.7% → 54.8% (78×) in perm DR between be0ce136 and 0.0.2. + +Already ruled out before 2026-05-09: +- `L_greedy = NULL` → same 54.8% perm DR; L_greedy is not the cause +- `mixture_null_weight = 0.1` alone → perm DR still 0.5 + +--- + +## 2. Complete algorithmic delta: be0ce136 → HEAD (0.0.2) + +### A. be2722e — mixsqp warm start + +**Before**: cold start `x0 = c(init_pi0_w, rep(1e-6, K-1))`, `convtol.sqp=1e-8`, +`numiter.em=20`, `tol.svd` default. + +**After**: warm start `x0 = pi_prev` (previous IBSS iter's π), `convtol.sqp=1e-6`, +`numiter.em=10`, `tol.svd=0` (skip SVD). + +**Mechanism**: residual non-null π from iter t-1 carries into iter t; looser tolerance +may not converge back to null. + +### B. 6728cd3 — mixture_null_weight default 0.1 → 0.05 (×M scaling preserved) + +**Before**: effective penalty = 0.1 × M = 0.6 for M=6. +**After**: effective penalty = 0.05 × M = 0.3 for M=6. +Less null regularization → easier for mixsqp to place weight on non-null components. + +### C. New architecture — `fitted_g_per_effect` + inner EM loop + +**Before**: zero inner EM; π updated once per (l, outer_iter); no per-effect storage; +π state discarded between outer iterations. + +**After**: +- `fitted_g_per_effect[[l]]` persists each effect's π across outer iterations; + `pre_loglik_prior_hook` restores effect l's prior before each loglik call. +- `post_loglik_prior_hook` runs up to `inner_cap = max_inner_em_steps + 1 = 6` cycles + of {M-step → loglik → moments → KL} per (effect, outer_iter). + +Execution structure in HEAD: +``` +outer_iter → for l in 1..L: { + restore π from fitted_g_per_effect[[l]] (warm) + loglik → moments → KL + inner loop × 6: {M-step(warm) → loglik → moments → KL} + save π to fitted_g_per_effect[[l]] +} +``` + +This is the structurally largest change. + +### D. dc5515e — cpp → pure-R likelihood kernel + +Floating-point differences < 1e-12; negligible for FDR. + +### E. trim_null_effects now active + +Should reduce FDR by pruning low-V effects, but V[l] stays non-zero when warm start +keeps π non-null, so the effect is neutralized by changes A–C. + +--- + +## 3. Mathematical argument: why inner EM can inflate FDR + +SuSiE IBSS (Wang et al. 2020) is coordinate ascent on `ELBO(q, g)`: +- E-step (outer): fix g, compute posterior → alpha[l,] +- M-step (outer): fix alpha, update g = argmax_g ELBO + +Monotone convergence holds because each step does not decrease the ELBO. + +**The inner EM breaks this guarantee.** After the outer loglik fixes alpha[l,], the +inner loop re-runs the E-step (loglik → alpha) inside the same outer iteration. +These inner E-steps are not accounted for in the outer ELBO. + +Under null data the feedback amplifies noise: uniform alpha[l,] has finite-sample +variation ε_j → inner M-step produces slightly non-null g → inner E-step concentrates +alpha further → 6 cycles can produce detectable CSes from pure noise. + +Under signal the inner loop converges quickly to the true effect (self-limiting). +This null-vs-signal asymmetry is the mechanism. + +The warm start (change A) compounds this: `fitted_g_per_effect[[l]]` carries the +inner-loop-amplified non-null π from outer iter t to t+1, so at t+1 even the +first inner M-step starts from a non-null distribution. + +--- + +## 4. Issue 1 (primary): `mf_quantile_normalize` discards NA structure + +**File**: `R/utils_wavelet.R` + +**Root cause**: `rank(Y_wd[, j], ties.method = "random")` called without +`na.last = "keep"`. R assigns NA entries the largest ranks (acts like `na.last = TRUE`). + +```r +rank(c(1.2, 0.5, NA, 2.1, NA, -0.3), ties.method = "random") +# [1] 3 2 5 4 6 1 <- NAs get ranks 5, 6; not preserved as NA + +rank(c(1.2, 0.5, NA, 2.1, NA, -0.3), ties.method = "random", na.last = "keep") +# [1] 3 2 NA 4 NA 1 <- correct +``` + +**Effect**: NA rows steal the top `n_na` quantiles; complete-row wavelet coefficients +follow a right-truncated distribution with variance below 1. `sigma2` converges to +this deflated variance throughout IBSS. + +Estimated sigma2 deflation (region 4): + +| Outcome | n_cc | n_na | Estimated deflation | +|---------|------|------|---------------------| +| Ast | 71 | 13 | ~35% | +| Exc | 60 | 24 | ~48% | +| Inh | 70 | 14 | ~36% | +| Mic | 75 | 9 | ~28% | +| Oli | 73 | 11 | ~32% | +| OPC | 83 | 1 | ~6% | + +**Chain to FDR**: deflated sigma2 → deflated shat2 → inflated Bhat/Shat → inflated +Bayes factors → alpha concentrates on LD-block SNPs → spurious high-purity CSes. +Plausible mechanism; not tested in isolation with X and LD structure held fixed. + +**Status**: **fixed** in commit 54a10a5 (2026-05-20). `rank()` now applied only to +non-NA entries; NA positions preserved. + +Simulation v2 (na_frac factor, job 33180369) confirms: sigma2 with 20% NA rows +recovers from ~0.65 (unfixed) to ~0.90 (fixed) under null/normal conditions. + +--- + +## 5. Issue 2 (secondary): `get_var_y` includes corrupted NA rows + +**File**: `R/ibss_methods.R` + +After Issue 1, NA rows have large finite values rather than NA, so `na.rm = TRUE` +does not exclude them. The initial sigma2 may be inflated (opposite direction from +Issue 1's converged effect). Effect limited to iteration 1; `update_variance_components` +uses `na_idx` and overrides from iteration 2 onward. + +**Status**: resolved as a side-effect of Issue 1 fix; once NA rows stay NA through +qnorm, `na.rm = TRUE` correctly excludes them. + +--- + +## 6. Perm experiment: `fitted_g_per_effect` association + +SLURM job 33175249 (N_BINS=1024, 5 regions, permuted real data) compared two conditions +with all other settings fixed (`wavelet_qnorm = TRUE` in both): + +| Condition | `cross_iter_prior` | Description | +|---|---|---| +| π-persist | `TRUE` | π warm-started per effect across IBSS outer iterations | +| π-reset | `FALSE` | π re-estimated from shared G_prior each iteration | + +Per-region HP CS counts (purity ≥ 0.8, permuted data): + +| region | 64-bin π-persist | 64-bin π-reset | 1024-bin π-persist | 1024-bin π-reset | +|--------|:---:|:---:|:---:|:---:| +| 4 | 1 | 0 | 3 | 0 | +| 5 | 0 | 0 | 1 | 0 | +| 6 | 3 | 4 | 3 | 0 | +| 11 | 0 | 0 | 1 | 0 | +| 14 | 1 | 0 | 2 | 0 | +| **total** | **5** | **4** | **10** | **0** | + +At 1024 bins, π-persist produces 10 HP CS vs 0 for π-reset. Issue 1 (qnorm NA bug) +is active equally in both conditions, so the contrast isolates `fitted_g_per_effect`. +At 64 bins the difference is small (5 vs 4); region 6 fires under both conditions, +suggesting a region-specific pattern independent of prior persistence. + +Cold/warm mixsqp start and inner EM steps (conditions A–D in the interactive test +plan) made no detectable difference at 64 bins. + +**Mechanism**: `fitted_g_per_effect[[l]][[m]][[s]]` stores the fitted π for effect l, +outcome m, scale group s across IBSS outer iterations. Before each SER step, +`pre_loglik_prior_hook` restores effect l's π into `G_prior`. This is structurally +identical to `mvf.susie.alpha`'s `est_pi[[l]]` mechanism. + +At 1024 bins (10 wavelet scales) each effect has 10 × K mixture parameters that can +adapt per-effect. Real ATAC-seq wavelet coefficients may have non-spherical cross-scale +covariance; per-effect π persistence may fit that structure for a permuted variant, +producing a spurious high-purity CS. At 64 bins (6 scales) the same mechanism has +less leverage. + +`cross_iter_prior = FALSE` (π-reset) is closer to the standard IBSS derivation +(Wang et al. 2020), which assumes a shared prior across all effects. The parameter +is now explicitly exposed in `mfsusie()` (default `TRUE` for backward compatibility). + +**Bug note (2026-05-20)**: `.opv_mixsqp` and `.opv_ebnm_point` wrote to +`model$fitted_g_per_effect[[l]][[m]][[s]]` unconditionally, even when +`cross_iter_prior = FALSE` (where `fitted_g_per_effect` starts as NULL). R's +assignment to NULL creates partial list structure; `pre_loglik_prior_hook` then +crashed on the second effect. Fixed in commit 37d6723 with `!is.null()` guards. +All `cross_iter_prior = FALSE` tasks in simulation v2 failed; resubmitted as v2b +(job 33180369). + +--- + +## 7. Why simulation does not reproduce the inflation + +1. **No LD**: random binomial X has no LD; the purity filter (mean.abs.corr ≥ 0.8) + requires correlated variants in a CS. Without LD, no CS can pass purity regardless + of BF inflation. +2. **No NA rows**: simulated Y has no missingness, so Issue 1 does not activate. +3. **i.i.d. noise per entry**: skewed and lowcount Y have non-Gaussian marginals but + independent entries. If the real mechanism requires non-spherical cross-scale + wavelet covariance (as hypothesized in Section 6), i.i.d. simulation would not + generate this structure. + +--- + +## 8. Simulation v1 design (480 tasks, SLURM job 32610344) + +Fixed parameters: n=80, p=200, T=64, M=2, L=10, true causal index=50, +effect amplitude=3.0 on positions 20-40, `residual_variance_scope = "per_outcome"`, +`mixture_null_weight = 0.1`, 5 reps per cell. + +Variable factors (6): + +| factor | levels | +|---|---| +| `y_dist` | normal, skewed (20% outlier, sd=4), lowcount (NB log1p) | +| `signal` | signal, null | +| `wavelet_qnorm` | TRUE, FALSE | +| `prior_variance_scope` | per_outcome, per_scale_normal (per_scale not included) | +| `cross_iter_prior` | ON, OFF | +| `em_start` | warm (NULL), cold (convtol.sqp=1e-8, numiter.em=20, tol.svd=1e-10) | + +Total: 3×2×2×2×2×2 = 96 cells × 5 reps = 480 tasks. + +Results (null conditions, mean HP CS): + +| y_dist | scope | fitted_g | qnorm | mean hp_cs | +|--------|-------|----------|-------|:----------:| +| lowcount | per_scale_normal | ON | FALSE | **0.1** | +| all others | all | all | all | 0.0 | + +All 24 null cells give mean hp_cs = 0.0 except one marginal case (lowcount / +per_scale_normal / fitted_g=ON / qnorm=FALSE → single HP CS across 10 reps). +Consistent with Section 7: simulation lacks the LD and NA structure required +to reproduce real-data FDR inflation. + +Power: 1.0 across all signal conditions except one (skewed / per_outcome / +fitted_g=OFF → 0.9). + +--- + +## 9. Simulation v2 design (1440 tasks) + +Extends v1 by adding `na_frac` (0 / 0.10 / 0.20) as a 7th factor to verify sigma2 +recovery after the Issue 1 fix. + +Total: 3×2×2×2×2×2×3 = 288 cells × 5 reps = 1440 tasks. + +`na_frac` inserts whole NA rows independently per outcome (mimics samples with zero +cells of a given type). With na_frac=0.20 and the unfixed package, sigma2 mean ≈ 0.65 +(vs expected ≈ 0.90). After Issue 1 fix: sigma2 recovers to ≈ 0.90. + +v2 also first exposed the `cross_iter_prior = FALSE` crash (Section 6 bug note). +720 failed tasks resubmitted as v2b (job 33180369) with the fixed package. + +--- + +## 10. Status and next steps (as of 2026-05-20) + +| Issue | Status | +|---|---| +| Issue 1: qnorm NA bug | **Fixed** (commit 54a10a5) | +| Issue 2: get_var_y | **Resolved** as side-effect of Issue 1 fix | +| `cross_iter_prior=FALSE` crash | **Fixed** (commit 37d6723) | +| `cross_iter_prior` parameter exposed | **Done** (commit 54a10a5) | + +Remaining: + +| Action | Priority | +|---|---| +| Wait for v2b results; confirm sigma2 recovery across na_frac levels | High | +| Re-run 1024-bin perm with fixed qnorm package; quantify remaining FDR | High | +| Evaluate `cross_iter_prior=FALSE` power vs FDR tradeoff on real data | High | +| Decide default: keep `TRUE` (backward compat) or switch to `FALSE` | Pending | +| Apply same qnorm NA fix to `patched_mvf.susie.alpha` (`R/utils.R:256`) | Low | + +Driver: `inst/bench/profiling/fdr_sim_worker.R` +Results v1: `inst/bench/slurm/fdr_sim_results/task_XXXX.csv` +Results v2/v2b: `inst/bench/slurm/fdr_sim_v2_results/task_XXXX.csv` diff --git a/inst/notes/sessions/2026-05-11-mfsusier-vs-mvf-engineering-comparison.md b/inst/notes/sessions/2026-05-11-mfsusier-vs-mvf-engineering-comparison.md new file mode 100644 index 0000000..ad30a2e --- /dev/null +++ b/inst/notes/sessions/2026-05-11-mfsusier-vs-mvf-engineering-comparison.md @@ -0,0 +1,640 @@ +# Engineering comparison: mfsusieR HEAD vs mvf.susie.alpha + +Date: 2026-05-11 +Branch: fix-mu-storage +Companion MWE: `tests/testthat/test_mfsusier_vs_mvf_mwe.R` + +Every section is derived from reading all source files in both packages. +Files read: `mfsusieR` — mfsusie.R, individual_data_class.R, individual_data_methods.R, +ibss_methods.R, dwt.R, mfsusie_methods.R, prior_scale_mixture.R, em_helpers.R, +save_mu_method.R, model_class.R, zzz.R, utils_wavelet.R, posterior_mixture.R, +post_smooth_hmm.R, post_smooth_smash.R. +`mvf.susie.alpha` — multfsusie.R, EM.R, computational_routine.R, +operation_on_multfsusie_obj.R, operation_on_multfsusie_prior.R, +ELBO_mutlfsusie.R, utils_wavelet_transform.R, utils.R. + +--- + +## §1. Architecture + +### mfsusieR + +Entry point: `mfsusie()` (`R/mfsusie.R`). The function: +1. Calls `create_mf_individual()` to build the `mf_individual` data object + (wavelet-domain `D[[m]]` + metadata). +2. Calls `mf_prior_scale_mixture()` to build `G_prior` (per-outcome mixture-normal prior). +3. Assembles `params` (a named list matching what `susie_workhorse` expects). +4. Calls `susieR::susie_workhorse(data, params)`, which runs the IBSS loop, + dispatching every per-effect and per-iteration step to mfsusieR via S3 on + `mf_individual`. +5. Attaches `dwt_meta` (inverse-DWT metadata) and smoothing inputs. +6. Applies `save_mu_method` storage trim. + +S3 methods registered in `R/zzz.R` `.onLoad`: +- Per-effect: `compute_residuals`, `compute_ser_statistics`, `loglik`, + `calculate_posterior_moments`, `optimize_prior_variance`, + `update_fitted_values`, `compute_kl`, `SER_posterior_e_loglik`, + `pre_loglik_prior_hook`, `post_loglik_prior_hook`. +- Per-iteration: `update_variance_components`, `update_derived_quantities`, + `update_model_variance`, `Eloglik`, `get_objective`, `track_ibss_fit`, + `trim_null_effects`, `ibss_initialize`, `cleanup_extra_fields`. +- Finalize: `get_scale_factors`, `get_intercept`, `get_fitted`, + `get_variable_names`, `get_zscore`. + +The IBSS loop itself runs entirely inside `susie_workhorse`; mfsusieR adds +no loop code. + +### mvf.susie.alpha + +Entry point: `multfsusie()` (`R/multfsusie.R`). The function runs its own +`while (check > tol)` IBSS loop (lines 488-590): + +``` +while (check > tol && iter < maxit) { + for (l in 1:L) { + R_l <- cal_partial_resid(obj, l-1, X, Y, list_indx_lst) + bs <- cal_Bhat_Shat_multfsusie(R_l, X, obj, ind_analysis) + EM <- EM_pi_multsusie(bs, obj, G_prior, ...) # mixsqp M-step + obj <- update_multfsusie(obj, l, EM, bs, ...) # updates alpha, fitted_wc + } + obj <- greedy_backfit(obj, ...) + obj <- estimate_residual_variance(obj, Y, X, ind_analysis) + ELBO <- get_objective(obj, Y, X, ind_analysis) + check <- |ELBO[t] - ELBO[t-1]| / |ELBO[t]| (if convergence_method="elbo") +} +``` + +No S3 delegation. All functions called directly. + +--- + +## §2. Y input format + +| Package | Expected format | +|---------|----------------| +| mfsusieR | `list(Y_1, ..., Y_M)` — flat list of M matrices, each `n x T_m` | +| mvf | `list(Y_f = list(Y_1, ..., Y_M), Y_u = matrix n x K_u)` — separates functional (`Y_f`) and univariate (`Y_u`) modalities | + +mfsusieR supports scalar outcomes via `T_m = 1`: the DWT step is skipped and +the single column is treated as the wavelet-domain representation directly +(`individual_data_class.R:147`). Mixed ragged lists such as +`list(n x 1, n x 1, n x 64)` are valid inputs. There is no separate `Y_u` +channel; scalar outcomes share the same S3 code path as functional outcomes. + +mvf has a two-channel design: `Y_f` (functional, DWT applied) and `Y_u` +(univariate, no DWT). The `Y_u` path calls a dedicated `m_step_u` and stores +results separately in `fitted_u` / `fitted_u2` / `sigma2$sd_u`. The two +channels combine at the log-BF level inside `EM_pi_multsusie`. + +--- + +## §3. Wavelet preprocessing + +### mfsusieR (`R/dwt.R`, `R/utils_wavelet.R`) + +Per outcome `m`: +1. `remap_data(Y_m, pos_m)`: pads `T_m` to next power of 2. +2. `col_scale(Y_padded, center = TRUE, scale = TRUE)`: column-center + scale. + `column_center` and `column_scale` stored in `dwt_meta` for inverse. +3. `dwt_matrix()`: row-by-row `wd()` from `wavethresh`; packs `[D | C]` into an + `n x T_basis` matrix. +4. Optional `wavelet_standardize`: scales wavelet coefficients by their + across-row standard deviation. + When `wavelet_qnorm = TRUE` (default FALSE), applies rank-based quantile + normalization inside the wavelet domain column by column (`mf_quantile_normalize`). + NA bug present here: `rank(x, ties.method = "random")` without `na.last = "keep"` + assigns NA entries large positive ranks instead of NA, deflating sigma2 by + 28-48% on real data. See §10. +5. NA rows: `na_idx[[m]] <- which(complete.cases(Y[[m]]))`. Rows with any NA + are excluded from all residual computations for outcome `m`. + +`D[[m]]` (wavelet coefficients) stored as `n x T_basis[m]` on `mf_individual`. +`scale_index[[m]]` maps each wavelet scale to its column indices in packed D. + +### mvf (`R/utils_wavelet_transform.R`, `R/multfsusie.R`) + +Per outcome `k`: +1. `DWT2(Y_k)`: row-by-row `wd()`, stores `D` (n x T-1) and `C` (length n). + NA rows are imputed to 0 before DWT, then reinstated as NA after. Imputing + 0 changes wavelet coefficients in neighboring rows through filter support. +2. `pack_dwt(Y_k)`: `cbind(D, C)` — C in the last column. +3. Optional `quantile_trans = FALSE` (default): when TRUE applies quantile + normalization in POSITION space BEFORE the wavelet transform. This is different + from mfsusieR's `wavelet_qnorm` which operates in the wavelet domain. + +--- + +## §4. Bhat / Shat (D2 divergence) + +### mfsusieR (`R/individual_data_methods.R:mf_per_outcome_bhat_shat`) + +```r +bhat_m[j, t] <- XtR_m[j, t] / xtx[j] +shat2_m[j, t] <- sigma2_per_pos[t] / xtx[j] +``` + +where `xtx[j] = sum_i X_{ij}^2` and `sigma2_per_pos` is derived from the +global per-outcome sigma2. The same sigma2 is shared across all variables. + +### mvf (`R/computational_routine.R:cal_Bhat_Shat_multfsusie`) + +Calls `fsusieR:::cal_Bhat_Shat(R_l, X, v1, ...)` — fixed, no parameter to +select a different formula. The function computes the marginal per-variable +effect estimate from univariate regression of each wavelet column: + +``` +Bhat[j, t] = (X_j^T R_t) / ||X_j||^2 +Shat[j, t] = sqrt(Var(residual_t) / ||X_j||^2) # per-(j, t) marginal SE +``` + +This uses a per-(j, t) residual variance rather than a global sigma2. At a true +causal SNP `j*`, `||X_{j*}||^2` is large and the per-variable marginal SE is +smaller than mfsusieR's global-sigma2 SE, giving mvf higher log-BF at signals. + +This is NOT a bug in either package. mfsusieR's construction matches the SuSiE +variational derivation (posterior moments and likelihood use consistent sigma2); +mvf's construction follows fsusieR's marginal approach with potentially higher power +at signals but breaks the Gaussian-likelihood variational consistency property. + +--- + +## §5. Prior structure + +### mfsusieR (`R/prior_scale_mixture.R`) + +Four `prior_variance_scope` modes: + +| Mode | G_prior structure | M-step solver | +|------|------------------|--------------| +| `per_outcome` | One group per outcome, covers all T_basis columns | mixsqp (one solve per outcome per effect) | +| `per_scale` | One group per (outcome, wavelet scale) | mixsqp (S_m solves per outcome per effect) | +| `per_scale_normal` | One ebnm_point_normal per (outcome, scale) | `ebnm::ebnm_point_normal` | +| `per_scale_laplace` | One ebnm_point_laplace per (outcome, scale) | `ebnm::ebnm_point_laplace` | + +Grid init (mixsqp paths): marginal Bhat/Shat via `compute_marginal_bhat_shat`; +`sd_min = quantile(Shat, 0.1) / 10`, `sd_max = 2 * sqrt(max(Bhat^2 - Shat^2))`, +`gridmult = sqrt(2)` (following Stephens 2017 but with 10th-percentile lower end +to avoid degenerate tiny-grid components). + +### mvf (`R/operation_on_multfsusie_prior.R`) + +`init_prior_multfsusie()` calls: +- For functional modalities: `fsusieR:::init_prior.default()` which calls + `ashr::ash()` on marginal Bhat/Shat per outcome per scale, returning a + `mixture_normal_per_scale` ash object. +- For univariate modalities: `ashr::ash()` per column of `Y_u`. + +--- + +## §6. M-step (mixture-weight update) + +### mfsusieR (`R/em_helpers.R`, `R/individual_data_methods.R`) + +`mf_em_likelihood_per_scale()` builds the mixsqp `L` matrix from the `(bhat, shat)` +slice for a (outcome, scale, keep_idx) rectangle, plus a `(100, 0, ..., 0)` penalty row. + +`mf_em_m_step_per_scale()` calls `mixsqp`: +- Weight vector: `w = c(mixture_null_weight * idx_size, rep(zeta_keep, idx_size))`. +- `mixture_null_weight` scaled by M inside `.opv_mixsqp`: `mnw * max(1, M)`. +- Warm-started from `model$G_prior[[m]][[s]]$fitted_g$pi` (prior iter pi). + `control_mixsqp = NULL` (default) uses warm-start with fast defaults. + Cold-start equivalent: `list(convtol.sqp = 1e-8, numiter.em = 20, tol.svd = 1e-10)`. + +`max_SNP_EM` does NOT exist in mfsusieR; all p variables enter the M-step. + +### mvf (`R/EM.R`) + +`EM_pi_multsusie()`: for functional modalities, calls `fsusieR::m_step` per scale; +for univariate, calls `m_step_u`. Both use cold start every outer iteration: +```r +x0 <- c(init_pi0_w = 0.9, rep(1e-12, K - 1)) +``` + +`nullweight` in mvf is divided by K (number of modalities) inside `EM_pi_multsusie`: +`nullweight_m = nullweight / K`. + +`max_SNP_EM = 100` (default): before the M-step, limits the input to the top-100 +SNPs by log-BF. This is a computational shortcut with no mfsusieR equivalent; it +means the mixture weights are learned from at most 100 data points per (effect, scale). + +--- + +## §7. Inner EM loop (mfsusieR only) + +`post_loglik_prior_hook.mf_individual` (`R/individual_data_methods.R:672`): + +``` +inner_cap = max(0, max_inner_em_steps) + 1 # default max_inner_em_steps=5 -> cap=6 +for k in 1..inner_cap: + M-step: opv_fn(...) # updates G_prior + fitted_g_per_effect[[l]] + if k == inner_cap: break + re-run loglik / moments / KL to update alpha[l, ] against new pi + if |lbf[l] - lbf_prev| < inner_tol: break +``` + +After `inner_cap` M-step cycles, the per-effect `(alpha[l, ], pi_V[[l]])` end +in lockstep within one outer IBSS iteration. + +`max_inner_em_steps = 0` (one M-step, no re-evaluation) approximates mvf's +one-step behavior. `max_inner_em_steps = 5` (default) runs up to 6 cycles. + +The inner loop breaks the IBSS ELBO monotone guarantee for the outer loop. +A coherent ELBO is restored at iter end via `refresh_lbf_kl.mf_individual` +(inside `get_objective.mfsusie`). + +mvf has no inner EM loop; it runs one M-step per effect per outer iteration. + +--- + +## §8. Residual variance update (D1 divergence) + +### mfsusieR (`R/individual_data_methods.R:400-421`) + +`mf_get_ER2_per_position(data, model, m)`: +```r +res_m <- (D[[m]] - fitted[[m]])[na_idx[[m]], ] +rss_t <- colSums(res_m^2) +bias_t <- sum over l of: + colSums((alpha_l * pw) * mu2_l_m) # pw = xtx_diag_list[[m]], length p + - colSums((X %*% (alpha_l * mu_lm))^2) +sigma2_m <- sum(rss_t + bias_t) / (n_m * T_basis[m]) # per_outcome +``` + +The bias correction uses `predictor_weights = xtx_diag` in `colSums((alpha_l * pw) * mu2)`. +This is the correct derivation. + +### mvf (`R/operation_on_multfsusie_obj.R:get_ER2.multfsusie`) + +```r +ER2$f[k] <- sum((Y_f[[k]] - X %*% postF$post_f[[k]])^2) + - sum(postF$post_f[[k]]^2) + + sum(postF2$post_f_sd2[[k]]) +``` + +The second term uses `sum(E[b_l]^2)` (without `xtx_diag`) rather than the correct +`sum(xtx_diag * E[b_l^2]) - sum((X * E[b_l])^2)`. The missing `xtx_diag` factor +(O(n) on average) deflates the bias correction term, making ER2 smaller and sigma2 +smaller. This is the D1 bug. + +Practical effect: mfsusieR sigma2 is consistently larger than mvf sigma2 at the same +data. The ratio is empirically 1.5-2x in perm runs. Under-estimated sigma2 in mvf +makes lbf values larger, inflating CS counts and FDR. + +--- + +## §9. KL / ELBO (D3 divergence) + +### mfsusieR + +`compute_kl.mf_individual`: inherits from susieR's correct formula. +`get_objective.mfsusie` calls `refresh_lbf_kl.mf_individual` at iter end to +re-evaluate lbf/KL against the iter-final pi, returning a coherent ELBO. + +### mvf (`R/ELBO_mutlfsusie.R:55`) + +`cal_KL_l.multfsusie`: +```r +out <- -loglik_SFR(obj, l, Y, X, ind_analysis) + - loglik_SFR_post(obj, l, R_l, X, ind_analysis) +``` + +The correct formula is `KL = loglik_SFR_post - loglik_SFR` (posterior expected +log-likelihood minus prior marginal log-likelihood). mvf negates both terms. +Effect: `sum(KL)` in the ELBO is `-(sum_loglik_SFR + sum_loglik_SFR_post)`, +an O(L * n * T) inflation of the reported objective. Consecutive ELBO differences +still shrink as alpha stabilizes, so ELBO-convergence termination is unaffected. +PIP-convergence runs (default in both packages) are completely unaffected. + +--- + +## §10. NA handling and qnorm bug (mfsusieR) + +`mf_quantile_normalize` (`R/utils_wavelet.R`): +```r +rank(x, ties.method = "random") # missing: na.last = "keep" +``` +NA entries receive large positive ranks instead of NA. In real atac-seq data with +~5-10 samples missing for some cell types, this deflated sigma2 by 28-48%. + +Fix: `rank(x, na.last = "keep", ties.method = "random")`. This bug is in the +`wavelet_qnorm = TRUE` path (default FALSE), so it only affects users who enable +wavelet quantile normalization. + +mvf's `DWT2` imputes NA rows to 0 before DWT (a different trade-off that changes +neighboring wavelet coefficients via filter support). Neither solution is perfect; +the mfsusieR fix above is correct for the rank-normalization path. + +--- + +## §11. LFSR + +### mfsusieR + +`lfsr_from_gaussian(mean, sd) = pnorm(-|mean| / sd)` (`R/mfsusie_methods.R:460`). +Computed during `mf_post_smooth()` on the smoothed per-effect curve in position space. +Stored at `fit$smoothed[[method]]$lfsr_curves[[m]][[l]]` (a list of length-T_m +numeric vectors, one per (outcome, effect) pair). + +A per-variant clfsr at the wavelet-coefficient level is also stored at +`clfsr_curves[[m]][[l]]` (p x T_basis matrix) when `save_mu_method = "complete"`, +derived from `(mu[[l]][[m]], mu2[[l]][[m]])` via the Gaussian approximation. + +All four post-smooth methods (TI, HMM, smash, scalewise) produce lfsr. + +### mvf + +`HMM_regression.multfsusie` (called inside `out_prep` when `post_processing = "HMM"`) +delegates to `fsusieR::HMM_regression.susiF` per functional outcome. HMM-derived +lfsr is stored at `fit$lfsr[[l]]$est_lfsr_functional[[k]]`. + +For `post_processing` choices other than `"HMM"` (smash, TI, none), mvf does NOT +compute lfsr. The lfsr output is thus tightly coupled to the post-processing +choice baked into `out_prep`. + +--- + +## §12. Posthoc trait configuration probabilities + +### mfsusieR + +Implemented via `susieR::susie_post_outcome_configuration()`. mfsusieR stores a +per-(effect, variant, outcome) log Bayes factor array `lbf_variable_outcome` +(L x p x M) during the IBSS sweep (`R/individual_data_methods.R:231`). Users call +this susieR function after fitting: + +```r +susieR::susie_post_outcome_configuration(fit, by = "outcome", method = "susiex") +``` + +Two modes via `by`: +- `by = "outcome"` (for mfsusie): expands the single fit into M per-outcome views + using `lbf_variable_outcome[, , m]` slices. Each view has `alpha` (the shared + L x p matrix) and per-outcome `lbf` (L x p). Runs `susiex_configurations()` on + the M views. +- `by = "fit"`: treats the whole fit as one trait, using the joint composite + `lbf_variable` (L x p). Used for pairwise comparison across separate fits. + +Two computation methods via `method`: +- `"susiex"`: enumerates `2^M` configurations per CS tuple. For each tuple of + L-indices (one per outcome), computes + `logBF_trait[m] = sum_j alpha[l_m, j] * lbf[l_m, j, m]`, then + `prob_conf = normalize(exp(configs %*% logBF_trait))`, + `marginal_prob = crossprod(configs, prob_conf)`. + Same enumeration logic as mvf's `posthoc_multfsusie`. +- `"coloc"`: pairwise Bayes-factor coloc (`coloc.abf` style) for N=2 comparisons + across separate fits. Not applicable to a single mfsusie fit with `by = "outcome"`. + +Output: a `susie_post_outcome_configuration` object with either `$susiex` (list of +CS tuples, each with `cs_indices`, `logBF_trait`, `configs`, `config_prob`, +`marginal_prob`, `active`) or `$coloc_pairwise`. + +NOT called automatically; the user must invoke it after `mfsusie()`. + +### mvf + +`posthoc_multfsusie()` (`R/operation_on_multfsusie_obj.R:1514`), called inside +`out_prep` when `posthoc = TRUE` (default). Per credible set `l`: +1. Compute per-trait log BF: `logBF_l[k] = get_cs_logBF_multfsusie(alpha_l, lBF_per_trait_l)`. +2. Enumerate `2^S` trait configurations (S = number of modalities; capped at S <= 20). +3. `prob_conf = normalize(exp(configs %*% logBF_l))`. +4. Marginal per-trait probability: `posthoc_trait = colSums(configs * prob_conf)`. + +Output at `fit$posthoc[[l]]`: list with `logBF_trait`, `posthoc`, `active` +(posthoc >= 0.8), `configs`, `config_prob`. Called automatically by default. + +Reference: Yuan et al., Nat Genet 2024. + +### Key differences + +| Aspect | mfsusieR | mvf | +|--------|---------|-----| +| Where implemented | `susieR::susie_post_outcome_configuration()` | `posthoc_multfsusie()` in mvf | +| When called | User calls explicitly after `mfsusie()` | Automatic inside `out_prep` | +| Storage | `fit$lbf_variable_outcome` (L x p x M) enables the call | `fit$posthoc[[l]]` stores results | +| Methods | `"susiex"` (2^M enum) and `"coloc"` (pairwise BF) | susiex-style only | +| Input flexibility | Accepts single fit (`by="outcome"`) or list of fits (`by="fit"`) | Per-CS on one fit only | + +--- + +## §13. Output structure + +### mfsusieR + +| Field | Type | Content | +|-------|------|---------| +| `fit$alpha` | L x p **matrix** | variational SNP-level PIP per effect | +| `fit$mu[[l]][[m]]` | p x T_basis[m] matrix (or 1 x T if trimmed) | wavelet posterior mean | +| `fit$mu2[[l]][[m]]` | p x T_basis[m] matrix (or 1 x T if trimmed) | wavelet posterior second moment | +| `fit$sets$cs` | list of CS objects (susieR format), each with `$variables` and `$purity` | credible sets | +| `fit$pip` | length-p vector | aggregate PIP across L effects | +| `fit$sigma2` | list[M] of scalar or length-S_m vector | per-outcome residual variance (not SD) | +| `fit$pi_V[[l]][[m]]` | S_m x K matrix | mixture weights per (effect, outcome, scale) | +| `fit$dwt_meta` | list | inverse-DWT parameters | +| `fit$smoothed[[method]]` | list | populated after `mf_post_smooth()` | +| `fit$lbf_variable_outcome` | L x p x M array | per-(effect, variant, outcome) lbf; input to `susie_post_outcome_configuration()` | +| `fit$posthoc` | NULL | not stored; call `susieR::susie_post_outcome_configuration(fit)` explicitly | + +### mvf + +| Field | Type | Content | +|-------|------|---------| +| `fit$alpha` | **list of L plain numeric vectors**, each length p | NOT an L x p matrix | +| `fit$fitted_wc[[l]][[k]]` | p x T_basis[k] matrix | wavelet posterior mean per (effect, functional outcome) | +| `fit$fitted_wc2[[l]][[k]]` | p x T_basis[k] matrix | wavelet posterior second moment | +| `fit$cs` | list of L integer vectors (SNP indices) | flat list, NOT susieR format | +| `fit$purity` | matrix (one row per CS) | computed top-level by `fsusieR::cal_purity` | +| `fit$pip` | length-p vector | aggregate PIP | +| `fit$sigma2$sd_f` | length-M vector of **SDs (not variances)** | residual SD per outcome | +| `fit$fitted_func[[l]][[k]]` | length-T_basis vector | position-space curve; baked in by out_prep | +| `fit$cred_band[[l]][[k]]` | 2 x T_basis matrix | credible band; baked in | +| `fit$lfsr[[l]]$est_lfsr_functional[[k]]` | length-T_basis vector | only when post_processing = "HMM" | +| `fit$posthoc[[l]]` | list | config probabilities | + +Key access differences for MWE code: +- Alpha matrix: mfsusieR `fit$alpha[l, ]` vs mvf `fit$alpha[[l]]` +- Convert mvf alpha to matrix: `do.call(rbind, fit_mvf$alpha)` (NOT `lapply(x, function(a) a$alpha_f)`) +- Sigma2: mfsusieR `unlist(fit$sigma2)` = variances; mvf `unlist(fit$sigma2$sd_f)^2` = variances +- CS purity: mfsusieR `cs[[i]]$purity` (nested); mvf `fit$purity` (top-level) + +--- + +## §14. save_mu_method (mfsusieR only) + +Three modes via `mfsusie(save_mu_method = ...)`: +- `"complete"` (default): full p x T_basis per (l, m). Supports warm-start + (`model_init`), per-variant lfsr, `predict(newx)`. +- `"alpha_collapsed"`: replaces p x T by 1 x T alpha-weighted summary. + A separate `coef_wavelet[[l]][[m]]` (1 x T) is precomputed before collapse + (per-j csd_X scaling cannot be recovered post-collapse). Factor-p storage reduction. +- `"lead"`: keeps only the lead variable `j* = which.max(alpha[l, ])`; + `top_index[l]` records `j*`. Cheapest but biased to lead. + +`mf_thin(fit, method)` applies the trim post-fit without modifying the original. + +mvf always stores full p x T in `fitted_wc`. No equivalent storage mode. + +--- + +## §15. Warm start / model_init + +mfsusieR: `mfsusie(..., model_init = prior_fit)` warm-starts the IBSS loop from +a prior fit's `fitted_g_per_effect`, `alpha`, `mu`, and `fitted` fields. +Requires `save_mu_method = "complete"` on the prior fit. + +mvf: always cold start. No equivalent parameter. + +--- + +## §16. Greedy / backfit + +### mfsusieR + +`L_greedy = NULL` (default): no greedy expansion. `L` effects are allocated at +init; `trim_null_effects` prunes effects whose effective slab variance falls +below `prior_tol` (susieR default 1e-9). Number of effects is fixed. + +### mvf + +`greedy = TRUE` (default): starts with `L_start` effects, adds blocks of 7 via +`expand_multfsusie_obj` when all effects have non-dummy CS. `backfit = TRUE` +(default): prunes low-purity effects after each outer iter via `discard_cs`. +Both are on by default, making the loop structure substantially different from +mfsusieR's fixed-L approach. + +To achieve a comparable fixed-L run: `greedy = FALSE, backfit = FALSE`. + +--- + +## §17. Convergence + +| Parameter | mfsusieR default | mvf default | +|-----------|-----------------|-------------| +| Criterion | `convergence_method = "pip"` | ELBO-based (`check = |ELBO_diff| / |ELBO|`) | +| Tolerance | `tol = 1e-3` | `tol = 1e-3` | +| Max iter | `max_iter = 100` | `maxit = 100` | + +mfsusieR's pip convergence: `max(|pip[t] - pip[t-1]|) < tol`. Unaffected by the +ELBO sign error in mvf (D3). + +--- + +## §18. Nullweight parameterization + +Both packages allow setting the null-component penalty for the mixture M-step. + +mfsusieR: `mixture_null_weight` (default NULL, resolves to 0.05 internally). +Passed as the direct `mixture_null_weight` to `mf_em_m_step_per_scale`, then scaled +by M inside `.opv_mixsqp`: effective weight = `mixture_null_weight * max(1, M)`. + +mvf: `nullweight = 0.7` (default). Divided by K (number of modalities) inside +`EM_pi_multsusie`: `nullweight_m = nullweight / K`. + +For M = 3 (three functional outcomes), comparable settings are approximately: +mfsusieR `mixture_null_weight = 0.7 / 3 ≈ 0.23` vs mvf `nullweight = 0.7` +(which applies `0.7 / 3 ≈ 0.23` per outcome internally). The scaling strategy +is equivalent in intent; the default values differ. + +--- + +## §19. Confirmed divergences + +| ID | Component | mfsusieR | mvf | Impact | +|----|-----------|----------|-----|--------| +| D1 | ER2 bias correction | Correct (`xtx_diag` present in `colSums((alpha * pw) * mu2)`) | Missing `xtx_diag` factor; sigma2 deflated ~1.5-2x | FDR inflation in all real-data runs | +| D2 | Bhat/Shat | Global `sigma2/xtx` per outcome | Per-(j,t) marginal SE from univariate regression | Detection power difference at true signals; not a bug | +| D3 | KL/ELBO sign | Correct | `-loglik_SFR - loglik_SFR_post` instead of `loglik_SFR_post - loglik_SFR`; ELBO inflated by O(L*n*T) | ELBO value wrong; PIP and CS outputs unaffected | +| D4 | qnorm NA | `rank(x)` without `na.last = "keep"` deflates sigma2 28-48% on real data | DWT2 imputes NA to 0 (different trade-off) | Real-data FDR inflation when `wavelet_qnorm = TRUE` | + +D1 and D4 are bugs. D2 is an intentional design choice in each package's variational +derivation. D3 is a derivation error in mvf that does not affect PIP convergence runs. + +--- + +## §20. Parameter name mapping + +| Concept | mfsusieR | mvf | +|---------|---------|-----| +| Number of effects | `L` | `L` | +| Null weight | `mixture_null_weight` (default NULL -> 0.05; scaled by M) | `nullweight` (default 0.7; divided by K) | +| Max iterations | `max_iter` | `maxit` | +| Convergence tol | `tol` | `tol` | +| Prior mode | `prior_variance_scope = "per_outcome"/"per_scale"/...` | `prior = "mixture_normal"/"mixture_normal_per_scale"` | +| Inner EM steps | `max_inner_em_steps = 5L` | no equivalent | +| Mixsqp control | `control_mixsqp = NULL` (warm) / `list(...)` (cold) | no exposed control | +| Warm start | `model_init = prior_fit` | no equivalent | +| Wave qnorm | `wavelet_qnorm = FALSE` (in wavelet domain) | `quantile_trans = FALSE` (in position domain, before DWT) | +| Small-sample BF | `small_sample_correction = FALSE` | `cor_small = FALSE` | +| Post-processing | `mf_post_smooth(fit, method)` (separate step after fit) | `post_processing = "smash"/"TI"/"HMM"/"none"` (baked into return) | +| Posthoc | not available | `posthoc = TRUE` (default) | +| Storage trim | `save_mu_method = "complete"/"alpha_collapsed"/"lead"` | not available | +| Greedy | `L_greedy = NULL` (disabled by default) | `greedy = TRUE` (default) | +| Backfit | implicit via `trim_null_effects` | `backfit = TRUE` (default) | +| Max SNPs for M-step | all p (no limit) | `max_SNP_EM = 100` (top-100 by log-BF) | + +--- + +## §21. Summary + +### Differences with clear right/wrong + +**1. ER2 bias correction — mvf bug (D1)** +mvf's `get_ER2.multfsusie` is missing the `xtx_diag` factor in the bias +correction term. sigma2 is underestimated by 1.5-2x, making log-BF values +larger and inflating CS counts and FDR in real data. mfsusieR's formula is +correct. + +**2. KL sign — mvf bug (D3)** +mvf negates both terms in the KL formula (`-loglik_SFR - loglik_SFR_post` +instead of `loglik_SFR_post - loglik_SFR`). The reported ELBO is inflated by +O(L·n·T). Because both packages default to PIP convergence rather than ELBO +convergence, this error has no effect on PIP, CS, or any output the user sees. + +### Differences with no right/wrong (design choices) + +**3. Bhat/Shat (D2)** +mfsusieR uses global sigma2 / xtx[j], consistent with the SuSiE variational +derivation. mvf uses per-variable marginal SE from univariate regression, which +can give higher power at true signals but breaks variational consistency. Both +are internally self-consistent choices. + +**4. Inner EM loop — mfsusieR only** +mfsusieR runs up to 6 M-step + alpha update cycles per effect per outer +iteration (default `max_inner_em_steps = 5`). mvf runs one M-step per effect +per outer iteration. The inner loop tightens the prior/posterior agreement +within each outer step at the cost of IBSS ELBO monotonicity (restored at +iter end). No right or wrong; a precision vs speed trade-off. + +**5. M-step warm vs cold start** +mfsusieR defaults to warm start (initializes mixsqp from the previous +iteration's pi). mvf always cold-starts (`pi = (0.9, 1e-12, ...)`). Both +converge; warm start is faster. + +**6. max_SNP_EM — mvf approximation** +mvf limits the M-step input to the top-100 SNPs by log-BF. mfsusieR uses all +p variables. The top-100 truncation is a computational shortcut; mixture +weights are estimated from less data. Not a bug, but the approximation becomes +coarser at large p. + +**7. Architecture** +mfsusieR delegates the IBSS loop entirely to `susieR::susie_workhorse` via S3 +dispatch. mvf owns its own `while` loop and calls all functions directly. +Neither is wrong; mfsusieR inherits future susieR improvements automatically, +mvf is fully self-contained. + +### One-sentence summary + +The only bug with measurable numerical impact is D1 (ER2 bias correction in +mvf), which deflates sigma2 and inflates FDR. Everything else is either a +harmless derivation error (D3, KL sign) or a deliberate design difference +(Bhat/Shat, inner EM, warm start, max_SNP_EM, architecture) with no clear +winner. + +--- + +## §22. Next session + +- Fix the NA bug in `mf_quantile_normalize` (`R/utils_wavelet.R`), pending user + approval. Change: add `na.last = "keep"` to `rank()` call. +- Monitor SLURM job 32644042 (20 tasks, conditions A-D); aggregate results when + all 20 CSVs appear. +- Verify the MWE tests run without error when `mvf.susie.alpha` is installed. diff --git a/man/mfsusie.Rd b/man/mfsusie.Rd index 0566030..1e44e25 100644 --- a/man/mfsusie.Rd +++ b/man/mfsusie.Rd @@ -43,7 +43,8 @@ mfsusie( model_init = NULL, small_sample_correction = FALSE, max_inner_em_steps = 5L, - attach_smoothing_inputs = TRUE + attach_smoothing_inputs = TRUE, + save_mu_method = c("complete", "aggregated", "lead") ) } \arguments{ @@ -262,6 +263,38 @@ Set \code{FALSE} to drop these and call \code{mf_post_smooth(fit, X = X, Y = Y, ...)} instead; useful when sharing fits where the per-individual data should not travel with the fit.} + +\item{save_mu_method}{one of \code{"complete"} (default), +\code{"aggregated"}, or \code{"lead"}. Controls the storage shape +of the per-effect posterior moments \code{fit$mu} and \code{fit$mu2} +after the IBSS loop finishes. + +\code{"complete"} keeps the full \verb{p x T_basis[m]} per-(effect, +outcome) moments; this is the only mode that supports +\code{model_init} warm-starts, \code{predict.mfsusie(newx)}, and the +per-variant lfsr toggle in plots. + +\code{"aggregated"} replaces each \verb{p x T} matrix by the +alpha-weighted 1 x T summary +\verb{mu[[l]][[m]] = sum_j alpha[l, j] * mu_full[l, j, ]} (and +the analogous second-moment summary). Storage shrinks by +roughly factor \code{p}. The post-fit consumers \code{coef.mfsusie}, +\code{mf_post_smooth}, \code{summary}, \code{print}, and the alpha-aggregated +plot views are numerically equivalent to \code{"complete"}. To +keep \code{coef} lossless on the raw-X scale, the fit also +carries \verb{coef_wavelet[[l]][[m]] = sum_j alpha[l, j] * mu_full[l, j, ] / csd_X[j]} as a separate 1 x T summary. + +\code{"lead"} keeps only the lead variable per effect: +\verb{mu[[l]][[m]] = mu_full[l, j*, ]} where +\verb{j* = which.max(alpha[l, ])}, plus \verb{fit$top_index[l] = j*}. +This is a cheap single-variable coefficient summary, biased +toward the lead. \code{coef.mfsusie} and \code{mf_post_smooth} work but +are not the alpha-weighted posterior mean; document accordingly. + +Both 1D modes (\code{"aggregated"}, \code{"lead"}) error on +\code{predict.mfsusie(newx)}, the plot per-variant lfsr toggle, +and \code{model_init}. Use \code{mf_thin()} to thin a complete fit +after the fact while keeping the original for warm-starts.} } \value{ A list of class \code{c("mfsusie", "susie")} carrying: diff --git a/tests/testthat/test_mfsusier_vs_mvf_mwe.R b/tests/testthat/test_mfsusier_vs_mvf_mwe.R new file mode 100644 index 0000000..ef76273 --- /dev/null +++ b/tests/testthat/test_mfsusier_vs_mvf_mwe.R @@ -0,0 +1,267 @@ +# Full-pipeline comparison: mfsusieR HEAD vs mvf.susie.alpha +# +# Covers: fit -> HMM post-smooth -> CS -> LFSR -> posthoc +# Not a parity test. Documents call signatures, output structure, and +# known divergences (D1-D3). +# +# D1. sigma2: mfsusieR > mvf because mvf ER2 bias correction is missing +# predictor_weights (deflated ~2x). +# D2. Bhat/Shat: mvf uses per-variable marginal SE; mfsusieR uses global +# sigma2/xtx. Different algorithm, not a bug. +# D3. ELBO KL sign error in mvf; does not affect PIP convergence. +# +# Companion: inst/notes/sessions/2026-05-11-mfsusier-vs-mvf-engineering-comparison.md + +skip_if_no_mvf <- function() skip_if_not_installed("mvf.susie.alpha") + +# ── Data and parameters ─────────────────────────────────────────────────────── + +make_mwe_data <- function(seed = 42L, n = 60L, p = 80L, M = 2L, T_y = 32L) { + set.seed(seed) + X <- matrix(rnorm(n * p), n, p) + beta <- matrix(0, p, T_y) + for (j in c(10L, 60L)) { + t <- seq_len(T_y) + beta[j, ] <- exp(-0.5 * ((t - T_y / 2) / (T_y / 8))^2) * rnorm(1, sd = 0.8) + } + Y <- lapply(seq_len(M), function(m) + X %*% beta + matrix(rnorm(n * T_y, sd = 0.5), n, T_y)) + list(X = X, Y = Y, n = n, p = p, M = M, T_y = T_y, sig_idx = c(10L, 60L)) +} + +MWE_L <- 5L +MWE_CTRL <- list(convtol.sqp = 1e-8, numiter.em = 20L, tol.svd = 1e-10, verbose = FALSE) + +# ── Lazy fit cache ──────────────────────────────────────────────────────────── +# Fits both packages once; shared across all tests below. + +.cache <- new.env(parent = emptyenv()) + +.get_fits <- function() { + if (isTRUE(.cache$ready)) return(.cache) + skip_if_no_mvf() + d <- make_mwe_data() + pos <- lapply(seq_len(d$M), function(m) seq_len(d$T_y)) + + # mfsusieR ───────────────────────────────────────────────────────────────── + # 1. mfsusie(): returns fit with class "mfsusie" + # 2. mf_post_smooth(method="HMM"): adds fit$smoothed$HMM with + # $effect_curves[[m]][[l]] — length-T position-space curve + # $credible_bands[[m]][[l]] — T x 2 matrix [lower, upper] + # $lfsr_curves[[m]][[l]] — length-T LFSR in [0,1] + # indexed by outcome m (1..M) and effect l (1..L, all L) + # 3. susie_post_outcome_configuration(): reads fit$lbf_variable_outcome + # (L x p x M), enumerates 2^M configs per CS tuple + + fit_mf <- mfsusie( + X = d$X, Y = d$Y, pos = pos, + L = MWE_L, + prior_variance_scope = "per_outcome", + wavelet_qnorm = FALSE, + max_inner_em_steps = 0L, + mixture_null_weight = 0.12, + control_mixsqp = MWE_CTRL, + L_greedy = NULL, + tol = 1e-4, max_iter = 30L, verbose = FALSE + ) + fit_mf <- mf_post_smooth(fit_mf, method = "HMM") + pc_mf <- susieR::susie_post_outcome_configuration( + fit_mf, by = "outcome", method = "susiex" + ) + + # mvf ────────────────────────────────────────────────────────────────────── + # multfsusie() with post_processing="HMM" and posthoc=TRUE runs + # both inside out_prep(), trimming null effects before output. + # After trimming: length(fit$alpha) = length(fit$cs) = L_active (<= L). + # HMM output indexed by CS (l_cs = 1..L_active), modality (k = 1..M): + # fit$fitted_func[[l_cs]][[k]] — length-T effect curve + # fit$cred_band[[l_cs]][[k]] — 2 x T matrix [lower; upper] (rows, not cols) + # fit$lfsr[[l_cs]]$est_lfsr_functional[[k]] — length-T LFSR + # Posthoc at fit$posthoc[[l_cs]], called automatically. + + fit_mvf <- mvf.susie.alpha::multfsusie( + Y = list(Y_f = d$Y), + X = d$X, + pos = pos, + L = MWE_L, + prior = "mixture_normal", + nullweight = 0.7, + maxit = 30L, + tol = 1e-4, + greedy = FALSE, + backfit = FALSE, + post_processing = "HMM", + posthoc = TRUE, + verbose = FALSE + ) + + .cache$d <- d + .cache$mf <- fit_mf + .cache$pc_mf <- pc_mf + .cache$mvf <- fit_mvf + .cache$ready <- TRUE + .cache +} + +# ── Tests ───────────────────────────────────────────────────────────────────── + +test_that("both pipelines complete without error", { + c <- .get_fits() + expect_true(inherits(c$mf, "mfsusie")) + expect_true(is.list(c$mvf)) + expect_true(!is.null(c$mf$smoothed$HMM)) + expect_true(inherits(c$pc_mf, "susie_post_outcome_configuration")) +}) + +test_that("alpha: L x p matrix (mfsusieR) vs list of L_active vectors (mvf)", { + c <- .get_fits(); d <- c$d + # mfsusieR: always L x p, row sums = 1 + expect_equal(dim(c$mf$alpha), c(MWE_L, d$p)) + expect_equal(rowSums(c$mf$alpha), rep(1, MWE_L), tolerance = 1e-10) + # mvf: out_prep trims null effects; length = L_active (<= L) + L_active <- length(c$mvf$alpha) + expect_true(L_active <= MWE_L) + expect_equal(ncol(do.call(rbind, c$mvf$alpha)), d$p) + expect_equal(vapply(c$mvf$alpha, sum, numeric(1)), + rep(1, L_active), tolerance = 1e-10) + message(sprintf("L_active: mvf=%d / L=%d", L_active, MWE_L)) +}) + +test_that("PIP: both packages return a length-p vector", { + c <- .get_fits(); d <- c$d + expect_equal(length(c$mf$pip), d$p) + expect_equal(length(c$mvf$pip), d$p) + sig <- d$sig_idx + message(sprintf("PIP at signals: mfsusieR [%s], mvf [%s]", + paste(round(c$mf$pip[sig], 3), collapse = "/"), + paste(round(c$mvf$pip[sig], 3), collapse = "/"))) +}) + +test_that("sigma2: mfsusieR > mvf (D1: correct vs deflated ER2)", { + c <- .get_fits() + # mfsusieR: list of M scalars + s_mf <- unlist(c$mf$sigma2) + # mvf: $sigma2$sd_f stores SDs, not variances; square to compare + s_mvf <- unlist(c$mvf$sigma2$sd_f)^2 + expect_true(all(s_mf > s_mvf)) + message(sprintf("sigma2 mfsusieR/mvf ratio: [%s]", + paste(round(s_mf / s_mvf, 2), collapse = "/"))) +}) + +test_that("CS: count and purity accessible from both fits", { + c <- .get_fits() + # mfsusieR: fit$sets$cs — named list of integer vectors (SNP indices) + # fit$sets$purity — data.frame, one row per CS + cs_mf <- c$mf$sets$cs + expect_true(is.list(cs_mf)) + if (length(cs_mf) > 0L) { + expect_true(is.data.frame(c$mf$sets$purity) || is.matrix(c$mf$sets$purity)) + pur_mf <- c$mf$sets$purity[, "mean.abs.corr"] + message(sprintf("mfsusieR: %d CS, mean purity [%s]", + length(cs_mf), paste(round(pur_mf, 3), collapse = ", "))) + } + # mvf: fit$cs — list of L_active integer vectors (trimmed) + # fit$purity — matrix, one row per CS + cs_mvf <- c$mvf$cs + expect_true(is.list(cs_mvf)) + if (length(cs_mvf) > 0L && !is.null(c$mvf$purity)) { + pur_mvf <- if (is.matrix(c$mvf$purity)) rowMeans(c$mvf$purity, na.rm = TRUE) + else as.numeric(c$mvf$purity) + message(sprintf("mvf: %d CS, mean purity [%s]", + length(cs_mvf), paste(round(pur_mvf, 3), collapse = ", "))) + } + message(sprintf("CS count: mfsusieR=%d, mvf=%d", + length(cs_mf), length(cs_mvf))) +}) + +test_that("HMM effect curves: shape and indexing", { + c <- .get_fits(); d <- c$d + hmm <- c$mf$smoothed$HMM + + # mfsusieR: [[outcome m]][[effect l]], all M x L slots, each length T_y + expect_equal(length(hmm$effect_curves), d$M) + expect_equal(length(hmm$effect_curves[[1L]]), MWE_L) + expect_equal(length(hmm$effect_curves[[1L]][[1L]]), d$T_y) + + # mfsusieR credible bands: T x 2 (lower, upper as columns) + expect_equal(dim(hmm$credible_bands[[1L]][[1L]]), c(d$T_y, 2L)) + + # mvf: [[CS index]][[modality k]], only L_active slots + n_cs <- length(c$mvf$fitted_func) + if (n_cs > 0L) { + expect_equal(length(c$mvf$fitted_func[[1L]]), d$M) + expect_equal(length(c$mvf$fitted_func[[1L]][[1L]]), d$T_y) + # mvf credible bands: 2 x T (lower/upper as rows, opposite of mfsusieR) + expect_equal(dim(c$mvf$cred_band[[1L]][[1L]]), c(2L, d$T_y)) + } +}) + +test_that("LFSR: values in [0,1]; indexing differs between packages", { + c <- .get_fits(); d <- c$d + + # mfsusieR: $smoothed$HMM$lfsr_curves[[m]][[l]], all M x L effects + lfsr_mf <- c$mf$smoothed$HMM$lfsr_curves + expect_equal(length(lfsr_mf), d$M) + expect_equal(length(lfsr_mf[[1L]]), MWE_L) + for (m in seq_len(d$M)) + for (l in seq_len(MWE_L)) + expect_true(all(lfsr_mf[[m]][[l]] >= 0 & lfsr_mf[[m]][[l]] <= 1)) + + # mvf: $lfsr[[l_cs]]$est_lfsr_functional[[k]], only detected CS + n_cs <- length(c$mvf$lfsr) + if (n_cs > 0L) { + for (l in seq_len(n_cs)) + for (k in seq_len(d$M)) { + v <- c$mvf$lfsr[[l]]$est_lfsr_functional[[k]] + expect_true(all(v >= 0 & v <= 1)) + } + } + message(sprintf( + "LFSR slots: mfsusieR %dx%d=%d; mvf %d CS x %d outcomes=%d", + d$M, MWE_L, d$M * MWE_L, n_cs, d$M, n_cs * d$M)) +}) + +test_that("posthoc: explicit call (mfsusieR) vs automatic (mvf)", { + c <- .get_fits(); d <- c$d + + # mfsusieR: lbf_variable_outcome (L x p x M) is stored during IBSS; + # user calls susie_post_outcome_configuration() after fit. + expect_equal(dim(c$mf$lbf_variable_outcome), c(MWE_L, d$p, d$M)) + pc <- c$pc_mf + expect_true(is.list(pc$susiex)) + if (length(pc$susiex) > 0L) { + t1 <- pc$susiex[[1L]] + expect_true(all(c("cs_indices", "logBF_trait", "configs", + "config_prob", "marginal_prob") %in% names(t1))) + expect_equal(length(t1$logBF_trait), d$M) + expect_equal(nrow(t1$configs), 2L^d$M) + } + + # mvf: posthoc computed automatically inside out_prep(); stored at fit$posthoc. + # Length = L_active (same as fit$alpha, fit$cs). + expect_true(is.list(c$mvf$posthoc)) + expect_true(length(c$mvf$posthoc) <= MWE_L) + non_null <- Filter(Negate(is.null), c$mvf$posthoc) + if (length(non_null) > 0L) { + t1 <- non_null[[1L]] + expect_true(all(c("logBF_trait", "posthoc", "active", + "configs", "config_prob") %in% names(t1))) + expect_equal(nrow(t1$configs), 2L^length(t1$logBF_trait)) + } + message(sprintf("posthoc susiex tuples: mfsusieR=%d, mvf=%d", + length(pc$susiex), length(non_null))) +}) + +# ── Observed results on the MWE fixture (seed=42, n=60, p=80, M=2, T=32) ───── +# +# CS members: identical — CS1={SNP 10}, CS2={SNP 60} in both packages. +# PIP at signals: both return PIP=1 for SNP 10 and SNP 60. +# PIP at nulls: mfsusieR rounds to 0 for all null SNPs; mvf gives ~0.012 +# to a few null SNPs. Direct consequence of D1: mvf sigma2 is lower +# (0.822 vs 0.909), making log-BF slightly inflated on null SNPs. +# sigma2: mfsusieR ~0.909, mvf ~0.822, ratio ~1.11 (D1 ER2 bug). +# Convergence: mfsusieR 3 iterations, mvf 5 iterations. +# +# On this strong-signal dataset the two packages agree on the main findings +# (CS, top PIPs). The D1 sigma2 deflation becomes more consequential on +# weak-signal or null data, where inflated log-BF pushes null SNPs into CS. diff --git a/tests/testthat/test_post_smooth_smash.R b/tests/testthat/test_post_smooth_smash.R index 6ae3111..0d17acc 100644 --- a/tests/testthat/test_post_smooth_smash.R +++ b/tests/testthat/test_post_smooth_smash.R @@ -36,6 +36,7 @@ test_that("method = 'smash' kernel matches fsusieR::univariate_smash_regression }) test_that("mf_post_smooth(method = 'smash') populates effect_curves and credible_bands", { + testthat::skip_if_not_installed("smashr") set.seed(13L) n <- 60L; p <- 20L; T_m <- 64L X <- matrix(rnorm(n * p), n) diff --git a/tests/testthat/test_save_mu_method.R b/tests/testthat/test_save_mu_method.R new file mode 100644 index 0000000..f77ee35 --- /dev/null +++ b/tests/testthat/test_save_mu_method.R @@ -0,0 +1,152 @@ +# Locks the contract for save_mu_method = c("complete", "aggregated", "lead") +# and the mf_thin() post-fit helper. +# +# Invariants enforced: +# 1. complete is the default and produces fits with attr "complete". +# 2. aggregated reduces fit$mu / fit$mu2 to 1 x T per (l, m) and adds +# fit$coef_wavelet (1 x T) for the raw-X coef path. +# 3. lead reduces fit$mu / fit$mu2 to 1 x T per (l, m) and stores +# fit$top_index = which.max(alpha[l, ]). +# 4. coef.mfsusie complete == aggregated numerically (1e-12). +# 5. coef.mfsusie complete != lead in general (cheap-coef, biased). +# 6. mf_post_smooth scalewise complete == aggregated numerically (1e-12). +# 7. mf_thin(complete, "aggregated") == mfsusie(..., "aggregated"). +# 8. predict.mfsusie(newx) errors on 1D modes; fitted() works on 1D modes. +# 9. mfsusie(model_init = thinned_fit) errors with a save_mu_method message. +# 10. mf_thin() rejects re-thinning a 1D fit. + +# Each test_that block in this file is wrapped to print a [RUNTIME] +# line on completion, matching the convention used in test_missing_data_na.R +# and test_qnorm_*. The macro is intentionally local to this file. +.timed <- function(label, expr) { + t0 <- Sys.time() + force(expr) + cat(sprintf("[RUNTIME] %s: %.2f s\n", label, + as.numeric(difftime(Sys.time(), t0, units = "secs")))) +} + +build_toy_fit <- function(seed = 1L, save_mu_method = "complete", + L = 5L, max_iter = 6L) { + set.seed(seed) + n <- 50L; p <- 30L; Tlen <- 32L + X <- matrix(rnorm(n * p), n, p) + beta <- numeric(p); beta[c(3, 17)] <- c(2, -1.5) + Y <- list(matrix(rnorm(n * Tlen), n, Tlen) + as.numeric(X %*% beta)) + fit <- mfsusie(X, Y, L = L, max_iter = max_iter, verbose = FALSE, + save_mu_method = save_mu_method) + list(fit = fit, X = X, Y = Y) +} + +test_that("save_mu_method default is complete and recorded as attr", { + .timed("save_mu_method default", { + obj <- build_toy_fit() + expect_equal(attr(obj$fit, "save_mu_method"), "complete") + expect_true(nrow(obj$fit$mu[[1]][[1]]) == ncol(obj$X)) + expect_null(obj$fit$top_index) + expect_null(obj$fit$coef_wavelet) + }) +}) + +test_that("aggregated shrinks mu/mu2 to 1 x T and adds coef_wavelet", { + .timed("aggregated shape", { + obj <- build_toy_fit(save_mu_method = "aggregated") + expect_equal(attr(obj$fit, "save_mu_method"), "aggregated") + expect_equal(nrow(obj$fit$mu[[1]][[1]]), 1L) + expect_equal(nrow(obj$fit$mu2[[1]][[1]]), 1L) + expect_false(is.null(obj$fit$coef_wavelet)) + expect_equal(nrow(obj$fit$coef_wavelet[[1]][[1]]), 1L) + }) +}) + +test_that("lead shrinks mu/mu2 to 1 x T and stores top_index", { + .timed("lead shape", { + obj <- build_toy_fit(save_mu_method = "lead") + expect_equal(attr(obj$fit, "save_mu_method"), "lead") + expect_equal(nrow(obj$fit$mu[[1]][[1]]), 1L) + expect_equal(nrow(obj$fit$mu2[[1]][[1]]), 1L) + expect_equal(length(obj$fit$top_index), nrow(obj$fit$alpha)) + for (l in seq_along(obj$fit$top_index)) { + expect_equal(obj$fit$top_index[l], which.max(obj$fit$alpha[l, ])) + } + }) +}) + +test_that("coef.mfsusie aggregated is numerically equivalent to complete", { + .timed("coef aggregated equiv", { + obj_full <- build_toy_fit(save_mu_method = "complete") + obj_ac <- build_toy_fit(save_mu_method = "aggregated") + expect_equal(coef(obj_full$fit), coef(obj_ac$fit), tolerance = 1e-12) + }) +}) + +test_that("coef.mfsusie lead diverges from complete (cheap-coef, biased)", { + .timed("coef lead divergence", { + obj_full <- build_toy_fit(save_mu_method = "complete") + obj_ld <- build_toy_fit(save_mu_method = "lead") + diff_max <- max(abs(coef(obj_full$fit)[[1]] - coef(obj_ld$fit)[[1]])) + expect_gt(diff_max, 1e-3) + }) +}) + +test_that("mf_post_smooth scalewise aggregated equals complete (1e-12)", { + .timed("post_smooth aggregated equiv", { + obj_full <- build_toy_fit(save_mu_method = "complete") + obj_ac <- build_toy_fit(save_mu_method = "aggregated") + sm_full <- mf_post_smooth(obj_full$fit, method = "scalewise") + sm_ac <- mf_post_smooth(obj_ac$fit, method = "scalewise") + expect_equal(sm_full$smoothed$scalewise$effect_curves, + sm_ac$smoothed$scalewise$effect_curves, + tolerance = 1e-12) + }) +}) + +test_that("mf_thin(complete, 'aggregated') matches a fresh aggregated fit", { + .timed("mf_thin aggregated equiv", { + obj_full <- build_toy_fit(save_mu_method = "complete") + obj_ac <- build_toy_fit(save_mu_method = "aggregated") + thinned <- mf_thin(obj_full$fit, method = "aggregated") + expect_equal(attr(thinned, "save_mu_method"), "aggregated") + expect_equal(coef(thinned), coef(obj_ac$fit), tolerance = 1e-12) + }) +}) + +test_that("mf_thin(complete, 'lead') matches a fresh lead fit", { + .timed("mf_thin lead equiv", { + obj_full <- build_toy_fit(save_mu_method = "complete") + obj_ld <- build_toy_fit(save_mu_method = "lead") + thinned <- mf_thin(obj_full$fit, method = "lead") + expect_equal(attr(thinned, "save_mu_method"), "lead") + expect_equal(thinned$top_index, obj_ld$fit$top_index) + expect_equal(coef(thinned), coef(obj_ld$fit), tolerance = 1e-12) + }) +}) + +test_that("predict.mfsusie(newx) errors on 1D modes; fitted() works", { + .timed("predict guard 1D", { + obj_ac <- build_toy_fit(save_mu_method = "aggregated") + obj_ld <- build_toy_fit(save_mu_method = "lead") + expect_error(predict(obj_ac$fit, newx = obj_ac$X), "save_mu_method") + expect_error(predict(obj_ld$fit, newx = obj_ld$X), "save_mu_method") + # fitted() reads fit$fitted directly, no per-variant mu needed. + expect_silent(fitted(obj_ac$fit)) + expect_silent(fitted(obj_ld$fit)) + }) +}) + +test_that("mfsusie(model_init = thinned_fit) errors with save_mu_method message", { + .timed("model_init guard 1D", { + obj_ac <- build_toy_fit(save_mu_method = "aggregated") + expect_error( + mfsusie(obj_ac$X, obj_ac$Y, L = 5L, max_iter = 1L, verbose = FALSE, + model_init = obj_ac$fit), + "save_mu_method") + }) +}) + +test_that("mf_thin() rejects re-thinning a fit that is already 1D", { + .timed("mf_thin rejects 1D", { + obj_ac <- build_toy_fit(save_mu_method = "aggregated") + expect_error(mf_thin(obj_ac$fit, method = "lead"), + "save_mu_method") + }) +}) diff --git a/vignettes/mfsusie_long_running_fits.Rmd b/vignettes/mfsusie_long_running_fits.Rmd index 1d6bf4f..7c2058c 100644 --- a/vignettes/mfsusie_long_running_fits.Rmd +++ b/vignettes/mfsusie_long_running_fits.Rmd @@ -214,26 +214,35 @@ continuation needs only a small increment past that point. ## Diagnostic tracing inside a long run -For loci that resist convergence, `track_fit = TRUE` keeps a -per-iteration snapshot list at `fit$trace`. Each entry records -`alpha` (the per-effect SNP posterior, `L x p`), `sigma2` (the -per-outcome residual variance), `pi_V` (the mixture weights), -and the iteration's ELBO. +For loci that resist convergence, `track_fit = TRUE` populates +`fit$trace` with the compact `susie_track` object that susieR +0.16.1+ emits. It is a single list of stacked tables: +`alpha` (long-format: one row per iteration / effect / variable +with `alpha >= 1/p`, thresholded for compactness), `effect` +(per-(iteration, effect) summaries including `V`, +`top_variable`, `top_alpha`, `effect_lbf`), and `iteration` +(per-iteration scalars: `sigma2`, `max_alpha`, `n_effects_kept`, +...). Iteration is 0-indexed. ```{r track-fit, fig.height = 4.5, fig.width = 8} fit_traced <- mfsusie(X, Y_signal, max_iter = 20, tol = 1e-3, verbose = FALSE, track_fit = TRUE) -length(fit_traced$trace) -str(fit_traced$trace[[1L]], max.level = 1L) +nrow(fit_traced$trace$iteration) +str(fit_traced$trace, max.level = 1L) # How quickly does the per-iteration posterior at each true -# causal SNP settle? +# causal SNP settle? `trace$alpha` stores only rows with +# alpha >= 1/p; below-threshold variables read as 0. true_snps <- c(37L, 88L) -alpha_path <- vapply(seq_along(fit_traced$trace), function(it) { - alpha_it <- fit_traced$trace[[it]]$alpha # L x p - vapply(true_snps, function(j) max(alpha_it[, j]), - numeric(1L)) +alpha_df <- fit_traced$trace$alpha +n_iter <- nrow(fit_traced$trace$iteration) +alpha_path <- vapply(seq(0L, n_iter - 1L), function(it) { + rows <- alpha_df[alpha_df$iteration == it, , drop = FALSE] + vapply(true_snps, function(j) { + vals <- rows$alpha[rows$variable == j] + if (length(vals) == 0L) 0 else max(vals) + }, numeric(1L)) }, numeric(2L)) rownames(alpha_path) <- paste0("SNP ", true_snps) @@ -263,9 +272,10 @@ par(mfrow = c(1L, 1L)) converge, looking at the alpha trajectories at candidate SNPs exposes whether the loop is oscillating between competing configurations or simply tightening posterior mass on a -single one. `track_fit = TRUE` is memory-heavy on real data -(each snapshot copies `alpha`, `sigma2`, `pi_V`); reserve it -for diagnostic runs rather than the genome-wide screen pass. +single one. `track_fit = TRUE` adds compact per-iteration +records via `susieR::make_track_snapshot()` (alpha entries are +thresholded at `1/p`); reserve it for diagnostic runs rather +than the genome-wide screen pass. ## Session info This is the version of R and the packages that were used to generate diff --git a/vignettes/post_processing.Rmd b/vignettes/post_processing.Rmd index 7326c58..a74c4ac 100644 --- a/vignettes/post_processing.Rmd +++ b/vignettes/post_processing.Rmd @@ -266,16 +266,154 @@ str(fit_ash$smoothed$ash$effect_curves[[1]][[1]]) `mf_post_smooth(method = "TI" | "HMM" | "ash", ...)` forwards extra arguments to `ashr::ash`; `method = "smash"` forwards to -`smashr::smash.gaus`. The most useful knob is `nullweight`, -which controls how aggressively the null component is preferred: +`smashr::smash.gaus`. The most useful knob is ash's own +`nullweight` (not to be confused with `mfsusie()`'s +`mixture_null_weight`, which controls the IBSS prior fit): ```{r nullweight-passthrough} fit_strong <- mf_post_smooth(fit, method = "ash", nullweight = 300) ``` -Higher `nullweight` shrinks effects toward zero more -aggressively and is the right choice on noisy fixtures with -weak signal. +Higher `nullweight` (the post-smoother knob) shrinks the +smoothed effect curves toward zero more aggressively and is the +right choice on noisy fixtures with weak signal. + +## Storage modes: `save_mu_method` + +`mfsusie()` stores per-effect, per-outcome posterior moments in +`fit$mu` and `fit$mu2`. Under the default `save_mu_method = +"complete"` these are `p x T_basis[m]` matrices, which can +dominate the fit's footprint when `p` is large. Two thinning +modes shrink them by roughly factor `p`, with different +trade-offs: + +| Mode | `mu[[l]][[m]]` shape | `coef` / `mf_post_smooth` numerics | `predict(newx)` | per-variant lfsr | warm-start (`model_init`) | +|---|---|---|---|---|---| +| `"complete"` (default) | `p x T_basis[m]` | exact | works | works | works | +| `"aggregated"` | `1 x T_basis[m]` (= `alpha %*% mu_full`) | numerically equivalent to `"complete"` (1e-12) | errors | errors | errors | +| `"lead"` | `1 x T_basis[m]` (= `mu_full[j*, ]`, `j* = which.max(alpha[l, ])`) | cheap lead-variable summary, biased toward the lead | errors | errors | errors | + +The two 1D modes drop the per-variant axis the SER step needs, +so they are not valid as `model_init` checkpoints. To get both +a small distribution copy and a warm-start checkpoint, fit +once in `"complete"` and use `mf_thin()`. The `saveRDS` calls +below write to a temp directory so the vignette does not leave +files in the working directory. + +```{r save-mu-method-thin} +checkpoint_path <- tempfile("mfsusie_checkpoint_", fileext = ".rds") +distribution_path <- tempfile("mfsusie_distribution_", fileext = ".rds") +fit_full <- fit # complete, warm-start ready +fit_thin <- mfsusieR:::mf_thin(fit_full, method = "aggregated") +saveRDS(fit_full, checkpoint_path) # for resuming a fit +saveRDS(fit_thin, distribution_path) # ~factor p smaller +``` + +`coef(fit_thin)` and `mf_post_smooth(fit_thin, method = ...)` +return the same numbers as `coef(fit_full)` and +`mf_post_smooth(fit_full, ...)` to numerical precision under +`"aggregated"`, and the cheap lead-variable summary under +`"lead"`. + +### Inspecting the structure + +To see the three storage shapes side by side, fit the same data +under each mode and look at the per-(effect, outcome) slot. + +```{r save-mu-method-build, message = FALSE, warning = FALSE} +fit_complete <- fsusie(Y, X, pos = positions, save_mu_method = "complete") +fit_alpha <- fsusie(Y, X, pos = positions, save_mu_method = "aggregated") +fit_lead <- fsusie(Y, X, pos = positions, save_mu_method = "lead") + +# "complete": fit$mu[[l]][[m]] is p x T_basis[m]. fit$top_index +# and fit$coef_wavelet are absent. +str(fit_complete$mu[[1]][[1]]) + +# "aggregated": fit$mu[[l]][[m]] and fit$mu2[[l]][[m]] are +# 1 x T_basis[m] (= alpha %*% mu_full / mu2_full). fit$coef_wavelet +# carries the raw-X coef summary 1 x T_basis[m]. +str(fit_alpha$mu[[1]][[1]]) +str(fit_alpha$coef_wavelet[[1]][[1]]) + +# "lead": fit$mu[[l]][[m]] is the lead row mu_full[j*, ]; +# fit$top_index[l] = j* = which.max(alpha[l, ]). +str(fit_lead$mu[[1]][[1]]) +fit_lead$top_index +``` + +The `attr(fit, "save_mu_method")` attribute records which mode +generated the object so downstream code (`coef.mfsusie`, +`mf_post_smooth`, the `model_init` guard, `mf_thin`) can dispatch +without re-inferring from shape. + +```{r save-mu-method-attrs} +sapply(list(complete = fit_complete, + aggregated = fit_alpha, + lead = fit_lead), + function(f) attr(f, "save_mu_method")) +``` + +The storage-size payoff is tangible: `object.size()` and the +on-disk `.rds` size both shrink by roughly factor `p` (modulo +the constant overhead from `alpha`, `lbf_variable`, `fitted`, +etc., which are unchanged across modes). + +```{r save-mu-method-sizes} +ac_path <- tempfile("mfsusieR_aggregated_", fileext = ".rds") +ld_path <- tempfile("mfsusieR_lead_", fileext = ".rds") +co_path <- tempfile("mfsusieR_complete_", fileext = ".rds") +saveRDS(fit_complete, co_path) +saveRDS(fit_alpha, ac_path) +saveRDS(fit_lead, ld_path) + +size_table <- data.frame( + mode = c("complete", "aggregated", "lead"), + object_mb = c(object.size(fit_complete), + object.size(fit_alpha), + object.size(fit_lead)) / (1024 * 1024), + rds_kb = c(file.size(co_path), + file.size(ac_path), + file.size(ld_path)) / 1024, + has_top_index = c(FALSE, + !is.null(fit_alpha$top_index), + !is.null(fit_lead$top_index)), + has_coef_wavelet = c(FALSE, + !is.null(fit_alpha$coef_wavelet), + !is.null(fit_lead$coef_wavelet)) +) +size_table +``` + +The `coef()` outputs match exactly between `"complete"` and +`"aggregated"` (lossless for the coef path); `"lead"` +diverges by the cheap-coef bias. + +```{r save-mu-method-coef-equiv} +max_abs_diff_alpha <- max(abs(coef(fit_complete)[[1]] - coef(fit_alpha)[[1]])) +max_abs_diff_lead <- max(abs(coef(fit_complete)[[1]] - coef(fit_lead)[[1]])) +c(complete_vs_aggregated = max_abs_diff_alpha, + complete_vs_lead = max_abs_diff_lead) +``` + +The shrink ratio in the table above (~2x in-memory, ~1.85x on-disk +at the vignette's `p = 150`) is small only because the constant +overhead of the non-mu fields (`alpha`, `lbf_variable`, `fitted`, +`dwt_meta`, ...) dominates at this scale. At realistic +fine-mapping `p = 500-3000` the ratio approaches the theoretical +factor `p`. For example a separate p = 500 / T = 64 / M = 2 / L = 5 +fixture saved at 7.8 MB under `"complete"` and 0.50 MB under +`"aggregated"` (15.7x in-memory, 12.9x on disk). + +The `coef()` zero-difference between `"complete"` and `"lead"` on +this fixture is also a small-`p` artifact: every credible set +collapses onto a single lead variable with purity ~1 here, so +`mu[j*, ]` equals `alpha %*% mu_full` to floating-point precision. +On a CS with multiple high-alpha members the `"lead"` mode does +diverge from `"complete"` (the cheap-coef bias the docstring +warns about); see the +`inst/notes/sessions/2026-05-03-2314-per-scale-normal-baseline-results.md` +benchmark, where the same comparison at p = 500 gives +`max abs diff = 0.108`. ### Per-CS lfsr bubble grid