Overview
AbstractMessage.natural_logpdf (autofit/messages/interface.py:98) reduces 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 is zero density. But posinf/neginf are left at their defaults, and nan_to_num replaces a genuine -inf with negative float max. The call therefore does the opposite of its intent for exactly 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):
| value |
message.logpdf |
why |
-1.0 |
-inf |
log(-1) is NaN → nan=-inf applies |
0.0 |
-1.7976931348623157e+308 |
log(0) is -inf → default neginf clamps |
That asymmetry is the proof of mechanism, and the clamped value is exactly -sys.float_info.max.
UniformPrior and LogUniformPrior return -inf correctly outside their boxes, so this is not visible for every family — it needs an expression that reaches -inf rather than NaN.
Why it matters
-1.8e308 is finite, and isfinite is the test a lot of this codebase branches on:
optax.apply_if_finite — the mechanism autofit/non_linear/clipper.py is built around. That module's entire premise is that leaving the prior support makes the objective non-finite.
- Two such terms summed overflow to
-inf while one does not, so behaviour depended on how many parameters were out of support.
Plan
- Pass
neginf=-xp.inf, posinf=xp.inf so only NaN is replaced.
- Cover both halves of the reduction, pin that in-support values are untouched, and assert the general property that no prior family reports a finite density off its support.
Origin
Loose end from #1532, noted in its Shipped comment and not filed at the time. PyAutoMind prompt: draft/bug/priors/natural_logpdf_clamps_neginf.md.
Deliberately out of scope
TruncatedGaussianPrior's message returns finite logpdf well outside its limits (-8.20 at -1.0 for a (0, 3) support). That is 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. Not addressed here, and the new general-property test deliberately excludes it rather than papering over it.
Overview
AbstractMessage.natural_logpdf(autofit/messages/interface.py:98) reduces throughThe
nan=-xp.infis deliberate — an out-of-support NaN is zero density. Butposinf/neginfare left at their defaults, andnan_to_numreplaces a genuine-infwith negative float max. The call therefore does the opposite of its intent for exactly the inputs that already had the right answer:NaN-inf-inf✅-inf-inf-1.7976931348623157e+308❌Measured on
main@6e2d8c8LogGaussianPrior(0.4, 1.3):message.logpdf-1.0-inflog(-1)is NaN →nan=-infapplies0.0-1.7976931348623157e+308log(0)is-inf→ defaultneginfclampsThat asymmetry is the proof of mechanism, and the clamped value is exactly
-sys.float_info.max.UniformPriorandLogUniformPriorreturn-infcorrectly outside their boxes, so this is not visible for every family — it needs an expression that reaches-infrather thanNaN.Why it matters
-1.8e308is finite, andisfiniteis the test a lot of this codebase branches on:optax.apply_if_finite— the mechanismautofit/non_linear/clipper.pyis built around. That module's entire premise is that leaving the prior support makes the objective non-finite.-infwhile one does not, so behaviour depended on how many parameters were out of support.Plan
neginf=-xp.inf, posinf=xp.infso onlyNaNis replaced.Origin
Loose end from #1532, noted in its Shipped comment and not filed at the time. PyAutoMind prompt:
draft/bug/priors/natural_logpdf_clamps_neginf.md.Deliberately out of scope
TruncatedGaussianPrior's message returns finitelogpdfwell outside its limits (-8.20at-1.0for a(0, 3)support). That is separate looseness inTruncatedNormalMessage, not this clamp — the prior-levellog_prior_from_valueis correct there, which is why the P6 property tests pass. Not addressed here, and the new general-property test deliberately excludes it rather than papering over it.