Repository navigation
Description
Overview
Currently a function that returns FitImaging cannot be wrapped in jax.jit — the autoarray types reachable from it are not registered as JAX pytrees. The jax-visualization pilot (#1227) took Path C (eager JAX, no jit) as a workaround. This task lifts the restriction so Path A — full jax.jit(analysis.fit_from) — becomes viable, using autolens_workspace_test/scripts/jax_likelihood_functions/imaging/mge.py as the test vehicle. No pixelization / inversion is needed on the MGE path, so solver-state questions can be deferred.
Plan
- Restore the
AbstractNDArray.__init__auto-registration pattern that lived at PyAutoArray tag2025.12.15.2(commit378cb2c6), re-derived in current code style rather than cherry-picked, and gated so it only fires whenuse_jax=True. - Register autogalaxy types reachable from
Tracer(Galaxy,Redshift, light and mass profile dataclasses) using theautofit.jax.pytrees._build_instance_pytree_funcshelper — no prior pytree work exists in PyAutoGalaxy. - Confirm
Tracer/Galaxiesnested-list containers work via Python list recursion onceGalaxyis registered; add explicit handlers if not. - Register
FitImagingitself last. - Drive the work from a new
mge_pytree.pyintegration test that wrapsanalysis.fit_frominjax.jiton the existing MGE pipeline — iterate until aFitImagingwithjax.Arrayleaves comes out the other side. - Leave pixelization / inversion / NNLS solver state for a follow-up task.
- Leave
use_jax_for_visualizationdispatch unchanged (will be flipped in a later task).
Detailed implementation plan
Affected Repositories
- PyAutoLens (primary — CLAUDE.md doc being lifted)
- PyAutoArray —
AbstractNDArraypytree auto-registration restored - PyAutoGalaxy —
Galaxy,Redshift, light & mass profile pytree registration - PyAutoFit — may need helper extension in
autofit.jax.pytrees - autolens_workspace_test — new
mge_pytree.pyintegration test
Work Classification
Both (library-heavy, plus a workspace integration test).
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| PyAutoArray | main | clean |
| PyAutoFit | main | clean |
| PyAutoGalaxy | main | clean |
| PyAutoLens | main | clean |
| autolens_workspace_test | main | clean |
Suggested branch: feature/fit-imaging-pytree
Worktree root: ~/Code/PyAutoLabs-wt/fit-imaging-pytree/ (created later by /start_library)
Historical context — short-cut from git history
Pytree auto-registration already existed in PyAutoArray at tag 2025.12.15.2:
autoarray/abstract_ndarray.py—AbstractNDArray.__init__calledregister_pytree_node(type(self), instance_flatten, instance_unflatten)on first instantiation. Generic__dict__-walk with__no_flatten__exclusion tuple. Reconstruction via__new__+setattr— side-effect-free.- Removed during the Nov 2025 xp refactor (
6224b926 jax imports deferred,30c6cbe6follow-up, merged viaa4762d7b). Motivation: defer JAX imports for non-JAX users. autoarray/operators/over_sampling/over_sampler.pystill has a manualtree_flatten/tree_unflattenpair — mixed-mode precedent in current code.
PyAutoLens only ever pytree-registered point-solver classes (Step, ShapeSolver, NullTracer — 492a2e76b restore trees). PyAutoGalaxy has no prior pytree work.
Implementation Steps
-
PyAutoArray — restore
AbstractNDArraypytree auto-registration, re-derived to match current code style. Gate behinduse_jax=True:- Add a process-level flag set by
Analysis.__init__whenuse_jax=True(or reuse the existingautoconf.jax_wrapperstate if that's what the registration should key off). - Re-implement
instance_flatten/instance_unflatteninautoarray/abstract_ndarray.pymatching the current codebase's typing and import conventions. __no_flatten__per-subclass tuple lists static attrs (masks, shapes, pixel_scales, config).- Add a targeted test in
PyAutoArray/test_autoarray/jax/following the three-step pattern fromautolens_workspace_test/scripts/hessian_jax.py. One test is enough for the feasibility gate.
- Add a process-level flag set by
-
autolens_workspace_test — seed the driver script: copy
scripts/jax_likelihood_functions/imaging/mge.py→mge_pytree.py, keep the same dataset / model / analysis, but replacefitness._vmap(parameters)with ajax.jit(analysis.fit_from)call onmodel.instance_from_prior_medians(). Assert the returnedFitImaging's leaves arejax.Array, not NumPy. -
Iterate on the traceback. Expected blockers (in likely order):
Grid2D,Mask2D— covered by step 1's auto-registration.Galaxy,Redshift, light-profile dataclasses (Gaussian,Sersic,Exponential, etc.), mass-profile dataclasses (NFWSph,ExternalShear,PowerLaw),BasisLinear/GaussianGradient→ register viaautofit.jax.pytrees._build_instance_pytree_funcspattern in PyAutoGalaxy.Tracer,Galaxies(nestedList[List[Galaxy]]) — confirm list recursion handles it, else wrap.FitImagingitself — register last, children = thejax.Array-bearing attributes (model image, residual map, chi-squared map, etc.), aux = everything else.
-
Regression guard. PyAutoArray, PyAutoFit, PyAutoGalaxy, PyAutoLens full test suites must stay green. The gated registration must no-op when
use_jax=Falseso non-JAX users see zero change. -
Deliverable check.
mge_pytree.pyproduces a JIT-wrappedFitImagingwithjax.Arrayleaves; existingmge.pystill asserts its expected likelihood; test suites pass.
Scope boundary
- No pixelization / inversion /
Mapper/ NNLS work (defer —mge.py's path doesn't touch them). - No
use_jax_for_visualizationbehaviour change (separate follow-up flips the dispatch tojax.jit). - No edits to
visualization_jax.py. - Do NOT restore the "xp is np" guard removals — those are for a later task once this PoC succeeds.
Key Files
PyAutoArray/autoarray/abstract_ndarray.py— restore pytree auto-registration, gatedPyAutoArray/test_autoarray/jax/test_pytrees.py(new or existing) — PoC testPyAutoGalaxy/autogalaxy/galaxy/galaxy.py— registerGalaxyvia_build_instance_pytree_funcsPyAutoGalaxy/autogalaxy/profiles/— register light & mass profile dataclassesPyAutoGalaxy/autogalaxy/cosmology/— registerRedshiftif neededPyAutoLens/autolens/lens/tracer.py— registerTracer/GalaxiesPyAutoLens/autolens/imaging/fit_imaging.py— registerFitImaging(last)PyAutoGalaxy/autogalaxy/imaging/fit_imaging.py— base class may also need registrationautolens_workspace_test/scripts/jax_likelihood_functions/imaging/mge_pytree.py— newPyAutoLens/CLAUDE.md— update the "autoarray types cannot be returned" restriction paragraph once PoC is green
Original Prompt
Click to expand starting prompt
Can we register @PyAutoLens/autolens/imaging/fit_imaging.py FitImaging (and the autoarray /
autogalaxy types it transitively contains) as JAX pytrees, so that a function that returns
FitImaging can be wrapped in jax.jit?
Why this matters
In the JAX-visualization pilot (#1227 on PyAutoFit, shipped via use_jax_for_visualization)
we intentionally took the "Path C" route: fit_from runs on the eager JAX path
(use_jax=True makes self._xp = jnp, operations run eagerly under JAX) and the
matplotlib plotters materialize arrays to NumPy at the boundary. No jax.jit wrapping.
The endgame — "Path A" — is full jax.jit wrapping of analysis.fit_from, so visualization
gets the same compile-time speedup the likelihood function does. This requires the
FitImaging return type (and every autoarray / autogalaxy / autolens type reachable from
it) to be a JAX pytree. PyAutoLens's CLAUDE.md currently documents the reverse:
Autoarray types (
Array2D,ArrayIrregular,VectorYX2DIrregular, etc.) are not
registered as JAX pytrees. They can be constructed inside a JIT trace, but cannot
be returned as the output of ajax.jit-compiled function.
This task is the feasibility study for lifting that restriction.
What's already in place
Recent autofit work added autofit.jax.pytrees (see @PyAutoFit/autofit/jax/pytrees.py)
which registers Model, Collection, ModelInstance, and user classes found by walking
a Model tree. The pattern:
children= dynamic (traced) arrays / sub-pytreesaux= static Python objects (concrete constants, class references, etc.)flatten/unflattenlift/restore the instance across the JIT boundary
The same pattern needs to extend down through the autoarray / autogalaxy layers.
Assess, don't implement (yet)
The deliverable is a feasibility assessment, not a fully-registered FitImaging. Produce
the assessment as a markdown document (e.g. in this prompt file's issued/ location, or
as a draft PR) answering:
-
Type inventory. Walk
FitImaging(@PyAutoLens/autolens/imaging/fit_imaging.py) and
list every distinct class reachable from a populated instance. For each class:- Where it's defined (autoarray / autogalaxy / autolens)
- Whether it currently carries
tree_flatten/tree_unflattenmethods - Whether its constructor is compatible with pytree unflatten (takes the children as
positional / keyword args) or whether a_build_*_pytree_funcshelper (like
autofit.jax.pytrees._build_instance_pytree_funcs) would be needed
-
Dynamic vs static classification. For each class, which attributes are dynamic
(JAX arrays — get traced) vs static (numpy arrays that mustn't change, masks, shapes,
pixel scales, redshifts, config objects)? The CLAUDE.md rule for autoarray is:
grid.array[:, 0]is traced,grid.maskis static. That rule needs to be restated
per type here. -
Structural blockers. Are there types that cannot be registered cleanly? Examples
to look for:- Classes whose
__init__does non-trivial work that can't be replayed during
unflatten (e.g. reading config, triggering a numba compilation) - Classes that hold live references to an
Inversionwhose solver state isn't
pickleable / is stateful under JAX Tracer/Galaxies— these areList[List[Galaxy]]nested containers; are they
already pytree-friendly via Python list recursion, or do they need explicit
registration?Mapper/LinearEqn/ NNLS solver state insideInversion— likely the hardest
- Classes whose
-
Interaction with the existing "xp is np" guard pattern. PyAutoLens has an established
convention (see@PyAutoGalaxy/autogalaxy/operate/lens_calc.py): functions that return
autoarray wrappers guard withif xp is np: return Array2D(...)else return raw
jax.Array. RegisteringArray2Das a pytree means these guards may become
unnecessary. Is removing them desirable, or should they stay for the non-JIT path? -
Proof-of-concept. Register one concrete type (suggest
Array2D— it's the
simplest leaf) and demonstrate that a toy functiondef f(x): return Array2D(values=x*2, mask=static_mask)runs underjax.jitand returns anArray2Dbacked by
jax.Array. This both validates the approach and surfaces any wrinkles that the static
analysis missed. -
Estimate. Given the inventory and the proof-of-concept, roughly how many types need
registration to makeFitImaginga pytree? Which ones are cheap (static dataclass-ish),
which are hairy (inversion, mapper)?
Scope boundary
- Do not change
use_jax_for_visualizationbehaviour. That flag currently dispatches
to the eager-JAX path; once this task produces a green light + registration PR, a
follow-up task will flip the dispatch tojax.jit. - Do not start mass pytree-registration in this task. One-type PoC only.
- Do not register
FitImagingitself yet — that's the goal after every type it
reaches is registered.
Starting points
@PyAutoLens/autolens/imaging/fit_imaging.py— theFitImagingsubclass@PyAutoGalaxy/autogalaxy/imaging/fit_imaging.py— baseFitImaging@PyAutoArray/autoarray/structures/arrays/uniform_2d.py—Array2D(suggested PoC type)@PyAutoFit/autofit/jax/pytrees.py— the existing autofit pytree machinery; mirror its
structure when proposing new registrations@PyAutoLens/CLAUDE.md— the "xp is np" guard rule this task's outcome will revise
Deliverables
- Type inventory + classification table (markdown)
- List of structural blockers with suggested workarounds
- Working PoC registering
Array2Dwith a minimal test in
@PyAutoArray/test_autoarray/jax/following the three-step pattern in
autolens_workspace_test/scripts/hessian_jax.py - Effort estimate + recommended ordering for the follow-up implementation task
Activity
jax: gated pytree registration for AbstractNDArray + register_instanc…
jax: register FitImaging, Tracer, DatasetModel as pytrees on use_jax …
Jammy2211 commentedon Apr 19, 2026
Library Shipped — 2026-04-19
Library PRs
Status
Library half of Path A is done.
jax.jit(analysis.fit_from)(instance)now returns a realFitImagingwithjax.Arrayleaves, matching the NumPy scalar to ~1e-8 rel on the MGE parametric lens + MGE source PoC.Workspace work remaining
A new workspace script
autolens_workspace_test/scripts/jax_likelihood_functions/imaging/mge_pytree.pydemonstrates the feature and is currently uncommitted. It will ship via/ship_workspacein a follow-up PR.Session Notes
FitImaging,Tracer,DatasetModel). Every other type reachable from aFitImagingreturn value either (a) was already registered byenable_pytrees()+register_model(model)(Galaxy, light/mass profiles), or (b) could ride asaux_dataon the owning class (dataset, settings, adapt_images, cosmology — all per-analysis constants).aux_dataescape hatch via the genericregister_instance_pytree(cls, no_flatten=...)helper did most of the heavy lifting. Any per-analysis constant that doesn't need tracing stays as an opaque Python reference across the JIT boundary.if xp is np:return wrapped else return raw array" boundary pattern instead of pytree registration. The pattern would have forcedfit_fromto return a flat array tuple (not aFitImaging), which defeats the point for visualisation callers. Pytree registration won.Mapper/ NNLS work,use_jax_for_visualizationdispatch flip to Path A, and removal of scatteredif xp is np:guards. These come in follow-up tasks once the parametric path is bedded in.Jammy2211 commentedon Apr 19, 2026
Shipped — 2026-04-19
PRs
Upstream PRs
Summary
Path A of the fit-imaging-pytree task now ships end-to-end. The library PRs added
gated pytree auto-registration for
AbstractNDArraysubclasses plus a genericregister_instance_pytree(cls, no_flatten=...)helper, and wiredFitImaging/Tracer/DatasetModelthroughAnalysisImaging._register_fit_imaging_pytrees.The workspace PR lands a PoC at
scripts/jax_likelihood_functions/imaging/mge_pytree.pythat jit-wrapsanalysis.fit_fromfor the MGE parametric lens + MGE source and asserts thereturned
FitImaging.log_likelihoodmatches the NumPy path to rtol=1e-4(observed ~1e-8).
Session Notes
no_flatten) did most of the heavy lifting — onlydataset,settings,adapt_images, andcosmologyneeded to ride as aux.Everything else was either auto-registered when an
AbstractNDArrayinstancewas built with
xp=jnp, or already handled byautofit.jax.pytrees.register_model.if xp is np:boundary pattern but rejected it:it would force every jit-returned method to drop its autoarray wrapper, which
doesn't compose with
analysis.fit_fromreturning a fullFitImaging.model.instance_from_prior_medians()as the JIT input — next stepwould be extending this to visualization calls that run downstream of the fit.
Jammy2211 commentedon May 28, 2026
Shipped via #445. Closing.