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
ell_comps priors are an independent box per component, so 21.5 % of the prior area is non-physical; JAX tracer-mode validation is a no-op there, so gradient lanes settle at |e| ≥ 1 (20.1 % of MultiStart lane best points campaign-wide, 0 of 246 hit lanes; positions-on doubles it — autolens_profiling#182). PyAutoFit already has a class-declared, traceable constraint channel (autofit/mapper/prior_model/constraint.py) and PyAutoGalaxy's EllProfile already declares the disk constraint — but MultiStartGradient only counts violations. Decision (Option 1): give the declaration projectable structure and make the clipper project onto the disk. Reparameterisation (Option 2) rejected for migration cost.
Plan
New __model_ball_constraints__ class hook (sibling of __model_constraint__, duck-typed) → [(("ell_comps",), radius)]; EllProfile declares radius = convert.ELL_COMPS_MAGNITUDE_CLAMP (0.999) — NOT 1−margin, which sits in the dead-gradient annulus [0.999, 1).
New ClipperPriorBoxJoint(ClipperPriorBox): box clip first, then jittable radial shrink (xp.where, xp.maximum(r, tiny) — no NaN at r=0, no grad poisoning), clipped_mask True on both pair members so momentum resets. Opt-in subclass ⇒ existing ClipperPriorBox identifiers unchanged; pin __identifier_fields__=("margin","strict_epsilon") on the base (no-op today).
Guard/warn in AbstractBFGS._bounds_from (a ball is not a scipy Bounds).
PyAutoGalaxy: widen analysis/dataset.pysave_results catch to SamplesException/FitException (mirror PyAutoLens#713) — separate commit.
Quantify on imaging/mge/hst CPU (two sweep.py arms, n_starts 16) — report fraction ≥1.0 and ≥0.999.
Fix stale comment autolens_profiling/scripts/misc/searches/clipper_campaign.py:23-24 (clipper DOES enter the identifier) — follow-up PR, not this one.
Tests
test_autofit/non_linear/test_clipper.pyTestJointBall: (−1,−1)→radius r angle-preserved; interior bit-identical; (0,0) not NaN; mask on both members; batched input; xp=np only (+ optional importorskip jax jit/grad-finite).
multi_start_gradient: lane seeded at |e|=1.4 is ≤ radius after one step.
test_autogalaxy/profiles/test_model_constraint.py: every EllProfile subclass declares the ball; validate_ell_comps standalone unchanged.
Claude Development Prompt: Joint ell_comps Disk Constraint
Type: feature
Target: PyAutoGalaxy
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Issued: 2026-08-27
Goal
Give the elliptical-components parametrisation a constraint the searches can
actually honour, so a gradient MAP optimizer cannot spend its budget — or
finish — outside the physical region.
ell_comps = (e1, e2) is physical only inside the unit disk |e| < 1. Every
profile's default priors are two INDEPENDENT box priors on e1 and e2
(typically [-1, 1] each), which is a SQUARE. The disk covers pi/4 of that
square, so 21.5% of the prior box is non-physical by area, and nothing in
the search stops a lane from sitting there.
What was measured (autolens_profiling, 2026-08-27 harvest, issue #182)
The existing validation is a no-op where it matters.validate_ell_comps
fires on standalone profile construction, but not on the tracer-mode path a
MultiStart lane actually evaluates — so a lane at |e| >= 1 is never told.
20.1% of MultiStart lane best points end at |e| >= 1 (1,252 of 6,240
lane best points across the recorded campaign).
0 of 246 lanes that HIT the target basin end there. The two populations
are cleanly separated: ending outside the disk is a property of failed lanes,
never of successful ones. That is what makes this a searchability defect
rather than a cosmetic one — the wasted 20% is wasted budget, not a wasted
answer.
Positions-on roughly DOUBLES the out-of-disk fraction (17% -> 29% of best
points on the Phase-4 diagnostic arms). The positions penalty pushes lanes
into the corners the box permits and the geometry does not.
Full write-ups: autolens_profiling/results/notes/inference/ phase_03_prodigy_reliability/RESULTS.md and phase_04_positions/RESULTS.md.
Options to weigh (this prompt does not pick one)
A joint disk assertion the clipper honours. Express e1^2 + e2^2 < 1 as
a constraint the search's prior-box clipper can project onto, rather than as
a post-hoc validation that raises. The clipper already insets a box; a
ball projection is the same operation in a different norm. Cheapest change,
keeps the existing parameter names and every recorded identifier stable, and
is the only option that helps a search already in flight.
Reparameterise. Sample in a coordinate whose whole domain is physical —
e.g. (magnitude, angle) with a bounded magnitude, or a squashing map onto
the disk. Removes the problem by construction, but changes the model's
parameter set, and therefore every target_id, prior config and recorded
posterior that names ell_comps_0/ell_comps_1. It needs a migration story
before it needs an implementation.
Hard constraint
Never make validate_ell_comps fire on tracers. Raising from the
tracer-mode path turns a 20%-of-lanes condition into a 20%-of-lanes crash in
the middle of multi-hour GPU fits, and would make a MultiStart search die on
exactly the lanes it is supposed to clip and move on from. The fix belongs in
the search's constraint handling (option 1) or in the parametrisation (option
2) — not in a validator that converts a survivable state into an exception.
Requirements
Decide between the two options above with the evidence in the write-ups
named, and record the decision before implementing.
Whatever lands, add a test that a lane placed at |e| >= 1 is handled by the
chosen mechanism (projected, or unrepresentable) rather than merely detected.
Quantify the effect on the measured 20.1% figure on at least one cell before
claiming the defect is closed.
Do not change validate_ell_comps's standalone-construction behaviour.
Out of scope
The positions-penalty scaling question (Gate B pt 2, autolens_profiling
issue feature/pickle #182) — the doubling is evidence FOR this task, not a task itself.
Overview
ell_compspriors are an independent box per component, so 21.5 % of the prior area is non-physical; JAX tracer-mode validation is a no-op there, so gradient lanes settle at |e| ≥ 1 (20.1 % of MultiStart lane best points campaign-wide, 0 of 246 hit lanes; positions-on doubles it — autolens_profiling#182). PyAutoFit already has a class-declared, traceable constraint channel (autofit/mapper/prior_model/constraint.py) and PyAutoGalaxy'sEllProfilealready declares the disk constraint — butMultiStartGradientonly counts violations. Decision (Option 1): give the declaration projectable structure and make the clipper project onto the disk. Reparameterisation (Option 2) rejected for migration cost.Plan
__model_ball_constraints__class hook (sibling of__model_constraint__, duck-typed) →[(("ell_comps",), radius)];EllProfiledeclares radius =convert.ELL_COMPS_MAGNITUDE_CLAMP(0.999) — NOT1−margin, which sits in the dead-gradient annulus [0.999, 1).AbstractModel.ball_constraint_index_pairs()→ static(i0, i1, radius)viaTuplePrior.prior_tuples+priors_ordered_by_ididentity map.ClipperPriorBoxJoint(ClipperPriorBox): box clip first, then jittable radial shrink (xp.where,xp.maximum(r, tiny)— no NaN at r=0, no grad poisoning),clipped_maskTrue on both pair members so momentum resets. Opt-in subclass ⇒ existingClipperPriorBoxidentifiers unchanged; pin__identifier_fields__=("margin","strict_epsilon")on the base (no-op today).AbstractBFGS._bounds_from(a ball is not a scipyBounds).analysis/dataset.pysave_resultscatch toSamplesException/FitException(mirror PyAutoLens#713) — separate commit.imaging/mge/hstCPU (twosweep.pyarms, n_starts 16) — report fraction ≥1.0 and ≥0.999.validate_ell_compsuntouched.Detailed implementation plan
Work Classification
Library (PyAutoFit primary, PyAutoGalaxy).
Affected Repositories
Branch Survey
Suggested branch:
feature/ell-comps-disk-constraintWorktree root:
~/Code/PyAutoLabs-wt/ell-comps-disk-constraint/Implementation Steps
autofit/mapper/prior_model/constraint.py—MODEL_BALL_CONSTRAINT = "__model_ball_constraints__",declares_ball_constraints(cls),ball_constraints_for(cls).autofit/mapper/prior_model/abstract.py(besideconstrained_model_tuples~:836) —ball_constraint_index_pairs(self), cached.autofit/non_linear/clipper.py—ClipperPriorBoxJoint; docstring "Two consumers" extended; base__identifier_fields__pinned.autofit/__init__.py(~:80-82) — export.autofit/non_linear/search/mle/bfgs/search.py:117-147— guard/warn on a joint clipper..../multi_start_gradient/search.py:1315-1319— optional joint-clip count in the log line.autogalaxy/profiles/geometry_profiles.py(~:240) —__model_ball_constraints__ = ((("ell_comps",), convert.ELL_COMPS_MAGNITUDE_CLAMP),).autogalaxy/analysis/analysis/dataset.py(~:178) — exception widening + test.autolens_profiling/scripts/misc/searches/clipper_campaign.py:23-24(clipper DOES enter the identifier) — follow-up PR, not this one.Tests
test_autofit/non_linear/test_clipper.pyTestJointBall: (−1,−1)→radius r angle-preserved; interior bit-identical; (0,0) not NaN; mask on both members; batched input; xp=np only (+ optional importorskip jax jit/grad-finite).test_autogalaxy/profiles/test_model_constraint.py: every EllProfile subclass declares the ball;validate_ell_compsstandalone unchanged.test_autogalaxy/analysis/analysis/test_analysis_dataset.py: mirrored save_results test.Key Files
PyAutoFit/autofit/non_linear/clipper.py,autofit/mapper/prior_model/{constraint,abstract}.py,autofit/non_linear/search/mle/bfgs/search.pyPyAutoGalaxy/autogalaxy/profiles/geometry_profiles.py,autogalaxy/convert.py(CLAMP),autogalaxy/profiles/validate.py(untouched),autogalaxy/analysis/analysis/dataset.pyOriginal Prompt
Click to expand starting prompt
Claude Development Prompt: Joint
ell_compsDisk ConstraintType: feature
Target: PyAutoGalaxy
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Issued: 2026-08-27
Goal
Give the elliptical-components parametrisation a constraint the searches can
actually honour, so a gradient MAP optimizer cannot spend its budget — or
finish — outside the physical region.
ell_comps = (e1, e2)is physical only inside the unit disk|e| < 1. Everyprofile's default priors are two INDEPENDENT box priors on
e1ande2(typically
[-1, 1]each), which is a SQUARE. The disk coverspi/4of thatsquare, so 21.5% of the prior box is non-physical by area, and nothing in
the search stops a lane from sitting there.
What was measured (autolens_profiling, 2026-08-27 harvest, issue #182)
validate_ell_compsfires on standalone profile construction, but not on the tracer-mode path a
MultiStart lane actually evaluates — so a lane at
|e| >= 1is never told.|e| >= 1(1,252 of 6,240lane best points across the recorded campaign).
are cleanly separated: ending outside the disk is a property of failed lanes,
never of successful ones. That is what makes this a searchability defect
rather than a cosmetic one — the wasted 20% is wasted budget, not a wasted
answer.
points on the Phase-4 diagnostic arms). The positions penalty pushes lanes
into the corners the box permits and the geometry does not.
Full write-ups:
autolens_profiling/results/notes/inference/ phase_03_prodigy_reliability/RESULTS.mdandphase_04_positions/RESULTS.md.Options to weigh (this prompt does not pick one)
e1^2 + e2^2 < 1asa constraint the search's prior-box clipper can project onto, rather than as
a post-hoc validation that raises. The clipper already insets a box; a
ball projection is the same operation in a different norm. Cheapest change,
keeps the existing parameter names and every recorded identifier stable, and
is the only option that helps a search already in flight.
e.g.
(magnitude, angle)with a bounded magnitude, or a squashing map ontothe disk. Removes the problem by construction, but changes the model's
parameter set, and therefore every
target_id, prior config and recordedposterior that names
ell_comps_0/ell_comps_1. It needs a migration storybefore it needs an implementation.
Hard constraint
Never make
validate_ell_compsfire on tracers. Raising from thetracer-mode path turns a 20%-of-lanes condition into a 20%-of-lanes crash in
the middle of multi-hour GPU fits, and would make a MultiStart search die on
exactly the lanes it is supposed to clip and move on from. The fix belongs in
the search's constraint handling (option 1) or in the parametrisation (option
2) — not in a validator that converts a survivable state into an exception.
Requirements
named, and record the decision before implementing.
|e| >= 1is handled by thechosen mechanism (projected, or unrepresentable) rather than merely detected.
claiming the defect is closed.
validate_ell_comps's standalone-construction behaviour.Out of scope
autolens_profilingissue feature/pickle #182) — the doubling is evidence FOR this task, not a task itself.