You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Three tidy-ups PyAutoFit#1527 left behind, plus one live bug found while doing the first.
VariableData.any reduced through var_all, so it meant "is there a variable whose elements are all True" rather than "is any element True". OptimisationState.valid asks (parameters < lower_limit).any() — so a parameter vector with some components outside their limits was reported valid, and only a variable violating on every component was caught. MeanField's valid.any() under-reported the same way.
That bug, not the truthiness guard the #1527 follow-up list named, is why the Laplace limits check under-enforced. It surfaced only because rewriting the guard needed a test, and OptimisationState.valid had no test coverage at all — the process lesson from #1477, third time it has paid out in this lineage.
Plan
Fix VariableData.any to reduce through var_any, restoring the limits check in OptimisationState.valid and the validity count in MeanField.
Rewrite OptimisationState.valid's guards from truthiness to is not None, so "was a limit supplied?" is stated rather than implied by VariableData happening to be a dict.
Cover both OptimisationState.valid and VariableData.any, which had no tests, and verify the new tests by inversion.
Detailed implementation plan
Work Classification
Library — PyAutoFit only. No workspace changes.
Affected Repositories
PyAutoFit (primary)
Branch Survey
Repository
Current Branch
Dirty?
./PyAutoFit
claude/loggaussian-prior-support-ngh59x (from main @ 34d6dff)
clean
Branch:claude/loggaussian-prior-support-ngh59x
Worktree root: none — this ran web-github against a direct clone at /home/user/pyautofit.
Implementation Steps
autofit/mapper/variable.py — VariableData.any dispatches to var_any, not var_all. The other four .any() call sites in the library (utils.py:539, multi_start_gradient/search.py:468, latent.py:274, and the numpy paths) are on numpy arrays and are untouched; the only VariableData consumers are line_search.py and mean_field.py:501, both of which want a real any.
autofit/graphical/laplace/line_search.py — valid's two guards become is not None, with a docstring recording why truthiness worked only by accident.
autofit/mapper/prior/{uniform,log_uniform,truncated_gaussian}.py — delete the limits overrides and the now-unused Tuple imports.
test_autofit/graphical/test_optimisation_state_valid.py — new; 8 tests over valid (no limits, empty limits, inside, below, above, on-the-bound, a 0.0 lower limit) and VariableData.any / .all().
Key Files
autofit/mapper/variable.py:396 — the any/all reduction.
autofit/graphical/laplace/line_search.py:106-127 — the limits guard.
The limits deletion is not a type change. The base coerces with float() and the overrides did not, which looked like a downstream risk via TruncatedGaussianPrior(mean, sigma, *limits) in prior passing. All three priors already store Python floats, so the coercion is a no-op; and under jax.jit a prior never reaches limits at all, because tree_unflatten → __init__ calls float() on the tracer and raises ConcretizationTypeError first — with or without this change.
The guard rewrite is behaviour-preserving. The only case whose guard differs is the empty VariableData: falsy under the old form, so the comparison was skipped; run under the new form, where an empty comparison's .any() is False. valid returns True either way.
The tests fail without the fix. Reverting var_any to var_all fails 3 of the 8.
Readiness gate: PyAutoHeart is not present in this session, so the documented fallback applies — per-repo pytest -x as the gate, GREEN.
Scope note
This covers two PyAutoMind prompts, against the usual one-prompt-one-PR rule, because both are the same cleanup left by #1527 and the second is three line deletions:
It also widens past both to fix VariableData.any, without which the guard being tidied does not actually work. Happy to split if a reviewer prefers.
Not done here
The check_limits EP question — the prior declares (0, inf) while its message stays at ±inf. Measured: EP is not wrong today, because the message's own density returns a clean -inf at negative values rather than NaN. Tracked in draft/research/graphical_ep/transformed_message_declares_support.md.
message.logpdf(0.0) is -1.798e308 where the prior says -inf. Noted, unverified, out of scope.
Origin
Follow-ups 1 and 2 from complete/2026/08/loggaussian-prior-declares-own-support.md (this repo's #1526 / #1527).
Overview
Three tidy-ups PyAutoFit#1527 left behind, plus one live bug found while doing the first.
VariableData.anyreduced throughvar_all, so it meant "is there a variable whose elements are all True" rather than "is any element True".OptimisationState.validasks(parameters < lower_limit).any()— so a parameter vector with some components outside their limits was reported valid, and only a variable violating on every component was caught.MeanField'svalid.any()under-reported the same way.That bug, not the truthiness guard the #1527 follow-up list named, is why the Laplace limits check under-enforced. It surfaced only because rewriting the guard needed a test, and
OptimisationState.validhad no test coverage at all — the process lesson from #1477, third time it has paid out in this lineage.Plan
VariableData.anyto reduce throughvar_any, restoring the limits check inOptimisationState.validand the validity count inMeanField.OptimisationState.valid's guards from truthiness tois not None, so "was a limit supplied?" is stated rather than implied byVariableDatahappening to be adict.limitsoverrides fix: LogGaussianPrior declares its own (0, inf) support #1527 made exact duplicates of the base, once thefloat()coercion difference is measured rather than assumed.OptimisationState.validandVariableData.any, which had no tests, and verify the new tests by inversion.Detailed implementation plan
Work Classification
Library — PyAutoFit only. No workspace changes.
Affected Repositories
Branch Survey
claude/loggaussian-prior-support-ngh59x(frommain@34d6dff)Branch:
claude/loggaussian-prior-support-ngh59xWorktree root: none — this ran
web-githubagainst a direct clone at/home/user/pyautofit.Implementation Steps
autofit/mapper/variable.py—VariableData.anydispatches tovar_any, notvar_all. The other four.any()call sites in the library (utils.py:539,multi_start_gradient/search.py:468,latent.py:274, and the numpy paths) are on numpy arrays and are untouched; the onlyVariableDataconsumers areline_search.pyandmean_field.py:501, both of which want a realany.autofit/graphical/laplace/line_search.py—valid's two guards becomeis not None, with a docstring recording why truthiness worked only by accident.autofit/mapper/prior/{uniform,log_uniform,truncated_gaussian}.py— delete thelimitsoverrides and the now-unusedTupleimports.test_autofit/graphical/test_optimisation_state_valid.py— new; 8 tests overvalid(no limits, empty limits, inside, below, above, on-the-bound, a0.0lower limit) andVariableData.any/.all().Key Files
autofit/mapper/variable.py:396— theany/allreduction.autofit/graphical/laplace/line_search.py:106-127— the limits guard.autofit/mapper/prior/abstract.py:360— the basePrior.limitsfix: LogGaussianPrior declares its own (0, inf) support #1527 made canonical.Measured, not assumed
limitsdeletion is not a type change. The base coerces withfloat()and the overrides did not, which looked like a downstream risk viaTruncatedGaussianPrior(mean, sigma, *limits)in prior passing. All three priors already store Python floats, so the coercion is a no-op; and underjax.jita prior never reacheslimitsat all, becausetree_unflatten→__init__callsfloat()on the tracer and raisesConcretizationTypeErrorfirst — with or without this change.VariableData: falsy under the old form, so the comparison was skipped; run under the new form, where an empty comparison's.any()isFalse.validreturnsTrueeither way.var_anytovar_allfails 3 of the 8.Validation
NUMBA_CACHE_DIR=… MPLCONFIGDIR=… python3 -m pytest -x -q test_autofit/— 2186 passed, 36 skipped (baseline onmain: 2178 / 36).pytest -xas the gate, GREEN.Scope note
This covers two PyAutoMind prompts, against the usual one-prompt-one-PR rule, because both are the same cleanup left by #1527 and the second is three line deletions:
draft/refactor/autofit/optimisation_state_limit_guard_truthiness.mddraft/refactor/autofit/redundant_prior_limits_overrides.mdIt also widens past both to fix
VariableData.any, without which the guard being tidied does not actually work. Happy to split if a reviewer prefers.Not done here
check_limitsEP question — the prior declares(0, inf)while its message stays at±inf. Measured: EP is not wrong today, because the message's own density returns a clean-infat negative values rather thanNaN. Tracked indraft/research/graphical_ep/transformed_message_declares_support.md.message.logpdf(0.0)is-1.798e308where the prior says-inf. Noted, unverified, out of scope.Origin
Follow-ups 1 and 2 from
complete/2026/08/loggaussian-prior-declares-own-support.md(this repo's #1526 / #1527).