Note
Go to the end to download the full example code.
Staged solve with progressive network visualization.
Demonstrates how to call build_viz_network()
after each solve_stage() to visualize the
reactor network as it grows stage by stage.
This is the detailed companion to the simple Report converged reactor states example. That example shows the minimal script generated by Download Python in the Boulder GUI; this example shows how to extend it to inspect intermediate results at each stage boundary.
Note
The Download Python script follows this same call structure.
The .py file produced by the Boulder GUI’s Download Python button
is identical to plot_staged_solve_from_yaml.py. You can extend it with
the per-stage draw and inspection calls shown here without changing any
of the core solve logic.
Workflow#
from_yaml— load, normalize, validate the YAML configbuild_stage_graph— parse topology (pure Python, no Cantera)For each stage:
solve_stage— build + integrate reactors to steady statebuild_viz_network— assemble aReactorNetcontaining only the stages solved so farnetwork.draw— visualise the partial network
Final
build_viz_network— full network with all connections restored
Requires: cantera, boulder, graphviz (optional, for network.draw)
from pathlib import Path
from typing import Any
from boulder.runner import BoulderRunner
Load and parse the configuration#
from_yaml runs load → normalize → validate.
build_stage_graph is pure config parsing; no Cantera code runs yet.
# sphinx-gallery sets cwd to the examples directory; configs/ is one level up.
config_path = str(Path.cwd().parent / "configs" / "staged_psr_pfr.yaml")
runner = BoulderRunner.from_yaml(config_path)
plan = runner.build_stage_graph()
print(f"Staged execution plan: {len(plan.ordered_stages)} stage(s)")
for i, stage in enumerate(plan.ordered_stages):
print(
f" Stage {i + 1}: '{stage.id}' nodes={stage.node_ids}"
f" mechanism={stage.mechanism}"
)
Staged execution plan: 2 stage(s)
Stage 1: 'psr_stage' nodes=['feed', 'psr'] mechanism=gri30.yaml
Stage 2: 'pfr_stage' nodes=['pfr_cell_1', 'pfr_cell_2', 'pfr_cell_3', 'pfr_cell_4'] mechanism=gri30.yaml
Initialise trajectory and inter-stage state carrier#
trajectory = runner.new_trajectory()
inlet_states: dict[str, Any] = {}
Stage 1/2: psr_stage — perfectly-stirred reactor#
solve_stage creates the Cantera reactor objects for this stage,
builds an intra-stage ct.ReactorNet, and advances to steady state.
The converged outlet state is written into inlet_states so that the
next stage can use it as its initial condition.
stage_1 = plan.ordered_stages[0]
runner.solve_stage(plan, stage_1, inlet_states, trajectory)
print(f"\n[{stage_1.id}] solved")
assert runner.converter is not None
for nid in stage_1.node_ids:
r = runner.converter.reactors[nid]
print(f" {nid}: T = {r.phase.T:.1f} K P = {r.phase.P / 1e5:.3f} bar")
[psr_stage] solved
feed: T = 300.0 K P = 1.013 bar
psr: T = 2105.1 K P = 1.013 bar
Visualize the network after stage 1#
build_viz_network assembles a single ReactorNet from
all reactor objects built so far. After stage 1 it contains only the PSR.
runner.build_viz_network(plan, trajectory)
net_after_stage1 = runner.network
assert net_after_stage1 is not None
print(f"\nViz network after stage 1: {[r.name for r in net_after_stage1.reactors]}")
try:
diagram = net_after_stage1.draw(print_state=True, species="X")
diagram.render("network_after_stage1", format="png", cleanup=True)
print("Saved network_after_stage1.png")
except ImportError:
print("graphviz not installed — skipping draw()")
Viz network after stage 1: ['psr']
Saved network_after_stage1.png
Stage 2/2: pfr_stage — plug-flow reactor chain#
The PSR outlet state is already in inlet_states; solve_stage
uses it to initialise the first PFR cell.
stage_2 = plan.ordered_stages[1]
runner.solve_stage(plan, stage_2, inlet_states, trajectory)
print(f"\n[{stage_2.id}] solved")
assert runner.converter is not None
for nid in stage_2.node_ids:
r = runner.converter.reactors[nid]
print(f" {nid}: T = {r.phase.T:.1f} K P = {r.phase.P / 1e5:.3f} bar")
[pfr_stage] solved
pfr_cell_1: T = 2117.9 K P = 1.013 bar
pfr_cell_2: T = 2176.6 K P = 1.013 bar
pfr_cell_3: T = 2194.5 K P = 1.013 bar
pfr_cell_4: T = 2197.0 K P = 1.013 bar
Visualize the complete network#
Now build_viz_network connects all stages, restoring inter-stage
connections (MassFlowControllers etc.) in the full ct.ReactorNet.
runner.build_viz_network(plan, trajectory)
network = runner.network
assert network is not None
print(f"\nFull viz network: {[r.name for r in network.reactors]}")
try:
diagram = network.draw(print_state=True, species="X")
diagram.render("network_complete", format="png", cleanup=True)
print("Saved network_complete.png")
except ImportError:
print("graphviz not installed — skipping draw()")
Full viz network: ['psr', 'pfr_cell_1', 'pfr_cell_2', 'pfr_cell_3', 'pfr_cell_4']
Saved network_complete.png
Report final reactor states#
print("\nSimulation completed.")
print(f"{'Reactor':<20} {'T [K]':>10} {'P [bar]':>10}")
print("-" * 42)
for r in network.reactors:
print(f"{r.name:<20} {r.phase.T:>10.1f} {r.phase.P / 1e5:>10.4f}")
Simulation completed.
Reactor T [K] P [bar]
------------------------------------------
psr 2105.1 1.0132
pfr_cell_1 2117.9 1.0133
pfr_cell_2 2176.6 1.0132
pfr_cell_3 2194.5 1.0133
pfr_cell_4 2197.0 1.0133
Total running time of the script: (0 minutes 0.428 seconds)