feat: report prior-support clipping in search.summary - #1478
Merged
Conversation
The Clipper (#1477) counts how often it fires -- n_clipped_lane_steps is accumulated per-lane in AbstractMultiStartGradient and written to search_internal -- but the count never reached the artefact a user actually reads to find out what a search did. search.summary said nothing about clipping at all. The channel already existed: search_summary_from_samples reads samples.samples_info and emits the NaN counters and their rates, guarded on the key so searches without them are unaffected. Two things were missing from it rather than a mechanism: - n_clipped_lane_steps never reached samples_info. It stopped at search_internal, so nothing downstream could see it. - n_constrained_lane_steps did reach samples_info but was never emitted, so the trapped-lane counter from #1475 has been invisible in search.summary since it shipped. Now reported. Clipping is reported in three cases, and the distinction between the last two is the point: - No clipper (ClipperNone, or a search predating the Clipper): emits nothing. The default path's summary is unchanged, which matters because this file is read by tooling and sits in every archived run's output. - Clipped and counted (MultiStartGradient): it enforces the constraint itself every step via Clipper.project, so it knows how often it fired and reports the count and the rate, denominated by n_starts * total_steps like the NaN rates beside it. - Clipped but not observable (LBFGS and the bound-supporting scipy methods): declarative, handing optimize.Bounds to scipy and letting scipy enforce, so project is never called and no mask exists. Reporting 0 there would read as "the clipper never fired" when it means "this search cannot know", so it says "not measured (bounds enforced by scipy)" instead. The clipper is published as its class NAME rather than a bool, so the summary can say which strategy ran and a later strategy needs no schema change. The count is per-LANE, not per-coordinate -- a lane clipped in three parameters on one step is one clipped lane-step -- matching how the counters beside it read, so all four stay directly comparable. It is restored from search_internal as a lifetime total, so a resumed run reports the whole run's clipping rather than the current process's share. Verified end-to-end against the search.summary files four real searches wrote, not just the formatting helper in isolation: LBFGS default (no clipping lines), LBFGS clipped ("not measured"), MultiStart default (no clipping lines), MultiStart clipped (Clipped Lane-Steps = 414, rate 0.958). One behaviour change to note: multi-start summaries gain a Constrained Lane-Steps line they did not have before. Everything else is additive and gated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzF2XmKQaqZRWZfZMxvTNR
This was referenced Aug 16, 2026
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.
The
Clipper(#1477) counts how often it fires —n_clipped_lane_stepsis accumulated per-lane inAbstractMultiStartGradientand written tosearch_internal— but the count never reached the artefact a user actually reads to find out what a search did.search.summarysaid nothing about clipping at all.The channel already existed
Worth stating, because it makes this change much smaller than it first looks.
search_summary_from_samplesalready readssamples.samples_infoand already emitsResurrections,Value-NaN Lane-Steps,Gradient-NaN Lane-Stepsand both rates, guarded on the key so searches without them are unaffected. Two things were missing from it, rather than a mechanism being absent:n_clipped_lane_stepsnever reachedsamples_info. It stopped atsearch_internal, so nothing downstream could see it.n_constrained_lane_stepsdid reachsamples_infobut was never emitted. The trapped-lane counter from Count multi-start lanes trapped outside a declared model constraint #1475 has been invisible insearch.summarysince it shipped. Now reported.Three cases, and the last two are the point
ClipperNone, or a search predating theClipper)MultiStartGradient)Clipper,Clipped Lane-Steps,Clipped Lane-Step RateLBFGS, other bound-supporting scipy methods)Clipper,Clipped Lane-Steps = not measured (bounds enforced by scipy)MultiStartGradientenforces the constraint itself every step viaClipper.project, so it knows exactly how often it fired.LBFGSis declarative — it handsoptimize.Boundsto scipy and lets scipy enforce, soprojectis never called and no mask exists. Reporting0there would read as "the clipper never fired" when it means "this search cannot know". It says so instead.The default path emits nothing, which matters because this file is read by tooling and sits in every archived run's output.
Design notes
search_internalas a lifetime total, so a resumed run reports the whole run's clipping rather than the current process's share.n_starts * total_stepsdenominator as the NaN rates, omitted when that is zero.Verification
test_autofit/non_linear+test_autofit/text: 501 passed, 3 skipped, 1 failed. The failure (test_nautilus.py::test__single_core_builds_no_pool) is pre-existing — reproduced identically on a stashed clean tree — as is theastropycollection error inpaths/test_save_and_load.py.6 new tests cover:
ClipperNoneemits nothing; counted reports count and rate; the no-count case says not-measured and never a bare0; an absent key leaves other searches unaffected; constrained emitted; constrained absent when never written.Also verified end-to-end against the
search.summaryfiles four real searches wrote, not just the formatting helper in isolation:Incidental, and worth someone's attention: on that toy 3-parameter Gaussian the unclipped arm showed
Value-NaN Lane-Steps = 378(94.5%) and the clipped arm0. The autolens_profiling#128 mechanism reproduces on a model with nothing astrophysical in it, which would make a far cheaper regression fixture than theimaging/mgecell.One behaviour change to note in review
Multi-start summaries gain a
Constrained Lane-Stepsline they did not have before. Everything else is strictly additive and gated, so theClipperNonepath is otherwise unchanged. If byte-identity for existing multi-start runs is wanted too, that one line can be dropped — it is independent of the clip count.🤖 Generated with Claude Code
https://claude.ai/code/session_01FzF2XmKQaqZRWZfZMxvTNR
Generated by Claude Code