Skip to content

atlas_py — The Atmosphere Engine

atlas_py is a pure Python reimplementation of the ATLAS12 stellar atmosphere code. It iterates a model atmosphere to self-consistency, solving hydrostatic equilibrium, the equation of state, opacity, convection, and the temperature-correction equation.

Why ATLAS12 (and not ATLAS9)

The point of reimplementing ATLAS12 specifically — rather than the more widely distributed ATLAS9 — is how opacity is computed.

  • ATLAS9 reads pre-tabulated opacity distribution functions (ODFs) keyed on a small set of bulk-metallicity tags (solar, α-enhanced, scaled by [M/H]). For targets that sit on one of those tags, this is fast and accurate. For off-grid abundance patterns (CEMP, Ap, individual α-elements set independently, r-process enhancements), the ODF would have to be re-tabulated — which most users never do.
  • ATLAS12 does direct opacity sampling: at each iteration, the opacity arrays are evaluated from the actual atomic and molecular line lists at the abundance pattern you supplied. The atmosphere then relaxes around that opacity.

For pykurucz, the practical consequence is that bulk (--mh, --am) and per-element (--abund Z:offset) abundances are handled identically by atlas_py: both are converted into the per-element abundance vector that compute_abundances() writes into the .atm abundance card, and the continuum opacity (kapcont_baseline) and line opacity (linop1 / xlinop) modules in engine/driver.py read those abundances off the layer-by-layer populations on every iteration. Nothing in the iteration code branches on "is this a scaled-solar or peculiar pattern"; to ATLAS, they are the same.

Purpose

Given an input atmosphere (either from the emulator warm-start or an external .atm file), atlas_py computes:

  • Saha–Boltzmann populations for all elements and ionization stages
  • Molecular equilibrium for ~50 molecular species (when MOLECULES ON)
  • Continuous opacity (H⁻, H I, He, metals, scattering)
  • Line opacity via LINOP1 and XLINOP
  • Radiative flux and Rosseland mean opacity
  • Convection and temperature corrections
  • Hydrogen line wings (HLINOP)

The output is a converged .atm file ready for spectrum synthesis.

Key Modules

engine/driver.py

The top-level driver (run_atlas) implements the main iteration loop. Each iteration follows the same eight-step sequence as Fortran ATLAS12:

POPSALL populations KAPCONT cont. opacity LINOP line opacity ROSS Rosseland RADIAP radiative flux TCORR T correction CONVEC convection JOSH RT solve
  1. POPS / POPSALL — Solve Saha–Boltzmann populations. If molecules are enabled, route through NMOLEC.
  2. KAPCONT — Evaluate continuous opacity on a frequency grid.
  3. LINOP / XLINOP — Accumulate line opacity from selected lines (fort.12 / fort.19).
  4. ROSS — Compute Rosseland mean opacity and optical depth scale.
  5. RADIAP — Integrate radiative flux and acceleration.
  6. TCORR — Apply temperature corrections based on flux errors.
  7. CONVEC — Compute convective flux and velocity (if convection is unstable).
  8. JOSH — Solve the transfer equation depth-by-depth for each frequency.

Fortran line references

The driver contains inline comments mapping each step to the corresponding Fortran label or subroutine name (e.g., # atlas12.for line 224). This makes parity debugging straightforward.

physics/*.py

The physics/ directory contains the numerical kernels:

Module Fortran Equivalent Purpose
popsall.py POPSALL Saha–Boltzmann + partition functions
nmolec.py NMOLEC Molecular equilibrium solver
kapcont.py KAPCONT Continuous opacity evaluation
line_opacity.py LINOP1, XLINOP Line-opacity accumulation
ross.py ROSS Rosseland mean opacity
radiap.py RADIAP Radiative flux and acceleration
tcorr.py TCORR Temperature correction
convec.py CONVEC Convective flux and velocity
josh.py JOSH Feautrier-style transfer solver
hydrogen_wings.py HLINOP Hydrogen line-wing opacity
selectlines.py SELECTLINES Line selection from catalogs

io/*.py

  • io/atmosphere.py — Reads and writes Kurucz-format .atm files (DECK6, abundance cards, metadata)
  • io/molecules.py — Parses molecules.new / molecules.dat for NMOLEC
  • io/readin.py — Parses ATLAS12 control decks (stdin input files)

The Iteration Loop in Detail

1. Setup and hydrostatic equilibrium

On the first iteration, the driver loads the input .atm, builds abundance arrays (XABUND), and initializes the runtime state (AtlasRuntimeState). If IFPRES=1 (the default), it integrates hydrostatic equilibrium to update gas pressure:

\[ P(J) = g \cdot \rho_X(J) + P_{\rm rad} + P_{\rm turb} + P_{\rm con} \]

2. Populations

popsall() solves the Saha equation for every element and ionization stage at every depth layer. If enable_molecules=True, nmolec() computes the partial pressures of ~50 molecular species via equilibrium constants and dissociation energies.

3. Opacity

kapcont_baseline() evaluates continuous opacity sources:

  • H⁻ bound-free and free-free
  • H I bound-free (Karsas & Latter cross-sections)
  • He I / He II photoionization
  • Metal photoionization
  • Rayleigh scattering (H, He, H₂)
  • Thomson scattering

For cool atmospheres, the COOLOP path adds CH, OH, and H₂ collisional opacity when molecular populations are present.

4. Line opacity

LINOP1 accumulates atomic line opacity from the selected fort.12 records. XLINOP adds NLTE/fort.19 lines if IFOP(17)=1. Both use Voigt profiles with thermal Doppler, van der Waals, Stark, and radiative broadening.

5. Radiative transfer and temperature correction

The JOSH solver integrates the transfer equation for each frequency, yielding the monochromatic flux HNU. RADIAP accumulates the total radiative flux and acceleration. TCORR compares the integrated flux against the expected Stefan-Boltzmann flux and applies a temperature correction at each layer.

6. Convergence check

After TCORR, the driver computes the maximum normalized change in the physical columns (RHOX, T, P, XNE, ABROSS, VTURB). If this change is below --convergence-epsilon for --convergence-consecutive iterations (and at least --convergence-min-iterations have passed), the loop exits early.

Config System

atlas_py uses three dataclasses defined in config.py:

from atlas_py.config import AtlasConfig, AtlasInput, AtlasOutput

cfg = AtlasConfig(
    inputs=AtlasInput(
        atmosphere_path=Path("warmstart.atm"),
        molecules_path=Path("data/lines/molecules.new"),
        fort11_path=Path("data/lines/gfpred29dec2014.bin"),
        # ... other catalog paths
    ),
    outputs=AtlasOutput(
        output_atm_path=Path("iterated.atm"),
        debug_state_path=Path("debug.npz"),  # optional
    ),
    iterations=30,
    enable_molecules=True,
    convergence_epsilon=1e-3,
)

Debug state output

Pass --debug-state debug.npz to dump the full internal state (populations, opacities, flux arrays) after the final iteration. This is invaluable for depth-by-depth parity comparisons with Fortran.

Next Steps

  • Read about synthe_py, the synthesis engine that consumes atlas_py outputs.
  • Explore the Emulator to see how the warm-start is generated.
  • See Fortran Parity for validation methodology and results.