Repository navigation
Description
Overview
Add use_jax=True constructor flag to SimulatorImaging, SimulatorInterferometer, and PointSolver. Internal pytree registration on first call so workspace users never import register_instance_pytree or register_model. Also fix the existing signature inconsistency where SimulatorImaging.via_image_from accepts xp=np but SimulatorInterferometer.via_image_from does not.
Phase 2 of the z_features/jax_user_intro.md series. Authoritative design doc: admin_jammy/notes/jax_interface.md (admin_jammy main f381393). The user-facing contract this PR implements:
simulator = al.SimulatorImaging(exposure_time=300.0, psf=psf, use_jax=True)
@jax.jit
def simulate(tracer):
return simulator.via_tracer_from(tracer=tracer, grid=grid)
dataset = simulate(tracer) # Imaging with jax.Array leavesNo register_instance_pytree. No register_model. No xp=jnp threading. One @jax.jit, one use_jax=True.
Plan
- Add
use_jax=Falseconstructor parameter toSimulatorImaging(PyAutoArray) +_xpproperty + optionalxp=Noneonvia_image_from. - Convert simulator internals to the
xpAPI as needed. Today'snp.isnan,preprocess.data_eps_with_poisson_noise_added,preprocess.data_with_complex_gaussian_noise_added, and similar NumPy-only call sites insidevia_image_frommust become xp-aware. JAX-side may need akey=argument andjax.random.poisson/jax.random.normalfor noise generation; sketch the API during implementation. - Mirror the same for
SimulatorInterferometerand fix its asymmetricvia_image_fromsignature (noxptoday). - PyAutoLens / PyAutoGalaxy simulator subclasses thread
use_jaxthrough theirvia_tracer_from/via_galaxies_fromoverrides. - Add
use_jax=FalsetoPointSolverconstructor; threaded intosolve(); defaultremove_infinities=Falseon the JAX path. - New
register_simulator_pytrees(tracer)helper that walks a concrete tracer and registers eachGalaxy/LightProfile/MassProfile/Pointclass, plusTracer,Imaging,Interferometercontainers. Location TBD per design doc — recommended path isautolens/jax/registration.py. - Add the
xp=np+ jnp-backed-grid mismatchValueErroratAbstractMaker.__init__(design doc §4.8 — helps users discover the@jax.jit + xp=jnppairing rule). - Test additions across PyAutoArray, PyAutoLens unit tests + workspace_test parity scripts (numpy-only unit tests per [[feedback_no_jax_in_unit_tests]]).
Detailed implementation plan
Affected Repositories
- PyAutoArray (primary — Simulator constructor flag, registration helper, mismatch error, internals xp-conversion)
- PyAutoLens (Simulator subclasses, PointSolver, registration walker if hosted here)
- PyAutoGalaxy (Simulator subclasses)
Work Classification
Library
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoArray | main | clean |
| ./PyAutoLens | main | pre-existing CLAUDE.md, README.md (unrelated) |
| ./PyAutoGalaxy | main | pre-existing CLAUDE.md (unrelated) |
Suggested branch: feature/simulator-use-jax
Worktree root: ~/Code/PyAutoLabs-wt/simulator-use-jax/ (created later by /start_library)
Implementation Steps
-
PyAutoArray —
SimulatorImaging.__init__(..., use_jax=False)inautoarray/dataset/imaging/simulator.py. Addself._pytrees_registered = Falseand_xpproperty. Makevia_image_from(image, over_sample_size=None, xp=None)defaultxptoself._xpwhenNone. -
PyAutoArray — convert
SimulatorImaginginternals to xp.via_image_fromtoday callsnp.isnan(noise_map.array).any()andpreprocess.data_eps_with_poisson_noise_added(NumPy-only). Convert:- Replace
np.isnanwithxp.isnan(or accept a host transfer on the JAX path if the check fires outside the jit). - Add an xp-aware path to
preprocess.data_eps_with_poisson_noise_added— JAX needs explicit RNG (jax.random.PRNGKey+jax.random.poisson). Accept an optionalkey=argument onSimulatorImagingfor the JAX path; fall back tonoise_seedsemantics on the NumPy path. - Mask construction (
Mask2D.all_false(...)) andArray2D(values=image.native, mask=mask)constructions insidevia_image_fromneed to construct correctly withjax.Arraybacking —AbstractNDArray.__init__auto-registers, so this should work as-is; verify under JIT.
- Replace
-
PyAutoArray —
SimulatorInterferometer.__init__(..., use_jax=False)inautoarray/dataset/interferometer/simulator.py. Addvia_image_from(image, xp=None)(fixing the asymmetry vsSimulatorImaging). Convert internals:- Thread
xpthroughtransformer.visibilities_from(Transformer needs xp-awareness — confirm TransformerDFT works under JAX; TransformerNUFFT documented as non-traceable per design doc §3b prompt). - Replace
preprocess.data_with_complex_gaussian_noise_addedwith xp-aware version (JAX needsjax.random.normal+PRNGKey). - Replace
np.isnan(noise_map).any()withxp.isnan.
- Thread
-
PyAutoArray — mismatch
ValueErrorat the top ofAbstractMaker.__init__inautoarray/structures/decorators/abstract.py. Default-on, opt-out via_strict_xp=Falsekwarg. Cite thelens_calc.pyguide (Phase 5d) in the error message. -
Registration walker. Implement
register_simulator_pytrees(tracer)(or equivalent name; see design doc §3.4 + Phase 2 prompt §"Open API decisions"). Recommended location:autolens/jax/registration.py(mirror of PyAutoFit'sautofit/jax/pytrees.py). Walks tracer once, registers each concrete class viaregister_instance_pytree. -
PyAutoLens —
SimulatorImagingoverride inautolens/imaging/simulator.pycalls the walker on firstvia_tracer_from(tracer, grid)whenuse_jax=True. Mirror forSimulatorInterferometerinautolens/interferometer/simulator.py. -
PyAutoGalaxy —
SimulatorImaging/SimulatorInterferometeroverrides threaduse_jaxsimilarly. The walker registersGalaxiesinstead ofTracer. -
PyAutoLens —
PointSolver(use_jax=False)in the PointSolver class. Defaultremove_infinities=Falseon JAX path. RegisterTraceron first solve. Auditsolve()internals for NumPy-only call sites that need xp conversion. -
Resolve open API decisions during implementation:
- Where does the registration walker live? (Recommended:
autolens/jax/registration.py.) - Is
Imaging/Interferometerpytree registration needed at the return path? Verify and register if so. - How does noise-generation RNG work? (PRNGKey injection vs
noise_seedsemantics.)
- Where does the registration walker live? (Recommended:
-
Tests — per [[feedback_no_jax_in_unit_tests]], library unit tests stay NumPy-only; cross-xp parity lives in workspace_test:
test_autoarray/dataset/imaging/test_simulator.py— constructor + decorator mismatch error.test_autoarray/structures/decorators/test_abstract.py— mismatch ValueError.test_autolens/imaging/test_simulator.py—use_jax=True+via_tracer_fromend-to-end.test_autolens/point/test_solver.py—PointSolver(use_jax=True).solve(...).autolens_workspace_test/scripts/imaging/simulator_use_jax_parity.py— numerical parity between use_jax=True/False (use zero/fixed noise seed).autolens_workspace_test/scripts/point_source/solver_use_jax_parity.py.
Key Files
PyAutoArray/autoarray/dataset/imaging/simulator.py—SimulatorImaging.__init__+via_image_from+ xp-conversion of internals.PyAutoArray/autoarray/dataset/interferometer/simulator.py—SimulatorInterferometer.__init__+ signature symmetry fix + xp-conversion.PyAutoArray/autoarray/dataset/preprocess.py— xp-aware noise-generation helpers.PyAutoArray/autoarray/structures/decorators/abstract.py—AbstractMaker.__init__mismatch error.PyAutoArray/autoarray/abstract_ndarray.py— confirmregister_instance_pytreecoversImaging/Interferometer.PyAutoLens/autolens/jax/registration.py(new) — registration walker (or alternate location).PyAutoLens/autolens/imaging/simulator.py—via_tracer_fromuse_jax thread.PyAutoLens/autolens/interferometer/simulator.py— same.PyAutoLens/autolens/point/solver.py(or current PointSolver location) —use_jax.PyAutoGalaxy/autogalaxy/imaging/simulator.py—via_galaxies_fromuse_jax thread.PyAutoGalaxy/autogalaxy/interferometer/simulator.py— same.
Out of scope (per design doc)
use_jax=TrueonTracer/Galaxy/LightProfile/MassProfile. Those are reached via user-written@jax.jit + xp=jnp(Phase 5d advanced guide).- Workspace script edits (Phase 3a/3b/3d for autolens, 4a/4b for autogalaxy).
- Migrating
cluster/simulator.py(Phase 3f, deferred). - Gradients (future series).
al.jax.enable_for_modeling()helper (open question §4.1; separate prompt).
Original Prompt
Click to expand starting prompt
# Phase 2 — `Simulator.use_jax=True` + `PointSolver.use_jax=True`
The library deliverable of `z_features/jax_user_intro.md`. Adds `use_jax=True`
constructor flags to the three classes the Phase 0 audit identified as needing
them, with internal pytree registration so workspace users never import
`register_instance_pytree` or `register_model`.
**Authoritative design doc:** `admin_jammy/notes/jax_interface.md` (admin_jammy
main `f381393`). Recommendation 2 is the contract:
> Add `Simulator.use_jax=True` (imaging + interferometer first) and
> `PointSolver.use_jax=True` (point_source). **Bounded to these three classes.**
> Do NOT proliferate `use_jax=True` onto `Tracer`, `Galaxy`, `LightProfile`,
> `MassProfile`. Those are reached via the user-written `@jax.jit + xp=jnp`
> path (the `lens_calc.py` advanced guide), not via constructor flags.
This phase ships the library changes. Phases 3a/3b/3d consume them with new
`__JAX Variant__` workspace examples.
**Run in Opus for design judgement; Sonnet via subagent is fine for the
mechanical test additions and parity scripts.** See CLAUDE.md model
delegation rules — the prompt-author / planner is Opus; per-file test
implementations can route to Sonnet.
## Scope
**In scope:**
1. `SimulatorImaging.__init__(..., use_jax=False)` — PyAutoArray.
2. `SimulatorInterferometer.__init__(..., use_jax=False)` — PyAutoArray. Also
fix the existing signature inconsistency: `via_image_from` does not accept
`xp` today. Add `xp=np` parameter symmetric with `SimulatorImaging`.
3. `PointSolver` (PyAutoLens) — add `use_jax=False`. Threaded into
`solver.solve(...)`. Defaults `remove_infinities=False` on the JAX path.
4. **Pytree registration helper** — a new function (location TBD; see
"Open API decisions" below) that walks a concrete `Tracer` and registers
each class it carries (Galaxy, light profiles, mass profiles, Point) as
a JAX pytree. The existing `register_instance_pytree(cls, no_flatten=())`
helper in `PyAutoArray/autoarray/abstract_ndarray.py:84-131` is the building
block — this phase wraps it for the simulator surface.
5. **The `xp=np + jnp-backed grid` mismatch error** (design doc §4.8). Add a
loud `ValueError` at `AbstractMaker.__init__` (or equivalent entry point)
when `xp is np` but `grid.use_jax` is True. Helps users discover the
`@jax.jit + xp=jnp` pairing rule.
**Out of scope:**
- Adding `use_jax=True` to `Tracer`, `Galaxy`, `LightProfile`, `MassProfile`.
Per recommendation 2's bound: no proliferation.
- Editing any workspace script. That's Phase 3 (3a/3b/3d for the new
`__JAX Variant__` blocks).
- Migrating `autolens_workspace/scripts/cluster/simulator.py` to use the new
API. Could be done as a worked example, but cluster is in-development per
the design's scope anchor — defer until cluster is stable and Phase 3f is
authored.
- Gradients (`jax.grad`). Explicitly deferred to a future series per the
z_features tracker.
- A user-facing `al.jax.enable_for_modeling()` helper. Open question 4.1 in
the design doc — flag here as deferred to a separate (future) prompt;
Phase 2 does not introduce it.
## Implementation
### 1. `SimulatorImaging.use_jax=True`
File: `PyAutoArray/autoarray/dataset/imaging/simulator.py`.
Signature change:
```python
class SimulatorImaging:
def __init__(
self,
exposure_time: float,
background_sky_level: float = 0.0,
subtract_background_sky: bool = True,
psf: Convolver = None,
use_real_space_convolution: bool = True,
normalize_psf: bool = True,
add_poisson_noise_to_data: bool = True,
include_poisson_noise_in_noise_map: bool = True,
noise_if_add_noise_false: float = 0.1,
noise_seed: int = -1,
use_jax: bool = False,
):
...
self.use_jax = use_jax
self._pytrees_registered = FalseInternal _xp property (mirror Analysis._xp):
@property
def _xp(self):
if self.use_jax:
import jax.numpy as jnp
return jnp
return npvia_image_from(image, over_sample_size=None, xp=None): when xp is None,
default to self._xp. Keep the explicit xp= parameter for library-author
flexibility but make it optional for users.
The PyAutoLens override at PyAutoLens/autolens/imaging/simulator.py —
SimulatorImaging.via_tracer_from(self, tracer, grid) — adds the pytree
registration step on first call:
def via_tracer_from(self, tracer, grid):
if self.use_jax and not self._pytrees_registered:
self._register_simulator_pytrees(tracer)
self._pytrees_registered = True
xp = self._xp
...Where _register_simulator_pytrees(tracer) walks the tracer once and calls
register_instance_pytree(...) for Tracer, Imaging, and every concrete
class reachable from the tracer (Galaxy, each LightProfile subclass,
each MassProfile subclass, Point). The autoarray-side AbstractNDArray
self-registers on construction when xp=jnp is passed, so the walker
doesn't need to handle Array2D / Grid2D etc.
2. SimulatorInterferometer.use_jax=True
File: PyAutoArray/autoarray/dataset/interferometer/simulator.py.
Today via_image_from(self, image) does NOT accept xp. Two fixes here:
- Add
xp=Noneparameter symmetric withSimulatorImaging. - Replace the hardcoded
np.isnan(noise_map).any()withxp.isnan(...)
and thedata_with_complex_gaussian_noise_addedhelper with a
JAX-compatible Poisson-/complex-Gaussian-noise variant. Today's helper
is numpy-only; needs an xp-aware path. If a JAX-safe RNG (jax.random)
is needed, accept akey=argument whenuse_jax=True.
The PyAutoLens override (autolens/interferometer/simulator.py) gains the
same pytree-registration hook as SimulatorImaging. Also: confirm
Interferometer itself needs pytree registration (it carries Convolver
and Transformer* which may not be pytrees) — design doc §4.2 flags this
as an open question that Phase 2 implementation resolves.
3. PointSolver.use_jax=True
File: PyAutoLens/autolens/point/solver.py (or wherever PointSolver lives;
the class is exported as al.PointSolver).
Signature change on __init__ and for_grid factory: accept use_jax=False.
Thread into solver.solve(...):
- When
self.use_jax, setxp=jnpinternally. - Default
remove_infinities=Falseon the JAX path (output is padded with
infsentinels for static-shape compatibility). - Register
Traceras a pytree on firstsolve()call.
Return type: Grid2DIrregular should be returned wrapped on both paths
(today the cluster simulator does .array unwrap because Grid2DIrregular
isn't pytree-registered for the return path). If the autoarray-side
AbstractNDArray._register_as_pytree covers Grid2DIrregular reliably
when constructed inside the solver with xp=jnp, the user can return the
wrapped object directly. Verify this during Phase 2 implementation — if
the autoarray pytree registration is unreliable for return paths, the
solver returns raw arrays on the JAX path and users do the rewrap outside
the jit (matches the design doc §3.4.1 pattern).
4. The xp=np + jnp-grid mismatch error (design doc §4.8)
File: PyAutoArray/autoarray/structures/decorators/abstract.py,
AbstractMaker.__init__.
Add at the top of __init__:
if xp is np and getattr(grid, "use_jax", False):
raise ValueError(
f"Called {func.__qualname__} with xp=np but the input grid is "
f"JAX-backed (grid.use_jax=True). Inside @jax.jit, pass xp=jnp "
f"explicitly. See the lens_calc.py guide for the JIT-it-yourself "
f"pattern."
)Default-on, opt-out via a _strict_xp=False kwarg on the call (for library
internals that legitimately want the host-transfer NumPy fallback path on
a jnp grid). Add a similar guard to AbstractNDArray.__init__ if there's a
parallel construction site that could trip it.
Tests
PyAutoArray tests stay numpy-only per [[feedback_no_jax_in_unit_tests]].
Cross-xp validation lives in autolens_workspace_test/ / autogalaxy_workspace_test/.
For PyAutoArray:
test_autoarray/dataset/imaging/test_simulator.py— new test:
SimulatorImaging(use_jax=True)constructs cleanly;via_image_fromwith
use_jax=Truereturns anImagingwhose.data.arrayis ajax.Array.
Use the autoconf JAX fixture if needed.test_autoarray/structures/decorators/test_abstract.py— new test for
thexp=np+ jnp-gridValueError.
For PyAutoLens:
test_autolens/imaging/test_simulator.py—SimulatorImaging(use_jax=True)via_tracer_fromend-to-end; assertdataset.data.arrayis jnp-backed.
test_autolens/point/test_solver.py—PointSolver(use_jax=True).solve(...)
returns positions; assert backing array isjax.Array.
For autolens_workspace_test/scripts/:
- Add a parity script:
autolens_workspace_test/scripts/imaging/simulator_use_jax_parity.pythat
assertsSimulatorImaging(use_jax=True)produces a dataset numerically
equal toSimulatorImaging(use_jax=False)(within reasonable tolerance —
noise seeds differ between RNG implementations; consider asserting
noise-free or zero-seed paths). - Add
autolens_workspace_test/scripts/point_source/solver_use_jax_parity.py
for thePointSolverchange.
These are workspace_test scripts, not unit tests — they exercise the
imaging/point-source pipelines end-to-end with both backends and confirm
parity, matching the existing pattern in
autolens_workspace_test/scripts/jax_likelihood_functions/.
Open API decisions
The implementing PR needs to make explicit choices on these:
-
Where does the pytree-walker live? Options:
PyAutoArray/autoarray/abstract_ndarray.py— alongside
register_instance_pytree. Library-internal, noautolensimport.
Requires the walker to be tracer-shape-agnostic (walks any object's
__dict__recursively and registers each class).PyAutoLens/autolens/lens/tracer.py— as aTracermethod. Cleaner
because it's tracer-specific, but couples the pytree registration to
the autolens import path.Simulator.via_tracer_fromwould call
tracer._register_for_jax().PyAutoLens/autolens/jax/registration.py— a new module. Most
discoverable for users who go looking for "where does the JAX
registration code live?"- Recommendation: option 3 (
autolens/jax/registration.pyor
autogalaxy/jax/registration.pymirror). Matches PyAutoFit's
autofit/jax/pytrees.pylayout. Keeps autoarray clean.
-
TracerandGalaxyregistration: walk-on-call or preemptive?- Walk-on-call: the simulator walks
traceron its first
via_tracer_frominvocation and registers each class it sees. Cheap
because it only fires once. Won't catch classes the user adds later. - Preemptive:
enable_jax_for_modeling()(still deferred per scope) walks
all known PyAutoLens classes at import time. Heavier startup, more
thorough. - Recommendation: walk-on-call. Aligns with the existing
AnalysisImaging._register_fit_imaging_pytrees()style.
- Walk-on-call: the simulator walks
-
Interferometerpytree registration: design doc §4.2 flags this.
Confirm during implementation: doesregister_instance_pytree(Interferometer, no_flatten=("transformer_class", "real_space_mask"))round-trip cleanly
throughjax.jit? If yes, register it. If not, document why and have
the user do.dataunwrap on the JIT boundary. -
Imagingpytree registration: same question for the imaging dataset.
no_flattencandidates:psf(Convolver),mask(Mask2D). Verify and
register. -
Error message wording for §4.8: cite the
lens_calc.pyguide
directly. Once Phase 5d ships, the error message should link to it.
Validation
After the library changes land:
- PyAutoArray tests pass with the new helpers; no JAX leakage into
the numpy unit test path. - PyAutoLens tests pass including the new use_jax parity tests.
- Workspace_test parity scripts pass — both numpy and JAX paths
produce numerically agreeing simulations. - The cluster simulator can be optionally migrated as a worked example
showing the API collapse (8 steps → 1-2 user-visible lines). Optional;
cluster is in-development per scope anchor. - The Phase 1 top-level start_here
__JAX__section still reads
correctly — its prose references "use_jax=True" on Simulator/PointSolver
as a thing that exists.
References
- Phase 0 design doc:
admin_jammy/notes/jax_interface.md(sections
2.2, 3.4, 3.5, 4.2, 4.8 are most relevant). - Z-features tracker:
PyAutoPrompt/z_features/jax_user_intro.md. - Phase 1 (sibling, ships in parallel):
PyAutoPrompt/workspaces/jax_start_here_intros.md— top-level start_here
__JAX__sections. - Phase 3 (downstream consumers):
autolens_workspace/jax_docs_imaging.md,
..._interferometer.md,..._point_source.md— these use the new
Simulator.use_jax=True/PointSolver.use_jax=TrueAPI in their
__JAX Variant__blocks. - Existing model templates:
PyAutoLens/autolens/imaging/model/analysis.py:168-188—
AnalysisImaging._register_fit_imaging_pytrees()is the canonical
auto-registration pattern this phase mirrors.PyAutoLens/autolens/point/model/analysis.py:172-208—
AnalysisPoint._register_fit_point_pytrees()for the point-source case.PyAutoArray/autoarray/abstract_ndarray.py:84-131—
register_instance_pytree(cls, no_flatten=())building block.
Out-of-band notes
- Library tests stay numpy-only per [[feedback_no_jax_in_unit_tests]].
- Smoke tests are a curated subset per [[feedback_smoke_tests_small_subset]];
do not mass-promote the new use_jax parity scripts intosmoke_tests.txt. [[feedback_jax_validation_vmap_not_jit]]— when validating that
use_jax=Trueworks end-to-end, usefitness._vmap(jnp.array(parameters)),
notjax.jit(fn)(concrete).
</details>
Activity
Jammy2211 commentedon May 24, 2026
Targeted Smoke — PointSolver use_jax (2026-05-24)
Focused on the one smoke script that exercises
PointSolver:autolens_workspace_test/scripts/jax_likelihood_functions/point_source/point.py-83.38049777774609matches NumPy to 1e-13)PointSolver change is purely additive (
use_jax=Falsedefault preserves all existing behaviour), so the 317/317 PyAutoLens unit tests + this targeted smoke + the cross-xp parity script in the PR's test plan are sufficient. Skipping the full 6-workspace smoke run.Merging PR PyAutoLens#538.
Jammy2211 commentedon May 24, 2026
PR 1 of 4 Shipped — 2026-05-24
PR (merged)
Summary
Added
use_jax=Trueconstructor flag toPointSolver(forwarded byfor_grid/for_limits_and_scale) and shipped the newautolens.jax.register_tracer_classes(tracer)public helper. Whenuse_jax=True,solve()defaultsxp=jnpandremove_infinities=Falsefor JAX static-shape compatibility. The registration walker mirrorsAnalysisImaging._register_fit_imaging_pytrees()for the standalone (non-Analysis) JIT-it-yourself path.Session Notes — design correction worth capturing
The Phase 0 design doc said "user never imports
register_*". We discovered during implementation that JAX flattens function args at trace time, before entering the decorated function — so the auto-registration that worked for the Analysis path (where the JIT arg isparameters: jnp.Array) cannot work for the user-written@jax.jit def f(tracer, coord)path. Users must callautolens.jax.register_tracer_classes(tracer)once before the first jitted call. This is one import + one call (vs cluster's existing ~60-line ceremony), so the user-facing cost is small — JAX support is opt-in for users who need speed anyway. Phase 0 design doc §3.1 / §3.4.1 should be amended in the next pass.Next (Phase 2 PRs 2-4)
SimulatorImaging.use_jax=True+ xp-conversion of simulator internals.SimulatorInterferometer.use_jax=True+via_image_from(xp=...)signature symmetry fix.xp=np+ jnp-backed-grid mismatchValueErrorinAbstractMaker.__init__(PyAutoArray-only).Plus a follow-up workspace_test PR with the parity script (
autolens_workspace_test/scripts/point_source/solver_use_jax_parity.py).Issue stays OPEN until Phase 2 PRs 2-4 ship.
Jammy2211 commentedon May 24, 2026
PR 2 of 4 Shipped — 2026-05-24
PRs (all merged in dependency order)
Summary
SimulatorImaging(use_jax=True)now works on the JAX-eager path across all three repos.simulator.via_tracer_from(tracer, grid)returns anImagingwithjax.Arraydata when the simulator was constructed withuse_jax=True. Poisson noise routes throughjax.random.PRNGKey(noise_seed)+jax.random.poisson. No user-sidexp=threading; the constructor flag does the work.Known limitation — flagged in simulator docstring
@jax.jitwrapping ofvia_tracer_fromis currently blocked by a pre-existing PyAutoArray jit-incompatibility:Array2D.nativeroutes througharray_2d_via_indexes_from, which uses indexed assignment of native-index tuples that is not jit-traceable. Affectsvia_image_from(re-wrap step) andtrimmed_after_convolution_from. A separate task is needed to refactor the slim/native reshape path. Eager JAX usage works today and is sufficient for batched simulations / parameter sweeps.Session Notes
The
if xp.isnan(noise_map.array).any():runtime guard is now NumPy-side only — Pythonif <tracer>:triggersTracerBoolConversionErrorunder JAX. The simulator docstring explains that JAX users must validate theirexposure_time/background_sky_levelthemselves; NaN propagates silently through the trace otherwise. Test-state pollution between PyAutoLens and PyAutoGalaxy imaging tests was observed (twoimaging/model/tests fail when both repos' suites run in sequence, pass standalone) — pre-existing, not caused by this PR.Next (Phase 2 PRs 3-4)
SimulatorInterferometer.use_jax=True+via_image_from(xp=...)signature symmetry fix.xp=np+ jnp-backed-grid mismatchValueErrorinAbstractMaker.__init__(PyAutoArray-only).Array2D.nativejit-safety refactor (unlocks@jax.jitfor SimulatorImaging).Issue stays OPEN until PRs 3-4 ship.
Jammy2211 commentedon May 24, 2026
PR 3 of 4 Shipped — 2026-05-24
PRs (all merged in dependency order)
Summary
SimulatorInterferometer(use_jax=True)now works on JAX-eager across all three repos. Complex Gaussian visibility noise routes throughjax.random.PRNGKey(noise_seed)+jax.random.normal. Also fixed the pre-existing signature asymmetry —aa.SimulatorInterferometer.via_image_fromnow acceptsxp=Nonematchingaa.SimulatorImaging.via_image_from.Same
.nativejit limitation as PR 2@jax.jit wrap of
via_image_fromis currently blocked by the autoarray slim/native reshape pre-existing limitation (flagged in PR 2 shipped comment). Eager JAX works for both imaging and interferometer; full @jax.jit support needs the separate slim/native reshape refactor task.Tests
PyAutoArray 834/834, PyAutoLens 317/317, PyAutoGalaxy 918/918 (no regressions). Cross-xp parity script at
autolens_workspace_test/scripts/interferometer/simulator_use_jax_parity.pyconfirms NumPy and JAX visibilities agree to atol=1e-8.Next (Phase 2 PR 4)
xp=np+ jnp-backed-grid mismatchValueErrorinAbstractMaker.__init__(PyAutoArray-only). Helps users discover the@jax.jit + xp=jnppairing rule the first time they break it.Array2D.nativejit-safety refactor.Issue stays OPEN until PR 4 ships.
Jammy2211 commentedon May 24, 2026
Phase 2 COMPLETE — 2026-05-24
All 4 PRs of Phase 2 (
z_features/jax_user_intro.md) shipped and merged.PR 4 (just merged)
ValueErroronxp=np+ jnp-backed-grid mismatch inAbstractMaker.__init__. Helps users discover the@jax.jit + xp=jnppairing rule the first time they break it.All Phase 2 PRs
PointSolver(use_jax=True)+autolens.jax.register_tracer_classesSimulatorImaging(use_jax=True)+ xp-aware Poisson noisevia_tracer_fromauto-default xpvia_galaxies_fromauto-default xpSimulatorInterferometer(use_jax=True)+ xp-aware Gaussian noise + signature symmetry fixvia_tracer_from/via_galaxies_fromauto-default xpvia_galaxies_fromauto-default xpxp=np+ jnp-grid mismatchValueErrorWhat's live now
Known limitation carried forward
@jax.jitwrap ofvia_tracer_fromfor image simulators (SimulatorImaging+SimulatorInterferometer) is blocked byArray2D.nativegoing througharray_2d_via_indexes_from(indexed assignment, not jit-traceable). Eager JAX path works today. The slim/native reshape refactor is a separate task (out of Phase 2 scope).PointSolver(use_jax=True)inside@jax.jitworks because it doesn't go through.native.Outstanding from the JAX user-intro series
Phase 2 is done. Remaining series work:
jax-start-here-intros): workspace prose for top-levelstart_here.py— in active.md, ready to run via/start_workspace.imaging,interferometer,point_source) — prompts authored, ready to/start_dev.data_structures.py,galaxies.py,tracer.py,lens_calc.py) — prompts authored, ready.Design correction surfaced during Phase 2 (already in PR 1 shipped comment): the Phase 0 design doc said "user never imports register_*" — that holds for Analysis modeling but NOT for the JIT-it-yourself path, where
from autolens.jax import register_tracer_classes+ one call is the user-visible setup. Workspace docs (Phase 5d especially) need to reflect this.Closing issue.