Skip to content

prompt: land the MGE lane-death task and the frozen-lane-counter record - #188

Merged
Jammy2211 merged 21 commits into
mainfrom
claude/mge-lane-death-ih8q92
Aug 15, 2026
Merged

prompt: land the MGE lane-death task and the frozen-lane-counter record#188
Jammy2211 merged 21 commits into
mainfrom
claude/mge-lane-death-ih8q92

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Lands the MGE lane-death investigation on main so it is visible to any session, and brings the frozen-lane-counter completion record with it.

Why this needs to be on main: the reproducer was written on claude/jax-sampling-flat-gradients-ptmqnl. PyAutoMind's auto-load gives a cold session whatever is in its checkout, which comes from main — so until this merges, a session picking the task up sees neither the prompt nor the scripts, and the "it travels with the file" property does not actually hold.

What is in here

  • complete/2026/08/frozen-lane-counter.md — the completion record for PyAutoFit#1475 (004f798) + PyAutoGalaxy#572 (695b27c), the trapped-lane counter. Includes the detector-design finding (a component-wise zero-gradient test catches 0/17 trapped lanes; the declared model constraint catches 17/17 plus the corner region).
  • active/mge_lane_death.md — the research prompt, carrying the verbatim result JSON and both self-contained driver scripts (rerun_cell.py, validate_zero.py) so the measurement can be reproduced from the file alone.
  • active.md — the task registered against autolens_profiling#128.
  • complete/index.md, dashboard.md — regenerated.

The measurement

imaging/mge, production dataset/model/analysis via build_for_cell, 16 starts x 150 steps, cloud CPU, 352s:

counter lane-steps share
n_value_nan_lane_steps 1498 62.42%
n_grad_nan_lane_steps 9 0.38%
n_constrained_lane_steps 0 0.00%
n_resurrections 0

Population falls alive 16/16alive 2/16. The ell_comps plateau is cleared as a suspect — that zero is validated by a positive control, without which it could not be distinguished from "nothing was watching".

This contradicts the resurrect docstring's justification for its resurrect=False default on the parametric MGE-class cell. The cause is not yet known; that is what autolens_profiling#128 is for.

Branch reconciliation

This branch merges claude/jax-sampling-flat-gradients-ptmqnl. Both branches independently created draft/research/autolens_profiling/mge_lane_death.md from 1f7cca85 (an add/add conflict); the merge takes that branch's version whole, since it carries the data, and adds analysis on top. That branch should not also be merged to main — its content is fully contained here.

Analysis added on top of the merged work

  • n_resurrections: 0 closes the mechanism: 1498 deaths and no redraws is resurrect=False working as documented. The default is wrong for this cell; nothing is broken.
  • 62% is a survival integral, not a hazard rate. A frozen lane keeps counting every subsequent step, so the same death curve reports ~75% at 300 steps — the scalar is not portable across budgets. Inverting it (sum(k_i) = 14*150 - 1498 = 602) gives a mean death step of ~43 of 150, which already answers "when they happen": mid-descent wall, not bad initial draws.
  • The step ordering is now by information per unit time — cause-finding on the existing ~6-minute CPU run before any GPU queue, since the reproduction confirms an effect already measured once while the cause is unanswered and cheap.
  • validate_zero.py's checks all call discovery/evaluation/the predicate directly and never exercise the accumulating search loop. What closes that gap is the JSON returning integer 0 rather than null through si.get() — so on a re-run, a null means broken plumbing, not a clean cell.

🤖 Generated with Claude Code

https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1


Generated by Claude Code

claude and others added 21 commits August 15, 2026 13:50
Files the PyAutoFit instrumentation task uncovered while assessing the
ell_comps flat-gradient hazard under JAX sampling.

_nan_lane_counts counts value-NaN and gradient-NaN lanes, but a lane on a
finite zero-gradient plateau (the |ell_comps| >= 0.999 clamp) escapes both,
is absorbing, and can false-trigger the autoconv convergence check. Detection
first: search results store no per-start traces today, so whether real runs
enter the region is currently unanswerable.

Scoped as instrumentation only — the penalty term is a separate follow-up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
Ran a toy reproduction (32 starts, 400 Prodigy steps, PyAutoGalaxy's clamp
verbatim, apply_if_finite + unbounded apply_updates) and it falsified the
detector this prompt originally specified.

