Nothing
# Shared fixtures --------------------------------------------------------------
# Simple 1-upper, 2-bottom hierarchy: u = b1 + b2
.A_simple <- matrix(c(1, 1), nrow = 1)
# Coherent base forecasts: upper = sum of bottoms
.mu_coh <- c(10, 4, 6) # u=10, b1=4, b2=6 => delta = A%*%b - u = 0
# Incoherent base forecasts: upper != sum of bottoms
.mu_inc <- c(11, 4, 6) # u=11, b1=4, b2=6 => delta = -1
# Posterior parameters
.nu_post <- 50
.Psi_post <- diag(c(3, 1, 1.5)) * .nu_post # scale ~ nu * sigma^2 per series
# 1. Output structure ----------------------------------------------------------
test_that("reconc_t output contains expected fields (bottom only)", {
res <- reconc_t(.A_simple, .mu_coh,
posterior = list(nu = .nu_post, Psi = .Psi_post)
)
expect_named(res, c("bottom_rec_mean", "bottom_rec_scale_matrix", "bottom_rec_df"),
ignore.order = TRUE
)
expect_length(res$bottom_rec_mean, ncol(.A_simple))
expect_equal(dim(res$bottom_rec_scale_matrix), c(ncol(.A_simple), ncol(.A_simple)))
expect_length(res$bottom_rec_df, 1)
})
# 2. Degrees of freedom formula ------------------------------------------------
test_that("bottom_rec_df equals nu_post - n_bottom + 1", {
res <- reconc_t(.A_simple, .mu_coh,
posterior = list(nu = .nu_post, Psi = .Psi_post)
)
expect_equal(res$bottom_rec_df, .nu_post - ncol(.A_simple) + 1)
})
# 3. Coherent forecasts: reconciled mean unchanged -----------------------------
test_that("reconciled mean is unchanged when base forecasts are already coherent", {
res <- reconc_t(.A_simple, .mu_coh,
posterior = list(nu = .nu_post, Psi = .Psi_post)
)
expect_equal(res$bottom_rec_mean, .mu_coh[(nrow(.A_simple) + 1):length(.mu_coh)],
tolerance = 1e-10
)
})
# 4. return_upper: upper_rec_mean == A %*% bottom_rec_mean ----------------------
test_that("return_upper=TRUE returns correct upper fields consistent with bottom", {
res <- reconc_t(.A_simple, .mu_inc,
posterior = list(nu = .nu_post, Psi = .Psi_post),
return_upper = TRUE
)
expect_true(all(c("upper_rec_mean", "upper_rec_scale_matrix", "upper_rec_df") %in% names(res)))
# upper_rec_mean must equal A %*% bottom_rec_mean (closure property)
expect_equal(
as.vector(res$upper_rec_mean),
as.vector(.A_simple %*% res$bottom_rec_mean),
tolerance = 1e-10
)
# upper_rec_scale_matrix must equal A %*% bottom_rec_scale_matrix %*% t(A)
expect_equal(
res$upper_rec_scale_matrix,
.A_simple %*% res$bottom_rec_scale_matrix %*% t(.A_simple),
tolerance = 1e-10
)
# degrees of freedom are the same for upper and bottom
expect_equal(res$upper_rec_df, res$bottom_rec_df)
})
test_that("return_upper=FALSE (default) does not return upper fields", {
res <- reconc_t(.A_simple, .mu_inc,
posterior = list(nu = .nu_post, Psi = .Psi_post)
)
expect_false("upper_rec_mean" %in% names(res))
expect_false("upper_rec_scale_matrix" %in% names(res))
})
# 5. Scale matrix is symmetric and positive-definite ---------------------------
test_that("bottom_rec_scale_matrix is symmetric and positive definite", {
res <- reconc_t(.A_simple, .mu_inc,
posterior = list(nu = .nu_post, Psi = .Psi_post)
)
S <- res$bottom_rec_scale_matrix
# Symmetry
expect_equal(S, t(S), tolerance = 1e-12)
# Positive definiteness: all eigenvalues > 0
eigs <- eigen(S, symmetric = TRUE, only.values = TRUE)$values
expect_true(all(eigs > 0))
})
# 6. Incoherence: reconciled mean is coherent ----------------------------------
# After reconciliation, A %*% bottom_rec_mean always equals upper_rec_mean
test_that("reconciled forecasts satisfy the hierarchical constraint", {
res <- reconc_t(.A_simple, .mu_inc,
posterior = list(nu = .nu_post, Psi = .Psi_post),
return_upper = TRUE
)
expect_equal(
as.vector(.A_simple %*% res$bottom_rec_mean),
as.vector(res$upper_rec_mean),
tolerance = 1e-10
)
})
# 7. Input via residuals (no error) --------------------------------------------
test_that("reconc_t runs without error when residuals and prior are provided", {
set.seed(42)
n <- 3
L <- 30 # training length
A <- .A_simple
mu <- .mu_inc
# Simulate residuals
res_mat <- matrix(rnorm(L * n), nrow = L, ncol = n)
# Simple diagonal prior
nu_prior <- 10
Psi_prior <- diag(n) * (nu_prior - n - 1)
expect_no_error(
reconc_t(A, mu, prior = list(nu = nu_prior, Psi = Psi_prior), residuals = res_mat)
)
})
# 8. Posterior input reproduces prior+residuals path -------------------------
test_that("direct posterior input gives same result as prior+residuals path", {
set.seed(7)
n <- 3
L <- 20
A <- .A_simple
mu <- .mu_inc
res_mat <- matrix(rnorm(L * n), nrow = L, ncol = n)
nu_prior <- 15
Psi_prior <- diag(n) * (nu_prior - n - 1)
# Path A: via prior + residuals
res_via_prior <- reconc_t(A, mu,
prior = list(nu = nu_prior, Psi = Psi_prior),
residuals = res_mat
)
# Manual posterior computation (mirrors internal logic)
Samp_cov <- crossprod(res_mat) / L
lam <- .L_SHRINK_RECONC_T
Samp_cov_shr <- (1 - lam) * Samp_cov + lam * diag(diag(Samp_cov))
nu_post_man <- nu_prior + L
Psi_post_man <- Psi_prior + L * Samp_cov_shr
# Path B: directly provide posterior
res_via_post <- reconc_t(A, mu,
posterior = list(nu = nu_post_man, Psi = Psi_post_man)
)
expect_equal(res_via_prior$bottom_rec_mean, res_via_post$bottom_rec_mean, tolerance = 1e-10)
expect_equal(res_via_prior$bottom_rec_scale_matrix, res_via_post$bottom_rec_scale_matrix,
tolerance = 1e-10
)
expect_equal(res_via_prior$bottom_rec_df, res_via_post$bottom_rec_df)
})
# 9. Unknown argument triggers a warning -------------------------------------
test_that("unknown arguments in ... trigger a warning", {
expect_warning(
reconc_t(.A_simple, .mu_coh,
posterior = list(nu = .nu_post, Psi = .Psi_post),
unknown_arg = 99
),
regexp = "unknown_arg"
)
})
# 10. Standard usage: y_train + residuals (CASE 2b) ---------------------------
# This path runs .compute_naive_cov + multi_log_score_optimization to estimate
# nu_prior and Psi_prior, then updates to the posterior and reconciles.
# Shared fixture for CASE 2b tests (mts y_train with frequency=1, no seasonality)
set.seed(123)
.n_series <- 3L # 1 upper + 2 bottom
.L_train <- 50L
.A_2b <- matrix(c(1, 1), nrow = 1)
.y_mat <- ts(matrix(rnorm(.L_train * .n_series, mean = 10), nrow = .L_train, ncol = .n_series),
frequency = 4) # mts object with frequency=4, but no actual seasonality
.res_mat <- matrix(rnorm(.L_train * .n_series), nrow = .L_train, ncol = .n_series)
.mu_2b <- c(11, 4, 6) # incoherent: 11 != 4 + 6 = 10
test_that("reconc_t runs without error with y_train + residuals (standard usage)", {
expect_no_error(
reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat)
)
})
test_that("reconc_t output structure is correct with y_train + residuals", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat)
expect_named(result, c("bottom_rec_mean", "bottom_rec_scale_matrix", "bottom_rec_df"),
ignore.order = TRUE
)
expect_length(result$bottom_rec_mean, ncol(.A_2b))
expect_equal(dim(result$bottom_rec_scale_matrix), c(ncol(.A_2b), ncol(.A_2b)))
expect_length(result$bottom_rec_df, 1)
expect_true(result$bottom_rec_df > 0)
})
test_that("reconciled forecasts satisfy hierarchical constraint with y_train + residuals", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat,
return_upper = TRUE
)
expect_equal(
as.vector(.A_2b %*% result$bottom_rec_mean),
as.vector(result$upper_rec_mean),
tolerance = 1e-10
)
})
test_that("bottom_rec_df equals posterior_nu - n_bottom + 1 with y_train + residuals", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat,
return_parameters = TRUE
)
expect_equal(result$bottom_rec_df, result$posterior_nu - ncol(.A_2b) + 1)
})
test_that("posterior_nu equals optimized nu_prior + L with y_train + residuals", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat,
return_parameters = TRUE
)
# posterior_nu = nu_prior + L; since nu_prior >= n + 2, posterior_nu > L
expect_true(result$posterior_nu > .L_train)
# Also must be valid for the t-distribution (> n_series - 1)
expect_true(result$posterior_nu > .n_series - 1)
})
test_that("prior and posterior parameters are returned with y_train + residuals (return_parameters=TRUE)", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat,
return_parameters = TRUE
)
expect_true(all(c("prior_nu", "prior_Psi", "posterior_nu", "posterior_Psi") %in% names(result)))
expect_true(is.numeric(result$prior_nu))
expect_true(is.matrix(result$prior_Psi))
expect_equal(dim(result$prior_Psi), c(.n_series, .n_series))
expect_true(is.numeric(result$posterior_nu))
expect_true(is.matrix(result$posterior_Psi))
expect_equal(dim(result$posterior_Psi), c(.n_series, .n_series))
})
test_that("bottom_rec_scale_matrix is symmetric and positive definite with y_train + residuals", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat)
S <- result$bottom_rec_scale_matrix
expect_equal(S, t(S), tolerance = 1e-12)
eigs <- eigen(S, symmetric = TRUE, only.values = TRUE)$values
expect_true(all(eigs > 0))
})
test_that("return_upper=TRUE works correctly with y_train + residuals", {
result <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat,
return_upper = TRUE
)
expect_true(all(c("upper_rec_mean", "upper_rec_scale_matrix", "upper_rec_df") %in% names(result)))
expect_equal(
result$upper_rec_scale_matrix,
.A_2b %*% result$bottom_rec_scale_matrix %*% t(.A_2b),
tolerance = 1e-10
)
expect_equal(result$upper_rec_df, result$bottom_rec_df)
})
test_that("reconc_t standard usage works with explicit freq argument", {
set.seed(42)
freq <- 4L
L <- 40L
y_seas <- matrix(rnorm(L * .n_series, mean = 10), nrow = L, ncol = .n_series)
res_seas <- matrix(rnorm(L * .n_series), nrow = L, ncol = .n_series)
expect_no_error(
reconc_t(.A_2b, .mu_2b, y_train = y_seas, residuals = res_seas, freq = freq)
)
})
test_that("reconc_t standard usage works when y_train is an mts object", {
set.seed(42)
freq <- 4L
L <- 40L
y_mts <- ts(matrix(rnorm(L * .n_series, mean = 10), nrow = L, ncol = .n_series),
frequency = freq
)
res_seas <- matrix(rnorm(L * .n_series), nrow = L, ncol = .n_series)
expect_no_error(
reconc_t(.A_2b, .mu_2b, y_train = y_mts, residuals = res_seas)
)
})
test_that("reconc_t standard usage gives deterministic results for fixed inputs", {
result1 <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat)
result2 <- reconc_t(.A_2b, .mu_2b, y_train = .y_mat, residuals = .res_mat)
expect_equal(result1$bottom_rec_mean, result2$bottom_rec_mean)
expect_equal(result1$bottom_rec_scale_matrix, result2$bottom_rec_scale_matrix)
expect_equal(result1$bottom_rec_df, result2$bottom_rec_df)
})
# 12. Regression test: hard-coded reference values ----------------------------
# Reference computed with:
# set.seed(123)
# n_series <- 3L; L_train <- 50L
# A_2b <- matrix(c(1,1), nrow=1)
# y_mat <- matrix(rnorm(L_train * n_series, mean=10), nrow=L_train, ncol=n_series)
# res_mat <- matrix(rnorm(L_train * n_series), nrow=L_train, ncol=n_series)
# mu_2b <- c(11, 4, 6)
# reconc_t(A_2b, mu_2b, y_train=y_mat, residuals=res_mat, return_parameters=TRUE)
test_that("reconc_t standard usage matches hard-coded reference values", {
set.seed(123)
n_series <- 3L
L_train <- 50L
A <- matrix(c(1, 1), nrow = 1)
y_mat <- matrix(rnorm(L_train * n_series, mean = 10), nrow = L_train, ncol = n_series)
res_mat <- matrix(rnorm(L_train * n_series), nrow = L_train, ncol = n_series)
mu <- c(11, 4, 6)
result <- suppressWarnings(
reconc_t(A, mu, y_train = y_mat, residuals = res_mat, return_parameters = TRUE)
)
# bottom_rec_mean
expect_equal(result$bottom_rec_mean,
c(4.314540, 6.391355),
tolerance = 1e-5
)
# bottom_rec_scale_matrix
expect_equal(result$bottom_rec_scale_matrix,
matrix(c(0.6503992, -0.3002090, -0.3002090, 0.5975068), nrow = 2),
tolerance = 1e-5
)
# bottom_rec_df
expect_equal(result$bottom_rec_df, 54)
# posterior_nu
expect_equal(result$posterior_nu, 55)
})
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.