feat: declare the ell_comps disk on EllProfile (PyAutoFit#1537) - #589
Merged
Conversation
…t onto it (PyAutoFit#1537)
`EllProfile.__model_constraint__` already MEASURES how far outside the ellipticity
clamp a profile sits, but a measure cannot say how to fix one. This declares the
structure PyAutoFit's new `ClipperPriorBoxJoint` projects onto:
__model_ball_constraints__ = ((("ell_comps",), convert.ELL_COMPS_MAGNITUDE_CLAMP),)
The radius is the CLAMP (0.999), deliberately not `1 - margin`. Between 0.999 and
1.0 the conversion to an axis ratio saturates, so the likelihood is flat radially
and a lane projected into that annulus has nothing to climb back out on -- it
would be moved from a region the model rejects into one the optimizer cannot
leave. Projecting onto the clamp puts it exactly where the radial gradient is
alive again.
Declared once at `EllProfile`, so it reaches every elliptical light and mass
profile; the spherical subclasses inherit it but pin `ell_comps` to an instance,
so PyAutoFit resolves no pair and projects nothing.
`validate_ell_comps` is deliberately unchanged. Making the guard fire on the
traced path would turn a 20%-of-lanes condition into a 20%-of-lanes crash in the
middle of a multi-hour fit -- the fix belongs in the search's constraint
handling, not in a validator that converts a survivable state into an exception.
Also widens `AnalysisDataset.save_results`' catch to
`(AttributeError, af.exc.SamplesException, af.exc.FitException)`, mirroring
PyAutoLens#713: building the galaxies materializes the maximum log likelihood
sample as a model instance, which the model may reject, and writing an optional
output file must never kill a completed fit before `paths.completed()`
(PyAutoFit#1535).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011joies4k5TdRqezPUK8YET
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
EllProfile.__model_constraint__already measures how far outside theellipticity clamp a profile's
ell_compssit, but a measure cannot say how to fixone. This declares the structure — the disk itself — which is what a search
needs to put a lane back inside it:
The radius is the clamp (0.999), deliberately not
1 - margin. Between 0.999and 1.0 the conversion to an axis ratio saturates, so the likelihood is flat in the
radial direction: a lane projected into that annulus would be moved from a region
the model rejects into one the optimizer cannot leave. Projecting onto the clamp
puts it exactly at the edge of the region where the radial gradient is alive again.
Declared once at
EllProfile, which is the single assignment site forell_comps,so it reaches every elliptical light and mass profile (28 classes across
ag.lp/ag.mp/ag.lmp, asserted by a namespace sweep so a profile added lateris covered without editing a list). The spherical subclasses inherit the
declaration but pin
ell_compsto an instance, so PyAutoFit resolves no free pairand projects nothing.
validate_ell_compsis untouched, on purpose. The prompt's hard constraint:making the guard fire on the traced path would turn a 20%-of-lanes condition into a
20%-of-lanes crash in the middle of multi-hour GPU fits, and would kill a
MultiStart search on exactly the lanes it is supposed to clip and move on from. A
dedicated test class pins its standalone-construction behaviour — still rejects
|e| > 1and the corner(0.8, 0.8), still accepts the saturating annulus, stillreturns early for a non-concrete magnitude.
Also: widens
AnalysisDataset.save_results' catch from bareAttributeErrorto
(AttributeError, af.exc.SamplesException, af.exc.FitException)with a loggedwarning, mirroring PyAutoLens#713 line-for-line. Building the galaxies materializes
the maximum log likelihood sample as a model instance, which the model may reject;
writing an optional output file must never kill a completed fit before
paths.completed()is called (PyAutoFit#1535).API Changes
Additive.
EllProfile(and therefore every elliptical profile) gains the classattribute
__model_ball_constraints__. It is inert unless a search is configuredwith
af.ClipperPriorBoxJoint— no profile construction, evaluation, modelcomposition or identifier changes.
AnalysisDataset.save_resultsnow logs andcontinues on two further exception types instead of propagating them.
See full details below.
Test Plan
test_autogalaxy/full suite: 1144 passed (45s)test_autogalaxy/profiles+test_autogalaxy/analysis: 768 passedtest_autolens/against this branch: 553 passed (the declaration reacheslens models unchanged)
test_autogalaxy/profiles/test_model_constraint.py: thenamespace sweep (with a non-vacuity guard), the radius-is-the-clamp
assertion, PyAutoFit resolving the declaration to
ell_comps_0/ell_comps_1indices on a real lens model, the spherical case contributing no pair, an
end-to-end projection of a lane at
|e| = 1.4, and the fourvalidate_ell_comps-is-unchanged assertions.test_analysis_dataset.py:save_resultsswallows and logsall three exception types and never writes
galaxies.json(parametrised).import jax.Measured, on one cell. With PyAutoFit#1538's clipper and an
Isothermallensmodel, 200,000 uniform draws from the
ell_compsprior box:1 - pi/4= 21.46%)ell_comps = (0.9, 0.9)(|e| = 1.2728) projects to|e| = 0.999000with theangle preserved and both members masked; the default
ClipperPriorBoxleaves itwhere it is.
Full API Changes (for automation & release notes)
Added
EllProfile.__model_ball_constraints__ = ((("ell_comps",), 0.999),)— inheritedby every elliptical light/mass profile. Read by
af.ClipperPriorBoxJoint(PyAutoFit#1538); inert otherwise.Changed Behaviour
AnalysisDataset.save_results— thegalaxies.jsonwrite now catches(AttributeError, af.exc.SamplesException, af.exc.FitException)and logs awarning, where it previously caught bare
AttributeErrorand silently passed.A rejected maximum-log-likelihood instance no longer propagates out of
save_resultsand costs the run its.completedmarker (PyAutoFit#1535,mirrors PyAutoLens#713).
Unchanged (asserted)
validate_ell_comps— standalone-construction behaviour is deliberatelyidentical, and pinned by test.
Migration
search = af.MultiStartAdam(clipper=af.ClipperPriorBoxJoint())oncePyAutoFit#1538 is merged.
Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
https://claude.ai/code/session_011joies4k5TdRqezPUK8YET