From bdd351aae254b1f13663f10b509f0a49d1f777b6 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 27 Aug 2026 15:00:18 +0000 Subject: [PATCH 1/2] intake: unblock the multiple_sources lensing-of-lens workspace task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No new prompt filed — this work was already in the backlog as draft/feature/workspaces/restore_multiple_sources_lensing_of_lens.md (filed 2026-04-28 from PyAutoLens#480's own text). Filing a second one would have duplicated it, so the existing prompt is updated instead. - Marked UNBLOCKED: #480 shipped in PyAutoLens#712 (c1bba66), issue closed. - Difficulty too-large -> large; that rating carried the "first fix #480" leg, which is done. - Title replaced: the first line was a truncated URL sentence, so the prompt rendered on the dashboard with no readable name. - Added step 7, the no_run.yaml removal. The original six steps never mentioned it, yet it is the step that actually re-enables the example — the scripts stay out of harness execution however good the code is until those two entries go. Answers the validation the prompt asked for and never got. It recorded real doubt about #480's diagnosis and asked for tests removing the mass profile and varying magnification_threshold. Both were run, and the doubt pointed at something real: the bug is configuration-dependent. With source_0's mass the last-plane magnifications are ~1e-3 and every candidate is rejected (0 images); without it they are ~2 and all four survive even under the old code. Removing the mass profile masks the bug rather than being incidental to the workaround, which is why the simplified example worked — and why step 1 restoring that mass is exactly what would have re-broken it before #712. Cross-linked with point_source_json_datasets_record_no_regime, which is parked behind step 7, so neither prompt gets re-derived or duplicated later. Dashboard regenerated; intake dashboard --check reports current. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0188mKnNzXptgLi1CUZ4H7NZ --- dashboard.html | 2 +- dashboard.md | 16 ++-- ...t_source_json_datasets_record_no_regime.md | 5 +- ...estore_multiple_sources_lensing_of_lens.md | 87 ++++++++++++++++--- 4 files changed, 88 insertions(+), 22 deletions(-) diff --git a/dashboard.html b/dashboard.html index a2ee0342a..03b32f551 100644 --- a/dashboard.html +++ b/dashboard.html @@ -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

diff --git a/dashboard.md b/dashboard.md index f61004efb..495b1bdf9 100644 --- a/dashboard.md +++ b/dashboard.md @@ -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 ``` 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. From 32b1c9533202f1f0bea6c6439dd3dd37b0b8a4bf Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Thu, 27 Aug 2026 15:04:43 +0000 Subject: [PATCH 2/2] intake: cross-validate multi-plane ray tracing against independent formulations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multi-plane ray tracing has no second opinion. Single-plane quantities do: test_isothermal.py::test__shear_yx_2d_from__matches_via_hessian pins the analytic closed form against the Richardson Hessian of the deflections, and its docstring says why — the two are wrong in different ways, so agreement is evidence where either alone is only self-consistency. traced_grid_2d_list_from and its scaling factors are tested only against themselves or against literals the same code produced. Motivated by the two bugs this session surfaced in exactly that machinery: PyAutoLens#480 lived four months in a core path, and the ray-traced Jacobian that finally settled it (agreeing with float64 JAX autodiff to 1.8e-09) was a one-off, not a fixture. The control arm of that same check then exposed the Richardson-step bug filed separately. Declares Model: Fable — the work is paper reading, derivation and narrative prose, not mechanical edits — and pairs the implementation against the published formalism rather than re-deriving from our source. Names the one certain reference (inspirehep 419263, which lens_calc.py already cites for its equations 55 and 60) and marks the rest as leads to verify rather than established citations. Flags the convention trap up front: deflections_between_planes_from is scaled against the final plane, so truncating a tracer to planes <= j gives 1.86 where the answer is 27.9. Measured during #480 — a convention mismatch, not a bug, and the kind of thing the guide exists to document. Ends as an @autolens_workspace guide, routed through /workspace; library tests and guide likely split into two PRs, library first. Dashboard regenerated; intake dashboard --check reports current. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0188mKnNzXptgLi1CUZ4H7NZ --- dashboard.html | 25 +-- dashboard.md | 42 +++-- .../cross_validate_multi_plane_ray_tracing.md | 145 ++++++++++++++++++ 3 files changed, 183 insertions(+), 29 deletions(-) create mode 100644 draft/test/autolens/cross_validate_multi_plane_ray_tracing.md diff --git a/dashboard.html b/dashboard.html index 03b32f551..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.

-
  • 1In flight
  • 3Parked
  • 5Planned
  • 135Backlog
+
  • 1In flight
  • 3Parked
  • 5Planned
  • 136Backlog

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

@@ -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

+

Cross-validate multi-plane ray tracing🧪 testautolenslargesupervisedhigh

Deep research: Can we speed up Delaunay in PyAutoArray?🔬 researchautoarraytoo-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 @@ -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 495b1bdf9..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 @@ -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/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`. + + + +