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

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, 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:

Continuum opacity is handled by specialized calculators in 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, 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 (specifically within OpartEmisPure._calc_tau), the total optical depth is computed as:

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:

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:

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, 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, 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, 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 using the factor opacity_factor = bar_cgs / m_u and scaling by layer pressure thickness, ensuring physical consistency before summation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →