Skip to content

Add FlowReactor/FlowReactorSurface support (distance-marched PFR) - #112

Merged
erwanp merged 3 commits into
mainfrom
claude/flow-reactor-support-8c0a3d
Jul 24, 2026
Merged

Add FlowReactor/FlowReactorSurface support (distance-marched PFR)#112
erwanp merged 3 commits into
mainfrom
claude/flow-reactor-support-8c0a3d

Conversation

@erwanp

@erwanp erwanp commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the deferred design doc docs/plans/2026-07-14-flow-reactor-support.md (removed by this PR now that it's built): support for Cantera's ct.FlowReactor (plug-flow, distance-marched) and an attached ct.ReactorSurface (catalytic wall chemistry), so scripts like upstream Cantera's surf_pfr.py can be modeled in Boulder end-to-end, both directions (YAML → Cantera and Cantera → YAML).

  • solver.axis: distance (boulder/config.py) — new opt-in stage-level solver key, default "time". Validated in the single choke point (_resolve_and_validate_solver_mode) shared by both STONE dialects (network: and stages:), so it never changes default behavior for existing time-integrated reactors.
  • FlowReactor node (boulder/cantera_converter.py) — new branch in create_reactor_from_node, following the existing per-reactor-type branch style: area, mass_flow_rate, surface_area_to_volume_ratio, energy.
  • Surface chemistry — modeled as a surface: property on the FlowReactor node rather than a new connection kind. ct.ReactorSurface isn't a flow-graph edge the way ct.Wall is, so a connection-based design would have forced it into stage-DAG/Sankey semantics that don't apply. The builder constructs the surface Interface first and derives the reactor's gas phase from its .adjacent[...], mirroring Cantera's own surf_pfr.py pattern (required for kinetics consistency between the two phases).
  • Distance-axis plotting — required no frontend code changes. The existing is_spatial/x series convention (built for CSTR-chain PFR approximations) already renders "Position (m)" plots in PlotsTab.tsx; the transient-solve trajectory recorder now tags distance-marched series with is_spatial: true instead of is_residence: true, reusing that path.
  • sim2stone reverse direction (boulder/sim2stone.py) — detects FlowReactor/attached surfaces directly off the live Cantera object (no AST guessing needed, unlike Func1 schedules, since nothing here is a discarded parametric form). One binding quirk found along the way: FlowReactor.mass_flow_rate is write-only in the Cantera 3.2 Python API — recovered via continuity (density × speed × area) instead of a direct read.
  • Example: configs/surf_pfr.yaml + docs/cantera_examples/surf_pfr.py (methane partial oxidation over Pt, vendored from upstream Cantera), wired into the existing fixture-script test harness (tests/test_sim2stone/test_fixture_scripts_sim2stone.py).
  • Tests: tests/test_sim2stone_flow_reactor.py covers both directions.

Known gap

download_script_emitter.py's standalone-script codegen doesn't yet support FlowReactor/FlowReactorSurface — it's a separate, hardcoded reactor-dispatch pathway from create_reactor_from_node. The two --download tests for surf_pfr.py are marked xfail for this reason; YAML emission, --output-yaml, and validate all work correctly.

Note on scope vs. the original design doc

The plan referenced a boulder_examples/examples/manifest.yaml catalog with status: unsupported entries — that catalog doesn't exist in this repo (checked: not in git history, not a submodule). The example/test targets were adapted to this repo's actual conventions (docs/cantera_examples/ + configs/ + tests/test_sim2stone/).

Test plan

  • make type-check (mypy strict) — clean
  • make qa (pre-commit run --all-files) — clean
  • Full backend suite: 694 passed, 10 skipped, 7 xfailed, 0 failed
  • Manual end-to-end smoke test: STONE YAML → DualCanteraConverter → real catalytic chemistry (CH4 consumed, CO produced) along the distance profile
  • Manual round-trip smoke test: hand-written ct.FlowReactor + ct.ReactorSurface script → sim_to_stone_yaml → re-parsed and rebuilt successfully
  • No frontend changes were made, so no npm run build/npm run test:unit was needed for this PR

Extensive AI use — code & PR fully written by Claude Sonnet 5

🤖 Generated with Claude Code

Added as an example in Boulder-Examples

image

parks4/boulder_examples#17

erwanp and others added 2 commits July 23, 2026 21:33
Implements docs/plans/2026-07-14-flow-reactor-support.md: STONE nodes for
Cantera's ct.FlowReactor (plug-flow, distance-marched) and an attached
ct.ReactorSurface (catalytic wall chemistry), plus round-trip support.

- config.py: new opt-in `solver.axis: distance` (default "time"), validated
  in the single choke point shared by both STONE dialects.
- cantera_converter.py: new FlowReactor branch in create_reactor_from_node
  (area, mass_flow_rate, surface_area_to_volume_ratio, energy); surface
  chemistry is a `surface:` property on the node rather than a new
  connection kind, since ct.ReactorSurface isn't a flow-graph edge. Builds
  the surface Interface first and derives the reactor's gas phase from its
  `.adjacent[...]`, mirroring Cantera's own surf_pfr.py. Distance-marched
  trajectories are tagged is_spatial/x, reusing the frontend's existing
  spatial-plot rendering with no UI code changes.
- sim2stone.py: reverse-direction (Cantera script -> STONE YAML) detection,
  reading FlowReactor/ReactorSurface state directly off the live object
  (no AST guessing needed, unlike Func1 schedules). FlowReactor's
  mass_flow_rate is write-only in the Cantera 3.2 binding, so it's
  recovered via continuity (density * speed * area) instead.
- New example: configs/surf_pfr.yaml + docs/cantera_examples/surf_pfr.py
  (methane partial oxidation over Pt, vendored from upstream Cantera),
  wired into the existing fixture-script test harness.
- tests/test_sim2stone_flow_reactor.py: round-trip coverage both
  directions.

Known gap: download_script_emitter.py's standalone-script codegen doesn't
yet support FlowReactor/FlowReactorSurface (a separate, hardcoded
reactor-dispatch pathway) -- the two --download tests for surf_pfr.py are
marked xfail; YAML emission, --output-yaml, and validate all work.

Extensive AI use — code & PR fully written by Claude Sonnet 5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the pre-commit failure on this branch after rebasing onto main.
FlowReactor nodes defaulted to the generic ellipse shape used for
stirred reactors, which doesn't read as a plug-flow duct. Adds it to
the existing PFR-style rectangle selector alongside the other
axial-flow reactor types.

Verified visually: examples/surf_pfr.yaml's FlowReactor_0 node renders
as a wide rectangle instead of a circle.
@erwanp
erwanp merged commit f9f416a into main Jul 24, 2026
8 checks passed
@erwanp
erwanp deleted the claude/flow-reactor-support-8c0a3d branch July 24, 2026 21:46
erwanp added a commit to parks4/boulder_examples that referenced this pull request Jul 27, 2026
…d PFR) (#17)

Adapts Cantera's surf_pfr.py (partial oxidation of methane over a
platinum catalyst) as a real ct.FlowReactor + ct.ReactorSurface,
solved distance-marched (solver.axis: distance), not a chain-of-CSTRs
approximation -- now possible thanks to parks4/boulder#112.

