diff --git a/garak/analyze/bootstrap_ci.py b/garak/analyze/bootstrap_ci.py index ab010f100..46e42a2a2 100644 --- a/garak/analyze/bootstrap_ci.py +++ b/garak/analyze/bootstrap_ci.py @@ -25,12 +25,16 @@ def _bootstrap_calculation( if confidence_level is None: confidence_level = _config.reporting.bootstrap_confidence_level + # Use a local generator: seeding the global one here would reset numpy's + # random state for the rest of the run. + seed = None if ( hasattr(_config, "run") and hasattr(_config.run, "seed") and _config.run.seed is not None ): - np.random.seed(_config.run.seed) + seed = _config.run.seed + rng = np.random.default_rng(seed) denominator = sensitivity + specificity - 1.0 if abs(denominator) < 0.01: @@ -51,7 +55,7 @@ def _bootstrap_calculation( is_perfect_detector = np.isclose(denominator, 1.0) for i in range(num_iterations): - resampled_results = np.random.choice(results, size=n, replace=True) + resampled_results = rng.choice(results, size=n, replace=True) p_obs = resampled_results.mean() if is_perfect_detector: diff --git a/tests/analyze/test_bootstrap_ci.py b/tests/analyze/test_bootstrap_ci.py index f3b8d1046..61c9c2745 100644 --- a/tests/analyze/test_bootstrap_ci.py +++ b/tests/analyze/test_bootstrap_ci.py @@ -212,3 +212,27 @@ def test_nonparametric_vs_parametric_advantage(): assert ci_lower < 40 # Lower bound below observed rate assert ci_upper > 40 # Upper bound above observed rate assert ci_upper - ci_lower < 40 # CI width should be reasonable for n=50 + + +def test_calculate_bootstrap_ci_leaves_global_rng_alone(): + """A CI calculation should not reset the global numpy RNG""" + results = [0] * 60 + [1] * 40 + _config.run.seed = 42 + + def draw(): + return np.random.choice(10, 3).tolist() + + np.random.seed(1234) + expected = [draw() for _ in range(3)] + + np.random.seed(1234) + actual = [] + for _ in range(3): + garak.analyze.bootstrap_ci.calculate_bootstrap_ci( + results=results, sensitivity=0.95, specificity=0.90 + ) + actual.append(draw()) + + _config.run.seed = None + + assert actual == expected, "CI calculation perturbed the global numpy RNG"