Chemistry file formats — mechanisms & species database¶
aeolus1d reads chemistry from three on-disk artefacts. This page is the authoritative schema for each.
Which one do I want?
| I want to… | Use | File |
|---|---|---|
| Define custom global / semi-global mass-action kinetics (gas, or gas + surface coverage) | [pipe.*.species] reaction = "mechanism" → mechanism_file |
a TOML MechanismSpec (§1) |
| Import a published elementary surface mechanism | [pipe.*.catalyst] kind = "mechanism" → mechanism_path |
a CHEMKIN .inp or Cantera .yaml file (§2) |
| Supply per-species thermodynamics (NASA-7 cp, molar mass, composition) | the species thermo database | one .npz per species (§3) |
The two mechanism systems are independent — different TOML fields,
different file formats, different solver seams. Don't confuse
mechanism_file (§1) with mechanism_path (§2).
Background on how these plug into the solver: Theory §7 Strang-split chemistry, §3.5 Species transport, and the Component index — species & reactions.
1. Declarative mechanism spec (mechanism_file, TOML)¶
The lightweight format: a mass-action mechanism you write by hand. A pipe opts in through its species block:
[[pipe]]
id = "doc"
# … geometry, mesh …
[pipe.species]
name = "CO" # primary species = mechanism species[0]
extras = ["O2", "CO2", "C3H6", "H2O"] # = mechanism species[1:]
reaction = "mechanism"
mechanism_file = "mechanisms/co_hc_oxidation.toml"
A relative mechanism_file resolves against the case file's directory
(the example above is a case in examples/). Since 2026-09-29; before, it
resolved against the working directory. A path found only there still loads,
with a FutureWarning naming both places, and that fallback will be removed.
An absolute path is used as written.
Alignment rule (enforced at build time): the mechanism's species
list must equal [primary, *extras] in the same order — otherwise
the build raises a ValueError. This keeps the reaction columns aligned
with the pipe's transported-species layout under every driver. The file
is read with tomllib, so mechanism_file is TOML only (the
CHEMKIN/Cantera path in §2 is the one that takes .inp/.yaml).
1.1 Gas-phase example¶
examples/mechanisms/co_hc_oxidation.toml — DOC-style CO + C₃H₆
oxidation:
species = ["CO", "O2", "CO2", "C3H6", "H2O"]
[[reactions]]
# CO + 1/2 O2 -> CO2
stoich = { CO = -1.0, O2 = -0.5, CO2 = 1.0 }
A = 1.0e10
Ea = 9.0e4
dH = 2.83e5
[[reactions]]
# C3H6 + 9/2 O2 -> 3 CO2 + 3 H2O
stoich = { C3H6 = -1.0, O2 = -4.5, CO2 = 3.0, H2O = 3.0 }
A = 2.5e9
Ea = 1.3e5
dH = 1.93e6
1.2 Schema (MechanismSpec)¶
Top-level keys (parsed by
aeolus1d.closures.mechanism.MechanismSpec.from_dict):
| key | type | required | meaning |
|---|---|---|---|
species |
list[str] | yes | gas species, canonical order; [0] is the pipe's primary |
reactions |
array of tables | yes | one [[reactions]] block per reaction (below) |
molar_mass |
table {species = kg/mol} |
no | overrides; common species default from the builtin table, so usually omitted |
surface_species |
list[str] | no | coverage variables θ_k ∈ [0,1] (see §1.3) |
site_capacity |
table {θ = mol-sites/m³} |
no | site density Ω_k per coverage |
theta_init |
table {θ = value} |
no | initial coverage, default 0 |
Each [[reactions]] table:
| key | type | required | meaning |
|---|---|---|---|
stoich |
inline table | yes | stoichiometry; negative = consumed, positive = produced. Gas and surface (θ_k) species allowed |
A |
float | yes | Arrhenius pre-exponential. Mass-action units — they depend on the reaction order (e.g. m³·mol⁻¹·s⁻¹ for a single first-order reactant) |
Ea |
float | yes | activation energy [J/mol] |
dH |
float | no (def 0) |
heat of reaction [J per mol of extent]; positive = exothermic |
orders |
inline table | no | override reaction orders; default is first order in each consumed species (occupied/concentration order for surfaces) |
vacancy |
inline table | no | (1−θ_k) orders for surface sites (site-availability dependence) |
A_rev, Ea_rev |
float | no (def 0) |
reverse rate; A_rev = 0 ⇒ irreversible |
orders_rev, vacancy_rev |
inline table | no | reverse-direction orders (default: first order in each produced gas species) |
1.3 Surface coverage¶
Add surface_species to model adsorption/desorption. A coverage θ_k
carries an occupied concentration Ω_k·θ_k and a vacant
concentration Ω_k·(1−θ_k); a surface stoich coefficient is the
coverage change per unit extent (dθ = extent / Ω). orders give the
occupied (θ) order, vacancy the (1−θ) order.
examples/mechanisms/nh3_ads_des_surface.toml — Langmuir NH₃ storage
(reproduces the bundled nh3_ads_des closure):
species = ["NH3"]
surface_species = ["theta_NH3"]
[site_capacity]
theta_NH3 = 100.0 # mol-sites / m^3 (typical Cu-zeolite)
[theta_init]
theta_NH3 = 0.0
[[reactions]]
# adsorption: NH3(gas) + free site -> NH3* rate = k·c_NH3·(1-θ)·Ω
stoich = { NH3 = -1.0, theta_NH3 = 1.0 }
orders = { NH3 = 1.0 }
vacancy = { theta_NH3 = 1.0 }
A = 1.0e3 # m^3 / (mol-site . s); non-activated
Ea = 0.0
dH = 8.0e4 # +exothermic on adsorption
[[reactions]]
# desorption: NH3* -> NH3(gas) + free site rate = k·θ·Ω
stoich = { NH3 = 1.0, theta_NH3 = -1.0 }
orders = { theta_NH3 = 1.0 }
A = 1.0e10
Ea = 8.0e4
dH = -8.0e4 # desorption is endothermic
When surface_species is present the mechanism compiles to a surface
kinetics object (py_reaction_surface) and each coverage is seeded into
the pipe's surface_state from theta_init; a pure-gas spec compiles to
py_reaction_multi. Working end-to-end case: examples/35_mechanism_pipe.toml
(plot: examples/plots/plot_35_mechanism_pipe.py).
GIL-free integration (opt-in)
The compiled surface mechanism (py_reaction_surface) is the one
reaction seam wired to the optional GIL-free chemistry backend: set
[case] chem_solver = "numbalsoda" (requires the optional
numbalsoda package) to integrate each cell's stiff mass-action ODE
through numbalsoda's nogil LSODA in a prange instead of the
batched SciPy LSODA. The per-cell right-hand side is the same kernel
(aeolus1d.closures.mechanism._mech_cell_dstate_jit), so the numerics
agree to integrator tolerance; any pipe it can't handle falls back to
SciPy. Bespoke catalyst closures (§2, Brinkmeier/Voltz/SCR) are not
on this path.
2. Elementary mechanism import (mechanism_path, CHEMKIN / Cantera)¶
For detailed mechanisms, the catalyst closure imports a CHEMKIN or Cantera file and builds surface kinetics over the brick:
[pipe.scr.catalyst]
kind = "mechanism"
mechanism_path = "mechanisms/scr_surface.inp" # .inp | .yaml | .yml
T_solid_init = 300.0
[pipe.scr.catalyst.surface_init]
theta_NH3 = 0.0
theta_NO = 0.0
| field | type | meaning |
|---|---|---|
mechanism_path |
str | path to the mechanism file; extension selects the importer |
surface_init |
table {θ = value} |
initial surface coverage per species |
T_solid_init |
float (def 300.0) |
initial substrate temperature [K] |
The extension dispatches the importer
(aeolus1d/io/build.py → _attach_mechanism_catalyst):
.inp→aeolus1d.chemistry.importers.chemkin_mech.import_chemkin_mech— standard CHEMKIN-II/IIIELEMENTS/SPECIES/REACTIONSsections, plusSITEblocks for surface species (CHEMKIN-Surface). Reaction lines carryA b Ea; theREACTIONSheader unit token (CAL/MOLE,KCAL/MOLE,JOULES/MOLE,KJOULES/MOLE,KELVINS) setsEaunits (converted to J/mol on import).=/<=>are reversible,=>irreversible; the non-standard<=is read as a right-to-left irreversible reaction (sides swapped on import). Rate coefficients accept a FortranD/dexponent (1.0D13→1e13)..yaml/.yml→aeolus1d.chemistry.importers.cantera_yaml.import_cantera_yaml_as_mechanism— a subset of Cantera ≥2.5 YAML: aunitsblock,specieswith NASA-7thermo, and areactionslist withrate-constant: {A, b, Ea}.
Both importers return the Mechanism dataclass
(aeolus1d.chemistry.reaction.Mechanism: elements, species,
reactions, surface_sites, …). Advanced kinetics (third-body /
(+M) falloff, LOW/TROE/SRI, PLOG, DUPLICATE) are recognised
where supported; Chebyshev rates and non-NASA-7 thermo are not. The
importer modules in src/aeolus1d/chemistry/importers/ are the
authoritative spec for the exact accepted subset — consult them before
relying on an exotic feature.
3. Species thermodynamic database (.npz)¶
Per-species NASA-7 thermodynamics live as one NumPy .npz per species
under src/aeolus1d/chemistry/db_data/. These supply molar mass, atomic
composition, and cp(T)/h(T)/s(T) for the imported mechanisms in §2.
Bundled species (14): CH4 CO CO2 H H2 H2O H2O2 HO2 N2 NH3 NO O O2 OH
(GRI-Mech 3.0 / Burcat-Goos thermo).
3.1 Per-file array schema¶
Each <name>.npz (verified against NH3.npz) contains:
| array | shape | dtype | meaning |
|---|---|---|---|
nasa7_low |
(7,) |
float64 | NASA-7 coefficients a₁…a₇, low-T range |
nasa7_high |
(7,) |
float64 | NASA-7 coefficients, high-T range |
T_min |
() |
float64 | low end of validity [K] |
T_mid |
() |
float64 | low/high polynomial switch [K] |
T_max |
() |
float64 | high end of validity [K] |
M_w |
() |
float64 | molar mass [g/mol] (= kg/kmol; e.g. NH₃ → 17.03056) |
elements_keys |
(n,) |
<U str |
element symbols, e.g. ['N','H'] |
elements_counts |
(n,) |
int64 | per-element atom counts, e.g. [1,3] |
phase |
() |
<U str |
phase tag, e.g. 'G' (gas) |
In memory this maps to the frozen SpeciesThermo dataclass
(aeolus1d.chemistry.species.SpeciesThermo).
Pickle-free by policy
The .npz files store plain typed arrays only — no pickled
Python objects — so they load with allow_pickle=False (a security
gate from the 2026-06-10 audit). Keep any species DB you author in
the same plain-typed form.
3.2 Resolving & adding species¶
from aeolus1d.chemistry.db import load_species, save_species
nh3 = load_species("NH3") # bundled db_data/
mine = load_species("C2H4", db_dir="my_db") # user directory
save_species(species_obj, db_dir="my_db") # write my_db/C2H4.npz
load_species(name, db_dir=None) reads <db_dir>/<name>.npz, falling
back to the bundled db_data/ when db_dir is None. New species come
from a thermo importer (aeolus1d.chemistry.importers.nasa7 /
janaf) then save_species. To re-emit the bundled DB in the
pickle-free format, run python scripts/regen_species_db.py [db_dir].
Burcat's database (~3700 species, fetched on demand)¶
The bundled seed covers ten common species. When a mechanism names something else, Burcat & Ruscic's Third Millennium database is the usual source — it carries NASA-7 polynomials for roughly 3700 species, with Active Thermochemical Tables updates.
It is not bundled. Burcat's terms are "free of charge on condition that
proper quotation is given" and "the sale of this information is strictly
forbidden and it cannot be listed, printed or copied in any commercial
publication form". aeolus1d is MIT licensed, which grants commercial use, so
shipping the file inside the wheel would hand users a bundle whose parts
disagree about what they may do with it. It is fetched to a user-local cache
instead — the same treatment local_cases/ gets for licensed vendor data.
aeolus1d thermo-db fetch # download once (~2.3 MB) into the cache
aeolus1d thermo-db info # cache location, species count, citation
The cache defaults to ~/.local/share/aeolus1d/burcat (honouring
XDG_DATA_HOME), overridable with AEOLUS_BURCAT_DIR. It sits deliberately
outside the installed package so licensed data never lands where git add
can reach it.
from aeolus1d.chemistry.burcat_db import ensure_burcat_db, load_burcat_species
ensure_burcat_db() # no-op once cached
c2h4 = load_burcat_species("C2H4") # SpeciesThermo
Species are keyed on the identifier alone (N2), with Burcat's full label
(N2 REF ELEMENT) registered as a second key — some species differ only by
that qualifier, e.g. CH4 RRHO versus CH4 ANHARMONIC.
Parsing uses aeolus1d.chemistry.importers.burcat, not the CHEMKIN
nasa7 importer. The file is NASA-7-like but differs on the first line of
every record — float element counts (N 2.), the molecular weight where
T_common belongs (so the split temperature is the database's 1000 K
convention), a fifth number on line 4, CRLF endings, and no
THERMO/END wrapper. The CHEMKIN parser recovers exactly one species from
it.
If you publish results that use it, cite:
A. Burcat and B. Ruscic, "Third Millennium Ideal Gas and Condensed Phase Thermochemical Database for Combustion with Updates from Active Thermochemical Tables", ANL-05/20 / TAE 960 (2005).
See also¶
- Theory §7 — Strang-split chemistry · §3.5 — Species transport
- Component index — species & reactions
- Closures — reference: family
reaction - Examples:
examples/35_mechanism_pipe.toml,examples/mechanisms/*.toml, and theexamples/plots/plot_3*_mechanism*.pyscripts. - ADRs: ADR-0001 operator-split stiff chemistry, ADR-0003 canonical extras layout.