ExoJAX Opacity Calculation Methods: Premodit, Modit, LPF, and CKD Explained
ExoJAX supports four distinct opacity calculation methods—Premodit, Modit, LPF (Line Profile Function), and CKD (Correlated-K Distribution)—each implemented as a JAX-compatible class in the exojax.opacity module to balance computational speed against spectroscopic accuracy.
The ExoJAX repository provides a modular opacity calculation framework designed for high-performance atmospheric retrieval and forward modeling. All opacity calculators inherit from the abstract base class OpaCalc defined in src/exojax/opacity/base.py, ensuring a consistent API across different algorithmic approaches. Choosing the appropriate opacity calculation method depends on your specific trade-offs between execution speed, memory consumption, and the level of spectral detail required.
Overview of ExoJAX Opacity Calculation Methods
ExoJAX organizes its opacity calculation methods into four concrete implementations, each optimized for specific computational scenarios. The inheritance structure places OpaPremodit, OpaModit, OpaDirect, and OpaCKD as specialized subclasses of OpaCalc.
Each class implements the core methods xsvector(T, P) for single-layer opacity and xsmatrix(Tarr, Parr) for atmospheric column calculations. The primary distinction lies in how each method handles the molecular line database and the convolution of line profiles with the spectral grid.
Premodit: Pre-computed Modified Discrete Integral Transform
Premodit (Pre-computed Modified Discrete Integral Transform) delivers the fastest opacity evaluation by pre-computing grids of line strengths and broadenings before the forward model execution.
Implementation Details
The OpaPremodit class resides in src/exojax/opacity/premodit/api.py. It inherits from OpaCalc and implements the apply_params method to build pre-computed grids once during initialization. The class supports memory optimization through the allow_32bit=True parameter, which reduces GPU memory footprint by using single-precision arithmetic.
Key features include automatic temperature range detection via auto_trange and grid stitching capabilities for handling large spectral ranges that exceed memory constraints.
Performance Characteristics
Premodit achieves sub-millisecond per-spectrum performance by avoiding redundant calculations. The pre-computation step stores modified discrete integral transform matrices for specific temperature-pressure pairs, enabling rapid interpolation during retrieval iterations. This makes Premodit ideal for gradient-based inversions and large-scale retrieval frameworks that require thousands of forward model evaluations.
Premodit Code Example
from exojax.database import MdbExomol
from exojax.opacity import OpaPremodit
import numpy as np
# Initialize wavenumber grid (1000-2000 cm^-1, 0.01 cm^-1 spacing)
nu_grid = np.arange(1000.0, 2000.0, 0.01)
# Load molecular database (ExoMol CO example)
mdb = MdbExomol("/path/to/CO/12C-16O/Li2015", nu_grid)
# Initialize Premodit with automatic temperature range and 32-bit optimization
opa = OpaPremodit(mdb, nu_grid,
allow_32bit=True,
auto_trange=(300.0, 2500.0))
# Compute cross-section at specific T,P
cross_section = opa.xsvector(T=1500.0, P=1.0) # cm^2 per molecule
Modit: On-the-Fly Modified Discrete Integral Transform
Modit (Modified Discrete Integral Transform) computes the DIT-style transform on demand for given temperature and pressure arrays, offering flexibility when pre-computation is impractical.
Implementation Details
The OpaModit class is implemented in src/exojax/opacity/modit/api.py. Unlike Premodit, Modit builds the discrete integral transform matrices during the forward pass using the dit_grid_resolution parameter to control accuracy. The implementation requires transferring the molecular database to GPU memory via mdb.gpu_transfer=True before initialization.
Modit supports open and close aliasing configurations through the set_aliasing method inherited from OpaCalc, allowing fine-tuning of the spectral convolution behavior.
Usage Patterns
Modit excels in scenarios requiring variable temperature-pressure structures between model calls. When you need different Tarr_list and Parr arrays for each forward evaluation, Modit avoids the overhead of rebuilding pre-computed grids while maintaining DIT efficiency. The xsmatrix method returns a 2D array of shape (n_temperatures, n_wavenumbers) suitable for atmospheric layer calculations.
Modit Code Example
from exojax.database import MdbExomol
from exojax.opacity import OpaModit
import numpy as np
# Setup grid and database
nu_grid = np.arange(1000.0, 2000.0, 0.01)
mdb = MdbExomol("/path/to/CO/12C-16O/Li2015", nu_grid)
mdb.gpu_transfer = True # Required for Modit
# Define temperature and pressure arrays
Tarr = np.linspace(300, 2500, 5) # 5 temperature points
Parr = np.full_like(Tarr, 1.0) # 1 bar for all layers
# Initialize Modit with specific grid resolution
opa = OpaModit(mdb, nu_grid,
Tarr_list=Tarr, Parr=Parr,
dit_grid_resolution=0.2)
# Compute cross-section matrix for all T,P pairs
cross_section_matrix = opa.xsmatrix(Tarr, Parr) # Shape: (5, n_nu)
LPF: Line-by-Line Profile Function (OpaDirect)
LPF (Line Profile Function), implemented as OpaDirect, performs brute-force line-by-line calculations using exact Voigt, Lorentz, and Doppler profiles without algorithmic approximation.
Implementation Details
The OpaDirect class is located in src/exojax/opacity/lpf/api.py. It provides the most physically faithful representation of molecular absorption by computing the exact convolution of line profiles with the spectral grid. The implementation utilizes exojax.opacity.lpf.lpf for low-level line profile calculations.
Unlike the DIT-based methods, OpaDirect does not pre-compute or transform line databases; instead, it evaluates the contribution of each line individually at every wavenumber grid point. This approach avoids the aliasing and truncation errors inherent in Fourier-transform methods but at significant computational cost.
When to Use Direct Calculation
Use OpaDirect when you require maximum accuracy for validation purposes or when analyzing narrow spectral windows with complex line blending that might challenge DIT approximations. It serves as the ground-truth benchmark against which Premodit and Modit are validated. However, for models involving millions of spectral lines or requiring thousands of forward evaluations, the computational intensity of LPF makes it impractical for retrieval workflows.
LPF Code Example
from exojax.database import MdbExomol
from exojax.opacity import OpaDirect
import numpy as np
# Setup
nu_grid = np.arange(1000.0, 2000.0, 0.01)
mdb = MdbExomol("/path/to/CO/12C-16O/Li2015", nu_grid)
# Initialize direct line-by-line calculator
opa = OpaDirect(mdb, nu_grid)
# Compute exact cross-section
cross_section = opa.xsvector(T=1500.0, P=1.0)
CKD: Correlated-K Distribution
CKD (Correlated-K Distribution) accelerates radiative transfer calculations by pre-computing k-distribution tables per spectral band, trading spectral resolution for computational speed in broadband applications.
Architecture
The OpaCKD class in src/exojax/opacity/ckd/api.py operates as a wrapper around any of the three base opacity calculators (Premodit, Modit, or LPF). Rather than implementing a separate physics algorithm, CKD reuses a base_opa instance to generate opacity statistics across spectral bands. The method attribute of the resulting object is "ckd", while the underlying physics method is preserved in the base calculator.
CKD divides the spectral range into bands of width band_width (specified in cm⁻¹) and computes Ng quadrature points per band, representing the cumulative distribution function of absorption coefficients within that band.
Pre-computation and Interpolation
The workflow involves two phases. First, the precompute_tables method generates k-distribution tables on a user-defined temperature-pressure grid, saving them to disk (e.g., co_ckd.npz). This step uses the base opacity calculator to compute high-resolution cross-sections and then sorts them into cumulative k-distributions per band.
During forward model execution, xsarray_ckd interpolates these pre-computed tables to the specific temperature and pressure of each atmospheric layer, returning an array of shape (Ng, n_bands) representing the quadrature weights for radiative transfer.
CKD Code Example
from exojax.database import MdbExomol
from exojax.opacity import OpaPremodit, OpaCKD
import numpy as np
# Setup base opacity (Premodit for speed)
nu_grid = np.arange(1000.0, 2000.0, 0.01)
mdb = MdbExomol("/path/to/CO/12C-16O/Li2015", nu_grid)
base_opa = OpaPremodit(mdb, nu_grid, auto_trange=(300.0, 2500.0))
# Initialize CKD wrapper
opa_ckd = OpaCKD(base_opa=base_opa, Ng=32, band_width=50.0)
# Pre-compute tables on T-P grid (do this once)
T_grid = np.linspace(300, 2500, 10)
P_grid = np.logspace(-2, 2, 8) # 0.01 to 100 bar
opa_ckd.precompute_tables(T_grid, P_grid, to_path="co_ckd.npz")
# Later: interpolate for specific conditions
ckd_cross = opa_ckd.xsarray_ckd(T=1500.0, P=1.0) # Shape: (32, n_bands)
Summary
ExoJAX delivers four distinct opacity calculation methods through the exojax.opacity module, each optimized for specific computational constraints:
- Premodit (
OpaPremoditinsrc/exojax/opacity/premodit/api.py) provides the fastest evaluation using pre-computed grids, ideal for retrieval workflows requiring thousands of forward model calls. - Modit (
OpaModitinsrc/exojax/opacity/modit/api.py) offers flexible, on-the-fly DIT calculations for variable temperature-pressure structures without pre-computation overhead. - LPF (
OpaDirectinsrc/exojax/opacity/lpf/api.py) executes exact line-by-line calculations for maximum accuracy when validating approximate methods or analyzing narrow spectral regions. - CKD (
OpaCKDinsrc/exojax/opacity/ckd/api.py) wraps any base opacity calculator to generate k-distribution tables, enabling broadband radiative transfer with orders-of-magnitude speed improvements.
All classes inherit from OpaCalc and expose uniform xsvector and xsmatrix methods, allowing seamless interchangeability within atmospheric modeling pipelines.
Frequently Asked Questions
Which opacity calculation method should I use for atmospheric retrieval?
For gradient-based atmospheric retrieval requiring thousands of forward model evaluations, Premodit is the recommended choice. According to the ExoJAX source code in src/exojax/opacity/premodit/api.py, OpaPremodit achieves sub-millisecond per-spectrum performance by utilizing pre-computed grids of line strengths and broadenings. If your retrieval involves variable temperature-pressure grids that change between iterations, Modit provides the necessary flexibility while maintaining DIT efficiency. Use LPF only for validation or small spectral windows, and CKD when broadband radiative transfer speed outweighs the need for full spectral resolution.
Can I switch between opacity methods without rewriting my model code?
Yes, all four opacity calculation methods inherit from the abstract base class OpaCalc defined in src/exojax/opacity/base.py, ensuring API consistency. Each class implements the standard interface methods xsvector(T, P) for single-layer calculations and xsmatrix(Tarr, Parr) for atmospheric columns. You can instantiate any calculator—OpaPremodit, OpaModit, OpaDirect, or OpaCKD—and pass it to your radiative transfer code without modifying the calling logic. The method attribute ("premodit", "modit", "lpf", or "ckd") allows runtime inspection for automated pipeline configuration.
How does Premodit achieve faster speeds than Modit?
Premodit achieves superior performance by pre-computing and caching the modified discrete integral transform grids during initialization, whereas Modit computes these transforms on-the-fly for each temperature-pressure query. In src/exojax/opacity/premodit/api.py, the OpaPremodit.apply_params method builds reusable grids of line strengths and broadenings across the expected temperature range (auto_trange), allowing subsequent calls to xsvector to perform simple lookups rather than full convolution calculations. Modit, implemented in src/exojax/opacity/modit/api.py, requires Tarr_list and Parr arrays at initialization to build the DIT matrix dynamically, consuming additional computation per instantiation. Premodit's allow_32bit=True option further accelerates GPU operations through single-precision arithmetic.
What is the memory overhead of using CKD tables?
The memory overhead of Correlated-K Distribution (CKD) tables scales with the number of spectral bands, quadrature points per band (Ng), and the temperature-pressure grid resolution. In src/exojax/opacity/ckd/api.py, the OpaCKD.precompute_tables method generates tables storing cumulative k-distributions for each band across the specified T_grid and P_grid. For a typical configuration with 32 quadrature points (Ng=32), 50 cm⁻¹ band widths, and a 10×8 temperature-pressure grid, the stored tables require approximately tens of megabytes—significantly less than the equivalent high-resolution cross-section databases. The to_path parameter allows saving tables to disk (e.g., "co_ckd.npz"), enabling memory-mapped loading via OpaCKD.from_saved_tables to minimize RAM usage during retrieval workflows.
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 →