How ExoJAX Leverages JAX for Automatic Differentiation in Spectral Modeling
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), 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, these weighting functions are defined using JAX primitives, enabling exact gradient computation via jax.grad:
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 and 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 demonstrates a complete forward model where jax.grad computes the gradient of a χ² loss function across all physical parameters without manual derivative code:
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 insrc/exojax/opacity/premodit/premodit.pyandsrc/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 insrc/exojax/postproc/spin_rotation.pyandsrc/exojax/rt/chord.pyto 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:
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:
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.pyusesjax.gradto compute exact temperature derivatives of line-strength weighting functions, supporting both first and second-order derivatives. - Radiative transfer solvers in
src/exojax/rt/trans.pyandsrc/exojax/rt/emis.pyare pure JAX functions that support end-to-end differentiation viajax.grad, validated by integration tests intests/integration/unittests_long/transmission/transmission_grad_test.py. - Performance is optimized through
@jitcompilation for XLA,vmapvectorization over spectral dimensions, andcustom_jvprules for stable gradients in rotation broadening (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 to radiative transfer in 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, 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 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) validates that end-to-end differentiation works correctly across hardware backends.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →