diff --git a/R/args.R b/R/args.R index 4b0b16b32..708889cd7 100644 --- a/R/args.R +++ b/R/args.R @@ -1336,6 +1336,10 @@ process_init_approx <- function(init, num_procs, model_variables = NULL, exp(log_weights - max(log_weights)), tail = "right", r_eff = 1, return_k = FALSE, verbose = FALSE ) + # posterior <= 1.7.1 can return weights of about -2e-16 when many weights + # underflow to 0. Remove this once the minimum posterior version includes + # stan-dev/posterior#468. + weights <- pmax(weights, 0) } if (num_candidates < nrow(draws_df)) { # Collapse duplicate candidates to one row, summing their weights so a vector diff --git a/tests/testthat/test-fit-init.R b/tests/testthat/test-fit-init.R index d29841871..e13e66727 100644 --- a/tests/testthat/test-fit-init.R +++ b/tests/testthat/test-fit-init.R @@ -149,6 +149,40 @@ test_that("Pathfinder fit initializations use the intended weights", { expect_equal(pareto_smooth_calls, 2L) }) +test_that("Pathfinder init weights are not negative after Pareto smoothing", { + # Most log weights are far below the maximum, so exp() underflows to 0 for + # them. posterior::pareto_smooth() can then return weights of about -2e-16 + # (posterior <= 1.7.1), which resample_draws() rejects. The mock gives those + # weights whatever the installed posterior version. Remove this test with + # the clamp once the minimum posterior version includes + # stan-dev/posterior#468. + lp <- c(-1000 - seq_len(350), -seq(0, 60, length.out = 50)) + pathfinder_fit <- structure( + list( + draws = function(format) posterior::as_draws_df(data.frame( + theta = seq_along(lp), + lp__ = lp, + lp_approx__ = 0 + )), + metadata = function() list( + num_paths = 10, + psis_resample = FALSE, + calculate_lp = TRUE + ), + return_codes = function() 0 + ), + class = "CmdStanPathfinder" + ) + local_mocked_bindings( + pareto_smooth = function(x, ...) x - 1e-16, + .package = "posterior" + ) + local_mocked_bindings(process_init = function(init, ...) init) + + inits <- process_init.CmdStanPathfinder(pathfinder_fit, 4) + expect_equal(posterior::ndraws(inits), 4) +}) + test_that("Pathfinder init candidates are distinct target parameter vectors", { draws <- posterior::as_draws_df(data.frame( theta = c(1, 1, 2, 3),