# How ExoJAX Opacity Calculators Handle Voigt Profiles, CIA, Mie, and Rayleigh Scattering

> Explore how ExoJAX opacity calculators leverage a modular framework for Voigt profiles, CIA, Rayleigh, and Mie scattering. Achieve differentiable, GPU-accelerated radiative transfer.

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

---

**ExoJAX implements a modular opacity framework where Voigt profiles, Collision-Induced Absorption (CIA), Rayleigh scattering, and Mie scattering each inherit from specialized base classes (`OpaCalc` for lines, `OpaCont` for continua) to enable differentiable, GPU-accelerated radiative transfer calculations.**

ExoJAX is a JAX-based radiative transfer code designed for exoplanet atmospheric retrievals. Its **opacity calculators** (opa) provide a unified interface for computing wavelength-dependent absorption and scattering cross-sections. The architecture separates line-by-line opacity—handled by `OpaDirect`, `OpaModit`, and `OpaPremodit`—from continuum processes managed via `OpaCont` subclasses, ensuring that Voigt broadening, CIA, and scattering physics can be combined seamlessly within automatic differentiation pipelines.

## Voigt Profile Implementation in ExoJAX Opacity Calculators

Line opacity in ExoJAX relies on the **Voigt profile**—the convolution of Doppler (Gaussian) and Lorentzian (pressure) broadening. The implementation spans low-level kernel functions and high-level calculator classes.

### The Folded Voigt Kernel (ditkernel.py)

The core mathematical operation resides in [`src/exojax/opacity/_common/ditkernel.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/_common/ditkernel.py). The function **`fold_voigt_kernel_logst`** (and its linear-grid variant `fold_voigt_kernel`) pre-computes the convolution of Gaussian and Lorentzian kernels on a uniform wavenumber grid. This "folded" approach allows the DIT (Direct Integral Transform) algorithm to evaluate millions of lines simultaneously via JAX’s vectorized operations.

### Line-by-Line Calculators (OpaDirect, OpaModit, OpaPremodit)

ExoJAX provides three line-by-line opacity calculators that wrap the Voigt kernel:

- **`OpaDirect`** ([`src/exojax/opacity/lpf/api.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/lpf/api.py)): Calls the vectorized **`voigt`** routine from [`src/exojax/opacity/lpf/lpf.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/lpf/lpf.py) directly. Suitable for small line lists or when memory is constrained.
- **`OpaModit`** ([`src/exojax/opacity/modit/api.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/modit/api.py)): Uses the Modit algorithm to pre-compute transform matrices for the Voigt profile, accelerating repeated calculations.
- **`OpaPremodit`** ([`src/exojax/opacity/premodit/api.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/api.py)): A pre-computed Modit variant that caches the DIT kernel for maximum GPU efficiency during retrievals.

All three classes inherit from **`OpaCalc`** ([`src/exojax/opacity/base.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/base.py)) and expose the **`xsmatrix`** method, which returns the cross-section matrix for a given temperature, pressure, and mixing ratio.

## Continuum Opacity Handling: CIA, Rayleigh, and Mie Scattering

Continuum processes—Collision-Induced Absorption (CIA), Rayleigh scattering, and Mie scattering—are implemented as subclasses of **`OpaCont`** in [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py). This design ensures continuum opacities share a uniform API with line opacities while implementing distinct physics.

### Collision-Induced Absorption (OpaCIA)

The **`OpaCIA`** class handles CIA, where transient dipoles form during molecular collisions (e.g., H₂–H₂, H₂–He). The implementation:

1. Reads tabulated CIA coefficients `σ(ν,T)` from the ExoMol-CIA database.
2. Interpolates the coefficients to the model temperature using `jnp.interp`.
3. Multiplies by the product of the colliding species densities (`n_i × n_j`) to yield the absorption cross-section.

This occurs in [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py), where `OpaCIA` implements the **`xsmatrix`** method consistent with line calculators.

### Rayleigh Scattering (OpaRayleigh)

**`OpaRayleigh`** computes the wavelength-dependent cross-section for Rayleigh scattering, which follows the analytic λ⁻⁴ dependence. The class:

