Python assembly: species and chemistry¶
Part of the pure-Python assembly ladder. A pipe carries gas dynamics whether or not it carries composition; this page adds the composition.
aeolus1d splits species into a primary column and any number of extras.
That is not cosmetic — the primary is the one closures reach for by default
(the filter's PM species, the cylinder's fuel), and the extras ride alongside
in a single (N, n_extra) array whose column order is an invariant. See
ADR-0003 for why: species
mis-slotting scrambled stoichiometry twice (MRs !100 / !101), and the layout
rule is what closed it.
Declaratively¶
from aeolus1d.io.build import build_network
from aeolus1d.io.schema import SpeciesExtraSpec, SpeciesSpec
species = SpeciesSpec(
name="NO", Y_init=1.0e-3, reaction="none",
extras=[SpeciesExtraSpec(name="NH3", Y_init=0.0),
SpeciesExtraSpec(name="N2", Y_init=0.7)],
)
pipes, junctions, far = build_network(case_of(duct(species=species)))
pipe = pipes["duct"]
primary : 'NO' Y[0]=0.001
extras : ['NH3', 'N2'] shape=(24, 2)
pipe.Y is the primary column, length N. pipe.Y_extras is (N, 2) here,
with pipe.species_names giving the column order — always read the names, never
assume an index.
By hand, no schema¶
The same pipe built from components. Note the ordering constraint: the primary must exist before extras can be appended.
from aeolus1d.components.pipe import Pipe
pipe = Pipe(L=0.2, N=24, gamma=1.4, Ng=2, A=1.0e-3)
pipe.set_uniform(rho=1.0e5 / (287.05 * 800.0), u=20.0, p=1.0e5)
pipe.set_species(1.0e-3, name="NO") # primary — must come first
pipe.add_extra_species("NH3", 0.0) # each call appends one column
pipe.add_extra_species("N2", 0.7)
hand-built: primary='NO' extras=['NH3', 'N2'] Y_extras=(24, 2)
set_species takes a scalar (uniform) or a length-N array, so a
non-uniform initial profile is just an array:
import numpy as np
pipe.set_species(np.linspace(0.0, 1.0e-3, pipe.N), name="NO")
Inert transport is a conservation check you can run¶
With reaction="none" nothing consumes or produces anything, so at steady
state the composition must be uniform across the domain and equal to what
the inlet carries — first-order upwind advection of a passive scalar is
conservative. That makes it a cheap, sharp test of the species plumbing:
pipes, junctions, t = run_case(case_of(duct(species=species)))
pipe = pipes["duct"]
uniform = np.allclose(pipe.Y, pipe.Y[0], rtol=1e-9)
after run: NO uniform? True Y_NO=0.001 sum(Y)=0.701
sum(Y) = 0.701 is 0.001 (NO) + 0.0 (NH3) + 0.7 (N2) — the mass fractions
are carried, not renormalised, so what you put in is what you get out. If this
ever comes back non-uniform on an inert run, the defect is in transport or in
the junction coupling, not in your chemistry.
tests/integration/test_species_conservation_network.py runs exactly this
argument across a two-pipe-plus-junction network, and extends it to a reacting
case: with a catalyst the individual species do change, but chemistry only
rearranges atoms, so the per-mass element ratios C/N and H/N must stay uniform.
That test deliberately gives the downstream pipe a non-canonical column
order — the shape the AVL Cruise M importer produces — because that is the
layout the mis-slotting bug scrambled.
Reactions¶
SpeciesSpec.reaction selects the kinetics for the primary column; mechanism
files and the surface-reaction seam are their own subject. Start at
Chemistry file formats for the file side, and
the closures reference for what each reaction
kind expects.
Where to go next¶
| Next | Page |
|---|---|
| Catalysts and filters that consume these species | Aftertreatment |
| The extras-layout invariant, in full | ADR-0003 |
| Mechanism + species-DB file formats | Chemistry file formats |