@@ -29,6 +29,71 @@ class NUFFTPlaceholder:
2929 _nufftax = None
3030
3131
32+ def _patch_nufftax_batchers ():
33+ """Rank-guard nufftax's vmap batching rules (shim for nufftax <0.7).
34+
35+ nufftax's batching fast path re-binds a primitive unchanged whenever only
36+ the source argument is vmapped, assuming the impl's single native batch
37+ axis can absorb it. Under nested batching — e.g. `jax.vmap(jax.value_and_grad(...))`
38+ over a fit whose `transform_mapping_matrix` already passes a batched stack —
39+ the dims accumulate past what the impl accepts and tracing crashes
40+ ("too many values to unpack"). Until that is fixed upstream
41+ (github.com/GragasLab/nufftax), re-register every primitive's batcher with
42+ the missing guard: within native rank, bind as before; beyond it, collapse
43+ the stacked batch axes into the one native axis and unflatten the output.
44+ """
45+ import jax
46+ from jax .interpreters import batching
47+
48+ P = _nufftax .transforms .primitives
49+
50+ # (primitive, impl, index of the source argument, unbatched source rank)
51+ table = [
52+ (P .nufft1d1_p , P ._impl_1d1 , 1 , 1 ),
53+ (P .nufft1d2_p , P ._impl_1d2 , 1 , 1 ),
54+ (P .nufft2d1_p , P ._impl_2d1 , 2 , 1 ),
55+ (P .nufft2d2_p , P ._impl_2d2 , 2 , 2 ),
56+ (P .nufft3d1_p , P ._impl_3d1 , 3 , 1 ),
57+ (P .nufft3d2_p , P ._impl_3d2 , 3 , 3 ),
58+ (P .nufft1d3_p , P ._impl_1d3 , 1 , 1 ),
59+ (P .nufft2d3_p , P ._impl_2d3 , 2 , 1 ),
60+ (P .nufft3d3_p , P ._impl_3d3 , 3 , 1 ),
61+ ]
62+
63+ def guarded_batcher (prim , impl_fn , source_idx , base_rank ):
64+ def batcher (args , dims , ** kwargs ):
65+ batched = [i for i , d in enumerate (dims ) if d is not None ]
66+ if batched == [source_idx ] and dims [source_idx ] == 0 :
67+ src = args [source_idx ]
68+ extra = src .ndim - (base_rank + 1 )
69+ if extra <= 0 :
70+ return prim .bind (* args , ** kwargs ), 0
71+ lead = src .shape [: extra + 1 ]
72+ flat = src .reshape ((- 1 ,) + src .shape [extra + 1 :])
73+ new_args = list (args )
74+ new_args [source_idx ] = flat
75+ out = prim .bind (* new_args , ** kwargs )
76+ return out .reshape (lead + out .shape [1 :]), 0
77+ out = jax .vmap (lambda * a : impl_fn (* a , ** kwargs ), in_axes = tuple (dims ))(
78+ * args
79+ )
80+ return out , 0
81+
82+ return batcher
83+
84+ for prim , impl , source_idx , base_rank in table :
85+ batching .primitive_batchers [prim ] = guarded_batcher (
86+ prim , impl , source_idx , base_rank
87+ )
88+
89+
90+ if _nufftax is not None :
91+ _version = tuple (int (v ) for v in _nufftax .__version__ .split ("." )[:2 ])
92+ # Only the 0.6.x series both has the primitives module and needs the shim.
93+ if (0 , 6 ) <= _version < (0 , 7 ):
94+ _patch_nufftax_batchers ()
95+
96+
3297def pynufft_exception ():
3398 raise ModuleNotFoundError (
3499 "\n --------------------\n "
0 commit comments