Skip to content

Commit 71cb762

Browse files
authored
Merge pull request #423 from PyAutoLabs/feature/interferometer-jax-jit
fix: make the interferometer simulator's @jax.jit path work
2 parents e994485 + 4332ec2 commit 71cb762

1 file changed

Lines changed: 56 additions & 1 deletion

File tree

autoarray/dataset/interferometer/simulator.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,58 @@
77
from autoarray import exc
88
from autoarray.dataset import preprocess
99

10+
_INTERFEROMETER_PYTREES_REGISTERED = False
11+
12+
13+
def _register_interferometer_pytrees() -> None:
14+
"""Register ``Interferometer`` so ``jax.jit(via_image_from)`` can flatten its return.
15+
16+
Counterpart of ``_register_imaging_pytrees`` in the imaging simulator (and of
17+
``AnalysisImaging._register_fit_imaging_pytrees``). Without it a jitted
18+
interferometer call raises ``TypeError: ... returned a value of type
19+
Interferometer, which is not a valid JAX type``.
20+
21+
``data`` and ``noise_map`` are the only per-simulation dynamic values.
22+
Everything else rides as aux: ``uv_wavelengths`` and ``real_space_mask`` are
23+
the fixed observation geometry, ``transformer`` and ``grids`` are constants for
24+
a given simulation, the over-sample sizes are static integer geometry, and the
25+
remaining slots are ``None``.
26+
27+
Unlike registration for a jitted function's *arguments* — which must happen
28+
before the first call, since JAX flattens arguments at trace time — this is in
29+
time, because the return value is flattened only after the body has run.
30+
Idempotent via the module-level flag.
31+
"""
32+
global _INTERFEROMETER_PYTREES_REGISTERED
33+
if _INTERFEROMETER_PYTREES_REGISTERED:
34+
return
35+
36+
from autoarray.abstract_ndarray import register_instance_pytree
37+
from autoarray.structures.visibilities import Visibilities
38+
39+
# The two dynamic children must themselves be pytrees, or they surface as
40+
# bare leaves and JAX rejects the return value ("... returned a value of type
41+
# Visibilities ... at output component [0]"). Unlike ``Array2D`` — which the
42+
# imaging path gets auto-registered — these are not registered anywhere else.
43+
register_instance_pytree(Visibilities)
44+
register_instance_pytree(VisibilitiesNoiseMap)
45+
46+
register_instance_pytree(
47+
Interferometer,
48+
no_flatten=(
49+
"uv_wavelengths",
50+
"real_space_mask",
51+
"transformer",
52+
"grids",
53+
"over_sample_size_lp",
54+
"over_sample_size_pixelization",
55+
"noise_covariance_matrix",
56+
"sparse_operator",
57+
),
58+
)
59+
60+
_INTERFEROMETER_PYTREES_REGISTERED = True
61+
1062

1163
class SimulatorInterferometer:
1264
def __init__(
@@ -115,11 +167,14 @@ def via_image_from(self, image, xp=None):
115167
if xp is None:
116168
xp = self._xp
117169

170+
if xp is not np:
171+
_register_interferometer_pytrees()
172+
118173
transformer = self.transformer_class(
119174
uv_wavelengths=self.uv_wavelengths, real_space_mask=image.mask
120175
)
121176

122-
visibilities = transformer.visibilities_from(image=image)
177+
visibilities = transformer.visibilities_from(image=image, xp=xp)
123178

124179
if self.noise_sigma is not None:
125180
visibilities = preprocess.data_with_complex_gaussian_noise_added(

0 commit comments

Comments
 (0)