synthe_py — The Spectrum Synthesis Engine¶
synthe_py is the heart of pykurucz: a pure Python reimplementation of Kurucz's SYNTHE code that computes emergent stellar spectra wavelength by wavelength. Given a model atmosphere and a line list, it evaluates continuum opacity, accumulates line opacity from ~1.3 million atomic lines and molecular transitions, and solves the radiative transfer equation.
Purpose¶
The synthesis engine takes a preprocessed atmosphere (.npz) and produces a .spec file containing:
- Wavelength (nm, vacuum)
- Total emergent flux \(F_\lambda\) (line + continuum)
- Continuum-only flux \(F_{\rm cont}\)
It is agnostic about the origin of the atmosphere — whether from atlas_py, MARCS, PHOENIX, or any other source — as long as the input follows the Kurucz .atm / .npz conventions.
Key Modules¶
engine/opacity.py¶
This is the main synthesis driver (run_synthesis). It implements:
- Wavelength grid generation — geometric spacing based on resolving power
- Atmosphere loading — prefers
.npzcaches, auto-refreshes stale versions - Line list compilation — reads atomic and molecular catalogs, compiles fort.12-style records
- Continuum opacity evaluation — per-wavelength, per-depth
- Line opacity accumulation — Voigt profile wing addition for atomic and molecular lines
- Radiative transfer dispatch — calls
solve_lte_spectrumfor each wavelength batch
Fortran parity in opacity.py
The line-opacity kernels (_accumulate_metal_profile_kernel, _accumulate_mol_wings_batch) are JIT-compiled with Numba and contain inline comments mapping every loop index and branching condition to the original Fortran labels (e.g., synthe.for 320-350).
engine/radiative.py¶
Implements the JOSH solver for the radiative transfer equation. Given total opacity (acont + aline + sigmac + sigmal) and source functions, it returns the emergent flux at the top of the atmosphere. See Radiative Transfer for the physics details.
io/lines/¶
io/lines/atomic.py— Readsgfallvac.latestand other atomic catalogsio/lines/molecular_compiler.py— Compiles molecular lines from Kurucz ASCII and binary formats (Schwenke TiO, Partridge–Schwenke H₂O)io/lines/compiler.py— Builds the unified fort.12-style record list used by the opacity kernel
physics/¶
| Module | Purpose |
|---|---|
continuum.py |
H⁻, H I, He, metal, and scattering continuous opacity |
profiles.py |
Voigt profile evaluation (Faddeeva function) |
hydrogen_wings.py |
Stark-broadened hydrogen line wings |
helium_profiles.py |
Tabulated helium line broadening |
populations.py |
Saha–Boltzmann and partition functions |
voigt_jit.py |
Numba-accelerated Voigt kernels |
Two-Stage Design¶
synthe_py is deliberately split into two stages:
- Preprocessing (
convert_atm_to_npz.py) — Solves the equation of state (populations, molecular equilibrium, continuous opacity) once per atmosphere. - Wavelength loop (
synthe_py.cli) — Repeats the line-opacity and RT steps for every wavelength point.
This separation means you can synthesize many different wavelength ranges or resolutions from the same atmosphere without recomputing populations.
Parallelism Model¶
The most expensive operation in synthesis is the line-opacity accumulation, which is \(\mathcal{O}(N_\lambda \times N_{\rm lines})\). synthe_py parallelizes this at two levels:
Wavelength-level parallelism¶
The wavelength grid is partitioned into chunks, each processed by a worker in a ThreadPoolExecutor. Each worker:
- Receives its subset of wavelengths
- Evaluates continuum opacity for its subset
- Searches the line list for lines near its wavelengths
- Accumulates Voigt profile wings into a local opacity buffer
- Solves the RT equation (JOSH) for each wavelength
Numba JIT within workers¶
The inner loops (Voigt evaluation, wing accumulation, JOSH integration) are JIT-compiled by Numba. This gives single-thread performance comparable to compiled Fortran, while the process pool exploits multi-core parallelism.
Worker count tuning
By default, synthe_py uses all logical CPUs. For machines with many cores (e.g., 64+), memory bandwidth can become the bottleneck. In that case, reduce --n-workers to the number of physical cores.
Config System¶
synthe_py uses dataclasses defined in config.py:
from synthe_py.config import SynthesisConfig, WavelengthGrid, LineDataConfig
cfg = SynthesisConfig(
wavelength_grid=WavelengthGrid(
start=300.0, end=1800.0, resolution=300_000.0
),
line_data=LineDataConfig(
atomic_catalog=Path("lines/gfallvac.latest"),
molecular_line_dirs=[Path("data/molecules")],
include_tio=True,
include_h2o=False,
),
atmosphere=AtmosphereInput(model_path=Path("model.atm")),
output=OutputConfig(spec_path=Path("spectrum.spec")),
cutoff=1e-3,
n_workers=None, # auto = all CPUs
)
Next Steps¶
- Read about
atlas_py, the atmosphere engine that produces the input.atm. - Explore the Emulator for the warm-start stage.
- See Data Flows for how
.atm,.npz, and.specrelate.