diff --git a/dashboard.html b/dashboard.html index a2ee0342a..85f95f1a5 100644 --- a/dashboard.html +++ b/dashboard.html @@ -204,7 +204,7 @@

PyAutoMindDashboard

Intent. Priority. Flow.

Every task the Mind is holding. Tap a task's 📋 and its /start_dev command is on your clipboard — paste it into a Claude Code chat to route Claude straight to that task. Recent is the same work by date — what has been happening rather than what to do next.

- +

Last updated 2026-08-27. This page is generated from active/, draft/ and the registry files, so it is only as current as they are. dashboard_refresh.yml re-renders it on every push to main — that heals a stale page, but not a stale prompt: a task that shipped without its prompt advancing to complete/ keeps rendering here as pickable backlog. Reconciling those is the refresh below.

TRIAGE: needs manual review before routing❓ triagemediumsafehigh

Numba CPU likelihood phase 1: batched MGE convolution + operated-matrix caching✨ featureautoarraymediumsupervisedhigh

Rectangular mesh split: Bilinear (fast CPU default) vs RTU (advanced/GPU)✨ featureautoarraymediumsupervisedhigh

@@ -242,11 +242,11 @@

Highest priority (filed as high) — showing 12 of 16

Numba CPU likelihood phase 2: kernel-CDF numba fast path (the 49-88% lever)✨ featureautoarraylargesupervisedhigh

LensCalc NumPy Hessian step is too coarse for multi-plane tracers🐛 bugautogalaxylargesupervisedhigh

multi_galaxy package: new regime package in autolens_workspace📖 docsautolenslargesupervisedhigh

+

Cross-validate multi-plane ray tracing🧪 testautolenslargesupervisedhigh

Deep research: Can we speed up Delaunay in PyAutoArray?🔬 researchautoarraytoo-largesupervisedhigh

einstein_radius_jit_from: replace static init_guess with a JAX-native seed finder♻️ refactorautogalaxytoo-largesupervisedhigh

Split lensing regimes: multi_galaxy / group / cluster (epic plan)📖 docsautolenstoo-largesupervisedhigh

Re-baseline the MGE imaging JIT profiling regression value🧪 testautolens_workspace_developertoo-largesupervisedhigh

-

Fix release JAX runtime compatibility and likelihood parity🐛 bughealth_fixestoo-largesupervisedhigh

Quick wins (small enough, and safe enough to run unattended)

(none right now)

In flight markdown version

@@ -269,7 +269,7 @@

Planned

latent-nan-guard-honest-run — planned 2026-07-22

Backlog markdown version

-

135 filed prompts, not started — sorted most-pickable first (priority, then size). 25 of them belong to an epic and are listed only under Epics below.

+

136 filed prompts, not started — sorted most-pickable first (priority, then size). 25 of them belong to an epic and are listed only under Epics below.

feature — 28 @@ -290,12 +290,12 @@

Backlog

Search settings-estimation + profiling infrastructure (n_starts / batch_size / n_batch)✨ featureautolens_profilinglargesupervisednormal

Adopt oversampled PSFs in the start-here dataset chain (option a)✨ featureautolens_workspacelargesupervisednormal

The Brain board should work in a session that has no gh✨ featurepyautobrainlargesupervisednormal

+

Restore lensing-of-lens in the multiple_sources workspace example✨ featureworkspaceslargesupervisednormal

