Tutorial 2 — Networks and junctions¶
Time: ~30 minutes. Prerequisites: Tutorial 1 finished.
Anchored on: examples/03_two_pipes_junction.toml.
A real engine isn't one pipe — it's a manifold, runners, plenums, an exhaust, a catalyst, a tailpipe, all stitched together. aeolus1d models that as a network: pipes that solve the 1D Euler equations on the inside, joined by junctions that are 0-D control volumes coupling the connected pipe ends.
This tutorial wires the simplest non-trivial network — two pipes joined by one plenum — and walks through what happens. You'll see the schema's polymorphic structure, learn how boundary conditions and junctions interact, and finish by opening the case in the GUI to drag pieces around.
The case¶
examples/03_two_pipes_junction.toml is the canonical "intake
manifold" test: a stagnation reservoir feeds an upstream pipe,
the gas crosses a plenum, and a static-pressure outlet pulls a
downstream pipe. At steady state the mass flow is uniform
along the path and the plenum pressure sits between p0 and
p_back.
Open it in any text editor.
Step 1 — Pipes (the 1-D parts)¶
[[pipe]]
id = "upstream"
length = 0.5
n_cells = 100
diameter = 0.036
[pipe.init]
kind = "uniform"
p = 1.0e5
T = 300.0
u = 0.0
[[pipe]]
id = "downstream"
length = 0.5
n_cells = 100
diameter = 0.036
…
Two pipes, each 0.5 m at ~36 mm internal diameter, both
initialised at standard atmosphere. id is the string handle
the rest of the case uses to refer back to this pipe — junction
ports + boundary conditions both look up pipes by id. (You
could also use area = 1.017876e-3 instead of diameter;
diameter just computes the same thing assuming a circular
cross-section.)
The [pipe.init] block uses kind = "uniform" — every
cell gets the same (p, T, u). Other kind options:
"riemann" (left / right halves, like the Sod tube),
"regional" (multiple uniform regions), "mode" (a
specific acoustic mode shape).
Step 2 — The junction¶
[[junction]]
id = "plenum"
volume = 5.0e-4
[junction.init]
p = 1.0e5
T = 300.0
[[junction.ports]]
pipe = "upstream"
end = "right"
area = 0.001017876
[[junction.ports]]
pipe = "downstream"
end = "left"
area = 0.001017876
A junction is a 0-D control volume with mass + energy state
that gets integrated by the same SSP-RK timeloop as the pipes.
volume is the chamber volume in m³; the mass and energy
inside follow dm/dt = Σ ṁ_port and dE/dt = Σ ṁ · h₀.
The [[junction.ports]] blocks list which pipe ends connect
to the chamber. pipe references the id of a previously-
declared pipe, end is "left" or "right", area
is the port flow area (often equal to the pipe's cross-
sectional area, but you can throat the connection — useful for
modelling a restriction at the runner-to-plenum interface).
A junction can have any number of ports. This is what lets you model a 4-cylinder intake plenum: one inlet port from the compressor, four outlet ports to the cylinder runners.
aeolus1d has five junction kinds, selected via kind:
kind = "plenum"(default) — the standard 0-D chamber with mass + energy state. What this tutorial uses.kind = "corberan"— a CP+h junction that conserves both mass-flux and total-pressure across a network branch (Bassett K_loss). Used for tee / wye splits where pressure preservation matters more than chamber dynamics. Carries no chamber state, sovolume/initare ignored.kind = "ehr"— Extended Helmholtz Resonator with acoustically-resolving per-port velocity state. Niche.kind = "passthrough"— a stateless 2-port connector (oneleftend + onerightend) that exchanges ghost cells directly between two pipes, with no chamber volume or capacitance. For splicing two pipe segments into one path.kind = "biased_plenum"— a build-time flow / species distributor: it expands at load time into onetransient_mass_flow_inletBC per outlet port, with mass flows and primary-species concentrations biased by Weltens UI targets. Used to model maldistribution across a catalyst-brick face.
Step 3 — Boundary conditions¶
[[bc]]
pipe = "upstream"
end = "left"
kind = "stagnation_inlet"
p0 = 1.1e5 # inlet stagnation pressure
T0 = 300.0 # inlet stagnation temperature
[[bc]]
pipe = "downstream"
end = "right"
kind = "pressure_outlet"
p_back = 1.0e5 # static back-pressure
A boundary condition fills the ghost cells at one pipe end so the solver sees a "reservoir" instead of a ragged edge. The two used here are the most common pair for steady-flow tests:
-
stagnation_inlet — characteristic-based BC that imposes
(p0, T0)on the incoming wave and reads the outgoing wave from the interior. The inlet velocity self-adjusts so the energy budget closes. Ideal when you have a "supply reservoir" upstream. -
pressure_outlet — characteristic-based BC that imposes static
p_backon the incoming wave and reads density + velocity from the interior. Ideal for "this is what the ambient sees."
Other kinds: wall (closed end, mirror BC), transmissive
(zero-gradient, what the Sod tube uses), nonreflecting
(LODI for cleaner outflow on long-running cases),
transient_* versions that schedule p₀(t) / T₀(t) /
p_back(t) for time-varying tests, and mass_flow_inlet /
velocity_inlet when you want to prescribe ṁ or u directly.
Step 4 — Run it¶
Two equivalent paths, same outputs. The terminal is the fastest for one-shot or scripted runs; the GUI gives you the network laid out as a graph with the components clickable, and is the one to pick when you want to drag pieces around without hand-editing TOML.
Option A — Terminal¶
aeolus1d run examples/03_two_pipes_junction.toml --out /tmp/network.h5
Then inspect both pipes and the junction:
import h5py
with h5py.File("/tmp/network.h5") as f:
for pid in ("upstream", "downstream"):
rho = f[f"pipes/{pid}/rho"][:]
u = f[f"pipes/{pid}/u"][:]
p = f[f"pipes/{pid}/p"][:]
print(f"{pid}: ṁ ≈ {(rho*u).mean()*1.018e-3:.4f} kg/s, "
f"p̄ = {p.mean()/1e5:.3f} atm")
j = f["junctions/junction_00"]
print(f"plenum: p = {j.attrs['p']/1e5:.3f} atm, "
f"T = {j.attrs['T']:.1f} K")
The mass flow rate ṁ = ρ·u·A should be roughly equal in
both pipes (steady state implies mass conservation through the
plenum), and the plenum pressure should sit between 1.0 atm
(p_back) and 1.1 atm (p0). For this case:
upstream: ṁ ≈ 0.0144 kg/s, p̄ = 1.099 atm
downstream: ṁ ≈ 0.0144 kg/s, p̄ = 1.000 atm
plenum: p = 1.051 atm, T = 304.2 K
Option B — GUI¶
aeolus1d gui
File → Open → examples/03_two_pipes_junction.toml.
You'll see three nodes on the canvas: two pipe rectangles and
a circular plenum, connected by edges representing the port
relationships.
Click Run in the bottom Runner dock (or F5). The simulation
fires as a subprocess; on clean exit the post-processing
docks load the run's out.h5 and render both pipes as
coloured tubes. (Each run lands in its own
<case-name>_<timestamp>/ folder under the run-output
directory — the per-OS temp folder by default, configurable
in Preferences → Run output; the exact path is echoed in the
Runner console.) The Plot dock on the right will plot ρ /
u / p along whichever pipe you select.
You don't need to read the same numbers programmatically with h5py — the result is right there in the post-processing view. But the h5py snippet from Option A still works on the GUI's output file (its path is printed in the Runner console, and the "Load Results…" button re-attaches any HDF5 to the post-processing docks).
Step 5 — Edit the network in the GUI¶
This is what the GUI is best at. With the case still open:
- Click on the plenum. The right-hand Inspector dock
shows the
Junctionform — name,volume, init state, the ports list. Editvolumefrom5e-4to2e-3. The case is marked dirty (title bar shows*). - Drag a new pipe in from the Palette dock (left). Drop it on the canvas; an inspector form appears.
- Drag-connect ports. Lighter-tinted port spheres on each component's edges expose the connection points; click + drag from one to a compatible port on another to wire them up.
- Run to see the modified case. Cycle: edit, run, view.
A few minutes of clicking will teach you the GUI's idiom faster than any documentation. The full GUI walkthrough is in ../gui.md.
Step 6 — Modify and rerun¶
Three useful experiments (all CLI or GUI):
-
Larger plenum. Bump
volumefrom 5e-4 to 5e-3. Expect: lower-frequency response to inlet pulses (the acoustic mode of a plenum isω² = γ·p / (ρ·V·L_eff), so larger V → lower ω). -
Higher driving pressure. Bump
p0from1.1e5to1.5e5. The driving pressure difference grows ~5× (0.1 → 0.5 atm), but the flow is sub-sonic soṁ ∝ √(2·Δp/ρ)— expect only roughly√5 ≈ 2×higher mass flow rate. -
Add a second outlet. Add a third pipe, route it from the plenum (extra
[[junction.ports]]entry), and put a pressure outlet at its other end. Now you have a tee. The upstream mass flow should split between the two outlets in proportion to their effective conductances. -
Try the Corberán junction. Change
[[junction]] kindto"corberan"(and delete thevolume/initblocks since Corberán doesn't carry chamber state). The steady-state mass flow should be identical, but the transient response is different — Corberán doesn't store mass / energy in the junction.
What you've done¶
- Read the schema's polymorphic structure (
[[pipe]]/[[junction]]/[[bc]]blocks;kindliterals selecting variants). - Built a steady-flow network test and verified mass conservation through a 0-D plenum.
- Edited the case in the GUI and re-ran from there.
- Probed three different
kindaxes.
Networks are the foundation of every engine and aftertreatment case in aeolus1d. The 4-cylinder example you'll see next is just 13 pipes + 3 junctions + 4 cylinders + 2 turbos + 1 shaft wired with the same idiom.
Next: Engine simulation end-to-end →¶
Now you can read aeolus1d's network grammar fluently. The engine tutorial drops you straight into the deep end with the full Phase 25 fuel chain — injection, combustion, blowback, catalyst HC oxidation, audio export.