tests/testthat/test-second-order.R

# Tests for second-order perturbation (v0.8.0)

# --- Helper: simple nonlinear model for testing ---
build_nl_ar1 <- function(rhoA = 0.90) {
  dsgenl_model(
    "Y = A",
    "A(+1) = A^rhoA",
    observed = "Y",
    unobserved = character(0),
    exo_state = "A",
    fixed = list(),
    start = list(rhoA = rhoA),
    ss_guess = c(Y = 1, A = 1)
  )
}

# Helper: model with genuine nonlinearity (curvature matters)
build_nl_consumption <- function() {
  dsgenl_model(
    "Y = A * H",
    "1 / C = beta * R / C(+1)",
    "Y = C",
    "R = 1 / beta",
    "A(+1) = A^rhoA",
    observed = "Y",
    unobserved = c("C", "H", "R"),
    exo_state = "A",
    fixed = list(beta = 0.99),
    start = list(rhoA = 0.90),
    ss_guess = c(Y = 1, C = 1, H = 1, R = 1.0101, A = 1)
  )
}

test_that("solve_dsge with order=2 returns second-order solution", {
  mod <- build_nl_ar1()
  sol <- solve_dsge(mod, params = c(rhoA = 0.90),
                    shock_sd = c(A = 0.01), order = 2)

  expect_s3_class(sol, "dsge_solution")
  expect_equal(sol$order, 2L)
  expect_false(is.null(sol$g_xx))
  expect_false(is.null(sol$h_xx))
  expect_false(is.null(sol$g_ss))
  expect_false(is.null(sol$h_ss))
})

test_that("first-order solution preserved in second-order object", {
  mod <- build_nl_ar1()
  sol1 <- solve_dsge(mod, params = c(rhoA = 0.90), shock_sd = c(A = 0.01), order = 1)
  sol2 <- solve_dsge(mod, params = c(rhoA = 0.90), shock_sd = c(A = 0.01), order = 2)

  # G and H should be identical
  expect_equal(sol2$G, sol1$G, tolerance = 1e-6)
  expect_equal(sol2$H, sol1$H, tolerance = 1e-6)
  expect_equal(sol2$M, sol1$M, tolerance = 1e-6)
  expect_true(sol2$stable)
})

test_that("g_xx and h_xx have correct dimensions", {
  mod <- build_nl_ar1()
  sol <- solve_dsge(mod, params = c(rhoA = 0.90),
                    shock_sd = c(A = 0.01), order = 2)

  n_c <- nrow(sol$G)
  n_s <- ncol(sol$G)

  expect_equal(dim(sol$g_xx), c(n_c, n_s, n_s))
  expect_equal(dim(sol$h_xx), c(n_s, n_s, n_s))
  expect_equal(length(sol$g_ss), n_c)
  expect_equal(length(sol$h_ss), n_s)
})

test_that("order=2 with linear model gives error", {
  m <- dsge_model(
    obs(y ~ z),
    state(z ~ rho * z),
    start = list(rho = 0.9)
  )
  expect_error(
    solve_dsge(m, params = c(rho = 0.9), order = 2),
    "dsgenl_model"
  )
})

test_that("order=1 is default and backward compatible", {
  mod <- build_nl_ar1()
  sol_default <- solve_dsge(mod, params = c(rhoA = 0.90), shock_sd = c(A = 0.01))
  sol_explicit <- solve_dsge(mod, params = c(rhoA = 0.90), shock_sd = c(A = 0.01), order = 1)

  expect_equal(sol_default$G, sol_explicit$G)
  expect_equal(sol_default$H, sol_explicit$H)
  expect_null(sol_default$order)  # first-order doesn't set order field
})

test_that("simulate_2nd_order produces output", {
  mod <- build_nl_consumption()
  sol <- suppressWarnings(
    solve_dsge(mod, params = c(beta = 0.99, rhoA = 0.90),
               shock_sd = c(A = 0.01), order = 2)
  )

  sim <- simulate_2nd_order(sol, n = 50, n_burn = 10, seed = 42)

  expect_equal(nrow(sim$states), 50L)
  expect_equal(nrow(sim$controls), 50L)
  expect_false(is.null(sim$state_levels))
  expect_false(is.null(sim$control_levels))
  expect_equal(sim$order, 2L)
})

