Skip to content

fix: natural_logpdf clamps a genuine -inf to -1.8e308 - #1534

Merged
Jammy2211 merged 1 commit into
mainfrom
claude/loggaussian-prior-support-ngh59x
Aug 27, 2026
Merged

fix: natural_logpdf clamps a genuine -inf to -1.8e308#1534
Jammy2211 merged 1 commit into
mainfrom
claude/loggaussian-prior-support-ngh59x

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

AbstractMessage.natural_logpdf reduced through xp.nan_to_num(..., nan=-xp.inf) with posinf/neginf left at their defaults. nan_to_num replaces a genuine -inf with negative float max, so the call did the opposite of its intent for the inputs that already had the right answer:

value reaching the reduction intended before after
NaN -inf -inf -inf
-inf -inf -1.7976931348623157e+308 -inf

Measured on main @ 6e2d8c8, LogGaussianPrior(0.4, 1.3):

message.logpdf(-1.0) = -inf                      # log(-1) is NaN  -> nan=-inf applies
message.logpdf( 0.0) = -1.7976931348623157e+308  # log(0) is -inf  -> default neginf clamps

The asymmetry is the proof of mechanism, and the clamped value is exactly -sys.float_info.max.

Closes #1533.

Why it matters

-1.8e308 is finite, and isfinite is what optax.apply_if_finite and autofit/non_linear/clipper.py branch on to detect a lane leaving the prior support — that module's entire premise is that leaving the box makes the objective non-finite. Two such terms summed overflow to -inf while one does not, so the behaviour depended on how many parameters were out of support.

API Changes

No API changes. One behaviour change, and it is the fix: message.logpdf now returns -inf rather than -1.8e308 at points whose density is genuinely zero and whose expression reaches -inf rather than NaN. In practice that is LogGaussianPrior at exactly 0.0; Uniform and LogUniform already returned -inf.

log_prior_from_value — the objective-facing path — is untouched and was already correct throughout.

Test Plan

  • Full suite: NUMBA_CACHE_DIR=… MPLCONFIGDIR=… python3 -m pytest -x -q test_autofit/2203 passed, 36 skipped. Baseline measured on this branch point (6e2d8c8, changes stashed): 2190 / 36. +13, no regressions.
  • New test_autofit/messages/test_natural_logpdf_neginf.py — 13 tests: the -inf half, the NaN half (pinned so fixing one cannot break the other), in-support values still finite across 8 points, and the general property over Uniform / LogUniform / LogGaussian that no family reports a finite density off its support.
  • Verified by inversion: reverting the fix fails 2 of the 13.
  • Readiness gate: PyAutoHeart is not in this session, so the documented fallback applies — per-repo pytest -x, GREEN on a clean tree.

Deliberately out of scope

TruncatedGaussianPrior's message returns finite logpdf well outside its limits (-8.20 at -1.0 for a (0, 3) support). Separate looseness in TruncatedNormalMessage, not this clamp — the prior-level log_prior_from_value is correct there, which is why the P6 property tests pass. The new general-property test excludes it explicitly, with a comment saying why, rather than quietly asserting something weaker across all families.


Generated by Claude Code

`AbstractMessage.natural_logpdf` reduced through

    xp.nan_to_num(log_base + eta_t - log_partition, nan=-xp.inf)

The `nan=-xp.inf` is deliberate: an out-of-support NaN (log of a negative value
under a transformed message) is zero density. But `posinf`/`neginf` were left at
their DEFAULTS, and `nan_to_num` replaces a genuine `-inf` with negative float
max. So the call did the opposite of its intent for the inputs that already had
the right answer:

    value reaching the reduction | intended | actual
    NaN                          | -inf     | -inf
    -inf                         | -inf     | -1.7976931348623157e+308

Measured on main @ 6e2d8c8, LogGaussianPrior(0.4, 1.3):

    message.logpdf(-1.0) = -inf                       (log(-1) is NaN)
    message.logpdf( 0.0) = -1.7976931348623157e+308   (log(0) is -inf)

That asymmetry is the proof of mechanism, and the second value is exactly
-sys.float_info.max.

It matters because -1.8e308 is FINITE, and `isfinite` is what
`optax.apply_if_finite` and `autofit.non_linear.clipper` branch on to detect a
lane leaving the prior support -- clipper.py's entire premise is that leaving
the box makes the objective non-finite. Two such terms summed overflow to -inf
while one does not, so the behaviour depended on how many parameters were out
of support.

Fix: pass `neginf`/`posinf` through, so only NaN is replaced.

Tests: new test_autofit/messages/test_natural_logpdf_neginf.py (13), covering
both halves of the reduction, in-support values unchanged, and the general
property that no prior family reports a finite density off its support.
Verified by inversion: reverting the fix fails 2 of the 13.

Full suite: 2203 passed, 36 skipped (baseline on 6e2d8c8: 2190 / 36, measured).

Co-Authored-By: Claude <noreply@anthropic.com>
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Aug 27, 2026 — with Claude
@Jammy2211
Jammy2211 merged commit 5c391fd into main Aug 27, 2026
4 checks passed
@Jammy2211
Jammy2211 deleted the claude/loggaussian-prior-support-ngh59x branch August 27, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: natural_logpdf clamps a genuine -inf to -1.8e308

2 participants