- adapters/surf_pfr.py: mirrors upstream's reactor setup, stops before
  the distance-marching loop (kept under `if False:`, same convention
  as piston.py's stepping guard) so sim2stone captures the true inlet
  state instead of the fully-converted outlet state. Builds via
  ct.FlowReactor + ct.ReactorSurface (not ct.FlowReactorSurface), the
  object pair Boulder's sim2stone/cantera_converter expect.
- examples/surf_pfr.yaml: generated STONE YAML, verified to build and
  solve via DualCanteraConverter with real catalytic conversion (CH4
  consumed, CO/H2 produced along the 3mm bed).
- examples/manifest.yaml: surf_pfr unsupported -> adapted; updates the
  now-dead design-doc links on 1D_pfr_surfchem/surf_pfr_chain (removed
  by boulder#112) to point at the PR instead, and clarifies why those
  two remain out of scope (different mechanism/diagnostics; algorithmic
  chain-of-WSRs pattern, respectively).
- tests/test_downloaded_scripts.py: skips surf_pfr's --download test
  (download_script_emitter.py doesn't support FlowReactor yet, a known
  gap documented in boulder#112 -- YAML validation, normalization, and
  the real solve all work).
- New catalog screenshot showing the mole-fraction conversion profile
  along the bed.

Depends on parks4/boulder#112 (FlowReactor/FlowReactorSurface support),
not yet merged/released -- see the PR description for how this was
verified against that branch.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant