Nothing
# The exported SBC front door (gcol33/tulpa#380).
#
# The scorer itself -- the predictive shapes, the within-atom PIT, the CRPS
# closed forms and the exact simultaneous band -- is arbitrated in
# test-sbc-crps.R against closed forms, brute-force simulation, numerical
# integration and the published Kolmogorov critical value. Nothing here
# re-arbitrates it. What is pinned here is the DOOR: that it runs the same
# experiment the drivers do, that its two guards fire on the premises they
# describe and only on those, and that the result reads through its methods.
#
# Every model below is conjugate and solved in closed form, so the whole file
# runs without a fit. The engine fixture goes through the door in
# test-sbc-crps.R and test-posterior-sbc.R, where it already has the seeds and
# the sample sizes its verdicts were measured at.
# ---------------------------------------------------------------------------
# Conjugate fixtures: y_i ~ N(mu, 1) with mu ~ N(0, 1), whose posterior is
# N(v * sum(y), v), v = 1 / (1 + n).
# ---------------------------------------------------------------------------
fd_sim <- function(seed) {
set.seed(seed)
mu <- stats::rnorm(1)
list(y = stats::rnorm(10L, mu, 1), theta = c(mu = mu))
}
fd_post <- function(y) {
v <- 1 / (1 + length(y))
c(mean = v * sum(y), var = v)
}
fd_fitter <- function(d) {
p <- fd_post(d$y)
list(exact = list(mu = sbc_normal(p[["mean"]], sqrt(p[["var"]]))),
narrow = list(mu = sbc_normal(p[["mean"]], sqrt(p[["var"]]) / 2)))
}
# The same model wired for posterior SBC.
fd_model <- function(n_obs = 20L, group_ids = NULL, pool = NULL) {
set.seed(7L)
d_obs <- list(y = stats::rnorm(n_obs, 0.4, 1), g = seq_len(n_obs))
m <- list(
data_obs = d_obs,
fit = function(data) fd_post(data$y),
draw_theta = function(fit, seed) {
set.seed(seed)
c(mu = stats::rnorm(1, fit[["mean"]], sqrt(fit[["var"]])))
},
simulate = function(theta, seed) {
set.seed(seed)
list(y = stats::rnorm(10L, theta[["mu"]], 1), g = seq_len(10L))
},
pool = function(obs, rep) list(y = c(obs$y, rep$y),
g = c(obs$g, rep$g + length(obs$g))),
arms = function(fit, data)
list(exact = list(mu = sbc_normal(fit[["mean"]], sqrt(fit[["var"]])))))
if (!is.null(group_ids)) m$group_ids <- group_ids
if (!is.null(pool)) m$pool <- pool
m
}
test_that("the five predictive shapes are exported and the verb is the only one", {
ex <- getNamespaceExports("tulpa")
expect_true(all(c("sbc", "sbc_mixture", "sbc_normal", "sbc_discrete",
"sbc_rank", "sbc_draws") %in% ex))
# The drivers, the report and the band stay internal: `sbc()` is the verb.
expect_false(any(c("recov_sbc", "recov_posterior_sbc", "sbc_report",
"sbc_crps_compare", "sbc_ecdf_band", "sbc_pit") %in% ex))
})
test_that("a replicate re-observing the same groups is refused", {
m <- fd_model(group_ids = function(data) data$g,
pool = function(obs, rep)
list(y = c(obs$y, rep$y), g = c(obs$g, rep$g)))
expect_error(sbc("posterior", model = m, n_sim = 5L), "FRESH groups")
})
skip_on_cran_budget()
test_that("the declaration is checked in both directions", {
skip_on_cran_budget()
# A quantity that DID move contradicts the declaration.
expect_error(sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 20L, flat_prior = "mu"),
"but the truth moved")
# A declaration naming something the fitter does not score.
expect_error(sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 20L, flat_prior = "sigma"),
"does not score")
})
test_that("a rank quantity needs no truth and is exempt from the guard", {
skip_on_cran_budget()
# Every arm reports it as a rank, so there is no prior to be proper.
fitter <- function(d)
list(a = list(r = sbc_rank(sum(stats::runif(40L) < 0.5), 40L)))
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fitter,
n_sim = 20L)
expect_identical(unique(res$report$quantity), "r")
expect_true(all(is.na(res$pit$crps)))
})
test_that("a pool that drops the observed data is refused", {
skip_on_cran_budget()
# The documented collapse: fitting the replicate ALONE is ordinary SBC under
# a hand-made prior, and the sequential-updating identity does not hold.
m <- fd_model(pool = function(obs, rep) rep)
expect_error(sbc("posterior", model = m, n_sim = 5L),
"AUGMENTED posterior")
# And one that drops the replicate: nothing is being calibrated.
m2 <- fd_model(pool = function(obs, rep) obs)
expect_error(sbc("posterior", model = m2, n_sim = 5L),
"not conditioned on")
# A pool that merely WRAPS both is not refused: containment holds.
m3 <- fd_model(pool = function(obs, rep)
list(y = c(obs$y, rep$y), g = c(obs$g, rep$g + length(obs$g)),
parts = list(obs = obs, rep = rep)))
expect_s3_class(sbc("posterior", model = m3, n_sim = 5L), "sbc")
})
test_that("the two experiments do not accept each other's arguments", {
skip_on_cran_budget()
m <- fd_model()
expect_error(sbc("posterior", model = m, n_sim = 5L, flat_prior = "mu"),
"prior-predictive experiment")
expect_error(sbc("posterior", simulator = fd_sim, n_sim = 5L),
"belong to the prior-predictive")
expect_error(sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
model = m, n_sim = 5L),
"posterior experiment's callback list")
expect_error(sbc("prior_predictive", n_sim = 5L), "needs simulator")
expect_error(sbc("posterior", n_sim = 5L), "needs model")
})
# ---------------------------------------------------------------------------
# 5. The CRPS scoping refusal survives the promotion
# ---------------------------------------------------------------------------
test_that("CRPS is refused as a ranking where it is not a proper posterior score", {
skip_on_cran_budget()
# The door offers only the two experiments in which the score IS proper, so a
# fixed-truth result cannot come out of it -- and the refusal underneath is
# still enforced.
expect_error(sbc("fixed", simulator = fd_sim, fitter = fd_fitter),
"should be one of")
fx <- recov_sbc(fd_sim, fd_fitter, n_seed = 4L, truth = "fixed")
expect_error(sbc_crps_compare(fx, "exact"), "prior-predictive")
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 20L)
expect_error(summary(res, baseline = "nosucharm"), "is not one of")
})
test_that("proper_prior is not 'verified' on a run that probed nothing", {
skip_on_cran_budget()
# gcol33/tulpa#716: .sbc_check_proper_prior() returns early with n_probe = 0
# when every scored quantity is of the "rank" kind, having probed no truth at
# all, and the premise was hardcoded "verified". The 0 does disclose it to a
# careful reader; the word that survives into a summary is "verified".
expect_identical(
tulpa:::.sbc_premise_proper_prior(list(n_probe = 3L)), "verified")
expect_false(
grepl("verified", tulpa:::.sbc_premise_proper_prior(list(n_probe = 0L))))
expect_match(tulpa:::.sbc_premise_proper_prior(list(n_probe = 0L)),
"not applicable")
})
skip_on_cran()
# ---------------------------------------------------------------------------
# 1. One implementation of the scorer
# ---------------------------------------------------------------------------
test_that("the scorer is the package's, not a private copy in the helper", {
skip_on_cran()
ns <- asNamespace("tulpa")
for (f in c("sbc_mixture", "sbc_normal", "sbc_discrete", "sbc_rank",
"sbc_draws", "sbc_pit", "sbc_fold", "sbc_crps",
"sbc_crossing_prob", "sbc_ecdf_band", "sbc_ecdf_test",
"recov_sbc", "recov_posterior_sbc", "sbc_report",
"sbc_crps_compare")) {
expect_true(exists(f, envir = ns, inherits = FALSE),
label = sprintf("%s lives in the package", f))
expect_identical(environment(get(f, envir = ns)), ns,
label = sprintf("%s is bound in the namespace", f))
}
# And the helper does not redefine any of them, which is the duplicate the
# promotion exists to prevent.
h <- readLines(test_path("helper-sbc.R"))
defs <- grep("^\\s*(sbc_[a-z_0-9]+|recov_[a-z_0-9]+)\\s*<-\\s*function", h,
value = TRUE)
redefined <- sub("^\\s*([A-Za-z_.0-9]+)\\s*<-.*$", "\\1", defs)
expect_length(intersect(redefined,
c("sbc_mixture", "sbc_normal", "sbc_discrete",
"sbc_rank", "sbc_draws", "sbc_pit", "sbc_fold",
"sbc_crps", "sbc_crossing_prob", "sbc_ecdf_band",
"sbc_ecdf_test", "recov_sbc",
"recov_posterior_sbc", "sbc_report",
"sbc_crps_compare")), 0L)
})
# ---------------------------------------------------------------------------
# 2. The door runs the driver's own experiment
# ---------------------------------------------------------------------------
test_that("the front door reproduces the driver bit for bit", {
skip_on_cran()
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 40L, seed = 11L)
expect_s3_class(res, "sbc")
expect_identical(res$pit,
recov_sbc(fd_sim, fd_fitter, n_seed = 40L, seed_off = 11L,
truth = "prior_draw"))
expect_identical(res$report, sbc_report(res$pit, level = 0.95))
expect_identical(attr(res$pit, "truth"), "prior_draw")
expect_identical(res$crps_role, "proper posterior score")
expect_equal(res$bands[["40"]]$coverage, 0.95, tolerance = 1e-5)
})
test_that("the exact arm calibrates and the known-bad control does not", {
skip_on_cran()
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 120L, seed = 5000L)
r <- res$report
expect_true(r$inside[r$arm == "exact"])
expect_true(r$inside_folded[r$arm == "exact"])
expect_gt(r$p_unif[r$arm == "exact"], 0.01)
# A posterior half as wide as the truth is a symmetric dispersion error, so
# the folded read is what has to catch it.
expect_false(r$inside_folded[r$arm == "narrow"])
# And the proper score ranks them the same way round.
cmp <- summary(res, baseline = "exact")$compare
expect_gt(cmp$delta[cmp$arm == "narrow"], 0)
})
test_that("n_ref reaches the fitter and quantities restrict the scoring", {
skip_on_cran()
fitter <- function(d, n_ref = 50L)
list(a = list(mu = sbc_normal(mean(d$y), 0.3),
r = sbc_rank(sum(stats::runif(n_ref) < 0.5), n_ref)))
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fitter,
n_sim = 20L, n_ref = 13L)
expect_identical(unique(res$pit$kind[res$pit$quantity == "r"]), "rank")
expect_true(all(res$pit$pit[res$pit$quantity == "r"] <= 1))
expect_identical(res$n_ref, 13L)
only <- sbc("prior_predictive", simulator = fd_sim, fitter = fitter,
n_sim = 20L, quantities = "mu")
expect_identical(unique(only$report$quantity), "mu")
})
# ---------------------------------------------------------------------------
# 3. The proper-prior guard
# ---------------------------------------------------------------------------
test_that("a truth held fixed across simulations is refused, naming the way out", {
skip_on_cran()
fixed <- function(seed) {
set.seed(seed)
list(y = stats::rnorm(10L, 0.3, 1), theta = c(mu = 0.3))
}
expect_error(
sbc("prior_predictive", simulator = fixed, fitter = fd_fitter, n_sim = 30L),
"PROPER prior")
expect_error(
sbc("prior_predictive", simulator = fixed, fitter = fd_fitter, n_sim = 30L),
'experiment = "posterior"')
# Admitted by the structural argument the caller asserts, which travels on
# the result.
ok <- sbc("prior_predictive", simulator = fixed, fitter = fd_fitter,
n_sim = 30L, flat_prior = "mu")
expect_identical(ok$premises$flat_prior, "mu")
expect_identical(ok$premises$proper_prior, "verified")
expect_identical(ok$premises$n_probed, 10L)
})
# ---------------------------------------------------------------------------
# 4. The posterior experiment's two premises
# ---------------------------------------------------------------------------
test_that("the posterior door runs and records what it verified", {
skip_on_cran()
m <- fd_model(group_ids = function(data) data$g)
res <- sbc("posterior", model = m, n_sim = 25L)
expect_identical(res$experiment, "posterior")
expect_identical(attr(res$pit, "truth"), "posterior_draw")
expect_identical(res$premises$pooling, "verified")
expect_identical(res$premises$fresh_groups, "verified (disjoint group labels)")
# The exact posterior calibrates under the construction.
expect_true(res$report$inside)
# Without `group_ids` the premise is recorded as UNVERIFIED, not assumed.
res2 <- sbc("posterior", model = fd_model(), n_sim = 25L)
expect_identical(res2$premises$fresh_groups, "undeclared")
expect_identical(res2$pit, res$pit)
})
# ---------------------------------------------------------------------------
# 6. The methods
# ---------------------------------------------------------------------------
test_that("print, summary, plot and diagnostics read the result", {
skip_on_cran()
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 40L, seed = 3L)
out <- utils::capture.output(print(res))
expect_true(any(grepl("Simulation-based calibration", out)))
expect_true(any(grepl("proper posterior score", out)))
expect_true(any(grepl("inside the band", out)))
s <- summary(res, baseline = "exact")
expect_s3_class(s, "sbc_summary")
sout <- utils::capture.output(print(s))
expect_true(any(grepl("Paired CRPS against arm", sout)))
# pdf() is the one device present on every platform. png() selects whatever
# getOption("bitmapType") names, which resolves to the X11 driver on a
# headless machine and cannot start there even where capabilities("png") is
# TRUE. Nothing below reads raster output.
pf <- tempfile(fileext = ".pdf")
grDevices::pdf(pf, width = 6, height = 4)
plot(res)
plot(res, arm = "exact", folded = TRUE)
grDevices::dev.off()
expect_true(file.exists(pf))
expect_error(plot(res, arm = "nosucharm"), "no \\(arm, quantity\\) selected")
# A panel's annotation reads the sample the panel draws. A folded panel
# carrying the raw p-value and the raw band verdict is the fold's own failure
# mode: it reports "inside" over a curve outside the band, on exactly the
# symmetric dispersion error the raw ECDF cancels. The columns are read off
# `res$report`, whose folded p-value is `p_unif_folded`; `p_folded` is the
# name the PRINT method renames it to, and reading that here would be
# `NULL[i]`, a zero-length label rather than a wrong one.
expect_true(all(c("p_unif", "inside", "p_unif_folded", "inside_folded") %in%
names(res$report)))
r <- data.frame(p_unif = 0.90, inside = TRUE,
p_unif_folded = 0.0001, inside_folded = FALSE)
expect_identical(.sbc_panel_note(r, 1L, folded = FALSE), "p = 0.9")
expect_identical(.sbc_panel_note(r, 1L, folded = TRUE),
"p = 0.0001, outside band")
d <- diagnostics(res)
expect_true(all(c("arm", "quantity", "inside", "p_unif") %in% names(d)))
expect_identical(attr(d, "experiment"), "prior_predictive")
})
test_that("a fit's reliability table carries the calibration verdict alongside", {
skip_on_cran()
set.seed(1L)
n <- 120L
x <- stats::rnorm(n)
y <- stats::rbinom(n, 1L, stats::plogis(-0.2 + 0.6 * x))
fit <- tulpa(y ~ x, data.frame(y = y, x = x), family = "binomial",
mode = "smc")
res <- sbc("prior_predictive", simulator = fd_sim, fitter = fd_fitter,
n_sim = 30L)
plain <- diagnostics(fit)
both <- diagnostics(fit, sbc = res)
expect_null(attr(plain, "sbc_verdict"))
expect_identical(attr(both, "sbc_experiment"), "prior_predictive")
# The verdict rides ALONGSIDE the reliability table; the table itself is
# untouched.
stripped <- both
attr(stripped, "sbc_report") <- NULL
attr(stripped, "sbc_experiment") <- NULL
attr(stripped, "sbc_verdict") <- NULL
expect_identical(plain, stripped)
out <- utils::capture.output(print(both))
expect_true(any(grepl("calibration \\(SBC", out)))
expect_error(diagnostics(fit, sbc = "not an sbc result"), "takes an `sbc`")
})
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.