Skip to content

Choosing a differentiation mode

Start with the question you want to answer. If two physical inputs change, and you want to know what happens across a whole field map, use forward mode. If many particle dimensions or positions change, and you want to improve one number such as a scattering cross section, use reverse mode.

Both compute the same first derivatives. They differ in how the work grows: forward mode follows input changes through the calculation, while reverse mode starts with an output of interest and works back to the inputs.

Physical question Result you need Good starting choice
How does every field value change when the common sphere radius or the illumination wavelength changes? Two derivative maps, keeping amplitude and phase Forward mode: jax.jacfwd
Which sphere radii and positions increase the scattering cross section? One derivative for every radius and coordinate Reverse mode: jax.grad or jax.value_and_grad

Two inputs, a whole field map

The field sensitivity example illuminates three spheres and samples their scattered electric field on a 5 × 5 grid. The complete map has 75 complex values: three components at each point. Its two inputs are the common sphere radius and k0, which sets the wavelength.

derivatives = jax.jacfwd(field_map)(parameters)

This returns shape (5, 5, 3, 2). The last axis selects the input parameter; every other axis matches the field map. A matrix or array containing every output derivative with respect to every input is called a Jacobian. Forward mode needs only two input directions here. Reverse mode would need to ask separately about all 150 real and imaginary field values to recover the same information.

If you need only one particular combined change, such as all radii growing by 1 nm while the frequency stays fixed, jax.jvp computes that one direction. It can be cheaper than requesting every derivative. It answers a smaller question, so the timing comparison below asks both modes for the complete Jacobian.

Many inputs, one objective

The cluster gradient example illuminates eight spheres and asks for their total scattering cross section. Each sphere has a radius and three center coordinates: 32 real inputs in all, with one real output.

cross_section, gradient = jax.value_and_grad(scattering)(parameters)

The gradient has the same (8, 4) shape as the parameters. A positive radius entry says that a small radius increase raises the cross section, with the other inputs held fixed. Position entries describe small translations along x, y and z. Reverse mode obtains all these entries from one output; forward mode would follow 32 input directions to produce the same full gradient.

The examples use the ordinary sphere, cluster and plane-wave APIs. JAX is selected by the traced inputs. Framework adapters shows the corresponding forward- and reverse-mode operations for PyTorch, Advect and HIPS Autograd.

Measured examples

These are warm times for the complete calculation and its derivatives, measured on 2026-10-04 on an AMD Ryzen 9 9950X with four CPU threads, Python 3.13 and JAX 0.11.2 in double precision. The native extension used a release build of the unreleased forward-mode implementation, before the subsequent simplification pass. The archived source fingerprints identify that measured version. Each entry is the median of seven samples; both modes return the same complete derivative array.

Calculation Forward mode Reverse mode Faster choice
Three spheres, 5 × 5 field map: 2 inputs → 150 real outputs 3.77 ms 138.58 ms Forward, 36.8×
Eight spheres, one cross section: 32 inputs → 1 output 77.38 ms 4.47 ms Reverse, 17.3×

A second run gave 3.78 vs 138.38 ms for the field map, and 76.91 vs 4.43 ms for the cross section. The derivatives agreed between modes to better than 5e-16 relative error; the independent finite-difference checks agreed to better than 3e-10.

Preparation was separate: tracing and compilation took 0.18 s forward and 0.19 s reverse for the field map, and 0.22 s forward and 0.12 s reverse for the cross section. The warm times include a fresh scattering calculation on every call. Numerical factors are shared within that call, not reused from an earlier call.

The size of each derivative request explains the result: two input directions are much cheaper than 150 output directions, while one output direction is much cheaper than 32 input directions. These numbers describe these JAX workflows on this machine; they are not fixed speedups for every problem or framework. The following script lets you repeat or resize both comparisons. The recorded samples and source fingerprints are in benchmarks/forward-reverse-20261004.json in the repository.

Compare equal results on your machine

After installing the checkout with its JAX extra, run:

uv run --script scripts/benchmark_forward_reverse.py

The script reuses the exact calculations from both examples. For the field map it compares jax.jacfwd with jax.jacrev, representing each complex field value by its real and imaginary parts so that both return the same real array. For the cross section it compares jax.jacfwd with jax.grad. It checks equality of the entire result and checks a joint parameter perturbation against central differences before measuring anything.

For each mode it prints two separate times:

  • Prepare: JAX traces the calculation and compiles it for these array shapes.
  • Warm median: the median time for later calls, including the scattering calculation and its derivatives. The result is ready before the clock stops.

The default uses four CPU threads, seven samples of at least 40 ms each, a 5 × 5 field grid and eight spheres. The two modes alternate within each case. Run on an otherwise idle machine; to fix the CPU cores on Linux, prefix the command with taskset -c 8-11, replacing the core numbers with suitable ones for your machine. The JSON output under benchmarks/results/local/forward-reverse/ records the samples, compilation times, dimensions, accuracy checks, versions and source fingerprints.

Use --check-only to check the two derivative results without measuring time, or --grid-points and --particles to change the sizes. The comparison includes the full derivative arrays; it does not compare a single direction with a full gradient. Actual timing depends on problem size and framework overhead, so use the measured results to choose for your calculation.

All these derivatives hold the mode cutoff, particle count and sampling grid fixed. They are first-order sensitivities at the chosen parameters; the examples also need ordinary physical convergence checks before being used for a design decision.