fix: enforce prior bounds in the NumPy-path objective (UniformPrior, LogUniformPrior) - #1490
Merged
Merged
Conversation
…LogUniformPrior) Restores the prior-support enforcement lost in release 2025.10.16.1, when assert_within_limits / PriorLimitException were removed (#1155) without a replacement on the NumPy path: UniformPrior.log_prior_from_value returned 0.0 unconditionally and LogUniformPrior returned a finite -log(value) outside [lower_limit, upper_limit], so searches that form a log posterior on the NumPy path (Emcee, Zeus, Drawer, LBFGS) never penalised an out-of-support sample. An Emcee walker on a poorly-constrained parameter under UniformPrior(-0.1, 0.1) escaped to |value| ~ 1e14 (100% of accepted samples outside the box). - UniformPrior.log_prior_from_value: 0.0 inside the box, -inf outside, on the NumPy path exactly as on the JAX path (which was made strict in 2e35407 for JAX only, creating the asymmetry). - LogUniformPrior.log_prior_from_value: -inf outside [lower_limit, upper_limit] on the NumPy path (previously only value <= 0 was -inf); keeps the double-where so no log of a non-positive value is evaluated. - Fitness.log_likelihood_from: skip the log-prior subtraction when the prior sum is non-finite — -inf minus -inf is NaN, and the inversion feeds history / quick-update / resume bookkeeping. - clipper.py: dated correction of the module docstring's "MCMC samplers reject -inf proposals" claim, false in the 2025.10.16.1 -> #1489 window. - bfgs/search.py: comment updated — both branches now see the hard wall. - Tests: new test_prior_bounds_1489.py (scalar/array bounds on both priors, -inf figure of merit for out-of-box vectors, NaN-free inversion, end-to-end Emcee containment); test_prior.py updated where it pinned the unbounded behaviour; test_clipper.py LBFGS foil updated (the clipper-less fit can no longer escape past the prior edge). Before/after (same seed): well-constrained 3-param Gaussian fit posterior statistically unchanged (medians agree to ~0.02 with sigma ~0.1-0.5); unconstrained-parameter fit goes from 630/630 samples outside the box to 0/540, matching the pre-regression 2025.5.10.1 behaviour. Full suite: 1734 passed, 8 skipped (2 pre-existing environment failures also fail on untouched main). Closes #1489 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DP4nPgsLuB2czRjrV4qcSk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restores the prior-support enforcement lost in release 2025.10.16.1, when
assert_within_limits/PriorLimitExceptionwere removed (#1155, part of the JAX jit-compat cleanup) without a replacement on the NumPy path. Since thenUniformPrior.log_prior_from_valuereturned0.0unconditionally andLogUniformPriorreturned a finite-log(value)above itsupper_limit, so the searches that form a log posterior on the NumPy path (Emcee, Zeus, Drawer, LBFGS) never penalised an out-of-support sample — an Emcee walker on a poorly-constrained parameter underUniformPrior(-0.1, 0.1)escaped to |value| ≈ 1e14, with 100% of accepted samples outside the box. Full reproduction, git archaeology, and the fix-shape decision are on #1489.This is fix shape A from the issue: strict bounds in the priors themselves, exactly mirroring the JAX path (made strict in
2e3540771, which created the asymmetry). Shape C (defaultingLBFGStoClipperPriorBox) is deliberately not included — orthogonal follow-up.API Changes
No symbols added or removed — a behaviour restoration:
UniformPrior.log_prior_from_value(NumPy path):0.0inside[lower_limit, upper_limit],-infoutside (was: always0.0). Limits inclusive, matching JAX.LogUniformPrior.log_prior_from_value(NumPy path):-infeverywhere outside[lower_limit, upper_limit](was:-infonly forvalue <= 0; finite-log(value)aboveupper_limitand belowlower_limit).Fitness.log_likelihood_from: skips the log-prior subtraction when the prior sum is non-finite, so a rejected point inverts to-infrather thanNaN.See full details below.
Test Plan
test_autofit/mapper/prior/test_prior_bounds_1489.py: scalar + array bounds for both priors (inclusive limits, noRuntimeWarning),-inffigure of merit for an out-of-box vector, NaN-freelog_likelihood_frominversion, and an end-to-end Emcee containment test (guaranteed deterministic: walkers start in-box and every out-of-box proposal is rejected).test_prior.pyupdated where it pinned the unbounded behaviour;test_clipper.pyLBFGS end-to-end updated (its foil asserted the clipper-less fit escapes the box — that escape was the bug).test_messages.py::test_beta,test_nautilus.py::test__single_core_builds_no_pool) are missing-optional-dependency environment failures that reproduce identically on untouchedmain.Measured before/after (same script, same seed)
fe9f813)UniformPrior(-0.1, 0.1): samples outside boxThe well-constrained posterior is statistically unchanged (median shifts ~0.02 against σ ≈ 0.1–0.5); the containment matches the pre-regression release.
Full API Changes (for automation & release notes)
Removed
Added
Changed behaviour
autofit.mapper.prior.uniform.UniformPrior.log_prior_from_value(value, xp=np)— NumPy path now returns-infforvalueoutside[lower_limit, upper_limit](previously0.0unconditionally). JAX path unchanged.autofit.mapper.prior.log_uniform.LogUniformPrior.log_prior_from_value(value, xp=np)— NumPy path now returns-inffor anyvalueoutside[lower_limit, upper_limit](previously onlyvalue <= 0). JAX path unchanged.autofit.non_linear.fitness.Fitness.log_likelihood_from— returns-inf(notNaN) for a figure of merit produced by an out-of-support vector.Migration
UniformPrior/LogUniformPriorbound may change: chains now respect the declared box. This restores pre-2025.10.16.1 behaviour; no code changes required.Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DP4nPgsLuB2czRjrV4qcSk
Generated by Claude Code