test_that("simulate_2nd_order errors on first-order solution", {
  mod <- build_nl_ar1()
  sol <- solve_dsge(mod, params = c(rhoA = 0.90), shock_sd = c(A = 0.01), order = 1)

  expect_error(simulate_2nd_order(sol), "order = 2")
})

test_that("irf_2nd_order produces output", {
  mod <- build_nl_consumption()
  sol <- suppressWarnings(
    solve_dsge(mod, params = c(beta = 0.99, rhoA = 0.90),
               shock_sd = c(A = 0.01), order = 2)
  )

  irf <- irf_2nd_order(sol, shock = "A", size = 1, periods = 20)

  expect_s3_class(irf, "dsge_irf_2nd")
  expect_true("period" %in% names(irf))
  expect_true("variable" %in% names(irf))
  expect_true("response" %in% names(irf))
  expect_equal(irf$order[1], 2L)
})

test_that("irf_2nd_order errors on unknown shock", {
  mod <- build_nl_ar1()
  sol <- solve_dsge(mod, params = c(rhoA = 0.90), shock_sd = c(A = 0.01), order = 2)

  expect_error(irf_2nd_order(sol, shock = "bad_shock"), "Unknown shock")
})

test_that("second-order IRF is asymmetric for positive vs negative shocks", {
  mod <- build_nl_consumption()
  sol <- suppressWarnings(
    solve_dsge(mod, params = c(beta = 0.99, rhoA = 0.90),
               shock_sd = c(A = 0.01), order = 2)
  )

  irf_pos <- irf_2nd_order(sol, shock = "A", size = 3, periods = 10)
  irf_neg <- irf_2nd_order(sol, shock = "A", size = -3, periods = 10)

  # Extract Y responses
  y_pos <- irf_pos$response[irf_pos$variable == "Y"]
  y_neg <- irf_neg$response[irf_neg$variable == "Y"]

  # Second-order: positive and negative should NOT be mirror images
  # (they would be mirror images under first-order)
  # Test that the asymmetry is nonzero (even if small)
  asymmetry <- y_pos + y_neg  # would be zero for first-order
  # With small models the asymmetry may be tiny, so just check structure
  expect_equal(length(y_pos), length(y_neg))
  expect_equal(length(y_pos), 10L)
})

test_that("second-order solution with multi-variable model works", {
  mod <- build_nl_consumption()
  sol <- suppressWarnings(
    solve_dsge(mod, params = c(beta = 0.99, rhoA = 0.90),
               shock_sd = c(A = 0.01), order = 2)
  )

  expect_equal(sol$order, 2L)
  expect_true(nrow(sol$G) >= 2)  # multiple controls
  expect_true(ncol(sol$G) >= 1)  # at least one state

  # g_ss should be named
  expect_false(is.null(names(sol$g_ss)))
  expect_false(is.null(names(sol$h_ss)))
})

test_that("invalid order value gives error", {
  mod <- build_nl_ar1()
  expect_error(solve_dsge(mod, params = c(rhoA = 0.9), order = 4), "order must be 1, 2, or 3")
  expect_error(solve_dsge(mod, params = c(rhoA = 0.9), order = 0), "order must be 1, 2, or 3")
})

test_that("second-order constant correction (g_ss) is finite", {
  mod <- build_nl_consumption()
  sol <- suppressWarnings(
    solve_dsge(mod, params = c(beta = 0.99, rhoA = 0.90),
               shock_sd = c(A = 0.01), order = 2)
  )

  expect_true(all(is.finite(sol$g_ss)))
  expect_true(all(is.finite(sol$h_ss)))
})

test_that("second-order simulation is reproducible with seed", {
  mod <- build_nl_consumption()
  sol <- suppressWarnings(
    solve_dsge(mod, params = c(beta = 0.99, rhoA = 0.90),
               shock_sd = c(A = 0.01), order = 2)
  )

  sim1 <- simulate_2nd_order(sol, n = 20, n_burn = 5, seed = 123)
  sim2 <- simulate_2nd_order(sol, n = 20, n_burn = 5, seed = 123)

  expect_equal(sim1$states, sim2$states)
  expect_equal(sim1$controls, sim2$controls)
})

Try the dsge package in your browser

Any scripts or data that you put into this service are public.

dsge documentation built on Sept. 25, 2026, 5:08 p.m.