From 34d70fd35f518abcde60aa90a93f84cd470aa2ab Mon Sep 17 00:00:00 2001 From: Aki Vehtari Date: Thu, 15 Oct 2026 11:04:48 +0300 Subject: [PATCH 1/2] Use a relative tolerance in is_constant --- R/helpers.R | 7 ++++++- tests/testthat/test_0_helpers.R | 12 ++++++++++++ tests/testthat/test_E_loo.R | 12 ++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/R/helpers.R b/R/helpers.R index 37d407a8..962e93bc 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -277,6 +277,11 @@ release_questions <- function() { } # nocov end +# `x` is constant if `max(x) - min(x)` is at most `tol` times the largest +# absolute value in `x`. Default is `.Machine$double.eps`. is_constant <- function(x, tol = .Machine$double.eps) { - abs(max(x) - min(x)) < tol + r <- range(x) + spread <- r[2] - r[1] + # the equality check covers all-zero and all-infinite x + r[1] == r[2] || (is.finite(spread) && spread <= tol * max(abs(r))) } diff --git a/tests/testthat/test_0_helpers.R b/tests/testthat/test_0_helpers.R index a0add421..01a808d6 100644 --- a/tests/testthat/test_0_helpers.R +++ b/tests/testthat/test_0_helpers.R @@ -91,3 +91,15 @@ test_that("loo_cores works", { expect_warning(expect_equal(loo_cores(10), 2), "deprecated") options(loo.cores = NULL) }) + +test_that("is_constant uses a tolerance relative to the magnitude of x", { + expect_true(is_constant(rep(0, 5))) + expect_true(is_constant(rep(1e-300, 5))) + expect_true(is_constant(rep(-Inf, 5))) + expect_true(is_constant(c(1, 1 + .Machine$double.eps))) + expect_false(is_constant(c(1e-20, 2e-20))) + expect_false(is_constant(c(1e-300, 2e-300))) + expect_false(is_constant(c(-Inf, 2))) + expect_false(is_constant(c(1, 2))) + expect_true(is.na(is_constant(c(1, NA)))) +}) diff --git a/tests/testthat/test_E_loo.R b/tests/testthat/test_E_loo.R index 913ca9aa..cc7f75c1 100644 --- a/tests/testthat/test_E_loo.R +++ b/tests/testthat/test_E_loo.R @@ -245,3 +245,15 @@ test_that("weighted variance works", { x <- 1e8 + c(-1, 0, 1) expect_equal(.wvar(x, rep(1 / 3, 3)), 1) }) + +test_that("E_loo pareto_k does not depend on the scale of x", { + # posterior::pareto_khat() of h * r also needs the relative tolerance + # with an older posterior it returns NA here + skip_if(posterior:::is_constant(c(1e-20, 2e-20)), + "posterior::is_constant() has an absolute tolerance") + k <- E_loo(x, psis_mat, type = "mean", log_ratios = log_rats)$pareto_k + for (s in c(1e-20, 1e-200)) { + k_s <- E_loo(s * x, psis_mat, type = "mean", log_ratios = log_rats)$pareto_k + expect_equal(k_s, k, tolerance = 1e-10) + } +}) From 65dc87ed642c22ffa10c17a72c8622fe2a4926ac Mon Sep 17 00:00:00 2001 From: Florence Bockting Date: Fri, 16 Oct 2026 11:41:02 +0300 Subject: [PATCH 2/2] chore: add NEWS.md --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 877c1afe..544c0891 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,8 @@ comparison-model minus reference-model by @florence-bockting in #391 * Fix `crps()` and `scrps()` ignoring the `permutations` argument for numeric input by @thanasibakis in #398 * Speed up the Bayesian bootstrap in `pseudobma_weights()` by @VisruthSK in #368 +* Use a tolerance relative to the magnitude of `x` in `is_constant()` by +@avehtari in #407 # loo 2.10.1