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:
OpaPremodit– Pre-computed grids for fast evaluation (src/exojax/opacity/premodit/api.py)OpaModit– Modified Discrete Integral Transform for on-the-fly calculations (src/exojax/opacity/modit/api.py)OpaDirect– Direct line-by-line profile evaluation (src/exojax/opacity/lpf/api.py)
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 absorptionOpaRayleigh– Rayleigh scattering cross-sectionsOpaMie– 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 coefficientOpaHminus.logahminus_matrix()– Returns log₁₀ of the H⁻ absorption coefficientOpaRayleigh.xsvector()– Returns Rayleigh scattering cross-sectionsOpaMie.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 opacitiessingle_layer_optical_depth_CIA()/layer_optical_depth_CIA()– For CIA continuasingle_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:
xsis the cross-section or absorption coefficientdPis the layer pressure thicknessVMRis the volume mixing ratiommwis the mean molecular weightgis gravityopacity_factor = bar_cgs / m_uensures 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 producedtaumatrices. - Integration is element-wise addition of line (
dtau_line) and continuum (dtau_continuum) optical depths in radiative-transfer solvers likeArtEmisPureandArtTransPure. - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →