test_that("ou_params returns correct alpha and tau", { kappa <- 0.05; sigma_psi <- 0.10 p <- ou_params(kappa, sigma_psi) expect_equal(p$alpha, exp(-kappa), tolerance = 1e-12) expect_equal(p$tau^2, sigma_psi^2 / (2*kappa) * (1 - exp(-2*kappa)), tolerance = 1e-12) }) test_that("seir_simulate returns correct dimensions", { fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) x0 <- c(1e6-100, 50, 50, 0, log(0.25)) set.seed(1) traj <- seir_simulate(50, 0.05, 0.10, log(0.25), x0, fixed) expect_s3_class(traj, "data.frame") expect_equal(nrow(traj), 51) expect_named(traj, c("day","S","E","I","R","Psi","beta","obs")) expect_true(all(traj$S >= 0)) expect_true(all(traj$E >= 0)) expect_true(all(traj$obs >= 0)) }) test_that("seir_simulate uses exact OU (stationarity check)", { # Mean of Psi over many steps should be near mu_psi fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) mu_psi <- log(0.25) x0 <- c(1e6-100, 50, 50, 0, mu_psi) set.seed(99) traj <- seir_simulate(2000, 0.05, 0.10, mu_psi, x0, fixed) expect_equal(mean(traj$Psi[200:2001]), mu_psi, tolerance = 0.1) }) test_that("npf_seir runs and returns expected structure", { fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) x0 <- c(1e6-100, 50, 50, 0, log(0.25)) set.seed(1) traj <- seir_simulate(60, 0.05, 0.10, log(0.25), x0, fixed) obs <- traj$obs[-1] fit <- npf_seir(obs, fixed, K=10, M=20, seed=42) expect_s3_class(fit, "npf_seir") expect_length(fit$Rt_mean, 60) expect_length(fit$beta_mean, 60) expect_equal(dim(fit$final_theta), c(10, 3)) expect_equal(length(fit$final_w), 10) expect_length(fit$final_inner, 10) expect_equal(sum(fit$final_w), 1, tolerance = 1e-10) # First obs used for init so Rt_mean[1] should be NA expect_true(is.na(fit$Rt_mean[1])) # Non-burn-in entries should be real expect_true(!any(is.na(fit$Rt_mean[5:60]))) }) test_that("summary.npf_seir validates burn", { fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) x0 <- c(1e6-100, 50, 50, 0, log(0.25)) set.seed(1) traj <- seir_simulate(20, 0.05, 0.10, log(0.25), x0, fixed) fit <- npf_seir(traj$obs[-1], fixed, K=10, M=20, seed=42) expect_error(summary(fit, burn = 100), "burn must be smaller") }) test_that("plot.npf_seir supports which selector", { fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) x0 <- c(1e6-100, 50, 50, 0, log(0.25)) set.seed(1) traj <- seir_simulate(20, 0.05, 0.10, log(0.25), x0, fixed) fit <- npf_seir(traj$obs[-1], fixed, K=10, M=20, seed=42) expect_invisible(plot(fit, which = "Rt")) expect_invisible(plot(fit, which = "beta")) expect_invisible(plot(fit, which = "both")) }) test_that("npf_seir with explicit x0 fills from n=1", { fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) x0 <- c(1e6-100, 50, 50, 0, log(0.25)) set.seed(1) traj <- seir_simulate(30, 0.05, 0.10, log(0.25), x0, fixed) obs <- traj$obs[-1] fit <- npf_seir(obs, fixed, K=10, M=20, seed=42, x0=x0) # With explicit x0, first step is filtered expect_false(is.na(fit$Rt_mean[1])) }) test_that("cori_rt returns data frame with correct structure", { set.seed(1) inc <- rpois(30, 100) rt <- cori_rt(inc, tau=7) expect_s3_class(rt, "data.frame") expect_equal(nrow(rt), 30) expect_named(rt, c("t","Rt_mean","Rt_lo","Rt_hi")) # First tau-1 rows should be NaN (no complete window) expect_true(all(is.nan(rt$Rt_mean[1:6]))) # Later rows should be positive expect_true(all(rt$Rt_mean[7:30] > 0)) }) test_that("predict.npf_seir returns sensible forecasts", { fixed <- list(eps=1/5, gamma=1/10, delta=1/(70*365), b=1/(70*365), q=0.3, N=1e6, sigma_comp=0.01) x0 <- c(1e6-100, 50, 50, 0, log(0.25)) set.seed(1) traj <- seir_simulate(40, 0.05, 0.10, log(0.25), x0, fixed) fit <- npf_seir(traj$obs[-1], fixed, K=10, M=20, seed=42) fc <- predict(fit, horizon=7, n_draws=100) expect_length(fc$mean, 7) expect_true(all(fc$mean >= 0)) expect_true(all(fc$hi >= fc$lo)) })