Nothing
dat <- lavaan::HolzingerSwineford1939
mod <- "
visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9
"
NSAMP <- 3
test_that("Setting dp argument", {
DP <- priors_for(theta = "gamma(1,1)", psi = "gamma(1,1)")
fit <- asem(
mod,
dat,
dp = DP,
verbose = FALSE,
nsamp = NSAMP
)
pt <- lavaan::partable(fit)
where_theta_var <- grep("theta_var", pt$mat)
expect_true(all(pt$prior[where_theta_var] == "gamma(1,1)"))
where_psi_var <- grep("psi_var", pt$mat)
expect_true(all(pt$prior[where_psi_var] == "gamma(1,1)"))
})
test_that("Straight from textual model", {
mod <- "
visual =~ x1 + prior('normal(1,2)')*x2 + x3
textual =~ x4 + x5 + prior('normal(3,1.5)')*x6
speed =~ x7 + x8 + x9
x1 ~~ prior('gamma(3,3)')*x1
"
expect_no_error({
fit <- asem(
mod,
dat,
verbose = FALSE,
nsamp = NSAMP
)
})
expect_no_error(out <- capture.output(summary(fit)))
pt <- lavaan::partable(fit)
expect_true(pt$prior[grep("visual=~x2", pt$names)] == "normal(1,2)")
expect_true(pt$prior[grep("textual=~x6", pt$names)] == "normal(3,1.5)")
expect_true(pt$prior[grep("x1~~x1", pt$names)] == "gamma(3,3)")
fit_inlv <- get_inlavaan_internal(fit)
pt <- fit_inlv$partable
idx <- which(
pt$names %in% c("visual=~x2", "textual=~x6", "x1~~x1", "textual~~textual")
)
short_pt <- lapply(pt, function(x) x[idx])
short_pt$free <- 1:4
res <- prior_logdens_vectorized(c(2, 4, 1, 1), prepare_priors_for_optim(short_pt), debug = TRUE)
# Check log densities
expect_equal(
as.numeric(res$lp),
c(
dnorm(2, 1, 2, log = TRUE),
dnorm(4, 3, 1.5, log = TRUE),
dgamma(exp(1), 3, 3, log = TRUE),
dgamma(exp(1 / 2), 1, 0.5, log = TRUE)
)
)
# Check jacobians
expect_equal(
as.numeric(res$ljcb),
c(
0, # log(abs(1))
0, # log(abs(1))
log(abs(exp(1))), # t = log x ---> log(abs(d/dt e^t))
log(abs(0.5 * exp(1 / 2))) # t = log(sqrt(x)) ---> log(abs(0.5 exp^{t/2}))
)
)
})
## ---- TESTS FOR NEW VECTORISED LOG PRIOR AND GRADIENT ------------------------
get_test_objects <- function(model, data, group = NULL, group.equal = "") {
# 1. Fit standard lavaan to get data structures
lav_fit <- lavaan::cfa(
model,
data = data,
group = group,
group.equal = group.equal,
se = "none", # Speed up
do.fit = FALSE # We only need the setup
)
# 2. Run inlavaanify (assuming it is exported or available in namespace)
pt <- inlavaanify_partable(
pt = lavaan::parTable(lav_fit),
lavdata = lav_fit@Data,
lavoptions = lav_fit@Options
)
# 3. Create the Cache
cache <- prepare_priors_for_optim(pt)
# 4. Generate Random Theta (Validation Vector)
# Size is the maximum integer in the 'free' column
n_theta <- max(pt$free, na.rm = TRUE)
set.seed(42) # Reproducibility
theta <- rnorm(n_theta)
list(pt = pt, cache = cache, theta = theta)
}
test_that("Vectorized vs Old: Standard Single-Group CFA", {
# Setup
dat <- lavaan::HolzingerSwineford1939
mod <- "
visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
"
obj <- get_test_objects(mod, dat)
# --- LOG DENSITY TEST ---
new_dens <- prior_logdens_vectorized(obj$theta, obj$cache)
expect_true(is.finite(new_dens), info = "Log-density should be finite")
# --- GRADIENT TEST (against numerical finite differences) ---
new_grad <- prior_grad_vectorized(obj$theta, obj$cache)
h <- 1e-5
num_grad <- sapply(seq_along(obj$theta), function(i) {
tp <- obj$theta; tp[i] <- tp[i] + h
tm <- obj$theta; tm[i] <- tm[i] - h
(prior_logdens_vectorized(tp, obj$cache) - prior_logdens_vectorized(tm, obj$cache)) / (2 * h)
})
# Accumulate cache-indexed grad back to full theta-length vector
full_grad <- numeric(length(obj$theta))
full_grad[obj$cache$free_id] <- new_grad
expect_equal(
full_grad,
num_grad,
tolerance = 1e-4,
info = "Gradient should match finite differences in single-group models"
)
})
test_that("Vectorized vs Old: Multi-Group with Equality Constraints", {
# This is the critical test for the indexing bug
dat <- lavaan::HolzingerSwineford1939
mod <- "
visual =~ x1 + x2
textual =~ x4 + x5
"
# Create object with equality constraints
obj <- get_test_objects(
mod,
dat,
group = "school",
group.equal = "loadings"
)
# --- LOG DENSITY TEST ---
new_dens <- prior_logdens_vectorized(obj$theta, obj$cache)
expect_true(is.finite(new_dens), info = "Log-density should be finite")
# --- GRADIENT TEST (against numerical finite differences) ---
new_grad <- prior_grad_vectorized(obj$theta, obj$cache)
h <- 1e-5
num_grad <- sapply(seq_along(obj$theta), function(i) {
tp <- obj$theta; tp[i] <- tp[i] + h
tm <- obj$theta; tm[i] <- tm[i] - h
(prior_logdens_vectorized(tp, obj$cache) - prior_logdens_vectorized(tm, obj$cache)) / (2 * h)
})
# Accumulate cache-indexed grad back to full theta-length vector
full_grad <- numeric(length(obj$theta))
full_grad[obj$cache$free_id] <- new_grad
expect_equal(
full_grad,
num_grad,
tolerance = 1e-4,
info = "Gradient should match finite differences even with equality constraints"
)
})
test_that("Vectorized version handles different Prior Types correctly", {
# Mock a pt with Normal, Gamma, and Beta to ensure coverage
# We construct a fake cache manually to test the math isolated from the parser
# 3 params: 1=Normal, 2=Gamma[sd], 3=Beta
theta <- c(0.5, 0.5, 0.0) # Arbitrary values
cache <- list(
free_id = c(1L, 2L, 3L),
# Exp, Identity, Identity
trans_type = c(1L, 0L, 0L),
# Normal, Gamma, Beta
prior_type = c(1L, 2L, 3L),
p1 = c(0, 1, 1), # mu, shape, alpha
p2 = c(1, 0.5, 1), # sd, rate, beta
is_sd_prior = c(FALSE, TRUE, FALSE),
prior_names = c("norm", "gam", "beta")
)
# Calculate New
res_grad <- prior_grad_vectorized(theta, cache)
# We expect a result vector of length 3
expect_equal(length(res_grad), 3)
expect_true(all(is.finite(res_grad)))
})
test_that("Beta prior gradient with tanh transform matches numerical", {
# Regression test: non-trivial Beta(5,5) on correlation with tanh transform
# Previously the gradient was off by a factor of 2
cache <- list(
free_id = 1L,
trans_type = 2L, # tanh (correlation)
prior_type = 3L, # beta
p1 = 5, # beta(5,5)
p2 = 5,
is_sd_prior = FALSE,
prior_names = "rho"
)
h <- 1e-7
for (th in c(-0.8, -0.3, 0.0, 0.3, 0.8)) {
ag <- as.numeric(prior_grad_vectorized(th, cache))
ng <- (prior_logdens_vectorized(th + h, cache) -
prior_logdens_vectorized(th - h, cache)) / (2 * h)
expect_equal(ag, ng, tolerance = 1e-5,
info = paste0("Beta(5,5) tanh gradient at theta=", th))
}
# Also test asymmetric Beta(2,8)
cache$p1 <- 2
cache$p2 <- 8
for (th in c(-0.5, 0.0, 0.5)) {
ag <- as.numeric(prior_grad_vectorized(th, cache))
ng <- (prior_logdens_vectorized(th + h, cache) -
prior_logdens_vectorized(th - h, cache)) / (2 * h)
expect_equal(ag, ng, tolerance = 1e-5,
info = paste0("Beta(2,8) tanh gradient at theta=", th))
}
})
test_that("dbeta_box with log = TRUE returns log-density", {
x <- seq(0, 100, length.out = 20)
ld <- dbeta_box(x, shape1 = 2, shape2 = 5, a = 0, b = 100, log = TRUE)
nld <- dbeta_box(x, shape1 = 2, shape2 = 5, a = 0, b = 100, log = FALSE)
expect_equal(ld, log(nld))
expect_true(all(is.finite(ld[x > 0 & x < 100])))
})
test_that("dbeta_box stops when b <= a", {
expect_error(dbeta_box(0.5, shape1 = 1, shape2 = 1, a = 1, b = 0),
"Require finite scalars with b > a")
expect_error(dbeta_box(0.5, shape1 = 1, shape2 = 1, a = 0, b = 0),
"Require finite scalars with b > a")
})
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.