# How ExoJAX Leverages JAX for Automatic Differentiation in Spectral Modeling

> Explore how ExoJAX uses JAX transformations like grad, jit, and vmap for automatic differentiation in spectral modeling. Optimize radiative transfer models with exact derivatives.

- Repository: [Hajime Kawahara/exojax](https://github.com/hajimekawahara/exojax)
- Tags: deep-dive
- Published: 2026-03-03

---

**ExoJAX uses JAX's `grad`, `jit`, and `vmap` transformations to compute exact derivatives of radiative transfer models through every stage—from molecular line strengths to transmission spectra—enabling gradient-based optimization without manual differentiation.**

ExoJAX is an open-source toolkit for exoplanet atmospheric analysis built entirely on JAX. By representing atmospheric quantities as JAX arrays and implementing radiative transfer as pure functions, the library achieves **automatic differentiation in spectral modeling** across the entire forward model pipeline, from molecular databases to instrumental response.

## JAX-Native Architecture for End-to-End Differentiability

All modules import `jax.numpy as jnp` (e.g., [`src/exojax/utils/photometry.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/utils/photometry.py)), ensuring temperature profiles, opacity matrices, and pressure grids remain immutable JAX arrays. This functional design makes every component eligible for JAX transformations, allowing gradients to propagate through line lists, cross-section calculations, and radiative transfer solvers.

### Immutable Arrays and Pure Functions

ExoJAX stores all state vectors—atmospheric temperature-pressure profiles, molecular abundances, and wavenumber grids—as JAX arrays rather than standard NumPy arrays. This architectural choice treats radiative transfer calculations as pure mathematical functions, satisfying JAX's requirements for automatic differentiation and just-in-time compilation while maintaining compatibility with GPU and TPU accelerators.

## Differentiable Line-Strength Calculations with Pre-MODIT

The **Pre-MODIT** opacity method requires temperature derivatives of line-strength weighting functions to build Taylor-expanded approximations. In [`src/exojax/opacity/premodit/lbderror.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/lbderror.py), these weighting functions are defined using JAX primitives, enabling exact gradient computation via `jax.grad`:

```python
from jax import grad
from exojax.opacity.premodit.lbderror import weight_point1_dE, weight_point2_dE

dfw1 = grad(weight_point1_dE, argnums=0)   # ∂w₁/∂T

dfw2 = grad(weight_point2_dE, argnums=0)   # ∂w₂/∂T

```

Higher-order derivatives are constructed by nesting `grad` calls (`ddfw1 = grad(dfw1, argnums=0)`), which are used internally by `single_tilde_line_strength_first` and `single_tilde_line_strength_second`. Because these functions contain only pure JAX operations, the gradients are computed analytically and JIT-compiled for execution on accelerators.

## End-to-End Differentiable Radiative Transfer

The **transmission** and **emission** solvers in [`src/exojax/rt/trans.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/trans.py) and [`src/exojax/rt/emis.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/emis.py) implement pure JAX functions that operate on opacity tensors (`dtau_ckd`) and geometry vectors. Decorated with `@jit`, these solvers can be differentiated with respect to temperature profiles, molecular abundances, gravity, radius, and radial velocity.

The integration test in [`tests/integration/unittests_long/transmission/transmission_grad_test.py`](https://github.com/hajimekawahara/exojax/blob/main/tests/integration/unittests_long/transmission/transmission_grad_test.py) demonstrates a complete forward model where `jax.grad` computes the gradient of a χ² loss function across all physical parameters without manual derivative code:

```python
def model(params):
    mmr_CO, mu_fid, T_fid, gravity_btm, radius_btm, RV = params
    Tarr = T_fid * np.ones_like(art.pressure)
    mmr_arr = art.constant_profile(mmr_CO)
    mmw = mu_fid * jnp.ones_like(art.pressure)
    gravity = art.gravity_profile(Tarr, mmw, radius_btm, gravity_btm)
    xsmatrix = opa.xsmatrix(Tarr, art.pressure)
    dtau = art.opacity_profile_xs(xsmatrix, mmr_arr, opa.mdb.molmass, gravity)
    Rp2 = art.run(dtau, Tarr, mmw, radius_btm, gravity_btm)
    Rp2_sample = sop_inst.sampling(Rp2, RV, inst_nus)
    return jnp.sqrt(Rp2_sample)

def objective(params):
    return jnp.sum((np.array(rprs[::-1]) - model(params)) ** 2)

grad = jax.grad(objective)                 # ← automatic differentiation

gradient = grad(params)

```

All sub-routines—including `OpaPremodit`, `ArtTransPure`, and the opacity grid—are JAX-compatible, enabling gradient computation through the entire atmospheric retrieval chain.

## Performance Optimizations via JAX Transformations

ExoJAX applies several JAX transformations to accelerate gradient computations and enable hardware acceleration:

- **`@jit`** — Applied to heavy computational kernels in [`src/exojax/opacity/premodit/premodit.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/premodit.py) and [`src/exojax/rt/rtransfer.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/rtransfer.py), compiling line-strength scans and radiative transfer loops to XLA for CPU, GPU, or TPU execution.
- **`vmap`** — Vectorizes operations over wavenumbers, atmospheric layers, and spectral lines, transforming Python loops into batch matrix operations that execute in parallel.
- **`custom_jvp`** — Used in [`src/exojax/postproc/spin_rotation.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/postproc/spin_rotation.py) and [`src/exojax/rt/chord.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/chord.py) to provide analytically stable Jacobian-vector products for rotation broadening and chord geometry, ensuring numerically robust gradients during optimization.

These transformations allow ExoJAX to evaluate forward models and their gradients orders of magnitude faster than pure NumPy implementations or finite-difference approximations.

## Practical Implementation Examples

### Computing Gradients of Transmission Spectra

The following example demonstrates how to compute the gradient of a transmission spectrum with respect to temperature and CO abundance using `jax.grad`:

```python
import jax, jax.numpy as jnp
from exojax.opacity import OpaPremodit
from exojax.rt import ArtTransPure
from exojax.database.api import MdbHitran
from exojax.utils.grids import wavenumber_grid

# ---- set up a minimal atmosphere ---------------------------------

nu_grid, _, _ = wavenumber_grid(23000., 25000., 2000, unit="AA", xsmode="premodit")
art = ArtTransPure(pressure_top=1e-15, pressure_btm=1e1, nlayer=50)
art.change_temperature_range(500., 600.)

mdb = MdbHitran("CO", nu_grid, gpu_transfer=True, isotope=1)
opa = OpaPremodit(mdb=mdb, nu_grid=nu_grid, auto_trange=[500., 600.])

# ---- define a simple loss (difference to a mock observation) -----

def loss(params):
    Tref, mmr = params
    Tarr = Tref * jnp.ones_like(art.pressure)
    mmr_arr = art.constant_profile(mmr)
    xsm = opa.xsmatrix(Tarr, art.pressure)
    dtau = art.opacity_profile_xs(xsm, mmr_arr, opa.mdb.molmass, art.gravity_profile(Tarr, mmr_arr, 1.0, 1.0))
    Rp2 = art.run(dtau, Tarr, mmr_arr, 1.0, 1.0)
    return jnp.mean(Rp2)                     # any scalar observable

# gradient w.r.t. (Tref, mmr) → automatic differentiation

grad_fn = jax.grad(loss)
print(grad_fn(jnp.array([550.0, 1e-4])))

```

The `grad_fn` call traverses the entire pipeline—line-strength calculations, opacity interpolation, radiative transfer, and geometry—thanks to JAX's automatic differentiation engine.

### Higher-Order Derivatives for Line Strengths

For applications requiring curvature information, ExoJAX computes higher-order derivatives by nesting gradient calls:

```python
from exojax.opacity.premodit.lbderror import weight_point1_dE, weight_point2_dE
from jax import grad

# First derivative w.r.t. temperature

dw1_dt = grad(weight_point1_dE, argnums=0)
dw2_dt = grad(weight_point2_dE, argnums=0)

# Second derivative

d2w1_dt2 = grad(dw1_dt, argnums=0)
d2w2_dt2 = grad(dw2_dt, argnums=0)

```

These derivatives power the Taylor-expanded line-strength approximations used in the Pre-MODIT algorithm for rapid cross-section evaluation.

## Summary

- ExoJAX represents all atmospheric data as JAX arrays (`jax.numpy`) to enable **automatic differentiation** throughout the modeling pipeline, from molecular databases to instrumental convolution.
- The Pre-MODIT opacity engine in [`src/exojax/opacity/premodit/lbderror.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/lbderror.py) uses `jax.grad` to compute exact temperature derivatives of line-strength weighting functions, supporting both first and second-order derivatives.
- Radiative transfer solvers in [`src/exojax/rt/trans.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/trans.py) and [`src/exojax/rt/emis.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/emis.py) are pure JAX functions that support end-to-end differentiation via `jax.grad`, validated by integration tests in [`tests/integration/unittests_long/transmission/transmission_grad_test.py`](https://github.com/hajimekawahara/exojax/blob/main/tests/integration/unittests_long/transmission/transmission_grad_test.py).
- Performance is optimized through `@jit` compilation for XLA, `vmap` vectorization over spectral dimensions, and `custom_jvp` rules for stable gradients in rotation broadening ([`src/exojax/postproc/spin_rotation.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/postproc/spin_rotation.py)).
- The library enables gradient-based atmospheric retrievals without requiring users to write manual derivative code for complex radiative transfer physics.

## Frequently Asked Questions

### What makes ExoJAX differentiable compared to traditional radiative transfer codes?

Traditional codes rely on finite-difference approximations or manually derived Jacobians that must be updated when physics changes. ExoJAX implements the entire forward model—from molecular spectroscopy in [`src/exojax/opacity/premodit/premodit.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/premodit.py) to radiative transfer in [`src/exojax/rt/rtransfer.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/rtransfer.py)—as pure JAX functions. This allows `jax.grad` to automatically compute exact derivatives through all operations using automatic differentiation, eliminating manual derivative maintenance while achieving accelerator-native performance.

### Can ExoJAX compute higher-order derivatives for atmospheric retrievals?

Yes. JAX supports nested gradient calls, enabling arbitrary-order differentiation. In [`src/exojax/opacity/premodit/lbderror.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/lbderror.py), second-order temperature derivatives of weighting functions are computed by applying `grad` twice (`ddfw1 = grad(dfw1, argnums=0)`). These higher-order derivatives support Taylor-expanded line-strength approximations and can be used to implement second-order optimization methods like Newton-Raphson for atmospheric retrieval.

### How does ExoJAX handle differentiation through rotation broadening?

The library uses `custom_jvp` (custom Jacobian-vector products) in [`src/exojax/postproc/spin_rotation.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/postproc/spin_rotation.py) to define stable derivatives for rotational convolution operations. This custom rule ensures that gradients remain numerically stable when fitting planetary or stellar rotation velocities, preventing the discontinuities or numerical artifacts that can occur with automatic differentiation through brute-force convolution implementations.

### Is the automatic differentiation in ExoJAX compatible with GPU acceleration?

Yes. Because ExoJAX uses standard JAX transformations, the same code executes on CPUs, GPUs, and TPUs without modification. The `jax.grad` calls compile through XLA alongside the forward model, allowing gradient computations to leverage GPU parallelism. The transmission gradient test ([`tests/integration/unittests_long/transmission/transmission_grad_test.py`](https://github.com/hajimekawahara/exojax/blob/main/tests/integration/unittests_long/transmission/transmission_grad_test.py)) validates that end-to-end differentiation works correctly across hardware backends.