Nothing
# ============================================================================ #
# Outcome PDF Functions (Log-Likelihood)
# ============================================================================ #
#
# These functions compute pointwise log-likelihoods for each observation and
# posterior sample. They are used for LOO-PSIS diagnostics and model comparison.
#
# Parallels the structure of rng.R but returns density values instead of
# random samples.
#
# Note: For binomial and Poisson models, each "observation" consists of a pair
# of data points (ai+ci or x1i+x2i) that together define a single effect size
# estimate. The log-likelihood for the observation is the sum of log-likelihoods
# for both components.
#
# ============================================================================ #
# ---------------------------------------------------------------------------- #
# .rowLogSumExps
# ---------------------------------------------------------------------------- #
#
# Compute log(sum(exp(x))) for each row of a matrix, using the log-sum-exp
# trick for numerical stability.
#
# @param lx numeric matrix of log-values
#
# @return numeric vector of length nrow(lx)
#
# ---------------------------------------------------------------------------- #
.rowLogSumExps <- function(lx) {
if (!is.matrix(lx)) {
lx <- as.matrix(lx)
}
if (!anyNA(lx)) {
row_max <- lx[cbind(seq_len(nrow(lx)), max.col(lx, ties.method = "first"))]
out <- row_max
finite_rows <- is.finite(row_max)
if (any(finite_rows)) {
out[finite_rows] <- row_max[finite_rows] +
log(rowSums(exp(lx[finite_rows, , drop = FALSE] - row_max[finite_rows])))
}
return(out)
}
has_value <- rowSums(!is.na(lx)) > 0
lx[is.na(lx)] <- -Inf
row_max <- lx[cbind(seq_len(nrow(lx)), max.col(lx, ties.method = "first"))]
out <- rep(NA_real_, nrow(lx))
infinite_rows <- has_value & is.infinite(row_max)
out[infinite_rows] <- row_max[infinite_rows]
finite_rows <- has_value & is.finite(row_max)
if (any(finite_rows)) {
out[finite_rows] <- row_max[finite_rows] +
log(rowSums(exp(lx[finite_rows, , drop = FALSE] - row_max[finite_rows])))
}
return(out)
}
# ---------------------------------------------------------------------------- #
# .apply_log_lik_weights
# ---------------------------------------------------------------------------- #
#
# Match JAGS weighted-density semantics by multiplying log-likelihood
# contributions by the user-supplied data weights.
#
# @param log_lik S x K matrix of log-likelihood values.
# @param weights numeric vector of length K, or NULL.
#
# @return weighted log-likelihood matrix.
#
# ---------------------------------------------------------------------------- #
.apply_log_lik_weights <- function(log_lik, weights) {
if (is.null(weights)) {
return(log_lik)
}
if (length(weights) != ncol(log_lik)) {
stop("Data weights length does not match the log-likelihood matrix.",
call. = FALSE)
}
return(sweep(log_lik, 2, weights, "*"))
}
# ---------------------------------------------------------------------------- #
# .get_log_lik_data_weights
# ---------------------------------------------------------------------------- #
#
# @param object brma object.
#
# @return numeric vector of data weights, or NULL.
#
# ---------------------------------------------------------------------------- #
.get_log_lik_data_weights <- function(object) {
if (!.is_weights(object)) {
return(NULL)
}
return(object[["data"]][["outcome"]][["weights"]])
}
# ---------------------------------------------------------------------------- #
# .glmm_likelihood_weights
# ---------------------------------------------------------------------------- #
#
# Validate and default GLMM pair likelihood weights.
#
# ---------------------------------------------------------------------------- #
.glmm_likelihood_weights <- function(weights, K) {
if (is.null(weights)) {
return(rep(1, K))
}
BayesTools::check_real(
weights, "weights",
check_length = K, allow_NULL = FALSE, allow_NA = FALSE,
lower = 0, allow_bound = FALSE
)
return(as.numeric(weights))
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.norm
# ---------------------------------------------------------------------------- #
#
# Compute pointwise log-likelihoods for normal outcome models.
#
# For normal outcome models, the observed effect y_i has likelihood:
# y_i ~ N(mu_i, tau_within^2 + se_i^2)
#
# This function computes the log-density for each observation at each posterior
# sample.
#
# @param yi numeric vector of length K; observed effect sizes
# @param mu_samples S x K matrix of location samples (with cluster effects if multilevel)
# @param tau_within S x K matrix of estimate-level heterogeneity samples
# @param sei numeric vector of length K; standard errors
#
# @return S x K matrix of log-likelihood values
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.norm <- function(yi, mu_samples, tau_within, sei) {
S <- nrow(mu_samples)
K <- ncol(mu_samples)
# replicate yi and sei across samples: matrix(vec, S, K, byrow = TRUE)
yi_mat <- matrix(yi, nrow = S, ncol = K, byrow = TRUE)
sei_mat <- matrix(sei, nrow = S, ncol = K, byrow = TRUE)
# compute total SD: sqrt(tau^2 + se^2)
total_sd <- sqrt(tau_within^2 + sei_mat^2)
# compute log-likelihood for each cell
log_lik <- stats::dnorm(yi_mat, mean = mu_samples, sd = total_sd, log = TRUE)
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.selnorm
# ---------------------------------------------------------------------------- #
#
# Selected-normal likelihood using the compiled p-bin kernel.
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.selnorm <- function(yi, mu_samples, tau_within, sei,
selection_context, weights = NULL) {
S <- nrow(mu_samples)
K <- ncol(mu_samples)
sei_mat <- matrix(sei, nrow = S, ncol = K, byrow = TRUE)
total_sd <- sqrt(tau_within^2 + sei_mat^2)
return(.selnorm_kernel_loglik_matrix(
yi = yi,
mu_num = mu_samples,
sigma_num = total_sd,
mu_norm = mu_samples,
sigma_norm = total_sd,
sei = sei,
omega = selection_context[["omega"]],
selection_spec = selection_context,
alpha = selection_context[["alpha"]],
phack_kind = selection_context[["phack_kind"]],
kernel_mode = selection_context[["kernel_mode"]],
weights = weights
))
}
# ---------------------------------------------------------------------------- #
# .has_native_glmm
# ---------------------------------------------------------------------------- #
#
# @param outcome_type character; GLMM outcome type, either "bin" or "pois".
#
# @return TRUE when native GLMM likelihood kernels are available for the
# requested outcome type.
#
# ---------------------------------------------------------------------------- #
.has_native_glmm <- function(outcome_type) {
if (outcome_type == "bin") {
return(is.loaded("RoBMA_glmm_binom_marginal_loglik", PACKAGE = "RoBMA"))
}
if (outcome_type == "pois") {
return(is.loaded("RoBMA_glmm_pois_marginal_loglik", PACKAGE = "RoBMA"))
}
stop("Unknown GLMM outcome type.", call. = FALSE)
}
.has_native_glmm_cluster <- function() {
return(
is.loaded("RoBMA_glmm_binom_cluster_loglik", PACKAGE = "RoBMA") &&
is.loaded("RoBMA_glmm_pois_cluster_loglik", PACKAGE = "RoBMA")
)
}
# ---------------------------------------------------------------------------- #
# .cluster_indices_flatten
# ---------------------------------------------------------------------------- #
#
# Flatten a cluster index list while preserving list order.
#
# ---------------------------------------------------------------------------- #
.cluster_indices_flatten <- function(cluster_indices) {
return(list(
index = as.integer(unlist(cluster_indices, use.names = FALSE)),
size = as.integer(lengths(cluster_indices))
))
}
# ---------------------------------------------------------------------------- #
# .gauss_legendre_nodes
# ---------------------------------------------------------------------------- #
#
# Compute Gauss-Legendre quadrature nodes and weights on (0, 1).
#
# ---------------------------------------------------------------------------- #
.gauss_legendre_nodes_cache <- new.env(parent = emptyenv())
.gauss_legendre_nodes <- function(n) {
key <- as.character(n)
if (exists(key, envir = .gauss_legendre_nodes_cache, inherits = FALSE)) {
return(get(key, envir = .gauss_legendre_nodes_cache, inherits = FALSE))
}
i <- seq_len(n - 1)
b <- i / sqrt(4 * i^2 - 1)
J <- diag(0, n)
J[cbind(i, i + 1)] <- b
J[cbind(i + 1, i)] <- b
eig <- eigen(J, symmetric = TRUE)
nodes_raw <- eig$values
weights_raw <- 2 * eig$vectors[1, ]^2
nodes <- (nodes_raw + 1) / 2
weights <- weights_raw / 2
ord <- order(nodes)
out <- list(
nodes = nodes[ord],
weights = weights[ord],
log_weights = log(weights[ord])
)
assign(key, out, envir = .gauss_legendre_nodes_cache)
return(out)
}
# ---------------------------------------------------------------------------- #
# .glmm_prior_quantile_grid
# ---------------------------------------------------------------------------- #
#
# Construct quadrature nodes under a prior using the probability integral
# transform. The returned weights integrate with respect to the prior measure.
#
# ---------------------------------------------------------------------------- #
.glmm_prior_quantile_grid <- function(prior, n) {
gl <- .gauss_legendre_nodes(n)
q_grid <- as.numeric(BayesTools::quant(prior, gl[["nodes"]]))
if (anyNA(q_grid) || any(!is.finite(q_grid))) {
stop("The GLMM nuisance prior produced non-finite quadrature nodes.",
call. = FALSE)
}
return(list(
grid = q_grid,
log_weights = gl[["log_weights"]]
))
}
# ---------------------------------------------------------------------------- #
# .glmm_binom_logit_pi_grid
# ---------------------------------------------------------------------------- #
#
# Construct prior-scale nuisance grids for binomial GLMM likelihoods.
#
# ---------------------------------------------------------------------------- #
.glmm_binom_logit_pi_grid <- function(ai, ci, n1i, n2i, prior_pi, n_pi) {
K <- length(ai)
pi_grid <- .glmm_prior_quantile_grid(prior = prior_pi, n = n_pi)
if (any(pi_grid[["grid"]] < 0 | pi_grid[["grid"]] > 1)) {
stop("The binomial GLMM baserate prior produced nodes outside [0, 1].",
call. = FALSE)
}
pi_nodes <- pmin(pmax(pi_grid[["grid"]], .Machine$double.eps),
1 - .Machine$double.eps)
logit_pi_grid <- matrix(qlogis(pi_nodes), nrow = n_pi, ncol = K)
log_pi_weights <- matrix(pi_grid[["log_weights"]], nrow = n_pi, ncol = K)
return(list(
grid = logit_pi_grid,
log_weights = log_pi_weights
))
}
# ---------------------------------------------------------------------------- #
# .glmm_pois_log_phi_grid
# ---------------------------------------------------------------------------- #
#
# Construct prior-scale nuisance grids for Poisson GLMM likelihoods.
#
# ---------------------------------------------------------------------------- #
.glmm_pois_log_phi_grid <- function(x1i, x2i, t1i, t2i, prior_phi, n_phi) {
K <- length(x1i)
phi_grid <- .glmm_prior_quantile_grid(prior = prior_phi, n = n_phi)
log_phi_grid <- matrix(phi_grid[["grid"]], nrow = n_phi, ncol = K)
log_phi_weights <- matrix(phi_grid[["log_weights"]], nrow = n_phi, ncol = K)
return(list(
grid = log_phi_grid,
log_weights = log_phi_weights
))
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.binom
# ---------------------------------------------------------------------------- #
#
# Native wrapper for binomial GLMM marginal log-likelihoods.
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.binom <- function(ai, ci, n1i, n2i, mu_samples, tau_within, prior_pi,
weights = NULL, n_theta = 15, n_pi = 30) {
if (!.has_native_glmm("bin")) {
return(.outcome_pdf.binom_r(
ai = ai,
ci = ci,
n1i = n1i,
n2i = n2i,
mu_samples = mu_samples,
tau_within = tau_within,
prior_pi = prior_pi,
weights = weights,
n_theta = n_theta,
n_pi = n_pi
))
}
gh <- .gauss_hermite_nodes(n_theta)
pi_grid <- .glmm_binom_logit_pi_grid(
ai = ai,
ci = ci,
n1i = n1i,
n2i = n2i,
prior_pi = prior_pi,
n_pi = n_pi
)
weights_arg <- if (is.null(weights)) NULL else .native_numeric_vector(
.glmm_likelihood_weights(weights, ncol(mu_samples))
)
return(.Call(
"RoBMA_glmm_binom_marginal_loglik",
.native_integer_vector(ai),
.native_integer_vector(ci),
.native_integer_vector(n1i),
.native_integer_vector(n2i),
.native_numeric_matrix(mu_samples),
.native_numeric_matrix(tau_within),
weights_arg,
.native_numeric_vector(gh[["nodes"]]),
.native_numeric_vector(gh[["log_weights"]]),
.native_numeric_matrix(pi_grid[["grid"]]),
.native_numeric_matrix(pi_grid[["log_weights"]]),
PACKAGE = "RoBMA"
))
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.binom
# ---------------------------------------------------------------------------- #
#
# Compute pointwise marginal log-likelihoods for binomial outcome models.
#
# For binomial GLMM models, we need to compute the marginal likelihood by
# integrating over the latent parameters (theta, pi):
# log p(data) = log ∫∫ p(ai, ci | theta, pi, mu) p(theta) p(pi) d(theta) d(pi)
#
# This function uses Gauss-Hermite quadrature for the theta dimension (N(0,1)
# prior) which requires far fewer points than quantile spacing for similar
# accuracy. The pi dimension uses Gauss-Legendre quadrature on the prior CDF
# scale, so the integration covers the full baserate prior support.
#
# The integration over samples is vectorized for efficiency: instead of looping
# over S samples, we compute all samples simultaneously using matrix operations.
#
# Performance optimizations:
# - Gauss-Hermite quadrature for theta (15 points vs 30 for same accuracy)
# - Pre-computed log-binomial coefficients (avoids repeated lgamma calls)
# - Direct log-likelihood calculation without dbinom overhead
# - Log-sum-exp trick for numerical stability
# - Minimized memory allocations in inner loop
#
# @param ai integer vector of length K; events in treatment group
# @param ci integer vector of length K; events in control group
# @param n1i integer vector of length K; treatment group sizes
# @param n2i integer vector of length K; control group sizes
# @param mu_samples S x K matrix of log-odds ratio samples (without theta contribution)
# @param tau_within S x K matrix of estimate-level heterogeneity samples
# @param prior_pi BayesTools prior object for pi
# @param n_theta integer; number of Gauss-Hermite points for theta (default: 15)
# @param n_pi integer; number of grid points for pi dimension (default: 30)
#
# @return S x K matrix of marginal log-likelihood values (one per estimate)
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.binom_r <- function(ai, ci, n1i, n2i, mu_samples, tau_within, prior_pi,
weights = NULL, n_theta = 15, n_pi = 30) {
S <- nrow(mu_samples)
K <- ncol(mu_samples)
weights <- .glmm_likelihood_weights(weights, K)
# initialize output matrix
log_lik <- matrix(NA_real_, nrow = S, ncol = K)
# --- 1. SETUP THETA GRID (Gauss-Hermite Quadrature) ---
gh <- .gauss_hermite_nodes(n_theta)
theta_grid <- gh$nodes
log_theta_weights <- gh$log_weights
pi_quadrature <- .glmm_binom_logit_pi_grid(
ai = ai,
ci = ci,
n1i = n1i,
n2i = n2i,
prior_pi = prior_pi,
n_pi = n_pi
)
# Expand theta grid for vectorized computation
theta_expanded <- rep(theta_grid, times = n_pi) # length G
# --- 2. PRE-COMPUTE BINOMIAL COEFFICIENTS (Outside Loop) ---
log_binom_coef_a <- lgamma(n1i + 1) - lgamma(ai + 1) - lgamma(n1i - ai + 1)
log_binom_coef_c <- lgamma(n2i + 1) - lgamma(ci + 1) - lgamma(n2i - ci + 1)
# --- 3. LOOP OVER OBSERVATIONS ---
for (k in seq_len(K)) {
lpi_grid <- pi_quadrature[["grid"]][, k]
log_pi_weights <- pi_quadrature[["log_weights"]][, k]
# Create log-weight vector for all grid points
log_weights <- as.vector(outer(log_theta_weights, log_pi_weights, "+"))
# Expand pi grid
lpi_expanded <- rep(lpi_grid, each = n_theta)
# --- 4. VECTORIZED INTEGRATION ---
effect_mat <- mu_samples[, k] + outer(tau_within[, k], theta_expanded)
half_effect <- 0.5 * effect_mat
logit_p1 <- sweep(half_effect, 2, lpi_expanded, "+")
logit_p2 <- sweep(-half_effect, 2, lpi_expanded, "+")
log_p1 <- plogis(logit_p1, log.p = TRUE)
log_1mp1 <- plogis(logit_p1, lower.tail = FALSE, log.p = TRUE)
log_p2 <- plogis(logit_p2, log.p = TRUE)
log_1mp2 <- plogis(logit_p2, lower.tail = FALSE, log.p = TRUE)
log_lik_grid <- log_binom_coef_a[k] + ai[k] * log_p1 + (n1i[k] - ai[k]) * log_1mp1 +
log_binom_coef_c[k] + ci[k] * log_p2 + (n2i[k] - ci[k]) * log_1mp2
log_lik_grid <- weights[k] * log_lik_grid
log_terms <- sweep(log_lik_grid, 2, log_weights, "+")
log_lik[, k] <- .rowLogSumExps(log_terms)
}
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.binom_conditional
# ---------------------------------------------------------------------------- #
#
# Compute conditional log-likelihoods for binomial outcome models.
#
# This path is used by bridge sampling where pi and theta are sampled model
# parameters and should not be integrated out.
#
# @param ai integer vector of length K; events in treatment group
# @param ci integer vector of length K; events in control group
# @param n1i integer vector of length K; treatment group sizes
# @param n2i integer vector of length K; control group sizes
# @param mu_samples S x K matrix of log-odds ratio samples
# @param logit_baserate S x K matrix of logit base-rate samples
#
# @return S x K matrix of conditional log-likelihood values
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.binom_conditional <- function(ai, ci, n1i, n2i, mu_samples,
logit_baserate) {
S <- nrow(mu_samples)
K <- ncol(mu_samples)
logit_p1 <- logit_baserate + 0.5 * mu_samples
logit_p2 <- logit_baserate - 0.5 * mu_samples
log_lik <- matrix(
stats::dbinom(
x = rep(ai, each = S),
size = rep(n1i, each = S),
prob = as.vector(.inv_logit(logit_p1)),
log = TRUE
) + stats::dbinom(
x = rep(ci, each = S),
size = rep(n2i, each = S),
prob = as.vector(.inv_logit(logit_p2)),
log = TRUE
),
nrow = S,
ncol = K
)
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .gauss_hermite_nodes
# ---------------------------------------------------------------------------- #
#
# Compute Gauss-Hermite quadrature nodes and weights for standard normal.
#
# Gauss-Hermite quadrature integrates ∫ f(x) exp(-x²) dx exactly for polynomials
# up to degree 2n-1. For the standard normal N(0,1), we transform:
# ∫ f(z) * (1/√(2π)) * exp(-z²/2) dz
#
# Using substitution u = z/√2, this becomes:
# (1/√π) ∫ f(√2 * u) * exp(-u²) du
#
# So for N(0,1): nodes = √2 * GH_nodes, and we need to normalize weights
# so they sum to 1 (representing probability mass).
#
# This function uses the Golub-Welsch algorithm (eigenvalue method).
#
# @param n integer; number of quadrature points
#
# @return list with components:
# - nodes: n quadrature points (transformed for N(0,1))
# - weights: n quadrature weights (sum to 1 for N(0,1))
# - log_weights: log of weights for numerical stability
#
# ---------------------------------------------------------------------------- #
.gauss_hermite_nodes_cache <- new.env(parent = emptyenv())
.gauss_hermite_nodes <- function(n) {
key <- as.character(n)
if (exists(key, envir = .gauss_hermite_nodes_cache, inherits = FALSE)) {
return(get(key, envir = .gauss_hermite_nodes_cache, inherits = FALSE))
}
# Golub-Welsch algorithm: eigenvalues of symmetric tridiagonal matrix
# For Gauss-Hermite (physicist's), the recurrence coefficients are:
# α_k = 0, β_k = k/2 for k = 1, ..., n-1
# The Jacobi matrix has off-diagonal elements √β_k = √(k/2)
i <- seq_len(n - 1)
b <- sqrt(i / 2)
# Build symmetric tridiagonal matrix
H <- diag(0, n)
H[cbind(i, i + 1)] <- b
H[cbind(i + 1, i)] <- b
# Eigendecomposition
eig <- eigen(H, symmetric = TRUE)
# Nodes are eigenvalues
# Weights: w_i = μ₀ * (first component of eigenvector i)²
# where μ₀ = ∫ exp(-x²) dx = √π
nodes_raw <- eig$values
weights_raw <- sqrt(pi) * eig$vectors[1, ]^2
# Transform for standard normal N(0,1):
# ∫ f(z) φ(z) dz = (1/√π) ∫ f(√2 u) exp(-u²) du ≈ (1/√π) Σ w_i f(√2 u_i)
# So: nodes = √2 * raw_nodes, weights = raw_weights / √π
nodes <- sqrt(2) * nodes_raw
weights <- weights_raw / sqrt(pi)
# Verify: weights should sum to 1
# (The raw weights sum to √π because μ₀ = √π and Σ v_{1i}² = 1 for orthonormal eigenvectors)
# Sort by nodes (eigen returns in decreasing order of eigenvalue)
ord <- order(nodes)
out <- list(
nodes = nodes[ord],
weights = weights[ord],
log_weights = log(weights[ord])
)
assign(key, out, envir = .gauss_hermite_nodes_cache)
return(out)
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.pois
# ---------------------------------------------------------------------------- #
#
# Native wrapper for Poisson GLMM marginal log-likelihoods.
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.pois <- function(x1i, x2i, t1i, t2i, mu_samples, tau_within, prior_phi,
weights = NULL, n_theta = 15, n_phi = 30) {
if (!.has_native_glmm("pois")) {
return(.outcome_pdf.pois_r(
x1i = x1i,
x2i = x2i,
t1i = t1i,
t2i = t2i,
mu_samples = mu_samples,
tau_within = tau_within,
prior_phi = prior_phi,
weights = weights,
n_theta = n_theta,
n_phi = n_phi
))
}
gh <- .gauss_hermite_nodes(n_theta)
phi_grid <- .glmm_pois_log_phi_grid(
x1i = x1i,
x2i = x2i,
t1i = t1i,
t2i = t2i,
prior_phi = prior_phi,
n_phi = n_phi
)
weights_arg <- if (is.null(weights)) NULL else .native_numeric_vector(
.glmm_likelihood_weights(weights, ncol(mu_samples))
)
return(.Call(
"RoBMA_glmm_pois_marginal_loglik",
.native_integer_vector(x1i),
.native_integer_vector(x2i),
.native_numeric_vector(t1i),
.native_numeric_vector(t2i),
.native_numeric_matrix(mu_samples),
.native_numeric_matrix(tau_within),
weights_arg,
.native_numeric_vector(gh[["nodes"]]),
.native_numeric_vector(gh[["log_weights"]]),
.native_numeric_matrix(phi_grid[["grid"]]),
.native_numeric_matrix(phi_grid[["log_weights"]]),
PACKAGE = "RoBMA"
))
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.pois
# ---------------------------------------------------------------------------- #
#
# Compute pointwise marginal log-likelihoods for Poisson outcome models.
#
# For Poisson GLMM models, we need to compute the marginal likelihood by
# integrating over the latent parameters (theta, phi):
# log p(data) = log ∫∫ p(x1i, x2i | theta, phi, mu) p(theta) p(phi) d(theta) d(phi)
#
# This function uses Gauss-Hermite quadrature for the theta dimension (N(0,1)
# prior) which requires far fewer points than quantile spacing for similar
# accuracy. The phi dimension uses Gauss-Legendre quadrature on the prior CDF
# scale, so the integration covers the full log-rate prior support.
#
# The integration over samples is vectorized for efficiency: instead of looping
# over S samples, we compute all samples simultaneously using matrix operations.
#
# Performance optimizations:
# - Gauss-Hermite quadrature for theta (15 points vs 30 for same accuracy)
# - Pre-computed log-factorial terms (avoids repeated lgamma calls)
# - Direct log-likelihood calculation without dpois overhead
# - Log-sum-exp trick for numerical stability
# - Minimized memory allocations in inner loop
#
# @param x1i integer vector of length K; events in treatment group
# @param x2i integer vector of length K; events in control group
# @param t1i numeric vector of length K; treatment exposure times
# @param t2i numeric vector of length K; control exposure times
# @param mu_samples S x K matrix of log incidence rate ratio samples (without theta contribution)
# @param tau_within S x K matrix of estimate-level heterogeneity samples
# @param prior_phi BayesTools prior object for phi
# @param n_theta integer; number of Gauss-Hermite points for theta (default: 15)
# @param n_phi integer; number of grid points for phi dimension (default: 30)
#
# @return S x K matrix of marginal log-likelihood values (one per estimate)
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.pois_r <- function(x1i, x2i, t1i, t2i, mu_samples, tau_within, prior_phi,
weights = NULL, n_theta = 15, n_phi = 30) {
S <- nrow(mu_samples)
K <- ncol(mu_samples)
weights <- .glmm_likelihood_weights(weights, K)
# initialize output matrix
log_lik <- matrix(NA_real_, nrow = S, ncol = K)
# --- 1. SETUP THETA GRID (Gauss-Hermite Quadrature) ---
gh <- .gauss_hermite_nodes(n_theta)
theta_grid <- gh$nodes
log_theta_weights <- gh$log_weights
phi_quadrature <- .glmm_pois_log_phi_grid(
x1i = x1i,
x2i = x2i,
t1i = t1i,
t2i = t2i,
prior_phi = prior_phi,
n_phi = n_phi
)
# Expand theta grid for vectorized computation
theta_expanded <- rep(theta_grid, times = n_phi) # length G
# --- 2. PRE-COMPUTE LOG-FACTORIAL TERMS (Outside Loop) ---
log_factorial_x1 <- lgamma(x1i + 1)
log_factorial_x2 <- lgamma(x2i + 1)
log_t1i <- log(t1i)
log_t2i <- log(t2i)
# --- 3. LOOP OVER OBSERVATIONS ---
for (k in seq_len(K)) {
phi_grid <- phi_quadrature[["grid"]][, k]
log_phi_weights <- phi_quadrature[["log_weights"]][, k]
# Create log-weight vector for all grid points
log_weights <- as.vector(outer(log_theta_weights, log_phi_weights, "+"))
# Expand phi grid
phi_expanded <- rep(phi_grid, each = n_theta)
# --- 4. VECTORIZED INTEGRATION ---
effect_mat <- mu_samples[, k] + outer(tau_within[, k], theta_expanded)
half_effect <- 0.5 * effect_mat
log_lambda1 <- sweep(half_effect, 2, phi_expanded + log_t1i[k], "+")
log_lambda2 <- sweep(-half_effect, 2, phi_expanded + log_t2i[k], "+")
log_lik_grid <- x1i[k] * log_lambda1 - exp(log_lambda1) - log_factorial_x1[k] +
x2i[k] * log_lambda2 - exp(log_lambda2) - log_factorial_x2[k]
log_lik_grid <- weights[k] * log_lik_grid
log_terms <- sweep(log_lik_grid, 2, log_weights, "+")
log_lik[, k] <- .rowLogSumExps(log_terms)
}
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .outcome_pdf.pois_conditional
# ---------------------------------------------------------------------------- #
#
# Compute conditional log-likelihoods for Poisson outcome models.
#
# This path is used by bridge sampling where phi and theta are sampled model
# parameters and should not be integrated out.
#
# @param x1i integer vector of length K; treatment events
# @param x2i integer vector of length K; control events
# @param t1i numeric vector of length K; treatment exposure times
# @param t2i numeric vector of length K; control exposure times
# @param mu_samples S x K matrix of log incidence rate ratio samples
# @param log_phi S x K matrix of midpoint log-rate samples
#
# @return S x K matrix of conditional log-likelihood values
#
# ---------------------------------------------------------------------------- #
.outcome_pdf.pois_conditional <- function(x1i, x2i, t1i, t2i, mu_samples,
log_phi) {
S <- nrow(mu_samples)
K <- ncol(mu_samples)
log_t1i <- matrix(log(t1i), nrow = S, ncol = K, byrow = TRUE)
log_t2i <- matrix(log(t2i), nrow = S, ncol = K, byrow = TRUE)
log_lambda1 <- log_phi + 0.5 * mu_samples + log_t1i
log_lambda2 <- log_phi - 0.5 * mu_samples + log_t2i
log_lik <- matrix(
stats::dpois(
x = rep(x1i, each = S),
lambda = as.vector(exp(log_lambda1)),
log = TRUE
) + stats::dpois(
x = rep(x2i, each = S),
lambda = as.vector(exp(log_lambda2)),
log = TRUE
),
nrow = S,
ncol = K
)
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .get_cluster_likelihood_n_gamma
# ---------------------------------------------------------------------------- #
#
# @return integer; number of Gauss-Hermite nodes for cluster likelihoods.
#
# ---------------------------------------------------------------------------- #
.get_cluster_likelihood_n_gamma <- function() {
n_gamma <- RoBMA.get_option("cluster_likelihood.n_gamma")
BayesTools::check_int(n_gamma, "cluster_likelihood.n_gamma", lower = 3)
return(as.integer(n_gamma))
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_gamma_quadrature
# ---------------------------------------------------------------------------- #
#
# Integrate the held-out cluster effect gamma_g with Gauss-Hermite quadrature.
#
# @param cluster_indices list of cluster index vectors.
# @param mu_samples S x K matrix of fixed-effect means.
# @param tau_between_samples S x K matrix of cluster-level SDs.
# @param log_lik_fun function(idx, mu_node) returning S x length(idx)
# conditional log-likelihood matrix.
# @param weights optional data weights.
# @param n_gamma number of Gauss-Hermite nodes.
#
# @return S x G cluster-unit log-likelihood matrix.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_gamma_quadrature <- function(cluster_indices, mu_samples,
tau_between_samples, log_lik_fun,
weights = NULL,
n_gamma = .get_cluster_likelihood_n_gamma()) {
gh <- .gauss_hermite_nodes(n_gamma)
S <- nrow(mu_samples)
G <- length(cluster_indices)
log_lik <- matrix(NA_real_, nrow = S, ncol = G)
for (g in seq_along(cluster_indices)) {
idx <- cluster_indices[[g]]
log_terms <- matrix(NA_real_, nrow = S, ncol = n_gamma)
for (j in seq_len(n_gamma)) {
mu_node <- mu_samples[, idx, drop = FALSE] +
gh$nodes[j] * tau_between_samples[, idx, drop = FALSE]
point_log_lik <- log_lik_fun(idx, mu_node)
point_log_lik <- .apply_log_lik_weights(point_log_lik, weights[idx])
log_terms[, j] <- rowSums(point_log_lik) + gh$log_weights[j]
}
log_lik[, g] <- .rowLogSumExps(log_terms)
}
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .log_lik.brma
# ---------------------------------------------------------------------------- #
#
# Compute the log-likelihood matrix for a brma object.
#
# @param object brma object.
# @param unit character; output/deletion unit.
#
# @return S x K or S x G matrix of log-likelihood values.
#
# ---------------------------------------------------------------------------- #
.log_lik.brma <- function(object, unit = "estimate") {
unit <- .normalize_unit(unit)
if (unit == "estimate") {
return(.log_lik_estimate.brma(object))
} else {
return(.log_lik_cluster.brma(object))
}
}
# ---------------------------------------------------------------------------- #
# .pdf.brma
# ---------------------------------------------------------------------------- #
#
# Back-end wrapper kept for internal callers that use the old helper name.
#
# @param object brma object.
# @param unit character; output/deletion unit.
#
# @return S x K or S x G matrix of log-likelihood values.
#
# ---------------------------------------------------------------------------- #
.pdf.brma <- function(object, unit = "estimate") {
return(.log_lik.brma(object = object, unit = unit))
}
# ---------------------------------------------------------------------------- #
# .estimate_likelihood_setup.brma
# ---------------------------------------------------------------------------- #
#
# Extract common posterior quantities for estimate-unit predictive likelihoods.
#
# The estimate-unit target conditions on fitted cluster effects for multilevel
# models and integrates over estimate-level heterogeneity.
#
# @param object brma object.
#
# @return list with model metadata and posterior matrices.
#
# ---------------------------------------------------------------------------- #
.estimate_likelihood_setup.brma <- function(object) {
priors <- object[["priors"]]
data <- object[["data"]]
is_multilevel <- .is_multilevel(object)
is_mods <- .is_mods(object)
is_scale <- .is_scale(object)
is_PET <- .is_PET(object)
is_PEESE <- .is_PEESE(object)
is_weightfunction <- .is_weightfunction(object)
outcome_type <- .outcome_type(object)
effect_direction <- .effect_direction(object)
posterior_samples <- .get_posterior_samples(object[["fit"]])
yi <- .outcome_data_yi(object)
sei <- .outcome_data_sei(object)
K <- length(yi)
tau_result <- .evaluate.brma.tau(
fit = object[["fit"]],
scale_data = data[["scale"]],
scale_formula = if (is_scale) .create_fit_formula_list(data = data, "scale") else NULL,
scale_priors = priors[["scale"]],
is_scale = is_scale,
is_multilevel = is_multilevel,
K = K,
posterior_samples = posterior_samples
)
mu_samples <- .evaluate.brma.mu(
fit = object[["fit"]],
outcome_data = data[["outcome"]],
mods_data = data[["mods"]],
mods_formula = if (is_mods) .create_fit_formula_list(data = data, "mods") else NULL,
mods_priors = priors[["mods"]],
is_mods = is_mods,
is_PET = is_PET,
is_PEESE = is_PEESE,
effect_direction = effect_direction,
bias_adjusted = FALSE,
K = K,
posterior_samples = posterior_samples
)
fit_data <- NULL
if (is_multilevel || is_weightfunction) {
fit_data <- .create_fit_data(data = data, priors = priors)
}
if (is_multilevel) {
mu_samples <- mu_samples + .evaluate.brma.cluster_effects(
fit = object[["fit"]],
tau_between = tau_result[["tau_between"]],
cluster = fit_data[["cluster"]],
same_data = TRUE,
effect_direction = effect_direction,
posterior_samples = posterior_samples
)
}
return(list(
priors = priors,
data = data,
fit_data = fit_data,
yi = yi,
sei = sei,
K = K,
S = nrow(mu_samples),
mu = mu_samples,
tau_within = tau_result[["tau_within"]],
tau_between = tau_result[["tau_between"]],
is_weightfunction = is_weightfunction,
outcome_type = outcome_type,
effect_direction = effect_direction,
posterior_samples = posterior_samples
))
}
# ---------------------------------------------------------------------------- #
# .log_lik_estimate.brma
# ---------------------------------------------------------------------------- #
#
# Estimate-unit likelihood, one contribution per effect-size estimate.
#
# For normal multilevel models this is the model likelihood factor conditional
# on the cluster effect and marginal over estimate-level heterogeneity.
#
# @param object brma object.
#
# @return S x K matrix of log-likelihood values.
#
# ---------------------------------------------------------------------------- #
.log_lik_estimate.brma <- function(object) {
setup <- .estimate_likelihood_setup.brma(object)
priors <- setup[["priors"]]
data <- setup[["data"]]
yi <- setup[["yi"]]
sei <- setup[["sei"]]
K <- setup[["K"]]
mu_samples <- setup[["mu"]]
tau_within_samples <- setup[["tau_within"]]
is_weightfunction <- setup[["is_weightfunction"]]
outcome_type <- setup[["outcome_type"]]
effect_direction <- setup[["effect_direction"]]
data_weights <- .get_log_lik_data_weights(object)
### extract posterior samples once if needed
if (is_weightfunction) {
posterior_samples <- setup[["posterior_samples"]]
}
### compute log-likelihood based on outcome type
if (outcome_type == "norm") {
# flip for negative effect direction
if (effect_direction == "negative") {
mu_samples_pdf <- -mu_samples
yi_pdf <- -yi
} else {
mu_samples_pdf <- mu_samples
yi_pdf <- yi
}
# dispatch between weighted and standard normal
if (is_weightfunction) {
selection_context <- .selection_context(
object = object,
posterior_samples = posterior_samples
)
log_lik <- .outcome_pdf.selnorm(
yi = yi,
mu_samples = mu_samples,
tau_within = tau_within_samples,
sei = sei,
selection_context = selection_context
)
} else {
# standard normal likelihood
log_lik <- .outcome_pdf.norm(
yi = yi_pdf,
mu_samples = mu_samples_pdf,
tau_within = tau_within_samples,
sei = sei
)
}
} else if (outcome_type == "bin") {
# compute marginal log-likelihood via integration over theta and pi
# the new .outcome_pdf.binom handles integration internally
log_lik <- .outcome_pdf.binom(
ai = data[["outcome"]][["ai"]],
ci = data[["outcome"]][["ci"]],
n1i = data[["outcome"]][["n1i"]],
n2i = data[["outcome"]][["n2i"]],
mu_samples = mu_samples,
tau_within = tau_within_samples,
prior_pi = priors[["outcome"]][["pi"]],
weights = data_weights
)
} else if (outcome_type == "pois") {
# compute marginal log-likelihood via integration over theta and phi
# the new .outcome_pdf.pois handles integration internally
log_lik <- .outcome_pdf.pois(
x1i = data[["outcome"]][["x1i"]],
x2i = data[["outcome"]][["x2i"]],
t1i = data[["outcome"]][["t1i"]],
t2i = data[["outcome"]][["t2i"]],
mu_samples = mu_samples,
tau_within = tau_within_samples,
prior_phi = priors[["outcome"]][["phi"]],
weights = data_weights
)
} else {
stop("Unsupported outcome type for estimate-unit log-likelihood.",
call. = FALSE)
}
# add column names and target metadata
if (outcome_type == "norm") {
log_lik <- .apply_log_lik_weights(log_lik, data_weights)
}
colnames(log_lik) <- paste0("log_lik[", seq_len(K), "]")
attr(log_lik, "RoBMA_target") <- list(
unit = "estimate",
conditioning_depth = "estimate",
n = K,
targets = seq_len(K),
data_hash = .get_outcome_hash(object)
)
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster.brma
# ---------------------------------------------------------------------------- #
#
# Cluster-unit likelihood for multilevel models.
#
# Each column is the joint held-out-cluster log-likelihood contribution.
#
# @param object brma object.
#
# @return S x G matrix of log-likelihood values.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster.brma <- function(object) {
if (!.is_multilevel(object)) {
stop("Cluster-unit log-likelihood is only available for multilevel models.",
call. = FALSE)
}
outcome_type <- .outcome_type(object)
is_weightfunction <- .is_weightfunction(object)
is_weights <- .is_weights(object)
if (outcome_type == "norm" && !is_weightfunction && !is_weights) {
return(.log_lik_cluster_norm.brma(object))
} else if (outcome_type == "norm") {
return(.log_lik_cluster_norm_quadrature.brma(object))
} else if (outcome_type %in% c("bin", "pois")) {
return(.log_lik_cluster_glmm.brma(object))
} else {
stop("Unsupported outcome type for cluster-unit log-likelihood.",
call. = FALSE)
}
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_setup.brma
# ---------------------------------------------------------------------------- #
#
# Extract common posterior matrices for cluster-unit likelihoods.
#
# @param object brma object.
#
# @return list with model metadata and posterior matrices.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_setup.brma <- function(object) {
priors <- object[["priors"]]
data <- object[["data"]]
is_mods <- .is_mods(object)
is_scale <- .is_scale(object)
is_PET <- .is_PET(object)
is_PEESE <- .is_PEESE(object)
effect_direction <- .effect_direction(object)
K <- nrow(data[["outcome"]])
posterior_samples <- .get_posterior_samples(object[["fit"]])
mu_samples <- .evaluate.brma.mu(
fit = object[["fit"]],
outcome_data = data[["outcome"]],
mods_data = data[["mods"]],
mods_formula = if (is_mods) .create_fit_formula_list(data = data, "mods") else NULL,
mods_priors = priors[["mods"]],
is_mods = is_mods,
is_PET = is_PET,
is_PEESE = is_PEESE,
effect_direction = effect_direction,
bias_adjusted = FALSE,
K = K,
posterior_samples = posterior_samples
)
tau_result <- .evaluate.brma.tau(
fit = object[["fit"]],
scale_data = data[["scale"]],
scale_formula = if (is_scale) .create_fit_formula_list(data = data, "scale") else NULL,
scale_priors = priors[["scale"]],
is_scale = is_scale,
is_multilevel = TRUE,
K = K,
posterior_samples = posterior_samples
)
return(list(
priors = priors,
data = data,
K = K,
S = nrow(mu_samples),
mu = mu_samples,
tau_within = tau_result[["tau_within"]],
tau_between = tau_result[["tau_between"]],
cluster = .get_cluster_indices(object),
weights = .get_log_lik_data_weights(object),
data_hash = .get_outcome_hash(object),
effect_direction = effect_direction,
posterior_samples = posterior_samples
))
}
# ---------------------------------------------------------------------------- #
# .add_cluster_log_lik_metadata
# ---------------------------------------------------------------------------- #
#
# @param log_lik S x G log-likelihood matrix.
# @param cluster_indices named list of cluster index vectors.
# @param data_hash character; hash of the outcome target.
#
# @return log-likelihood matrix with names and metadata.
#
# ---------------------------------------------------------------------------- #
.add_cluster_log_lik_metadata <- function(log_lik, cluster_indices, data_hash) {
cluster_labels <- names(cluster_indices)
colnames(log_lik) <- paste0("log_lik_cluster[", cluster_labels, "]")
attr(log_lik, "RoBMA_target") <- list(
unit = "cluster",
conditioning_depth = "cluster",
n = length(cluster_indices),
targets = cluster_labels,
data_hash = data_hash
)
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_norm.brma
# ---------------------------------------------------------------------------- #
#
# Analytic cluster-unit likelihood for unweighted normal multilevel models
# without selection. The normal cluster effect is integrated by the block
# covariance.
#
# @param object brma object.
#
# @return S x G cluster-unit log-likelihood matrix.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_norm.brma <- function(object) {
setup <- .log_lik_cluster_setup.brma(object)
yi <- .outcome_data_yi(object)
vi <- .outcome_data_vi(object)
if (setup[["effect_direction"]] == "negative") {
mu_samples <- -setup[["mu"]]
yi <- -yi
} else {
mu_samples <- setup[["mu"]]
}
cluster_indices <- setup[["cluster"]]
log_lik <- matrix(NA_real_, nrow = setup[["S"]], ncol = length(cluster_indices))
for (g in seq_along(cluster_indices)) {
idx <- cluster_indices[[g]]
for (s in seq_len(setup[["S"]])) {
log_lik[s, g] <- .log_dmvnorm_diag_rank_one(
x = yi[idx],
mean = mu_samples[s, idx],
diagonal = vi[idx] + setup[["tau_within"]][s, idx]^2,
rank_one = setup[["tau_between"]][s, idx]
)
}
}
return(.add_cluster_log_lik_metadata(log_lik, cluster_indices, setup[["data_hash"]]))
}
# ---------------------------------------------------------------------------- #
# .log_dmvnorm_diag_rank_one
# ---------------------------------------------------------------------------- #
#
# Log-density for N(mean, diag(diagonal) + rank_one %*% t(rank_one)).
#
# ---------------------------------------------------------------------------- #
.log_dmvnorm_diag_rank_one <- function(x, mean, diagonal, rank_one) {
residual <- x - mean
if (!all(is.finite(diagonal)) || any(diagonal <= 0)) {
covariance <- diag(diagonal, nrow = length(diagonal), ncol = length(diagonal)) +
tcrossprod(rank_one)
return(mvtnorm::dmvnorm(
x = x,
mean = mean,
sigma = covariance,
log = TRUE
))
}
inv_diag <- 1 / diagonal
inv_rank <- rank_one * inv_diag
denom <- 1 + sum(rank_one * inv_rank)
if (!is.finite(denom) || denom <= .Machine$double.eps) {
covariance <- diag(diagonal, nrow = length(diagonal), ncol = length(diagonal)) +
tcrossprod(rank_one)
return(mvtnorm::dmvnorm(
x = x,
mean = mean,
sigma = covariance,
log = TRUE
))
}
log_det <- sum(log(diagonal)) + log(denom)
quad <- sum(residual^2 * inv_diag) -
sum(rank_one * residual * inv_diag)^2 / denom
return(-0.5 * (length(x) * log(2 * pi) + log_det + quad))
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_norm_quadrature.brma
# ---------------------------------------------------------------------------- #
#
# Gamma-quadrature cluster-unit likelihood for selected-normal models and
# data-weighted normal models. Conditional on gamma, per-estimate likelihood
# contributions factorize.
#
# @param object brma object.
#
# @return S x G cluster-unit log-likelihood matrix.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_norm_quadrature.brma <- function(object) {
setup <- .log_lik_cluster_setup.brma(object)
is_weightfunction <- .is_weightfunction(object)
yi <- .outcome_data_yi(object)
sei <- .outcome_data_sei(object)
if (setup[["effect_direction"]] == "negative" && !is_weightfunction) {
mu_samples <- -setup[["mu"]]
yi <- -yi
} else {
mu_samples <- setup[["mu"]]
}
if (is_weightfunction) {
posterior_samples <- setup[["posterior_samples"]]
selection_context <- .selection_context(
object = object,
posterior_samples = posterior_samples
)
}
log_lik_fun <- function(idx, mu_node) {
if (is_weightfunction) {
local_context <- selection_context
local_context[["obs_bin"]] <- selection_context[["obs_bin"]][idx]
return(.outcome_pdf.selnorm(
yi = yi[idx],
mu_samples = mu_node,
tau_within = setup[["tau_within"]][, idx, drop = FALSE],
sei = sei[idx],
selection_context = local_context
))
} else {
return(.outcome_pdf.norm(
yi = yi[idx],
mu_samples = mu_node,
tau_within = setup[["tau_within"]][, idx, drop = FALSE],
sei = sei[idx]
))
}
}
log_lik <- .log_lik_cluster_gamma_quadrature(
cluster_indices = setup[["cluster"]],
mu_samples = mu_samples,
tau_between_samples = setup[["tau_between"]],
log_lik_fun = log_lik_fun,
weights = setup[["weights"]]
)
return(.add_cluster_log_lik_metadata(log_lik, setup[["cluster"]], setup[["data_hash"]]))
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_glmm.brma
# ---------------------------------------------------------------------------- #
#
# Gamma-quadrature cluster-unit likelihood for binomial and Poisson multilevel
# models. Conditional on gamma, existing estimate-level GLMM quadrature helpers
# are reused.
#
# @param object brma object.
#
# @return S x G cluster-unit log-likelihood matrix.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_glmm.brma <- function(object) {
setup <- .log_lik_cluster_setup.brma(object)
data <- setup[["data"]]
priors <- setup[["priors"]]
outcome_type <- .outcome_type(object)
if (.has_native_glmm_cluster()) {
log_lik <- .log_lik_cluster_glmm_native(
setup = setup,
data = data,
priors = priors,
outcome_type = outcome_type
)
} else {
log_lik <- .log_lik_cluster_glmm_r(
setup = setup,
data = data,
priors = priors,
outcome_type = outcome_type
)
}
return(.add_cluster_log_lik_metadata(log_lik, setup[["cluster"]], setup[["data_hash"]]))
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_glmm_r
# ---------------------------------------------------------------------------- #
#
# R-composed reference implementation for GLMM cluster likelihoods.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_glmm_r <- function(setup, data, priors, outcome_type,
n_theta = 15,
n_gamma = .get_cluster_likelihood_n_gamma(),
n_pi = 30, n_phi = 30) {
log_lik_fun <- switch(
outcome_type,
"bin" = function(idx, mu_node) {
.outcome_pdf.binom(
ai = data[["outcome"]][["ai"]][idx],
ci = data[["outcome"]][["ci"]][idx],
n1i = data[["outcome"]][["n1i"]][idx],
n2i = data[["outcome"]][["n2i"]][idx],
mu_samples = mu_node,
tau_within = setup[["tau_within"]][, idx, drop = FALSE],
prior_pi = priors[["outcome"]][["pi"]],
weights = setup[["weights"]][idx],
n_theta = n_theta,
n_pi = n_pi
)
},
"pois" = function(idx, mu_node) {
.outcome_pdf.pois(
x1i = data[["outcome"]][["x1i"]][idx],
x2i = data[["outcome"]][["x2i"]][idx],
t1i = data[["outcome"]][["t1i"]][idx],
t2i = data[["outcome"]][["t2i"]][idx],
mu_samples = mu_node,
tau_within = setup[["tau_within"]][, idx, drop = FALSE],
prior_phi = priors[["outcome"]][["phi"]],
weights = setup[["weights"]][idx],
n_theta = n_theta,
n_phi = n_phi
)
}
)
log_lik <- .log_lik_cluster_gamma_quadrature(
cluster_indices = setup[["cluster"]],
mu_samples = setup[["mu"]],
tau_between_samples = setup[["tau_between"]],
log_lik_fun = log_lik_fun,
n_gamma = n_gamma
)
return(log_lik)
}
# ---------------------------------------------------------------------------- #
# .log_lik_cluster_glmm_native
# ---------------------------------------------------------------------------- #
#
# Native batched implementation for GLMM cluster likelihoods.
#
# ---------------------------------------------------------------------------- #
.log_lik_cluster_glmm_native <- function(setup, data, priors, outcome_type,
n_theta = 15,
n_gamma = .get_cluster_likelihood_n_gamma(),
n_pi = 30, n_phi = 30) {
gh_theta <- .gauss_hermite_nodes(n_theta)
gh_gamma <- .gauss_hermite_nodes(n_gamma)
cluster <- .cluster_indices_flatten(setup[["cluster"]])
weights <- if (is.null(setup[["weights"]])) NULL else .native_numeric_vector(setup[["weights"]])
if (outcome_type == "bin") {
pi_grid <- .glmm_binom_logit_pi_grid(
ai = data[["outcome"]][["ai"]],
ci = data[["outcome"]][["ci"]],
n1i = data[["outcome"]][["n1i"]],
n2i = data[["outcome"]][["n2i"]],
prior_pi = priors[["outcome"]][["pi"]],
n_pi = n_pi
)
return(.Call(
"RoBMA_glmm_binom_cluster_loglik",
.native_integer_vector(data[["outcome"]][["ai"]]),
.native_integer_vector(data[["outcome"]][["ci"]]),
.native_integer_vector(data[["outcome"]][["n1i"]]),
.native_integer_vector(data[["outcome"]][["n2i"]]),
.native_numeric_matrix(setup[["mu"]]),
.native_numeric_matrix(setup[["tau_within"]]),
.native_numeric_matrix(setup[["tau_between"]]),
cluster[["index"]],
cluster[["size"]],
weights,
.native_numeric_vector(gh_theta[["nodes"]]),
.native_numeric_vector(gh_theta[["log_weights"]]),
.native_numeric_matrix(pi_grid[["grid"]]),
.native_numeric_matrix(pi_grid[["log_weights"]]),
.native_numeric_vector(gh_gamma[["nodes"]]),
.native_numeric_vector(gh_gamma[["log_weights"]]),
PACKAGE = "RoBMA"
))
}
if (outcome_type == "pois") {
phi_grid <- .glmm_pois_log_phi_grid(
x1i = data[["outcome"]][["x1i"]],
x2i = data[["outcome"]][["x2i"]],
t1i = data[["outcome"]][["t1i"]],
t2i = data[["outcome"]][["t2i"]],
prior_phi = priors[["outcome"]][["phi"]],
n_phi = n_phi
)
return(.Call(
"RoBMA_glmm_pois_cluster_loglik",
.native_integer_vector(data[["outcome"]][["x1i"]]),
.native_integer_vector(data[["outcome"]][["x2i"]]),
.native_numeric_vector(data[["outcome"]][["t1i"]]),
.native_numeric_vector(data[["outcome"]][["t2i"]]),
.native_numeric_matrix(setup[["mu"]]),
.native_numeric_matrix(setup[["tau_within"]]),
.native_numeric_matrix(setup[["tau_between"]]),
cluster[["index"]],
cluster[["size"]],
weights,
.native_numeric_vector(gh_theta[["nodes"]]),
.native_numeric_vector(gh_theta[["log_weights"]]),
.native_numeric_matrix(phi_grid[["grid"]]),
.native_numeric_matrix(phi_grid[["log_weights"]]),
.native_numeric_vector(gh_gamma[["nodes"]]),
.native_numeric_vector(gh_gamma[["log_weights"]]),
PACKAGE = "RoBMA"
))
}
stop("Unsupported outcome type for GLMM cluster-unit log-likelihood.",
call. = FALSE)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.