# Integration tests for bpm_valsamp() (R/valsamp.R): solve for the sample size # that meets precision / assurance targets via pre-posterior simulation and # Robbins-Monro stochastic root finding. Stochastic and slow, so n_sim is kept # small and the heavier net-benefit optimisation is exercised separately. test_that("bpm_valsamp() returns an estimated sample size for a precision target", { skip_on_cran() set.seed(42) res <- quietly(bpm_valsamp( evidence = example_evidence(), targets = list(eciw.cstat = 0.1), n_sim = 40 )) expect_type(res, "list") expect_true(all(c("results", "sample", "evidence", "targets") %in% names(res))) expect_true("eciw.cstat" %in% names(res$results)) n <- res$results[["eciw.cstat"]] expect_true(is.finite(n) && n > 0) expect_s3_class(res$evidence, "bpm_evidence") }) test_that("bpm_valsamp() carries through the supplied targets and a sample", { skip_on_cran() set.seed(7) targets <- list(eciw.cal_slp = 0.3) res <- quietly(bpm_valsamp( evidence = example_evidence(), targets = targets, n_sim = 40 )) expect_identical(res$targets, targets) expect_s3_class(res$sample, "data.frame") expect_true(nrow(res$sample) == 40) # The simulated c-statistic draws are kept inside the valid (0.51, 0.99) band. expect_true(all(res$sample$cstat >= 0.51 & res$sample$cstat <= 0.99)) }) test_that("bpm_valsamp() accepts a previously simulated sample as evidence", { skip_on_cran() set.seed(11) first <- quietly(bpm_valsamp( evidence = example_evidence(), targets = list(eciw.cstat = 0.1), n_sim = 40 )) # Re-using the returned $sample data frame should not re-process evidence. res <- quietly(bpm_valsamp( evidence = first$sample, targets = list(eciw.cstat = 0.1) )) expect_true("eciw.cstat" %in% names(res$results)) }) # NB targets. StoSOO searches over log(N), so small and large sample sizes are # resolved alike. These tests check the answers against bpm_valprec() # evaluated on the same pre-posterior sample. test_that("bpm_valsamp() returns NA for an oa.nb target already met under current information", { skip_on_cran() set.seed(2026) # With this evidence, assurance with no new data is about 0.82 at threshold 0.2. expect_message( res <- suppressWarnings(bpm_valsamp( evidence = example_evidence(), targets = list(oa.nb = 0.75), n_sim = 1000, threshold = 0.2, ex_args = no_progress )), "already meets the target" ) expect_true(is.na(res$results[["oa.nb"]])) expect_gte(res$nb$oa.nb$value0, 0.75) expect_true(is.na(res$nb$oa.nb$N)) # value0 is the no-data assurance that bpm_valprec() reports as oa.nb0. prec <- quietly(bpm_valprec( N = 100, evidence = res$sample, targets = list(oa.nb = TRUE), threshold = 0.2 )) expect_equal(unname(prec$results[1, "oa.nb0"]), res$nb$oa.nb$value0) }) test_that("bpm_valsamp() resolves small oa.nb and voi.nb sample sizes, matching bpm_valprec()", { skip_on_cran() set.seed(2) res <- quietly(bpm_valsamp( evidence = example_evidence(), targets = list(oa.nb = 0.87, voi.nb = 0.8), n_sim = 2000, threshold = 0.2 )) n_oa <- res$results[["oa.nb"]] n_voi <- res$results[["voi.nb"]] expect_true(res$nb$oa.nb$value0 < 0.87) # This target is met in the low hundreds. expect_true(is.finite(n_oa) && n_oa < 306) expect_true(is.finite(n_voi)) prec <- quietly(bpm_valprec( N = c(round(n_oa / 2), n_oa, 2 * n_oa, n_voi), evidence = res$sample, targets = list(oa.nb = TRUE, voi.nb = TRUE), threshold = 0.2 )) m <- prec$results expect_true(m[1, "oa.nb"] < 0.87) expect_true(abs(m[2, "oa.nb"] - 0.87) < 0.025) expect_true(m[3, "oa.nb"] > 0.87) expect_true(abs(m[4, "voi.nb"] - 0.8) < 0.05) # A target not reached within the search range gives NA and a warning. expect_warning( capped <- suppressMessages(bpm_valsamp( evidence = res$sample, targets = list(oa.nb = 0.99), threshold = 0.2, ex_args = c(no_progress, list(nb_n_range = c(1, 100))) )), "not reached" ) expect_true(is.na(capped$results[["oa.nb"]])) }) test_that("bpm_valsamp() returns NA for both NB targets when there is no decision uncertainty", { skip_on_cran() set.seed(3) # A precise, well-calibrated model: it has the highest NB in every draw. evidence <- list( prev ~ beta(3000, 7000), cstat ~ beta(9000, 1000), cal_mean ~ norm(0, 0.001), cal_slp ~ norm(1, 0.001) ) msgs <- character(0) res <- withCallingHandlers( suppressWarnings(bpm_valsamp( evidence = evidence, targets = list(oa.nb = 0.9, voi.nb = 0.8), n_sim = 200, threshold = 0.3, impute_cor = FALSE, ex_args = no_progress )), message = function(m) { msgs <<- c(msgs, conditionMessage(m)) invokeRestart("muffleMessage") } ) expect_equal(res$nb$oa.nb$value0, 1) expect_true(is.na(res$results[["oa.nb"]])) expect_true(is.na(res$results[["voi.nb"]])) expect_true(any(grepl("already meets the target", msgs))) expect_true(any(grepl("EVPI = 0", msgs))) }) test_that("bpm_valsamp() rejects NB targets outside (0, 1)", { expect_error( bpm_valsamp(evidence = example_evidence(), targets = list(oa.nb = 1.2), threshold = 0.2), "strictly between 0 and 1" ) expect_error( bpm_valsamp(evidence = example_evidence(), targets = list(voi.nb = c(0.5, 0.8)), threshold = 0.2), "strictly between 0 and 1" ) })