1. Retrieves the molecule’s polarizability and refractive index (e.g., for H₂, He).
2. Evaluates the **`rayleigh_cross_section`** routine in [`opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/opacont.py), implementing the standard formula where cross-section scales with `λ⁻⁴`.
3. Returns the cross-section matrix via `xsmatrix`, scaled by the species number density.

### Mie Scattering (OpaMie)

For aerosol and cloud opacity, **`OpaMie`** handles Mie scattering from spherical particles. The implementation:

1. Loads pre-computed Mie tables (size distribution, complex refractive index) for compositions like MgSiO₃ or TiO₂.
2. Interpolates the extinction efficiency, scattering efficiency, and asymmetry parameters onto the model’s wavenumber grid based on the specified particle size distribution.
3. Scales the opacity by the particle number density to yield the final Mie cross-sections.

The `OpaMie` class resides in [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py) and, like other continuum classes, provides the `xsmatrix` interface for seamless integration with radiative transfer solvers.

## Practical Example: Combining Opacity Components

The following example demonstrates how to instantiate and combine ExoJAX opacity calculators for a complete atmospheric model:

```python
import jax.numpy as jnp
from exojax.opacity import OpaPremodit, OpaCIA, OpaRayleigh, OpaMie
from exojax.utils import molname

# 1. Line opacity: Voigt-profiled molecular lines (e.g., H2O from ExoMol)

mdb = molname.read_exomol("H2O")          # Returns MdbExomol object

opa_line = OpaPremodit(mdb)               # Fast pre-computed Modit for Voigt profiles

# 2. Continuum opacities

opa_cia = OpaCIA("H2-H2")                 # Collision-Induced Absorption

opa_ray = OpaRayleigh("H2")                 # Rayleigh scattering

opa_mie = OpaMie("MgSiO3", particle_radius=0.1)   # Mie scattering (particle radius in μm)

# 3. Define atmospheric state

pressure = jnp.logspace(5, 2, 50)           # Pressure grid (Pa)

temperature = 1500.0 * jnp.ones_like(pressure)
vmr = 0.01 * jnp.ones_like(pressure)       # Volume mixing ratio of H2O

# 4. Compute cross-sections

xs_line = opa_line.xsmatrix(temperature, pressure, vmr)
xs_cia = opa_cia.xsmatrix(temperature, pressure)
xs_ray = opa_ray.xsmatrix(temperature, pressure)
xs_mie = opa_mie.xsmatrix(temperature, pressure)

# 5. Total opacity (sum of all contributions)

xs_total = xs_line + xs_cia + xs_ray + xs_mie

```

All classes use the same `xsmatrix` method, enabling transparent summation of line and continuum contributions. The Voigt profile computation is encapsulated within `OpaPremodit` (or `OpaDirect`/`OpaModit`) and relies on the folded kernel described above.

## Summary

- **Voigt profiles** in ExoJAX are computed via the **`fold_voigt_kernel_logst`** function in [`src/exojax/opacity/_common/ditkernel.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/_common/ditkernel.py), wrapped by line calculators (`OpaDirect`, `OpaModit`, `OpaPremodit`) that inherit from `OpaCalc`.
- **CIA**, **Rayleigh**, and **Mie** scattering are implemented as subclasses of **`OpaCont`** in [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py), specifically `OpaCIA`, `OpaRayleigh`, and `OpaMie`.
- All opacity calculators expose a consistent **`xsmatrix`** interface, enabling seamless combination of line and continuum opacities within JAX-autodifferentiable radiative transfer models.
- The architecture separates low-level kernel physics (DIT/Modit algorithms) from high-level opacity classes, optimizing for both GPU acceleration and code clarity.

## Frequently Asked Questions

### What is the difference between OpaModit and OpaPremodit?

**`OpaModit`** computes the Modit (Modified Discrete Integral Transform) transform matrices on-the-fly during the first call, making it memory-efficient but slightly slower for repeated evaluations. **`OpaPremodit`** pre-computes and caches these transform matrices during initialization, consuming more memory but delivering maximum GPU acceleration during retrievals. Both use the same underlying Voigt kernel in [`ditkernel.py`](https://github.com/hajimekawahara/exojax/blob/main/ditkernel.py), but `OpaPremodit` is preferred for atmospheric retrieval workflows where the line list is fixed.

### How does ExoJAX optimize Voigt profile calculations for GPU acceleration?

ExoJAX leverages **JAX’s just-in-time compilation** (`@jit`) and vectorization to evaluate millions of spectral lines simultaneously. The `fold_voigt_kernel_logst` function in [`src/exojax/opacity/_common/ditkernel.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/_common/ditkernel.py) folds the Doppler and Lorentzian profiles into a single kernel operation that runs efficiently on GPU. Additionally, the `OpaPremodit` class pre-computes interpolation weights, reducing the computational graph depth during the forward pass of radiative transfer models.

### Can I combine multiple CIA sources in a single opacity calculation?

Yes. Because each CIA source (e.g., H₂–H₂, H₂–He) is instantiated as a separate `OpaCIA` object, you can sum their contributions just like any other opacity component. For example: `xs_total = opa_cia_h2h2.xsmatrix(T, P) + opa_cia_h2he.xsmatrix(T, P)`. Each `OpaCIA` instance reads its own tabulated coefficients from the ExoMol-CIA database and handles temperature interpolation independently, allowing you to build complex atmospheric models with multiple collision partners.

### Where are the Mie scattering tables stored and how are they interpolated?

Mie scattering tables in ExoJAX are stored as pre-computed grids of extinction efficiency, scattering efficiency, and asymmetry parameters as functions of size parameter and complex refractive index. The `OpaMie` class in [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py) loads these tables (typically for compositions like MgSiO₃ or TiO₂) and uses JAX’s interpolation functions to map them onto the model’s wavenumber grid based on the specified particle size distribution. The interpolated values are then scaled by particle number density to yield the final Mie opacity cross-sections.