fix: flag hierarchical parent-scale collapse in EP diagnostics (#1464) - #1465
Merged
Merged
Conversation
The F10 sigma-collapse guard passed a collapsed hierarchical parent scale silently. It missed on both limbs, for structural reasons rather than a mis-set threshold: - The floor limb needs std < 1e-8; the measured collapses sit at std 0.11 and ~1e-5 against a parent hyper-prior of mean 10, so it misses by orders of magnitude. - The monotone limb was effectively dead code in any multi-factor graph. snapshot() records every variable on every factor update, but a factor update only moves the marginals of the variables adjacent to that factor, so a variable's history is dominated by steps at which it did not move. Those give diff == 0 and defeat the strict np.diff(tail) < 0 test. It fired only in a unit test that hand-built a geomspace series. Both limbs are absolute and variable-agnostic because #1332 is a pathology in which every std goes to zero. The parent scale of a HierarchicalFactor collapses in a different shape: its mean goes to ~0 while the std stays moderate in absolute terms and is over-confident only relative to that mean — a confident claim of no scatter. - EPDiagnostics.register_hierarchical_scales records which variables are parent scale hyperparameters, reusing DeclarativeFactorGraph's existing hierarchical_factors accessor. It no-ops on a plain FactorGraph. - check_sigma_collapse gains a relative, boundary-aware limb for those variables: flagged when the mean falls below a fraction of its initial value and std/|mean| is small. - The monotone limb now drops consecutive unchanged rows before testing, restoring it to what it was written to mean. Thresholds separate the measured data cleanly: the two collapses (mean 0.80 and 0.0030) flag, the recover band (9.1-12.8) does not. Tests use those measured values as fixtures; five of them fail on unmodified main. The guard reports; it never alters the fit.
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
Closes #1464. Leg 1 of #1405 follow-up 2.
A hierarchical EP fit can settle into a basin where the parent scale
hyperparameter of a
HierarchicalFactorcollapses to ~0 and is reported with anover-confident ~0 error — a confident claim of "no scatter" that is wrong
(measured at 7% of 30 identical-problem runs on a clean known-answer toy, truth
σ=10). The F10 sigma-collapse guard passed this silently, so the bad value
reached the user with no warning at all.
It missed on both limbs, structurally rather than by a mis-set threshold:
std < 1e-8; the measured collapses sit at std0.11and~1e-5against a parent hyper-prior of mean 10. Misses by orders of magnitude.EPDiagnostics.snapshotrecords every variable on every factor update, but afactor update only moves the marginals of the variables adjacent to that
factor. A variable's history is therefore dominated by steps at which it did not
move, which give
diff == 0and defeat the strictnp.all(np.diff(tail) < 0)test. It fired only in a unit test that hand-built a
np.geomspaceseriesdirectly into
variable_rows.Both limbs are absolute and variable-agnostic, because #1332 is a pathology in
which every std goes to zero. This collapse has a different shape: the parent
scale's mean goes to ~0 while its std stays moderate in absolute terms and is
over-confident only relative to that mean. So an absolute std test cannot see it,
and the fix is a dedicated parent-scale check rather than a retuned threshold.
Three changes:
EPDiagnostics.register_hierarchical_scalesrecords which variables are parentscale hyperparameters, reusing the existing
DeclarativeFactorGraph.hierarchical_factorsaccessor. It no-ops on a plain
FactorGraph— diagnostics must never kill a fit.check_sigma_collapsegains a relative, boundary-aware limb for those variables:flagged when the mean falls below a fraction of its initial value and
std / |mean|is small.to what it was written to mean.
Thresholds separate the measured data cleanly: the two collapses (mean
0.80and0.0030) flag, the recover band (9.1–12.8 ± 0.9–2.4) does not.This change reports; it never alters the fit. Curing the collapse basin is
deliberately out of scope and stays on #1405.
API Changes
Additive and backward-compatible.
EPDiagnosticsgains aregister_hierarchical_scales(factor_graph)method and ascale_variablesattribute;
check_sigma_collapsegains two optional keyword arguments withdefaults. No symbol is removed, renamed, or has a required argument added, so no
caller needs to change.
One behaviour change worth noting for release notes:
check_sigma_collapsenow emits warnings in two situations where it previously returned nothing — a
collapsed hierarchical parent scale, and a genuine monotone std shrink in a
multi-factor graph (which the strict test could not previously detect). Callers
that assert on an empty warning list for a collapsed fit will now see a warning;
that is the point of the change.
See full details below.
Test Plan
pytest test_autofit/— 1714 passed, 4 skipped, 0 failedpytest test_autofit/graphical/functionality/test_diagnostics.py— 16 passedmain, so they pin newbehaviour rather than passing either way. The other 4 are negative
(no-false-positive) guards and pass on main by construction.
rather than invented ones: both COLLAPSE states flag, both ends of the
RECOVER band do not, an honestly uncertain small scale does not, and an
unregistered variable is not subject to the relative test.
sigmais registered and the parent
meancorrectly is not, and the shallowcollapse that
mainpassed silently now emits a specific warning.Full API Changes (for automation & release notes)
Added
EPDiagnostics.register_hierarchical_scales(factor_graph)— records the names ofhierarchical parent scale variables so
check_sigma_collapsecan apply thescale-specific test. Silently records nothing for a graph with no hierarchical
factors, or one that does not expose them.
EPDiagnostics.scale_variables: Set[str]— the set populated by the above.Changed Signature
check_sigma_collapse(diagnostics, std_floor=1e-8, monotone_steps=5, shrink_factor=1e-3)→ adds
scale_mean_fraction=0.2andscale_relative_error=0.5. Both optionalwith defaults; existing calls are unaffected.
Changed Behaviour
check_sigma_collapse— flags a registered parent scale variable whose mean hasfallen below
scale_mean_fractionof its initial value with a relative errorbelow
scale_relative_error, emitting ascale-collapse:warning naming Hierarchical EP: parent scale hyperparameter collapses to ~0 with over-confident error (+ init-crash) #1405.check_sigma_collapse— the monotone limb now compares a variable's updatesrather than every recorded step, so it can fire on a real multi-factor graph
instead of only on a synthetic strictly-decreasing series.
EPOptimiser.__init__— registers hierarchical scales from its factor graph.No signature change.
Migration
Generated by the PyAutoLabs agent workflow.
Generated by Claude Code