Repository navigation
Description
Overview
Sub-task B of the analysis_shared_state epic (tracker:
PyAutoPrompt/z_features/analysis_shared_state.md). Now that the generic mechanism
(PyAutoFit Analysis.shared_state_from + shared= kwarg) is on main (sub-task A, #1307),
this wires up the motivating consumer: the ALMA datacube likelihood, where N spectral
channels share one lens model and ~97% of each channel's inversion-setup work (ray-trace +
Delaunay mapper + mapping matrix L + curvature F = LᵀW̃L) is identical and currently rebuilt
N times. Implementing the lensing shared_state_from collapses that to one build per
evaluation, targeting ~17x on the inversion-setup block for a 34-channel cube.
Plan
- Implement
AnalysisInterferometer.shared_state_from(instance): build theTracerand the
channel-invariant inversion pieces (mapper + L, andFwhere uv/noise are channel-invariant)
once, returning them as a JAX pytree. - Make
AnalysisInterferometer.log_likelihood_function(instance, shared=None)consume the
shared pieces infit_frominstead of rebuilding;shared is Nonefalls back to today's path. - Resolve the architectural question:
FitInterferometer/ the autoarrayInversion/mapper
must accept pre-built shared pieces — determine whether this needs PyAutoArray changes or can
be threaded purely from PyAutoLens. - Add the channel-invariance precondition + per-channel fallback (the consumer owns correctness).
- Phase 5 (after library ships): opt the datacube workspace scripts in, add an
autolens_workspace_test fast-assert, and re-measure in autolens_profiling.
Detailed implementation plan
Affected Repositories
- Jammy2211/PyAutoLens (primary — AnalysisInterferometer + FitInterferometer)
- Jammy2211/PyAutoArray (likely — Inversion/mapper "use precomputed pieces" path; confirm at /start_library)
- Jammy2211/autolens_workspace (Phase 5 — datacube scripts)
- Jammy2211/autolens_workspace_test (Phase 5 — fast-assert)
- PyAutoLabs/autolens_profiling (Phase 5 — re-measure)
Work Classification
Both (library: PyAutoLens [+ likely PyAutoArray] first; workspaces/profiling at Phase 5)
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoLens | main | clean |
| ./autolens_workspace | main | clean |
| ./autolens_workspace_test | main | dirty (unregistered, Phase-5 only) |
| ./autolens_profiling | main | clean |
Suggested branch: feature/datacube-shared-state
Worktree root: ~/Code/PyAutoLabs-wt/datacube-shared-state/
Implementation Steps (Phase 4 — library)
- Scope the inversion-setup path:
FitInterferometer-> autoarrayInversion/mapper/curvature build. Decide where a "shared precomputed mapper+L+F" injection point goes (PyAutoLens-only vs PyAutoArray). AnalysisInterferometer.shared_state_from(instance)— build Tracer + shared inversion pieces once; return a registered pytree (recomputed inside jit each eval, not memoised).AnalysisInterferometer.log_likelihood_function(self, instance, shared=None)+fit_from(instance, shared=None)— consume shared pieces;shared is Nonerebuilds (single-channel path unchanged).- Channel-invariance precondition + per-channel fallback when uv_wavelengths/noise_map aren't ~channel-invariant.
- Tests:
test_autolens/interferometer/— shared vs unshared FitInterferometer give identical figure_of_merit; compute-once.
Key Files
PyAutoLens/autolens/interferometer/model/analysis.py—log_likelihood_function,fit_from, pytree registrationPyAutoLens/autolens/interferometer/fit_interferometer.py—FitInterferometer(where the inversion is built)- PyAutoArray Inversion/mapper/curvature (TBD — confirm injection point)
- Phase 5:
autolens_workspace/scripts/interferometer/features/datacube/{likelihood_function,modeling,delaunay}.py,autolens_profiling/likelihood_breakdown/datacube/delaunay.py
Original Prompt
Click to expand starting prompt
Datacube consumer for the cross-Analysis shared-state mechanism
Sub-task B of the analysis_shared_state epic (see
PyAutoPrompt/z_features/analysis_shared_state.md). Do not /start_dev this
until sub-task A (the PyAutoFit mechanism + 1D Gaussian toy + autofit workspace
tutorial/tests, PyAutoFit#1307) is close to shipping — the mechanism it consumes
is delivered there.
Primary repo: @PyAutoLens. Consumers/proof: @autolens_workspace,
@autolens_workspace_test, @autolens_profiling.
Depends on (delivered by sub-task A)
PyAutoFit ships the generic, domain-agnostic protocol:
Analysis.shared_state_from(instance) -> None(opt-in, defaultNone), the
per-evaluation cross-factor sibling ofmodify_before_fit.log_likelihood_function(self, instance, shared=None, ...)— defaulted kwarg.FactorGraphModel.log_likelihood_functioncomputes the shared object once
from the lead factor before the per-factor loop and forwards it to each factor
only when non-None(so non-cube graphs are byte-for-byte unchanged).
The 1D Gaussian toy in af.ex + autofit_workspace + autofit_workspace_test
is the worked, tested reference for how a consumer implements shared_state_from
and a shared-aware log_likelihood_function. Mirror it.
Scope (Phases 4-5 of the original prompt)
Phase 4 — PyAutoLens: the datacube consumer
- On the interferometer datacube path, implement the lensing-specific
shared_state_from: ray-trace the shared lens model once, build the Delaunay
mapper + mapping matrixL(and, whereuv_wavelengths/noise_mapare
channel-invariant, the curvatureF = LᵀW̃L) once, returning them as a normal
JAX pytree of traced arrays (recomputed inside the jitted region each eval,
never memoised on the instance — seefeedback_jax_closure_cache_busts). - Make
AnalysisInterferometer.log_likelihood_function(self, instance, shared=None)
consume the shared object in place of its own rebuild; theshared is None
fallback rebuilds everything so the single-channel path is unchanged. - Per-channel work that remains: data vector
D = Lᵀ·dirty_image(channel
visibilities), NNLS reconstruction, log-evidence. - Fall back to the current per-channel path when the channel-invariance
precondition does not hold (uv_wavelengths/noise_mapnot ~channel-invariant —
i.e. outside the narrow-emission-line regime). The consumer owns this guard;
PyAutoFit trusts the provider.
Phase 5 — autolens_workspace + autolens_workspace_test + profiling
- Update the datacube modeling/likelihood scripts
(autolens_workspace/scripts/interferometer/features/datacube/{likelihood_function,modeling,delaunay}.py)
to opt into the shared path. - Add a fast-assert datacube script to
autolens_workspace_testmirroring the
autofit_workspace_test pattern: proveshared_state_fromruns once per eval
(counter), shared-vs-unshared likelihood equality, and a tiny end-to-end run. - Re-run
autolens_profiling/likelihood_breakdown/datacube/delaunay.py(carrying
the inversion-setup sub-decomposition step) and record the new cube cost. Per
the decomposition, ~97% of the per-channel inversion work is shareable, so the
inversion-setup block should drop ~17× for a 34-channel cube (≈60 s → ≈3.5 s);
the cube total drops from ~170 s toward the per-channel residual (data-vector
matmul + NNLS + log-ev) plus one shared mapper+L+F build. Compare against the
inversion_setup_decompose_*.jsonartifact for the channel-invariant/variant
split that sets the ceiling. Re-measure at ALMA scale on a quiet A100 to pin the
cube-level number (laptop SMA seconds are provisional; the ratios are robust).
Critical files
PyAutoLens (Phase 4):
- the
AnalysisInterferometerlikelihood path + interferometerInversion/mapper construction
Workspace / test / profiling (Phase 5):
autolens_workspace/scripts/interferometer/features/datacube/{likelihood_function,modeling,delaunay}.pyautolens_workspace_test/— new fast-assert datacube scriptautolens_profiling/likelihood_breakdown/datacube/delaunay.pyautolens_profiling/likelihood_runtime/OPTIMIZATION_NOTES.md
Out of scope
- A bespoke
DataCubedata class / cube-specificInversion(the rejected option). - The dense-route variant (production uses the sparse / w̃ route; dense is not the target).
- Cross-factor gradients — likelihood value only.
Cross-references
PyAutoPrompt/z_features/analysis_shared_state.md— the epic tracker- PyAutoFit#1307 — sub-task A (the mechanism + toy this consumes)
- autolens_workspace#120 — Aris's shared-
Lᵀ W̃ Loptimisation, the origin PyAutoPrompt/issued/alma_datacube.md— Aris's Slack design + channel-invariance caveatPyAutoPrompt/autoarray/datacube.md— the "analysis list API does not share information" problem statement
Activity
Jammy2211 commentedon Jun 8, 2026
Scoping confirmed — 2026-06-07 (at /start_library)
Traced the inversion-setup path:
AnalysisInterferometer.fit_from→FitInterferometer.inversion(cached_property) →TracerToInversion.inversion(PyAutoLens) →aa.AbstractInversion(PyAutoArray). The mapper, mapping matrix L and curvature F live in PyAutoArray (autoarray/inversion/inversion/), so this is confirmed a PyAutoLens + PyAutoArray library task (not PyAutoLens-only).The autoarray
AbstractInversionalready splits cleanly along the share boundary (all@cached_property):mapping_matrix(L),curvature_matrix(F = LᵀW̃L), the mapper, and the curvature/reg log-det terms.data_vector(D),reconstruction(s),mapped_reconstructed_data, the data chi-squared term.Injection design:
AnalysisInterferometer.shared_state_from(instance)builds the Tracer + the channel-invariant inversion pieces once; each channel's Inversion is constructed reusing those (pre-seeding the cached mapper/L/F) so onlydata_vector/reconstructionrun per channel.shared is Nonerebuilds everything (single-channel path unchanged). Per-channel fallback when uv_wavelengths/noise_map aren't ~channel-invariant.Worktree set up:
~/Code/PyAutoLabs-wt/datacube-shared-state(PyAutoLens + PyAutoArray onfeature/datacube-shared-state).Jammy2211 commentedon Jun 8, 2026
Library PR Created (curvature-sharing) — 2026-06-08
The curvature-sharing increment of sub-task B is shipped across both library repos. All PRs are
pending-release.PRs
What shipped
PreloadsAPI (PyAutoArray):aa.AbstractPreloads/aa.PreloadsInterferometer(curvature_matrix=...); the inversion stack + factory take an optionalpreloads=; the sparse interferometer inversion returns the preloadedcurvature_matrix(F = LᵀW̃L) directly, skipping the dominant rebuild. The naming/history (preloads were removed once for being bug-prone; safe in the explicit-invariance shared-likelihood case) is recorded in theAbstractPreloadsdocstring.AnalysisInterferometer.shared_preloadsflag +shared_state_from(instance)building the channel-invariantPreloadsInterferometeronce;preloads/sharedthreaded throughfit_from→FitInterferometer→TracerToInversion→inversion_from;PreloadsInterferometerregistered as a JAX pytree.test_autoarray841 +test_autolens331 pass; shared vs unsharedfigure_of_meritidentical; JAXjitshared-path likelihood matches numpy.This is the dominant ~86% of the shareable inversion-setup cost (
F).Remaining
mapperfield toPreloadsInterferometer+ a reuse path inTracerToInversionso the Delaunay triangulation isn't rebuilt per channel (→ ~97% shareable).shared_preloads=True;autolens_workspace_testfast-assert + the committed JAX assertion;autolens_profilingre-measure.Jammy2211 commentedon Jun 8, 2026
Shipped — 2026-06-08
PRs
Summary
The datacube shared-state consumer work has landed across the library and workspace repositories. The remaining PyAutoPrompt active-task entry was stale after the previous session hit a limit; it has been moved to complete so follow-up workspace-test work can proceed without a false repo claim.
Jammy2211 commentedon Jul 10, 2026
Closing — measurement recorded (2026-07-10)
The one outstanding Phase 5 item (the autolens_profiling re-measure) is now recorded on
main(autolens_profiling 675a902):At N = 4 identical channels this is effectively the ideal ceiling — the per-channel inversion rebuild collapses entirely. Run on current
maina month after the merge, so it doubles as a regression check on the shipped path (curvature and Slice-2b mapper sharing both verified present).Remaining follow-up (tracked in
likelihood_runtime/OPTIMIZATION_NOTES.md, not this issue): the authoritative--instrument alma_highrun on a quiet A100, queued for the next RAL HPC session.All library + workspace PRs (#344, #566, workspace #218, workspace_test #138, profiling #47) merged 2026-06-08. Closing.