The clamp kills only the radial derivative; the angle still comes from an
unclamped arctan2, so in general position both ell_comps components carry a
large non-zero gradient while their radial projection is ~1e-12. A
component-wise zero test caught 0 of the 17 trapped lanes. Per-parameter
prior-limit escape caught 17/17 with no false positives, needs no model
semantics, and detects the actual cause.

Also records severity: the trap wasted 53% of the start budget but the
surviving lanes still recovered the truth, so it is a robustness cost rather
than a correctness fault except where n_starts is small or the good basin
is rare.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
Probed whether autogalaxy's FitException guard can be reused under JAX. It
cannot raise -- validate.py:153-154 returns early for non-concrete scalars,
and a plain raise on a traced condition gives TracerBoolConversionError, so
the escape hatch is load-bearing rather than an oversight.

checkify does survive vmap + value_and_grad with finite values and grads, but
collapses a batch to one abort-shaped error, so it cannot serve a per-lane
counter. The guard's predicate returned as a traced boolean does, and scores
20/20 against 17 real trapped lanes plus 3 synthetic corner-region lanes where
prior-limit escape scores 17/20.

Records that the clamp threshold (0.999) and the guard threshold (1.0) differ,
and the gap is reachable: the radial derivative is already exactly zero at
|ell_comps| = 0.9995 while validate_ell_comps still calls the point valid.

Leaves the widen-to-validity-channel decision open for start_dev rather than
silently re-scoping the header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
…t fixes

No user-facing model-composition call is needed: PyAutoFit already discovers
class-level metadata off the wrapped class (prior_model.py:200 reads
__default_fields__), and validate_ell_comps has exactly one call site, at
geometry_profiles.py:237 in EllProfile.__init__ -- the single base every
elliptical profile inherits.

Records that the predicate must be extracted and shared rather than restated,
and that the drift this prevents already exists today: the clamp's 0.999 in
convert.py and the guard's 1.0 in validate.py sit in different files with
nothing relating them, which is exactly the reachable annulus where the radial
gradient is dead while the guard still reports the point valid.

Also records the measured cost of the parameter-only validity channel (+23 HLO
lines on the toy; timing overhead below noise) and its ceiling: it cannot see
likelihood-internal hazards such as NNLS active-set pinning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
The previous commit message said it recorded the measured cost and the
likelihood-internal ceiling; it did not — that material had only been discussed,
not written. Adding it here rather than rewriting a pushed message.

Records that nothing flows up through the likelihood (predicate is a pure
function of the parameter vector, gradients bit-identical with the likelihood
untouched), the measured +23 HLO lines with wall-clock overhead below the noise
band, and the ceiling: parameter-only predicates cannot see NNLS active-set
pinning or conditioning-floor behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
…elds__

An earlier revision cited __default_fields__ as the pattern to follow, which
would send an implementer at the wrong hook. It is a narrow escape hatch
consulted only when make_prior returns a ConfigException, with two usages, both
internal message classes; it marks "not a model parameter" rather than
registering a constraint.

The real enabler is that Model.__init__ already introspects the class
(gather_namespaces, get_type_hints, per-argument prior resolution), so this is
one more lookup in an existing mechanism.

Also names the closer relative, add_assertion: the right concept, attached per
model instance instead of per class and raising FitException. Reframes the work
as assertions with two changes — class-declared, traced predicate — rather than
a new validity subsystem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
The counter reads only the sign of the violation measure, so it is scale-free
and carries no lambda. That leaves two properties of the measure untested by
anything shipped, both of which land on the penalty task: its units are the
constraint's own rather than the figure of merit's, which is what a lambda has
to absorb; and model_constraint_from_vector reduces components with maximum,
which is correct for counting but would let one constraint silently dominate
another in different units once the measure is multiplied into the FoM.

Also records that the counter shipped as PyAutoFit #1475 with CI green, and
that PyAutoGalaxy declares no constraint yet so it reads zero on real lens
models until EllProfile opts in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
Both halves merged: PyAutoFit #1475 (004f798a) adds the counter and the
class-declared __model_constraint__ protocol; PyAutoGalaxy #572 (695b27c5)
declares the ell_comps saturation region on EllProfile and gives the clamp one
definition instead of three bare literals.

