# How ExoJAX Integrates Spectral Lines and Continua: A Deep Dive into the Opacity Pipeline

> ExoJAX integrates spectral lines and continua by calculating separate cross-sections and absorption coefficients converting them to layer optical depth matrices and summing into a total dtau matrix for radiative transfer.

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

---

**ExoJAX integrates spectral lines and continua by computing separate line-by-line cross-sections and continuum absorption coefficients, converting both to layer optical-depth matrices via helper functions in [`layeropacity.py`](https://github.com/hajimekawahara/exojax/blob/main/layeropacity.py), and summing them element-wise into a total `dtau` matrix used by radiative-transfer solvers.**

ExoJAX is a JAX-based radiative-transfer code for exoplanet atmospheres maintained in the `hajimekawahara/exojax` repository. Understanding how ExoJAX handles the integration of spectral lines and continua is essential for building accurate atmospheric models, as the framework modularizes line and continuum opacity calculations before combining them into a single optical-depth matrix for transmission or emission spectra.

## Modular Opacity Architecture: Lines vs. Continua

ExoJAX employs a strict separation between **line opacity** and **continuum opacity** calculators, allowing users to mix and match physical processes while maintaining a unified interface.

**Line opacity** is provided by three distinct `Opa` classes, each optimized for different computational strategies:

- **`OpaPremodit`** – Pre-computed grids for fast evaluation ([`src/exojax/opacity/premodit/api.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/premodit/api.py))
- **`OpaModit`** – Modified Discrete Integral Transform for on-the-fly calculations ([`src/exojax/opacity/modit/api.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/modit/api.py))
- **`OpaDirect`** – Direct line-by-line profile evaluation ([`src/exojax/opacity/lpf/api.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/lpf/api.py))

**Continuum opacity** is handled by specialized calculators in [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py):

- **`OpaCIA`** – Collision-induced absorption (e.g., H₂-H₂, H₂-He)
- **`OpaHminus`** – H⁻ free-free and bound-free absorption
- **`OpaRayleigh`** – Rayleigh scattering cross-sections
- **`OpaMie`** – Mie scattering parameters for aerosols

Each calculator returns JAX arrays, ensuring automatic differentiation and JIT compilation compatibility throughout the pipeline.

## Computing Line Cross-Sections

Line calculators expose two primary methods for generating opacity. For a single temperature and pressure, **`xsvector()`** returns a cross-section vector; for atmospheric arrays, **`xsmatrix()`** returns a cross-section matrix.

The internal implementation varies by algorithm:

- **Premodit** uses pre-computed grids and interpolation
- **Modit** applies the Modified Discrete Integral Transform on the fly
- **Direct (LPF)** evaluates Voigt profiles directly for each line

All methods return cross-sections in units of cm² molecule⁻¹ on a consistent wavenumber grid.

## Computing Continuum Opacity

Continuum calculators follow a similar interface but return **absorption coefficients** (or scattering cross-sections) rather than line cross-sections:

- **`OpaCIA.logacia_vector()`** / **`logacia_matrix()`** – Returns log₁₀ of the CIA absorption coefficient
- **`OpaHminus.logahminus_matrix()`** – Returns log₁₀ of the H⁻ absorption coefficient
- **`OpaRayleigh.xsvector()`** – Returns Rayleigh scattering cross-sections
- **`OpaMie.mieparams_vector()`** / **`mieparams_matrix()`** – Returns Mie scattering parameters

The continuum values are computed on the same wavenumber grid as the line opacities, enabling direct addition in subsequent steps.

## From Cross-Sections to Layer Optical Depth

The critical integration step occurs in [`src/exojax/rt/layeropacity.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/layeropacity.py), where cross-sections are converted into **layer optical depths** (`dtau`). This conversion accounts for the atmospheric geometry and composition through the following helper functions:

- **`single_layer_optical_depth()`** / **`layer_optical_depth()`** – For line opacities
- **`single_layer_optical_depth_CIA()`** / **`layer_optical_depth_CIA()`** – For CIA continua
- **`single_layer_optical_depth_Hminus()`** / **`layer_optical_depth_Hminus()`** – For H⁻ continuum

The conversion applies the physical scaling:

```

dtau = xs × dP × VMR / (mmw × g) × opacity_factor

```

Where:
- `xs` is the cross-section or absorption coefficient
- `dP` is the layer pressure thickness
- `VMR` is the volume mixing ratio
- `mmw` is the mean molecular weight
- `g` is gravity
- `opacity_factor = bar_cgs / m_u` ensures consistent cgs units

## Summing Line and Continuum Contributions in Radiative Transfer

The final integration happens inside the radiative-transfer solvers (`ArtEmisPure`, `ArtTransPure`, etc.) where the optical-depth matrices are summed element-wise. According to the source code in [`src/exojax/rt/emis.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/emis.py) (specifically within `OpartEmisPure._calc_tau`), the total optical depth is computed as:

```python
dtau = dtau_line + dtau_continuum

```

Because both components share identical wavenumber grids and layer structures, this addition is a simple JAX array operation. The combined `dtau` matrix then feeds into the Beer-Lambert law for transmission spectra or source-function integration for emission spectra.

## Code Example: Transmission Spectrum with CIA Continuum

This example demonstrates computing a transmission spectrum combining CO line opacity with H₂-H₂ collision-induced absorption:

```python
import numpy as np
from exojax.spec import molname
from exojax.database import MdbExomol
from exojax.opacity import OpaPremodit, OpaCIA
from exojax.rt import ArtTransPure
from exojax.utils.constants import bar_cgs

# 1️⃣ Load a molecular line list (CO as an example)

mdb = MdbExomol("CO/12C-16O/Li2015", nu_grid=np.linspace(2000, 4000, 5000))

# 2️⃣ Initialise line‑opacity calculator (Premodit)

opa_line = OpaPremodit(mdb, nu_grid=mdb.nu_grid, auto_trange=[500, 2000])

# 3️⃣ Initialise CIA continuum (H2‑H2)

from exojax.database import CIA
cdb = CIA("H2-H2")
opa_cont = OpaCIA(cdb=cdb, nu_grid=mdb.nu_grid)

# 4️⃣ Build temperature‑pressure profile

T_arr = np.full(50, 1500.0)      # K

P_arr = np.logspace(-6, 2, 50)   # bar

dP = np.diff(np.append(P_arr, P_arr[-1] * 1.1))

# 5️⃣ Compute line and continuum optical‑depth matrices

dtau_line = opa_line.xsmatrix(T_arr, P_arr) * dP[:, None]
dtau_cia  = opa_cont.logacia_matrix(T_arr) * dP[:, None] / (bar_cgs)

# 6️⃣ Total optical depth

dtau = dtau_line + dtau_cia

# 7️⃣ Radiative transfer (pure transmission)

art = ArtTransPure(dtau, opa_line.nu_grid)
flux = art.run()

```

The code loads line opacities via `OpaPremodit` and CIA continua via `OpaCIA`, computes layer optical depths using pressure-thickness scaling, sums them into `dtau`, and passes the result to `ArtTransPure`.

## Code Example: Emission Spectrum with H-minus Continuum

This example adds H⁻ continuum to an emission calculation:

```python
from exojax.database import Hminus
from exojax.opacity import OpaHminus
from exojax.rt import ArtEmisPure

# H‑minus continuum calculator

opa_hminus = OpaHminus(nu_grid=mdb.nu_grid)

# electron and H‑atom mixing ratios

vmre = 1e-4 * np.ones_like(T_arr)
vmrh = 1e-2 * np.ones_like(T_arr)

# H‑minus optical depth (single‑layer version)

dtau_hminus = opa_hminus.single_layer_optical_depth_Hminus(
    nu_grid=mdb.nu_grid,
    temperature=T_arr[0],
    pressure=P_arr[0],
    dpressure=0.01*P_arr[0],
    vmre=vmre[0],
    vmrh=vmrh[0],
    mmw=2.33,
    g=1e3
)

# Add to line optical depth and run emission radiative transfer

dtau_total = dtau_line + dtau_hminus[None, :]
art_em = ArtEmisPure(dtau_total, mdb.nu_grid)
spectrum = art_em.run()

```

The `OpaHminus` class provides `single_layer_optical_depth_Hminus` and `layer_optical_depth_Hminus` methods that internally call `log_hminus_continuum` and apply the same pressure-thickness scaling as line calculators.

## Summary

- **ExoJAX modularizes opacity calculations** by separating line calculators (`OpaPremodit`, `OpaModit`, `OpaDirect`) from continuum calculators (`OpaCIA`, `OpaHminus`, `OpaRayleigh`, `OpaMie`).
- **Layer optical depth conversion** occurs in [`src/exojax/rt/layeropacity.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/rt/layeropacity.py), where cross-sections are scaled by pressure thickness, mixing ratios, and gravity to produce `dtau` matrices.
- **Integration is element-wise addition** of line (`dtau_line`) and continuum (`dtau_continuum`) optical depths in radiative-transfer solvers like `ArtEmisPure` and `ArtTransPure`.
- **All operations use JAX arrays**, enabling automatic differentiation and GPU acceleration while maintaining physical consistency across wavenumber grids.

## Frequently Asked Questions

### How does ExoJAX combine line and continuum opacity?

ExoJAX computes line and continuum opacities separately through dedicated calculator classes, converts both to optical-depth matrices using helper functions in [`layeropacity.py`](https://github.com/hajimekawahara/exojax/blob/main/layeropacity.py), and sums them via simple element-wise addition (`dtau = dtau_line + dtau_continuum`) in the radiative-transfer solvers.

### What continuum sources does ExoJAX support?

According to [`src/exojax/opacity/opacont.py`](https://github.com/hajimekawahara/exojax/blob/main/src/exojax/opacity/opacont.py), ExoJAX supports collision-induced absorption via `OpaCIA`, H⁻ free-free and bound-free absorption via `OpaHminus`, Rayleigh scattering via `OpaRayleigh`, and Mie scattering for aerosols via `OpaMie`.

### Why use separate calculators for lines and continua?

The modular design allows users to add new continuum sources without modifying line-opacity code, enables JAX-first differentiation through consistent array interfaces, and supports memory-efficient stitching algorithms (like Overlap-and-Add in Premodit) that operate independently on line grids before continuum addition.

### How are units handled when mixing line and continua opacities?

Line cross-sections (cm² molecule⁻¹) and continuum absorption coefficients (cm⁻¹ amagat⁻² for CIA, cm⁻¹ for H⁻) are both converted to unitless optical-depth values in [`layeropacity.py`](https://github.com/hajimekawahara/exojax/blob/main/layeropacity.py) using the factor `opacity_factor = bar_cgs / m_u` and scaling by layer pressure thickness, ensuring physical consistency before summation.