Nothing
#' Generic precision matrix operator
#'
#' Creates a flexible precision matrix K by allowing linear combinations of base matrices
#' with parameter-dependent coefficients. The model follows the form:
#' K = sum_i f_i(theta) * matrices_i, where f_i depends on the parameter transformation.
#'
#' This function can be used to represent various models like AR1, Matern, and RW1 in a unified framework.
#'
#' @param theta_K The parameter vector (in real scale). If missing, initialized as double(0)
#' @param matrices A list of matrices to be combined with parameter-dependent coefficients
#' @param h The integration weights vector
#' @param trans Transformation for each parameter. Must be a list where each element
#' is a character vector defining transformations for each matrix.
#' For example, `trans=list(kappa=c("exp2", "identity", "null"))` means parameter 'kappa'
#' affects matrix 1 with exp2 transformation, matrix 2 with identity, and doesn't affect matrix 3.
#' Available transformations: "identity", "exp", "exp2", "exp4", "sqrt", "square", "log", "tanh"
#' @param mesh The mesh object
#' @param model The model type name
#' @param zero_trace Whether the trace of K should be zero
#' @param param_name Optional parameter names stored on the returned operator for diagnostics
#' @param param_trans Optional parameter transformations stored on the returned operator for diagnostics
#' @param ... Additional arguments (ignored)
#'
#' @details
#' The `generic` function provides a flexible way to construct precision matrices by combining
#' existing matrices with transformed parameters. Here's how it works:
#'
#' 1. Each parameter in `theta_K` can affect one or more matrices in the `matrices` list
#' 2. The `trans` list defines how each parameter transforms before multiplying each matrix
#' 3. For each matrix, the coefficients from all parameters are multiplied together
#' 4. The final K is the sum of all transformed matrices
#'
#' Common use cases:
#'
#' - **AR1 model**: K = rho * C + G (where rho is between -1 and 1)
#' - **Matern (alpha=2)**: K = kappa^2 * C + G (where kappa > 0)
#' - **Matern (alpha=4)**: K = kappa^4 * C + 2*kappa^2 * G + G * Cinv * G
#'
#' @return An object of class \code{ngme_operator}. The object stores the sparse
#' precision matrix \code{K}, integration weights \code{h}, parameter vector
#' \code{theta_K}, base \code{matrices}, transformation specification
#' \code{trans}, and an \code{update_K} function that rebuilds \code{K} for new
#' parameter values. It is intended for use as the latent \code{model} inside
#' \code{f()}.
#'
#' @examples
#' # AR1 model with rho = 0.5
#' ar1_obj <- ar1(1:10, rho = 0.5)
#' g <- name2fun("tanh", inv = TRUE)
#' generic_ar1 <- generic(
#' theta_K = c(rho = g(0.5)), # Transform from real scale
#' trans = list(rho = c("tanh", "null")), # Apply tanh to first matrix
#' matrices = list(ar1_obj$C, ar1_obj$G),
#' h = ar1_obj$h
#' )
#'
#' # Matern model with kappa = 2
#' mesh <- fmesher::fm_mesh_1d(seq(0, 1, length.out = 10))
#' matern_obj <- matern(mesh)
#' log_kappa <- log(2)
#' generic_matern <- generic(
#' theta_K = c(kappa = log_kappa),
#' trans = list(kappa = c("exp2", "null")), # exp(2*kappa) for C, nothing for G
#' matrices = list(matern_obj$C, matern_obj$G),
#' h = matern_obj$h
#' )
#'
#' # Matern model with alpha = 4 and kappa = 2
#' set.seed(1)
#' mesh <- fmesher::fm_mesh_2d(cbind(x = runif(20), y = runif(20)))
#' matern_obj <- matern(mesh, alpha = 4)
#' C <- matern_obj$C
#' G <- matern_obj$G
#' Cinv <- C; diag(Cinv) <- 1 / Matrix::diag(C)
#'
#' generic_matern4 <- generic(
#' theta_K = c(kappa = log(2)),
#' trans = list(kappa = c("exp4", "exp2", "null")),
#' matrices = list(C, 2*G, G %*% Cinv %*% G),
#' h = matern_obj$h
#' )
#'
#' @export
generic <- function(
theta_K,
matrices,
h,
trans = NULL,
mesh = NULL,
zero_trace = FALSE,
model = "generic",
param_name = NULL,
param_trans = NULL,
...
) {
if (!is.null(mesh)) mesh <- ngme_build_mesh(mesh)
# If theta_K is not provided, initialize it as double(0)
if (missing(theta_K)) {
theta_K <- double(0)
}
# Validate matrices
stopifnot(
"matrices should not be NULL" = !is.null(matrices),
"matrices should be a list" = is.list(matrices),
"matrices should not be a list of NULL" = all(!sapply(matrices, is.null)),
"all matrices should have the same dimensions" = all(sapply(matrices, function(m) nrow(m) == nrow(matrices[[1]])) &
sapply(matrices, function(m) ncol(m) == ncol(matrices[[1]]))),
"h should be of the same dimension as the matrices" = length(h) == nrow(matrices[[1]])
)
# Convert old format to new format if needed
if (is.null(trans) && length(theta_K) > 0) {
# If trans is NULL, create default trans with only 1s for the identity matrices
trans <- list()
for (i in seq_along(theta_K)) {
trans[[i]] <- rep("null", length(matrices))
trans[[i]][i] <- "identity"
}
# If no parameters provided, just use default behavior (all matrices with coefficient 1)
} else if (is.character(trans)) {
# Convert old format to new format
old_trans <- trans
trans <- list()
for (i in seq_along(theta_K)) {
theta_K_name <- names(theta_K)[i]
trans[[theta_K_name]] <- rep("null", length(matrices))
trans[[theta_K_name]][i] <- old_trans[i]
}
}
# Validate the structure of trans
if (length(theta_K) > 0) {
# Only validate trans if we have parameters
for (theta_K_name in names(trans)) {
if (length(trans[[theta_K_name]]) != length(matrices)) {
stop(sprintf(
"For parameter '%s', the transformation vector length (%d) must match the number of matrices (%d)",
theta_K_name, length(trans[[theta_K_name]]), length(matrices)
))
}
}
}
# Helper function to compute the coefficient of each matrix
update_K <- function(theta_K) {
coef <- compute_coef_new(theta_K, trans, length(matrices))
K <- 0
for (i in seq_along(matrices)) {
K <- K + coef[i] * matrices[[i]]
}
return(K)
}
if (is.null(param_trans)) {
param_trans <- get_param_trans(trans)
}
if (is.null(param_name)) {
param_name <- names(theta_K)
}
ngme_operator(
model = model,
mesh = mesh,
K = ngme_as_sparse(update_K(theta_K)),
generic_type = "generic",
h = h,
theta_K = theta_K,
trans = trans,
update_K = update_K,
matrices = sapply(matrices, ngme_as_sparse),
symmetric = all(sapply(matrices, Matrix::isSymmetric)),
zero_trace = zero_trace,
param_name = param_name,
param_trans = param_trans,
...
)
}
# Helper function to get param_trans from trans list
# For each parameter, if only one transformation type is used (excluding null),
# use that transformation function, otherwise use identity
get_param_trans <- function(trans) {
if (is.null(trans) || length(trans) == 0) {
return(NULL)
}
result <- list()
for (param_name in names(trans)) {
# Get all transformations for this parameter (excluding "null")
trans_types <- unique(trans[[param_name]][trans[[param_name]] != "null"])
if (length(trans_types) == 1) {
# If only one transformation type is used, use that
result[[param_name]] <- name2fun(trans_types)
} else {
# If multiple or no transformation types are used, use identity
result[[param_name]] <- name2fun("identity")
}
}
return(result)
}
# Calculate coefficients for each matrix based on parameters and transformations
compute_coef_new <- function(theta_K, trans, n_matrices) {
coef <- rep(1, n_matrices)
# If no parameters, return all ones (default behavior)
if (length(theta_K) == 0) {
return(coef)
}
for (param_name in names(theta_K)) {
if (param_name %in% names(trans)) {
for (i in seq_along(trans[[param_name]])) {
trans_type <- trans[[param_name]][i]
if (trans_type != "null") {
# Apply the transformation function to the parameter value
transformed_value <- name2fun(trans_type)(theta_K[param_name])
# Multiply the coefficient by the transformed parameter value
coef[i] <- coef[i] * transformed_value
}
}
}
}
return(coef)
}
#' Convert transformation name to function
#'
#' This function converts a transformation name to the corresponding function.
#' Convertion is from original scale to real scale by default.
#' Available transformations:
#' \itemize{
#' \item \code{exp4}: \eqn{exp(4x)}, inverse is \eqn{log(x)/4}
#' \item \code{exp2}: \eqn{exp(2x)}, inverse is \eqn{log(x)/2}
#' \item \code{tanh}: Hyperbolic tangent transformation used for AR1 parameter, uses ar1_th2a and ar1_a2th
#' \item \code{identity}: Identity function, no transformation
#' \item \code{exp}: \eqn{exp(x)}, inverse is \eqn{log(x)}
#' \item \code{sqrt}: \eqn{sqrt(x)}, inverse is \eqn{x^2}
#' \item \code{square}: \eqn{x^2}, inverse is \eqn{sqrt(x)}
#' \item \code{log}: \eqn{log(x)}, inverse is \eqn{exp(x)}
#' }
#'
#' @param trans Character string specifying the transformation type from original scale to real scale
#' @param inv Logical, if TRUE returns the transformation function from real scale to original scale
#'
#' @return A function that applies the specified transformation
#' @export
name2fun <- function(trans, inv = FALSE) {
# Define transformation functions in a list
transformations <- list(
exp4 = list(
forward = function(x) exp(4 * x),
inverse = function(x) log(x) / 4
),
exp2 = list(
forward = function(x) exp(2 * x),
inverse = function(x) log(x) / 2
),
tanh = list(
forward = ar1_th2a, # restrict the range of rho to (-1, 1)
inverse = ar1_a2th
),
identity = list(
forward = function(x) x,
inverse = function(x) x
),
exp = list(
forward = function(x) exp(x),
inverse = function(x) log(x)
),
sqrt = list(
forward = function(x) sqrt(x),
inverse = function(x) x^2
),
square = list(
forward = function(x) x^2,
inverse = function(x) sqrt(x)
),
log = list(
forward = function(x) log(x),
inverse = function(x) exp(x)
)
)
# Check if transformation exists
if (!trans %in% names(transformations)) {
stop("Unknown transformation: ", trans)
}
# Return the appropriate function
if (!inv) {
return(transformations[[trans]]$forward)
} else {
return(transformations[[trans]]$inverse)
}
}
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.