The record keeps the reasoning that would otherwise be re-derived: that a
component-wise zero-gradient detector caught 0 of 17 trapped lanes and why, that
the exception could not be reused and checkify is the wrong shape, that the
clamp's 0.999 and the guard's 1.0 answer different questions with a reachable
annulus between them, and the traps (off-centre grid, spherical inheritance,
black not enforced).

Also records what the penalty term inherits — units, the max reduction, moving
the term inside the differentiated call — and the honest open tail: whether the
real Prodigy mesh/MGE runs entered the region is still unanswered, because those
cells were never persisted and the stored records carry no per-start traces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
Ran the real imaging/mge cell through the repo's own build_for_cell on cloud
CPU with the merged counter: zero constrained lane-steps across 2400 lane-steps.
For MGE the flat-gradient region is not the problem — though NaN death
dominates at 62% and the population fell to alive 2/16, so lanes are dying by
another mechanism before they could reach the plateau.

The pixelized mesh cells could not be run: two attempts, production and a
shrunk 12x12 mesh, both timed out having emitted zero steps. Memory is
solvable via batch_size=1; the JIT compile is not. They need the GPU, so the
mesh half of the original question stays open.

Also records that shrinking the source mesh is the wrong lever for cost (the
image-plane grid dominates, not the mesh), that the runner writes into
dataset/, and that jaxnnls is a required extra.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
…lanes

Positive control against the same production mge model: 44 constrained
components discovered, zero violation at prior medians, 6 of 15 free parameters
drive the constraint when pushed past the clamp, and the predicate counts the
trapped lane. So the zero means nothing entered the plateau, not that nothing
was watching.

Confirms two inheritance cases are benign — spherical profiles and
ExternalShear both carry the declaration with ell_comps pinned at (0, 0), so
shear magnitude is correctly not constrained by the unit-disc rule — and notes
that probing with concrete floats raises from validate_ell_comps before the
constraint is reached, which is the documented NumPy-path behaviour.

Files draft/research/autolens_profiling/mge_lane_death.md for the effect the
counter surfaced instead: 62% of lane-steps value-NaN and the population
falling to alive 2/16, which contradicts the resurrect docstring's claim that
the parametric MGE-class cell has only the measure-zero singularity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
Reproduce-first research prompt for the reported MGE-cell lane death: is the
parametric MGE likelihood only measure-zero singular, as the resurrect
docstring claims?

Records a provenance gap rather than papering over it. The launch context cites
complete/2026/08/frozen-lane-counter.md and PyAutoFit#1475 / PyAutoGalaxy#572;
none exist at origin/main 1f7cca8. The counters shipped as
multistart-nan-step-diagnostics.md (PyAutoFit#1472 -> #1473, autolens_profiling#127,
no PyAutoGalaxy leg), and no record mentions the 62% rate, alive 2/16, or the
ell_comps clearance. Step 1 therefore establishes the number rather than
assuming it.

