Skip to content

fix: OptimisationState limits guard, VariableData.any, redundant limits overrides - #1532

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

fix: OptimisationState limits guard, VariableData.any, redundant limits overrides#1532
Jammy2211 merged 1 commit into
mainfrom
claude/loggaussian-prior-support-ngh59x

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Three items PyAutoFit#1527 left behind, plus one live bug found while doing the first.

VariableData.any reduced through var_all. It meant "is there a variable whose elements are all True" rather than "is any element True" — for array([True, False]) it answered False. OptimisationState.valid asks (parameters < lower_limit).any(), so a parameter vector with some components outside their limits was reported valid; only a variable violating on every component was caught. MeanField's valid.any() under-reported the same way.

That, not the truthiness guard #1527's follow-up list named, is why the Laplace limits check under-enforced.

OptimisationState.valid guarded on truthiness. if self.lower_limit and … is now is not None. Both limits are VariableData (a dict keyed by free variable) or None, so the old form worked only by accident of dict truthiness being non-emptiness. #1527 read it as a scalar test and recorded a 0.0 bug that does not exist — but the guard was one type change away from making it real.

Three limits overrides were exact duplicates. #1527 made Prior.limits derive from lower_limit/upper_limit, leaving UniformPrior, LogUniformPrior and TruncatedGaussianPrior restating the base. Removed, with their now-unused Tuple imports.

Closes #1531.

API Changes

No public API changes. Prior.limits returns the same values and the same types for every prior family; the three deleted overrides were already returning what the base returns.

One behaviour change, and it is the bug fix: VariableData.any() now returns True where any element is True, rather than only where some variable's elements are all True. Two consumers see it — OptimisationState.valid (which now rejects partial limit violations, as intended) and MeanField's validity count (which now reports partial validity). Both are corrections. The library's other four .any() call sites are on numpy arrays and are unaffected.

Test Plan

  • Full suite: NUMBA_CACHE_DIR=… MPLCONFIGDIR=… python3 -m pytest -x -q test_autofit/2186 passed, 36 skipped (baseline on main: 2178 / 36, so +8 and no regressions).
  • New test_autofit/graphical/test_optimisation_state_valid.py — 8 tests. OptimisationState.valid and VariableData.any had no coverage at all before it, so the suite would have stayed green through any change to either.
  • Verified by inversion: reverting var_any to var_all fails 3 of the 8, including both partial-violation cases.
  • Readiness gate: PyAutoHeart is not present in this session, so the documented fallback applies — per-repo pytest -x, GREEN on a clean tree.

Measured, not argued

Claim How it was checked
The limits deletion is not a type change All three priors already store Python floats, so the base's float() is a no-op — values and types identical across all five prior families
…and not a jit regression either Under jax.jit a prior never reaches limits: tree_unflatten__init__ calls float() on the tracer and raises ConcretizationTypeError first, with and without this change
The guard rewrite is behaviour-preserving The only case whose guard differs is the empty VariableData — falsy before (comparison skipped), truthy now (comparison run, .any() is False). valid returns True either way
MeanField's .any() is the only other VariableData consumer Enumerated every .any() call site in autofit/; the rest are numpy

Scope

This PR covers two PyAutoMind prompts rather than one, against the usual one-prompt-one-PR rule — both are the same cleanup left by #1527, and the second is three line deletions:

  • draft/refactor/autofit/optimisation_state_limit_guard_truthiness.md
  • draft/refactor/autofit/redundant_prior_limits_overrides.md

It also widens past both to fix VariableData.any, without which the guard being tidied does not actually enforce anything. Happy to split either way if a reviewer prefers.

Not done here

  • The check_limits EP question. The prior declares (0, inf) while its message stays at ±inf, so MeanField.lower_limit hands OptimisationState a -inf for a strictly positive parameter. Measured: EP is not producing wrong results today, because the message's own density returns a clean -inf at negative values rather than NaN. The limits check is redundant for that prior, not load-bearing. Tracked in draft/research/graphical_ep/transformed_message_declares_support.md.
  • message.logpdf(0.0) is -1.798e308 (negative float max) where the prior says -inf. Noted, unverified, out of scope.

Generated by Claude Code

…ts overrides

Three items left behind by PyAutoFit#1527, plus one live bug found while doing
the first.

VariableData.any dispatched through var_all
-------------------------------------------
`VariableData.any` reduced via `var_all`, making it "is there a variable whose
elements are ALL True" rather than "is ANY element True". For an array like
`[True, False]` it answered False.

That is a correctness bug in `OptimisationState.valid`, which asks
`(parameters < lower_limit).any()`: 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. Both call sites want a real `any`; the other four `.any()` call sites in
the library are on numpy arrays and are untouched.

OptimisationState.valid guards on truthiness
--------------------------------------------
`if self.lower_limit and ...` now `is not None`. `lower_limit`/`upper_limit` are
VariableData (a dict keyed by free variable) or None, so the old form worked
only by accident of dict truthiness being non-emptiness. #1527's follow-up list
read it as a scalar test and recorded a `0.0` bug that does not exist — but the
guard was one type change away from making it real.

Behaviour-preserving: the only case whose guard differs is the empty
VariableData, where the comparison is empty and `.any()` is False either way.

Redundant limits overrides removed
----------------------------------
#1527 made `Prior.limits` derive from `lower_limit`/`upper_limit`, leaving the
`UniformPrior`, `LogUniformPrior` and `TruncatedGaussianPrior` overrides exact
duplicates. The base coerces with `float()` and the overrides did not, so this
was a possible type change — measured, and it is not one: all three store
Python floats already, and under `jax.jit` a prior never reaches `limits`
anyway (`tree_unflatten` -> `__init__` calls `float()` on the tracer and raises
first, with or without this change). Now-unused `Tuple` imports dropped.

Tests
-----
`OptimisationState.valid` and `VariableData.any` had NO test coverage, so the
suite would have stayed green through any change to either — the process lesson
from #1477. New test_autofit/graphical/test_optimisation_state_valid.py covers
both, verified by inversion: reverting `var_any` to `var_all` fails 3 of the 8.

Full suite: 2186 passed, 36 skipped (baseline 2178/36, +8 new).

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 6e2d8c8 into main Aug 27, 2026
4 checks passed
@Jammy2211
Jammy2211 deleted the claude/loggaussian-prior-support-ngh59x branch August 27, 2026 17:24
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: OptimisationState limits guard, VariableData.any, redundant limits overrides

2 participants