Follow-up to rectangular_adapt_cdf.md (issue #322) and Path A✨ featureautoarraytoo-largesupervisednormal

PIEMass.potential_2d_from: implement the missing lensing potential✨ featureautogalaxytoo-largesupervisednormal

autolens_jax_joss benchmark repo + real-data start_here pairing✨ featureautolens_jax_josstoo-largesupervisednormal

Context: PyAutoLens issue #542 follow-up (Gap 1, deferred during the✨ featurejax_substructuretoo-largesupervisednormal

Context: PyAutoLens issue #542 follow-up (Gap 2, deferred during the✨ featurejax_substructuretoo-largesupervisednormal

-

dPIE: optional central-dispersion (sigma_0) parameterization✨ featureautogalaxysmallsupervisedlow

Release board: local run_logs enrichment✨ featurepyautohandssmallsupervisedlow

@@ -388,7 +388,8 @@

Backlog

Deduplicate repos_sync.py's check/write pairs♻️ refactorpyautomindmediumsafelow

-test — 5 +test — 6 +

Cross-validate multi-plane ray tracing🧪 testautolenslargesupervisedhigh

Re-baseline the MGE imaging JIT profiling regression value🧪 testautolens_workspace_developertoo-largesupervisedhigh

Relevance-gate the reusable smoke workflow so a PR only runs…🧪 testpyautoheartmediumsupervisednormal

@@ -429,6 +430,12 @@

Backlog +2026-08-27 +filed +Cross-validate multi-plane ray tracing + + + 2026-08-26 filed jax_profiling/gradient/imaging/pixelization.py: 3.2% of its pin move… @@ -464,7 +471,7 @@

Backlog Un-park multi_galaxy/features/scaling_relation/slam once a capped run… - + 2026-08-24 filed Induct PyAutoReduce into the PyAutoHands release machinery (date… @@ -704,12 +711,6 @@

Backlog Un-park imaging/features/scaling_relation/slam — the PyAutoArray#431… - -2026-07-31 -filed -interferometer/start_here.py OOM in nightly release-validation… - -

Epics markdown version

diff --git a/dashboard.md b/dashboard.md index f61004efb..cc89c9a0e 100644 --- a/dashboard.md +++ b/dashboard.md @@ -45,11 +45,11 @@ anything you could not verify. | [In flight](#in-flight) (`active/`) | 1 | | [Parked](#parked) (`parked.md`) | 3 | | [Planned](#planned) (`planned.md`) | 5 | -| [Backlog](#backlog) (`draft/`) | 135 | +| [Backlog](#backlog) (`draft/`) | 136 | ## Start here -**Highest priority** (filed as `high`) — showing 12 of 16 +**Highest priority** (filed as `high`) — showing 12 of 17
📋 TRIAGE: needs manual review before routing — medium · safe · high @@ -107,6 +107,14 @@ anything you could not verify.
+
📋 Cross-validate multi-plane ray tracing — autolens · large · supervised · high + +``` +/start_dev draft/test/autolens/cross_validate_multi_plane_ray_tracing.md +``` + +
+
📋 Deep research: Can we speed up Delaunay in PyAutoArray? — autoarray · too-large · supervised · high ``` @@ -139,14 +147,6 @@ anything you could not verify.
-
📋 Fix release JAX runtime compatibility and likelihood parity — health_fixes · too-large · supervised · high - -``` -/start_dev draft/bug/health_fixes/jax_runtime_and_parity.md -``` - -
- **Quick wins** (small enough, and safe enough to run unattended) - _(none right now)_ @@ -247,7 +247,7 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned. ## Backlog -**135** filed prompts, not started. Each section is sorted most-pickable first (priority, then size). **25** of them belong to an epic and are listed only under [Epics](#epics) below. +**136** filed prompts, not started. Each section is sorted most-pickable first (priority, then size). **25** of them belong to an epic and are listed only under [Epics](#epics) below.
feature — 28 @@ -396,6 +396,14 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
+
📋 Restore lensing-of-lens in the multiple_sources workspace example — workspaces · large · supervised · normal + +``` +/start_dev draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md +``` + +
+
📋 Follow-up to rectangular_adapt_cdf.md (issue #322) and Path A — autoarray · too-large · supervised · normal ``` @@ -436,14 +444,6 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
-
📋 Once https://github.com/PyAutoLabs/PyAutoLens/issues/480 is fixed (PointSolver — workspaces · too-large · supervised · normal - -``` -/start_dev draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md -``` - -
-
📋 dPIE: optional central-dispersion (sigma_0) parameterization — autogalaxy · small · supervised · low ``` @@ -1072,7 +1072,15 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
-test — 5 +test — 6 + +
📋 Cross-validate multi-plane ray tracing — autolens · large · supervised · high + +``` +/start_dev draft/test/autolens/cross_validate_multi_plane_ray_tracing.md +``` + +
📋 Re-baseline the MGE imaging JIT profiling regression value — autolens_workspace_developer · too-large · supervised · high @@ -1183,18 +1191,19 @@ The 50 newest things to happen to the work in hand, newest first — issued, par | 2026-08-27 | filed | One construction path for plane-bound lensing quantities | | 2026-08-27 | filed | Multi-plane time delays | | 2026-08-27 | filed | LensCalc NumPy Hessian step is too coarse for multi-plane tracers | +| 2026-08-27 | filed | Cross-validate multi-plane ray tracing | | 2026-08-26 | filed | jax_profiling/gradient/imaging/pixelization.py: 3.2% of its pin move… | | 2026-08-26 | filed | The Brain board should work in a session that has no gh | | 2026-08-24 | filed | wiki-currency's --check-version gate rots on every library main merge | | 2026-08-24 | filed | interferometer/jax_grad/gradient.py: eager and jitted likelihoods… | | 2026-08-24 | filed | autocti_workspace has no Navigator Check, so its CI can never roll… | | 2026-08-24 | filed | Un-park multi_galaxy/features/scaling_relation/slam once a capped run… | -| 2026-08-24 | filed | Induct PyAutoReduce into the PyAutoHands release machinery (date… |
… 10 more (40 left) | Date | Event | Task | |------|-------|------| +| 2026-08-24 | filed | Induct PyAutoReduce into the PyAutoHands release machinery (date… | | 2026-08-24 | filed | Heart's local smoke runner cannot run any CTI workspace — no autocti… | | 2026-08-23 | filed | pynufft removal: unswept downstream residue (1 hard break + stale… | | 2026-08-23 | filed | Phase 3: stop installing pynufft in Hands/Heart CI and PyAutoCTI… | @@ -1204,12 +1213,12 @@ The 50 newest things to happen to the work in hand, newest first — issued, par | 2026-08-22 | filed | The reconstruction noise map describes a different estimator than the… | | 2026-08-22 | filed | Point-source JSON datasets record no resolution regime | | 2026-08-22 | filed | Is Intel macOS a supported platform, and what is the numpy-only… | -| 2026-08-21 | filed | Rectangular mesh split: Bilinear (fast CPU default) vs RTU… |
… 10 more (30 left) | Date | Event | Task | |------|-------|------| +| 2026-08-21 | filed | Rectangular mesh split: Bilinear (fast CPU default) vs RTU… | | 2026-08-20 | filed | Numba CPU likelihood phase 2: kernel-CDF numba fast path (the 49-88%… | | 2026-08-20 | filed | Numba CPU likelihood phase 1: batched MGE convolution +… | | 2026-08-19 | filed | autofit_profiling: bootstrap the repo + general PyAutoFit profiling… | @@ -1219,12 +1228,12 @@ The 50 newest things to happen to the work in hand, newest first — issued, par | 2026-08-19 | filed | Deduplicate repos_sync.py's check/write pairs | | 2026-08-19 | filed | Bug in autocti_workspace: the dataset_1d results/database example… | | 2026-08-18 | parked | single-source-density-design | -| 2026-08-18 | parked | prior-message-collapse-design |
… 10 more (20 left) | Date | Event | Task | |------|-------|------| +| 2026-08-18 | parked | prior-message-collapse-design | | 2026-08-18 | filed | @PyAutoFit TransformedMessage.logpdf/pdf omit the transform… | | 2026-08-17 | filed | Which other searches need prior-support handling — coverage audit… | | 2026-08-17 | filed | Teach repos_sync --write to stamp organ config surfaces | @@ -1234,12 +1243,12 @@ The 50 newest things to happen to the work in hand, newest first — issued, par | 2026-08-08 | parked | pyautoreduce-slacs1430-acs-comparison | | 2026-08-08 | filed | Regenerate autolens_workspace markdown/ so the MGE pages show… | | 2026-08-07 | filed | Regenerate setup_notebook-drifted notebooks in… | -| 2026-08-06 | filed | Triage: Convolver "No blurring_image provided" warning in canonical… |
… 10 more (10 left) | Date | Event | Task | |------|-------|------| +| 2026-08-06 | filed | Triage: Convolver "No blurring_image provided" warning in canonical… | | 2026-08-06 | filed | Rewrite PyAutoCTI docs/api — 55 of 89 autosummary entries are dead | | 2026-08-06 | filed | Dependency-cap refresh 2026-08: safe bumps, astropy 8 decision, two… | | 2026-08-05 | filed | Give PyAutoFit searches a seed — today no search can be made… | @@ -1249,7 +1258,6 @@ The 50 newest things to happen to the work in hand, newest first — issued, par | 2026-08-04 | filed | Nightly release has been blocked 8 nights running — triage the streak | | 2026-08-04 | filed | HowToLens ch4 tutorial 3: mask overlay is never actually drawn | | 2026-08-03 | filed | Un-park imaging/features/scaling_relation/slam — the PyAutoArray#431… | -| 2026-07-31 | filed | interferometer/start_here.py OOM in nightly release-validation… |
diff --git a/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md b/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md index 4fd816456..e84db6635 100644 --- a/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md +++ b/draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md @@ -158,7 +158,10 @@ The remaining chain, in order: 1. @autolens_workspace: revert the multi-source example to a richer multi-plane configuration (#480's own text asks for this — the example was simplified to work around the bug) and remove the two `multiple_sources` entries from - `config/build/no_run.yaml`. + `config/build/no_run.yaml`. **That work is already filed**, as + `draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md` (unblocked + 2026-08-27); its step 7 is the `no_run.yaml` removal this prompt waits on. Do not + file a duplicate. 2. Then the script runs, `dataset/point_source/multiple_sources` is written for real, and this prompt's exposure goes live — at which point steps 2-4 of "Suggested scope" below are the actual work. diff --git a/draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md b/draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md index 783931e4c..88e589bbc 100644 --- a/draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md +++ b/draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md @@ -1,23 +1,66 @@ -# Once https://github.com/PyAutoLabs/PyAutoLens/issues/480 is fixed (PointSolver +# Restore lensing-of-lens in the multiple_sources workspace example Type: feature Target: workspaces -Difficulty: too-large +Difficulty: large Autonomy: supervised Priority: normal -Status: formalised +Status: formalised — UNBLOCKED 2026-08-27, ready to start Filed: 2026-04-28 (backfilled from git) -Once https://github.com/PyAutoLabs/PyAutoLens/issues/480 is fixed (PointSolver -magnification filter must use `plane_redshift`, not the tracer's last plane), -revisit `@autolens_workspace/scripts/point_source/features/multiple_sources/`. +Revisit `@autolens_workspace/scripts/point_source/features/multiple_sources/` now +that PyAutoLens#480 is fixed. -First, let's fix https://github.com/PyAutoLabs/PyAutoLens/issues/480. I want -you to first validate that the cause of the issue described there is -actually right. I'm a bit unsure I totally buy it, so do some explicit -tests which more directly remove the mass profile but also edit the -`magnification_threshold` setting. I'm happy to be convinced, but need a bit -more confirmation. +## UNBLOCKED — #480 shipped 2026-08-27 + +PyAutoLens#480 (PointSolver magnification filter used the tracer's last plane +instead of `plane_redshift`) was fixed in PyAutoLabs/PyAutoLens#712, merged +`c1bba66`, and the issue is closed. Record: +`complete/2026/08/point-solver-magnification-plane-redshift.md`. + +`Difficulty` drops `too-large` -> `large`: that rating carried the "first fix #480" +leg, which is now done. What remains is the workspace work in steps 1-7 below. + +### The validation asked for below was done — and the doubt was well placed + +The original text (kept verbatim at the end of this section) asked for explicit +tests removing the mass profile and varying `magnification_threshold`, saying +"I'm a bit unsure I totally buy it". Both were run. The cause is confirmed, and +the skepticism turned out to point at something real: **the bug is +configuration-dependent, and removing source_0's mass profile masks it.** + +Measured on the #480 tracer (lens z=0.5 R_E=1.6; source_0 z=1.0; source_1 z=2.0), +solving for source_0 at `plane_redshift=1.0`: + +| source_0 | mu at z=1.0 (correct) | mu at z=2.0 (what the old code measured) | old code's result | +|---|---|---|---| +| **with** mass (R_E=0.2) | `27.9 9.06 23.1 16.0` | `7e-3 2e-3 1e-3 2e-3` | all rejected -> **0 images** | +| **without** mass | `27.9 9.06 23.1 16.0` | `1.86 1.54 2.38 2.56` | survive -> **4 images** | + +Three things follow, and they matter for this task: + +1. **This is why the simplified example worked.** Dropping source_0's mass was not + incidental to the workaround — it removes the very condition that triggers the + bug. Only a *compact* deflector at the intermediate plane de-magnifies hard + enough (by ~3-4 orders of magnitude) for the 0.1 threshold to reject every + candidate. Restoring that mass in step 1 is exactly what would have re-broken + the example before #712. +2. **`magnification_threshold=0.0` recovers the candidates in both rows**, which is + what isolates the fault to the post-filter rather than the triangle search. +3. **mu at z=1.0 is identical in both rows.** Correct physics — a deflector sitting + *at* plane j does not affect the mapping *to* plane j — and a useful sanity + check that the fixed path is measuring what it claims. + +Beyond this, the fix was cross-checked three ways at the solved plane (NumPy +Richardson FD, exact JAX autodiff in float64, and a ray-traced Jacobian sharing no +code with the Hessian path), agreeing to 1.7e-08. See the completion record. + +> Original request, kept for provenance: *"First, let's fix +> https://github.com/PyAutoLabs/PyAutoLens/issues/480. I want you to first validate +> that the cause of the issue described there is actually right. I'm a bit unsure I +> totally buy it, so do some explicit tests which more directly remove the mass +> profile but also edit the `magnification_threshold` setting. I'm happy to be +> convinced, but need a bit more confirmation."* When that workspace example was first written (autolens_workspace issue #97), the upstream PointSolver bug made it impossible to simulate or fit a configuration @@ -47,5 +90,25 @@ PointSolver bug is fixed: 6. Update the script docstrings and the folder/feature README to mention the lensing-of-lens richness again, and remove any "simplified to work around PyAutoLens #480" comments. +7. **Remove the two `no_run.yaml` exclusions** (not in the original text, and the + step that actually re-enables the example). In @autolens_workspace + `config/build/no_run.yaml`: + + ``` + - point_source/features/multiple_sources/simulator # Blocked by PyAutoLens #480: ... + - point_source/features/multiple_sources/modeling # Blocked by PyAutoLens #480: ... + ``` + + Match on the entry text, not line numbers — those have drifted twice already. + Note it is **two** scripts, simulator *and* modeling. Until these go, the + scripts stay out of harness execution however good the code is. + +## Downstream: this unblocks a second prompt + +`draft/bug/pyautolens/point_source_json_datasets_record_no_regime.md` is parked +behind exactly the `no_run.yaml` exclusion in step 7 — that exclusion is the sole +reason its exposure is not live. Its re-check log now names those entries +disappearing as its trigger. Doing step 7 fires it, so re-read that prompt when +this task lands rather than leaving it parked another cycle. diff --git a/draft/test/autolens/cross_validate_multi_plane_ray_tracing.md b/draft/test/autolens/cross_validate_multi_plane_ray_tracing.md new file mode 100644 index 000000000..72783e477 --- /dev/null +++ b/draft/test/autolens/cross_validate_multi_plane_ray_tracing.md @@ -0,0 +1,145 @@ +# Cross-validate multi-plane ray tracing + +Type: test +Target: PyAutoLens +Repos: +- PyAutoGalaxy +- PyAutoLens +- autolens_workspace +Difficulty: large +Autonomy: supervised +Priority: high +Status: formalised +Model: Fable (research-grade — paper reading + derivation + narrative guide prose) +Filed: 2026-08-27 + +Single-plane lensing quantities in this stack are cross-validated two independent ways. +Multi-plane ray tracing is not validated against anything. Build that, then publish it as +a @autolens_workspace guide. + +## The analogue that already exists + +@PyAutoGalaxy `test_autogalaxy/profiles/mass/total/test_isothermal.py::test__shear_yx_2d_from__matches_via_hessian` +is the pattern to copy: + +```python +shear_analytic = mp.shear_yx_2d_from(grid=grid) +shear_via_hessian = LensCalc.from_mass_obj(mp).shear_yx_2d_via_hessian_from(grid=grid) +np.testing.assert_allclose(shear_analytic, shear_via_hessian, rtol=1e-3, atol=1e-6) +``` + +Its docstring states the value plainly: the two paths "must agree to within +finite-difference accuracy", and the cross-check "guards against either path silently +drifting (e.g. a sign flip, a column swap, or a rotation-frame mistake)". A closed-form +formula and a numerical derivative of the deflections are wrong in *different* ways, so +agreement is evidence, where either alone is only self-consistency. + +**Multi-plane has no such second opinion.** `tracer_util.traced_grid_2d_list_from` and +its scaling factors are the only implementation of the multi-plane recursion, and every +downstream quantity — `deflections_between_planes_from`, plane-bound magnification, +`plane_index_via_redshift_from` — is tested against *itself* or against pinned literals +produced by the same code. + +## Why now: this cost us four months + +PyAutoLens#480 (fixed 2026-08-27, `complete/2026/08/point-solver-magnification-plane-redshift.md`) +was a plane-mismatch in exactly this machinery that survived four months in a core path. +Two things about how it was finally settled motivate this task: + +1. **The ray-traced Jacobian was the oracle that resolved it.** Central-differencing + `theta -> beta_j` through `traced_grid_2d_list_from` and taking `mu = 1/det(J)` shares no + code with the Hessian path, and it agreed with exact JAX autodiff to 1.8e-09. That + one-off cross-check is the seed of this task; it should be a permanent, general + fixture rather than something rebuilt by hand each time. +2. **The control arm found a second, unrelated bug.** Running the same comparison at the + *last* plane exposed `LensCalc._hessian_via_richardson`'s hardcoded 0.01" step being + off by 122% with the wrong sign on three of four points + (`draft/bug/autogalaxy/lenscalc_numpy_hessian_step_is_too_coarse.md`). A systematic + cross-validation would have caught that years ago. + +## What to build + +Compute the same multi-plane quantities by several genuinely independent routes and +assert they agree. Independence is the whole point: a route that reuses +`traced_grid_2d_list_from` is not a second opinion. + +Candidate routes, to be settled during the task: + +- **The implementation** — `tracer.traced_grid_2d_list_from` / `deflections_between_planes_from`. +- **A from-scratch multi-plane lens equation**, written directly from a paper's equations + against explicit angular-diameter distances, deliberately NOT sharing the codebase's + scaling-factor helper. This is the primary independent arm. +- **Numerical Jacobian** of `theta -> beta_j`, `mu = 1/det(J)` (validated on #480; stable + across h from 1e-4 to 1e-7). +- **Exact JAX autodiff** (`jacfwd`), which needs `jax_enable_x64` to be meaningful — + float32 gave 1.6e-02 residuals where float64 gave 1.8e-09. +- **Degenerate-case reductions**, the cheapest and sharpest checks: + - a multi-plane tracer with all mass in one plane must reduce to the single-plane + result, where the existing analytic closed forms apply; + - a two-plane tracer must match the two-plane formulas exactly; + - a deflector sitting *at* plane j must not affect the mapping *to* plane j (verified + incidentally during #480: mu at z=1.0 was identical with and without source_0's mass). +- **Analytic multi-plane configurations** where a closed form exists (e.g. aligned + isothermal spheres giving a double Einstein ring) — the strongest arm where available. + +Quantities to cover: traced positions per plane, `deflections_between_planes_from`, +magnification at each plane, convergence/shear at each plane, and time delays if +`draft/feature/autolens/multi_plane_time_delays.md` has landed by then. + +## Papers — pair against the literature, do not re-derive from the code + +The point is to check the implementation against the *published* formalism, so the +equations must come from the papers, not from reading our source and transcribing it. + +The one certain starting point: the codebase already cites +`https://inspirehep.net/literature/419263` for its equations 55 and 60 (Hessian and +magnification) in `autogalaxy/operate/lens_calc.py`. Start there, since it is the +reference the current code claims to implement. + +Then survey the standard multi-plane formalism. Candidate references to locate and +verify — **treat this list as leads to confirm, not as established citations**: +Blandford & Narayan (1986); Schneider, Ehlers & Falco, *Gravitational Lenses*; +Schneider (2014) on multi-plane lensing; McCully et al. (2014) on multi-plane algorithms; +Petkova, Metcalf & Giocoli (2014) (GLAMER). Cross-reading `lenstronomy`'s multi-plane +implementation is also worthwhile as a second independent codebase. + +Record which convention each source uses. The likely source of confusion — and worth +settling explicitly early — is **deflection normalisation**: our +`deflections_between_planes_from` returns `traced_grids[i] - traced_grids[j]`, i.e. +scaled against the final plane, so it is NOT the physical deflection at plane j in the +convention some papers use. Concretely, during #480 the obvious cross-check of truncating +the tracer to planes <= j gave 1.86 where the correct answer was 27.9. That is not a bug, +it is a convention mismatch — and it is exactly the kind of thing this task must +document rather than trip over. + +## Deliverable: a workspace guide + +This ends as a @autolens_workspace guide, not only a test module. Multi-plane +ray-tracing conventions are exactly the thing users get wrong and we have nothing +explaining them. Route the authorship through `/workspace` for placement, audience +register and the sibling guide to mirror. + +The guide should carry: the multi-plane lens equation as the papers state it, how our +scaling factors map onto it, the convention traps above (with the 1.86-vs-27.9 worked +example), and the cross-validation itself as runnable, inspectable code — a reader +should be able to check the library themselves. + +Split at start_dev time: the library test module and the workspace guide are one prompt +here but likely two PRs, library first. + +## Suggested scope + +1. Read the papers, fix the notation, write down the multi-plane lens equation and the + magnification/Jacobian at an arbitrary plane in the paper's convention. +2. Map our implementation onto it and document every convention difference explicitly. +3. Implement the independent arms as reusable fixtures. +4. Add the cross-validation tests, covering the degenerate reductions first (cheap, sharp) + and the general multi-plane case second. +5. Report any disagreement as a finding, not as a test to loosen. #480 and the Richardson + step bug both say the implementation is the more likely party to be wrong. +6. Author the workspace guide via `/workspace`. + + + +