JAX Transformability¶
Rheedium is a JAX framework, but JAX’s three transforms (grad, vmap,
jit) are not uniformly available across every function. This guide
states exactly what is supported where, so you can structure code without
surprises.
Summary matrix¶
Layer |
|
|
|
|---|---|---|---|
Numerical kernels — form factors, projected potentials, structure factors (fixed input shapes) |
✅ |
✅ |
✅ |
String-mode simulators — |
✅ |
✅ |
✅ (string arg must be static — see below) |
Grid/reflection builders — |
✅ † |
⚠️ |
❌ (data-dependent output shapes) |
Construction factories — |
n/a |
n/a |
❌ (by design: Python validation) |
† grad flows through the grid builders with respect to continuous
parameters (atom positions, temperature, calibrations). The grid
dimensions themselves are treated as concrete Python values, so you cannot
differentiate the output shape — only the values on a fixed grid.
grad and vmap: supported throughout¶
Differentiability is the headline feature and it holds for the parameters you
actually optimize. Gradients flow from a loss through the Ewald/CTR pipeline
back to atom positions, cell parameters, beam energy, and orientation weights
(this is what the rheedium.recon module relies on). vmap works for
parameter sweeps — azimuthal scans, orientation distributions, batched
structures.
jit: three cases¶
1. Fixed-shape kernels — just works¶
Functions whose output shape is fixed by their array inputs jit directly:
import jax
from rheedium.simul import atomic_scattering_factor
f = jax.jit(atomic_scattering_factor)
2. String-mode simulators — make the string static¶
Some simulators select behaviour with a Python str argument
(ctr_mixing_mode, parameterization, profile_type). A traced string can’t
drive a Python if, so the string must be a static argument. Two ways:
import jax, equinox as eqx
from rheedium.simul.simulator import compute_kinematic_intensities_with_ctrs
# Option A: stdlib JAX — name the static argument explicitly.
jitted = jax.jit(
compute_kinematic_intensities_with_ctrs,
static_argnames=("ctr_mixing_mode",),
)
# Option B: equinox (a rheedium dependency) — non-array args are made
# static automatically, so no static_argnames bookkeeping is needed.
jitted = eqx.filter_jit(compute_kinematic_intensities_with_ctrs)
Both produce a fully compiled function. The output shape is set by the input reflection array, so nothing else blocks compilation.
3. Grid / reflection builders — not jittable as written¶
The multislice potential builder sizes its grid from continuous geometry:
nx = int(jnp.ceil(x_extent / pixel_size)) # concretizes a value -> shape
and the Ewald construction keeps a data-dependent number of reflections.
JAX requires static output shapes under jit, so these top-level builders —
sliced_crystal_to_projected_potential_slices, multislice_simulator, and the
reflection-selecting Ewald path — cannot be jit-compiled directly. Run
them eagerly (they are still GPU-accelerated and differentiable w.r.t.
continuous inputs), and jit the fixed-shape kernels they feed. If you need a
fully compiled pipeline, fix the grid dimensions / reflection count up front
(pass them as static integers, or pad to a maximum) so the shapes become
static.
Rule of thumb¶
Optimizing parameters? Use
grad/vmapfreely — supported everywhere.Compiling a hot kernel with fixed array shapes?
jitit directly.Compiling a simulator with a
strmode? Mark the string static (static_argnames=...oreqx.filter_jit).A function that builds a grid or selects reflections from data? Keep it eager, or fix its sizes to make it
jit-able.