Phased: only step 1 (production-budget GPU reproduction across seeds, with a
descent-path control) is issued from this prompt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
Checked the profiling repo, not just Mind. Its main is at a34d6191 — the same
#127 merge the Mind record cites, so nothing has landed since, and no remote
branch carries lane-death work. The only MGE NaN-accounting artefact is
results/searches/multi_start_nan_accounting/local_cpu.json, which is the
overhead benchmark (16 starts x 5 steps x 3 reps, local_cpu, verdict "fused
accounting costs 4.1us on a 1.027s step") and reports no NaN counts at all.

Also notes that a 5-step budget cannot resolve a rate that accumulates along a
descent path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
…erun them

The 62% measurement and its positive control lived only in a session scratchpad,
which is not visible to any other session. Both drivers and the verbatim result
JSON are now in the prompt itself, so a cold session picking this up can
reproduce the number rather than take it on trust.

Includes the invocation, the environment prerequisites (Python 3.12+, jaxnnls,
autolens --no-deps), and the note that the positive control must probe with jnp
arrays because concrete floats raise from validate_ell_comps before the
constraint is reached.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GAFoogitLceTsgA7bfB4k
Both PRs exist and merged 2026-08-15 ~15:52 UTC, after the Mind main this
branch was cut from (1f7cca8), which is why the earlier revision could not find
them: PyAutoFit#1475 (004f798) adds _constrained_lane_count, PyAutoGalaxy#572
(695b27c) declares EllProfile.__model_constraint__. The "frozen-lane counter" is
that trapped-lane pair, not the #1473 gradient-NaN counter.

Verified the measure-zero claim verbatim at search.py:119 and quotes it.

Adds a three-counter table so value-NaN / gradient-NaN / constrained are not
conflated, and sharpens step 3: a 62% VALUE-NaN rate does not bear on the #1472
gradient-trigger deferral (value-NaN is already the trigger) — it bears on the
resurrect=False default, whose stated justification is the measure-zero claim.

Also flags that #572 declares a constraint on EllProfile only, so a zero
constrained reading may mean nothing declared it rather than nothing trapped.

Still not pushed anywhere: the completion record and the run artefacts behind
the 62% / alive 2/16. Step 1 now begins by recovering that log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
Reconciles two divergent copies of mge_lane_death.md that both branched from
1f7cca8: this branch's version and the reproducer version on
claude/jax-sampling-flat-gradients-ptmqnl. Takes the reproducer version whole —
it carries the real data — and brings in frozen-lane-counter.md, complete/index
and dashboard with it. The earlier "provenance gap" section is dropped: the
record now exists and the artefacts are in the file.

Adds analysis of the measurement rather than restating it:

- n_resurrections: 0 closes the mechanism — 1498 value-NaN lane-steps and
  nothing ever redrawn is resurrect=False working as documented. The default is
  wrong for this cell; nothing is broken.
- 62% is a survival integral, not a hazard rate. A frozen lane keeps counting
  every subsequent step, so the percentage rises with budget for a fixed death
  curve — the same physics reports 75% at 300 steps. Inverting it gives a mean
  death step of ~43 of 150, which already discriminates the "When they happen"
  hypotheses: not bad initial draws, trajectories walking into a wall early.
  Compare alive-versus-step curves across budgets, not the scalar.
- validate_zero.py covers discovery, evaluation and the predicate, all called
  directly — none of it exercises the accumulating search loop. What closes that
  gap is the JSON returning integer 0 rather than null through si.get(), so a
  null on a re-run means broken plumbing, not a clean cell.
- the MESH_SHAPE/HILBERT_PIXELS block is inert for mge; the 62% was measured on
  the unmodified production cell.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
Cause-finding moves ahead of the GPU reproduction. The reproduction confirms an
effect already measured once; the cause is unanswered, and it does not need
production budget or a GPU — the existing ~6-minute 16x150 CPU run already
produces the 1498 deaths, so dumping each lane's parameter vector at its death
step is a minutes-long loop rather than a queued one.

Grades the reproduction on the alive-versus-step curve rather than on recovering
"62%", since the scalar is budget-dependent by construction (same landscape,
~75% at 300 steps) and two runs at different budgets cannot be compared on it.

Marks "when they happen" as partly answered by the survival-curve inversion
(mean death ~step 43 of 150, so mid-descent rather than bad draws), and makes a
disagreement with that inversion a finding in its own right rather than a
re-derivation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
Advances the prompt draft/ -> active/ and registers the task, per the lifecycle.

The active.md entry records what is settled (62.42% value-NaN, alive 2/16,
ell_comps cleared by a validated zero, resurrect=False the reason dead lanes
stay dead) separately from what the task actually owes — the cause, which
nothing so far identifies.

Carries two things forward that are easy to lose: 62% is a survival integral so
re-runs must be graded on the alive-versus-step curve rather than the scalar,
and the step ordering puts cause-finding on the existing 6-minute CPU run ahead
of any GPU queue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
…kelihood

Root-caused on cloud CPU and written up on autolens_profiling#128.

The objective is -2 * (log_likelihood + sum(log_prior_list)). A UniformPrior is
-inf outside its box and MultiStartGradient steps in physical space with no
projection back onto it, so a lane crossing a hard prior edge reads non-finite
and resurrect=False never redraws it. The likelihood never went non-finite in
~7200 lane-steps across three arms.

Decisive arm: neutering log_prior_list_from_vector drops value-NaN from 1446 to
215 and survivors from 2 to 13, with all residual deaths being NaN-params. A
narrower hypothesis — widen the shear box, which accounted for 10 of 11 exits —
was refuted: deaths moved later and got marginally worse, because widening one
box only moves the wall.

Counter-finding that corrects the earlier framing: the ell_comps plateau was
masked, not cleared. The zero was correctly measured and the positive control
was sound, but it meant "nothing got that far" — with the prior deaths removed
the constrained count is 667 (27.79%).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
Fix for the MGE lane-death cause found in autolens_profiling#128: the search
steps in physical space while UniformPrior is -inf outside its box, so lanes
that overstep an edge read non-finite and are never redrawn.

Scoped search-agnostic because the exposure is not MultiStart-only. LBFGS builds
the same log-posterior Fitness, steps in physical space, and calls
optimize.minimize with no bounds= at all, despite L-BFGS-B supporting them.

Design departs from the Initializer analogy in one place: Clipper has two
structurally different consumers — MultiStartGradient enforces per step and
wants project(), LBFGS delegates to scipy and wants bounds — so the base class
serves both from one source of truth. project() returns the clipped-coordinate
mask, because the prototype left 5/16 lanes pinned to a bound with Prodigy state
still pushing outward, and the Clipper cannot reset momentum it does not own.

Default is ClipperNone and PR 1 is bit-identical, per the #1475 precedent;
flipping the default carries the benchmark re-baseline and is PR 2.

Also records two incidental bugs that only appear when lanes survive: float32
breaking save_json, and a crashed run poisoning the next same-named run into a
4-second no-op that reads as a clean result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
Sequences the prior-support fix as three PRs: PyAutoFit Clipper (opt-in,
bit-identical) -> autolens_profiling validation -> PyAutoFit default flip. This
files the middle one, which is where the fix is either justified or is not.

Anchors validation on a truth bar that already exists: the Nautilus run on the
same imaging/mge cell (max_log_likelihood 31786.78, A100 fp64). Nautilus samples
in unit-cube coordinates so it is structurally immune to this bug, which makes
it a reference answer rather than another data point. The load-bearing test is
therefore whether clipped MultiStartProdigy moves TOWARD it — "fewer lanes die"
would be satisfied by a change that keeps lanes alive and useless.

States the falsification conditions up front, and marks pinning at a bound as a
possible science finding about the shear prior rather than a clipping artefact.

Carries the traps this investigation already paid for: grade on the alive curve
not the scalar (the counter is a survival integral), delete output between arms
or a crashed run resumes into a 4-second no-op that reads as clean, and 0 is not
null.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
The refresh check failed on the branch: two new draft prompts and the
draft/ -> active/ move for mge_lane_death left dashboard.md stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vKZhT7hoMMnAGziPk24y1
@Jammy2211
Jammy2211 merged commit 35585de into main Aug 15, 2026
2 checks passed
Jammy2211 added a commit that referenced this pull request Aug 16, 2026
The cloud session did everything this campaign needs except run it. Adds a
SESSION HANDOVER block to the campaign prompt itself rather than a separate
handover doc, so the context travels with the file a GPU session will actually
open -- the same property #188 was merged to preserve.

What it carries:

- The three PyAutoFit changes that landed AFTER phase 1 (#1478 clip count in
  search.summary, #1479 NumpyEncoder, #1480 atomic writes), and the fact that
  all four are UNRELEASED. The task must run against a checkout at or after
  b6e89cd with autofit.__file__ verified, never a PyPI wheel.
- Which of this prompt's traps are now fixed rather than live -- and why the
  mitigations stay anyway: the step-count assertion catches every no-op cause,
  not only the float32 one.
- That the clip count is now a VALIDITY CHECK, not a statistic. A
  ClipperPriorBox arm reporting zero clips has not exercised the clipper and
  its "no change" result is meaningless -- a broken arm, not a null result.
- A seconds-long smoke test discovered while verifying #1478: the #128
  mechanism reproduces on a toy 3-parameter Gaussian (378 value-NaN lane-steps
  unclipped, 0 clipped, 414 clips at rate 0.958). Cheap way to confirm the
  clipper is wired up and firing before spending GPU time on imaging/mge.
- The mge-lane-death GPU/float64/multi-seed confirmation and the owed
  hazard-index entry, which want the same session and share these arms.
- Sequencing: this campaign, then ell_comps_trapping_unmasked.md on the same
  arms, which must not re-derive 27.79% from the prior-neutered arm.


Claude-Session: https://claude.ai/code/session_01FzF2XmKQaqZRWZfZMxvTNR

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants