|
7 | 7 | from autoarray import exc |
8 | 8 | from autoarray.dataset import preprocess |
9 | 9 |
|
| 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 | + |
10 | 62 |
|
11 | 63 | class SimulatorInterferometer: |
12 | 64 | def __init__( |
@@ -115,11 +167,14 @@ def via_image_from(self, image, xp=None): |
115 | 167 | if xp is None: |
116 | 168 | xp = self._xp |
117 | 169 |
|
| 170 | + if xp is not np: |
| 171 | + _register_interferometer_pytrees() |
| 172 | + |
118 | 173 | transformer = self.transformer_class( |
119 | 174 | uv_wavelengths=self.uv_wavelengths, real_space_mask=image.mask |
120 | 175 | ) |
121 | 176 |
|
122 | | - visibilities = transformer.visibilities_from(image=image) |
| 177 | + visibilities = transformer.visibilities_from(image=image, xp=xp) |
123 | 178 |
|
124 | 179 | if self.noise_sigma is not None: |
125 | 180 | visibilities = preprocess.data_with_complex_gaussian_noise_added( |
|
0 commit comments