bloc.reactors.plug_flow#

Plug-flow reactor (PFR) engines and wall-model subclasses.

Classes#

PFR

Plug Flow Reactor (PFR) engine — closed Lagrangian parcel march.

PFRWallProfile

PFR engine with a prescribed wall-temperature profile.

PFRHomogeneousShell

PFR engine with homogeneous-shell radial heat loss.

PFRThinShell

PFR engine with thin-shell local-temperature radial heat loss.

PFRGasTemperatureProfile

PFR engine with a prescribed GAS temperature profile T_gas(x).

PFRNetwork

Unified network driver for any PFR subclass.

Module Contents#

class bloc.reactors.plug_flow.PFR(gas, *args, **kwargs)#

Bases: cantera.ExtensibleIdealGasConstPressureMoleReactor

Plug Flow Reactor (PFR) engine — closed Lagrangian parcel march.

Physics#

A fixed mass of gas is tracked as it travels axially through a tube. The parcel is modelled as a closed constant-pressure system: no mass enters or leaves during the inner CVODE integration. At every spatial checkpoint the outlet state is written back to the stage reactor via copy_state().

The common invariant shared by all PFR subclasses is:

  1. A fresh carrier reactor is built by build_closed_carrier_net() from the stage reactor’s mechanism (_mech_source) and type.

  2. The carrier is marched from inlet to outlet.

  3. The outlet TPX is copied back to the stage reactor.

Subclasses override heat_flux_factory() and/or SOLVER_MODE to express the wall heat-transfer hypothesis:

  • PFRWallProfile — prescribed T_wall(x) with forced-convection + grey-gas radiation, adaptive net.step march.

  • PFRHomogeneousShell — radial insulation stack, spatial-mean temperature, Forward-Backward Sweep.

  • PFRThinShell — radial insulation stack, local T(z), single forward pass.

Why not ct.FlowReactor?#

Cantera’s FlowReactor is a steady-state plug-flow model that is integrated by ReactorNet over axial distance (DAE / IDAS), not over residence time (CVODES). It is adiabatic and frictionless by construction and has no hook for our wall heat-transfer laws. Cantera also enforces that a FlowReactor must run alone in its network — it cannot share a ReactorNet with time-dependent ODE reactors (MFC/PC stages, mixing vessels, recycle loops) because the two formulations use different independent variables and different integrators.

We therefore march a closed Lagrangian parcel on an extensible const-pressure mole reactor, which preserves wall heat injection via after_eval / heat_flux_factory and full compatibility with Boulder’s time-based stage networks.

See also: https://cantera.org/dev/python/zerodim.html#cantera.FlowReactor

param gas:

Cantera Solution at the initial thermodynamic state.

Notes

_meta must be populated by the network or builder before marching. Required keys depend on the subclass heat law; at minimum: length, diameter, mass_flow_rate.

SOLVER_MODE: ClassVar[str] = 'fixed_checkpoint'#
NETWORK_CLASS: ClassVar[type | None] = None#
before_update_state(y)#

Inject a pending initial state into CVODE’s y before the first eval.

heat_flux_factory(carrier)#

Return a heat-flux callable Q(t) [W/m²] for this wall model.

Override in subclasses to provide the wall heat-transfer law. Return None for an adiabatic parcel.

Parameters:

carrier – The fresh closed-carrier instance created by build_closed_carrier_net(). Subclasses may read carrier attributes (diameter, phase, _meta, …) to build the closure.

class bloc.reactors.plug_flow.PFRWallProfile(gas, *args, clone=False, diameter=0.1, kappa_grey=0.0, mass_flow_rate=0.0, **kwargs)#

Bases: PFR

PFR engine with a prescribed wall-temperature profile.

Physics#

The tube wall temperature T_wall(x) is provided as a callable. At each spatial position the network sets reactor.T_wall_K and reactor.x_position; the after_eval hook then injects the combined forced-convection and grey-gas-radiation flux directly into the energy ODE RHS:

\[Q_{wall} = h_{conv}\,A_{int}\,(T_{wall} - T_{gas}) - q_{rad}\,A_{int}\]

where \(A_{int} = (4/D)\,V\) is the internal surface area and \(h_{conv}\) is computed from the Nusselt correlation (internal_convection_h(), Baehr-Stephan / Hausen / Churchill-Zajic depending on the flow regime).

The parcel is marched with an adaptive net.step() loop so the integrator controls step size according to local stiffness.

Use cases#

  • Electrically or externally heated tube furnace with a known wall temperature profile.

  • Any reactor where the wall temperature is prescribed rather than computed from an insulation model.

See also

PFRHomogeneousShell

thick-shell radial loss.

PFRThinShell

thin-shell local-T radial loss.

SOLVER_MODE: ClassVar[str] = 'adaptive'#
diameter = 0.1#
kappa_grey = 0.0#
mass_flow_rate = 0.0#
T_wall_K: float | None = None#
x_position: float = 0.0#
after_eval(t, LHS, RHS)#

Inject wall convection + radiation into the energy ODE RHS.

NETWORK_CLASS: ClassVar[type | None] = None#
before_update_state(y)#

Inject a pending initial state into CVODE’s y before the first eval.

heat_flux_factory(carrier)#

Return a heat-flux callable Q(t) [W/m²] for this wall model.

Override in subclasses to provide the wall heat-transfer law. Return None for an adiabatic parcel.

Parameters:

carrier – The fresh closed-carrier instance created by build_closed_carrier_net(). Subclasses may read carrier attributes (diameter, phase, _meta, …) to build the closure.

class bloc.reactors.plug_flow.PFRHomogeneousShell(gas, *args, **kwargs)#

Bases: PFR

PFR engine with homogeneous-shell radial heat loss.

Physics#

Radial heat loss is modelled via a spatial-mean gas temperature and a lumped thermal resistance stack (insulation layers + external natural-convection / radiation). The total loss Phi_kW is converged by a Forward-Backward Sweep (FBS): an adiabatic predictor pass gives the mean T, which yields a first Phi_kW estimate; the corrector repeats with the updated wall flux until |ΔΦ| / Φ < 1 %.

The heat loss enters the Cantera ODE via a ct.Wall (area = 1 m²) whose heat_flux callable distributes Phi_kW uniformly in space (i.e. proportional to local velocity).

Use cases#

  • Insulated, thick-shell industrial vessels where axial wall conduction smears temperature gradients (e.g. the SPRING Carbon Growth Reactor).

  • Any reactor where the dominant resistance to heat loss is in the insulation layer rather than the gas film.

Not intended for#

  • Reactors with large axial temperature gradients and thin walls: use PFRThinShell instead.

See also

PFRWallProfile

prescribed wall-temperature profile.

PFRThinShell

thin-shell local-T radial loss.

SOLVER_MODE: ClassVar[str] = 'fixed_checkpoint'#
thermal_resistance_stack()#

Build the radial-conduction + external-convection resistance list.

Uses _meta geometry and insulation layers. Returns a list of thermal resistances [K/W] from inner wall to ambient, ordered from innermost insulation layer to external convection/radiation.

wall_thermal_report(T_mean_C)#

Return the wall heat-transfer breakdown at a mean gas temperature.

Single source of truth for the SPRING wall KPIs: the external film coefficients (h_CC, h_rad, h_eff [W/m²/K]), the external wall temperature (T_wall_ext [°C]) and the resulting wall heat loss (heat_loss_kW, including heat_loss_corr_factor). When _meta["adiabatic"] is set the loss is zero and the wall sits at ambient.

Parameters:

T_mean_C – Spatial-mean gas temperature in °C.

Returns:

{"h_CC", "h_rad", "h_eff", "T_wall_ext", "heat_loss_kW"}.

Return type:

dict

spatial_mean_temperature(states)#

Compute the spatial-mean gas temperature [°C] from a PFR profile.

Integrates T over the axial position recorded by _march_pfr_to_length in the x extra column, using the trapezoid rule (trapezoid(states.T, states.x) / states.x[-1]) so the FBS heat-loss estimate is identical between the staged and standalone paths.

Parameters:

states – SolutionArray with an x extra column (axial position [m]).

Returns:

Length-weighted mean temperature in °C. Falls back to the unweighted mean only if states.x is missing or zero.

Return type:

float

heat_loss(T_mean_C)#

Compute total wall heat loss [kW] for a given mean gas temperature.

Uses the linearized thermal resistance stack from thermal_resistance_stack() and multiplies by _meta["heat_loss_corr_factor"] (default 1.0) to account for thermal bridges and other unmodelled loss paths. Returns 0 when _meta["adiabatic"] is True.

Parameters:

T_mean_C – Spatial-mean gas temperature in °C.

Returns:

Heat loss in kW (positive = loss to ambient).

Return type:

float

solve_forward(Phi_kW)#

Integrate the PFR with a fixed heat-loss power and return the spatial profile.

Builds a PFRHomogeneousShellNet and delegates to its _spatial_ode_pass. This is the single-shot entry point for standalone use and tests; the FBS convergence loop in PFRHomogeneousShellNet.advance calls _spatial_ode_pass directly.

Parameters:

Phi_kW – Total wall heat loss to subtract from the inlet enthalpy [kW]. Positive value = heat removed from the gas.

Returns:

Spatial profile with extra columns t (residence time [s]) and x (axial position [m]).

Return type:

ct.SolutionArray

NETWORK_CLASS: ClassVar[type | None] = None#
before_update_state(y)#

Inject a pending initial state into CVODE’s y before the first eval.

heat_flux_factory(carrier)#

Return a heat-flux callable Q(t) [W/m²] for this wall model.

Override in subclasses to provide the wall heat-transfer law. Return None for an adiabatic parcel.

Parameters:

carrier – The fresh closed-carrier instance created by build_closed_carrier_net(). Subclasses may read carrier attributes (diameter, phase, _meta, …) to build the closure.

class bloc.reactors.plug_flow.PFRThinShell(gas, *args, **kwargs)#

Bases: PFR

PFR engine with thin-shell local-temperature radial heat loss.

Physics#

Heat loss depends on the local gas temperature T(z) rather than the spatial mean. The local loss per unit length is:

\[q'(z) = -\frac{T_{gas}(z) - T_{amb}}{R_{tot} \cdot L}\quad [W/m]\]

Because the flux is evaluated at each checkpoint via a callable that reads reactor.phase.T, a single forward pass is sufficient — no Forward-Backward Sweep is needed.

Use cases#

  • Quartz-tube / lab-tube reactors where axial wall conduction is negligible (thin, low-conductivity walls).

  • Scenarios where the gas temperature profile is steep and a spatial-mean-T hypothesis would over-predict the loss in the hot inlet zone.

See also

PFRWallProfile

prescribed wall-temperature profile.

PFRHomogeneousShell

thick-shell homogeneous loss.

SOLVER_MODE: ClassVar[str] = 'fixed_checkpoint'#
thermal_resistance_stack#
wall_thermal_report#
spatial_mean_temperature#
heat_loss#
solve_forward()#

Integrate the PFR with the local-T heat loss and return the spatial profile.

Single-shot entry point for standalone use and tests. Builds a PFRThinShellNet and runs one advance pass.

Returns:

Spatial profile with extra columns t and x.

Return type:

ct.SolutionArray

NETWORK_CLASS: ClassVar[type | None] = None#
before_update_state(y)#

Inject a pending initial state into CVODE’s y before the first eval.

heat_flux_factory(carrier)#

Return a heat-flux callable Q(t) [W/m²] for this wall model.

Override in subclasses to provide the wall heat-transfer law. Return None for an adiabatic parcel.

Parameters:

carrier – The fresh closed-carrier instance created by build_closed_carrier_net(). Subclasses may read carrier attributes (diameter, phase, _meta, …) to build the closure.

class bloc.reactors.plug_flow.PFRGasTemperatureProfile(gas, *args, clone=False, diameter=0.1, mass_flow_rate=0.0, **kwargs)#

Bases: PFR

PFR engine with a prescribed GAS temperature profile T_gas(x).

Physics#

This is the Dirichlet-on-gas boundary condition: the gas temperature itself is prescribed as a callable T_gas(x) and the energy equation is switched off. Only the kinetics evolve; at every spatial checkpoint the network resets the parcel temperature to T_gas(x):

\[T_{gas}(x) = T_{prescribed}(x), \qquad \frac{dY_k}{dt} = \frac{\dot\omega_k W_k}{\rho}\]

No wall film, no radiation and no resistance stack are involved — the temperature is imposed, not computed from a heat balance. Contrast with PFRWallProfile, which prescribes the wall temperature and lets the gas lag behind it through a convective + radiative film.

Numerically the parcel is marched by the shared march_lagrangian_parcel() with energy_off=True; the temperature is re-imposed before each checkpoint advance so the stepwise-constant history converges to the continuous profile as n_points grows. This is the closed-parcel / OO counterpart of the procedural solve_fixed_position_temperature_profile_pfr().

Use cases#

  • Kinetics validation against a measured in-stream gas temperature (thermocouple trace) where you want predicted species, not a predicted temperature.

  • Decoupling chemistry from the energy balance to isolate mechanism behaviour under a known thermal history.

See also

PFRWallProfile

prescribed wall temperature with convective + radiative coupling (gas temperature is free).

solve_fixed_position_temperature_profile_pfr()

procedural equivalent.

SOLVER_MODE: ClassVar[str] = 'fixed_checkpoint'#
IMPOSED_GAS_T: ClassVar[bool] = True#
diameter = 0.1#
mass_flow_rate = 0.0#
T_gas_K: float | None = None#
x_position: float = 0.0#
NETWORK_CLASS: ClassVar[type | None] = None#
before_update_state(y)#

Inject a pending initial state into CVODE’s y before the first eval.

heat_flux_factory(carrier)#

Return a heat-flux callable Q(t) [W/m²] for this wall model.

Override in subclasses to provide the wall heat-transfer law. Return None for an adiabatic parcel.

Parameters:

carrier – The fresh closed-carrier instance created by build_closed_carrier_net(). Subclasses may read carrier attributes (diameter, phase, _meta, …) to build the closure.

class bloc.reactors.plug_flow.PFRNetwork(reactor)#

Unified network driver for any PFR subclass.

A generic single-pass driver that runs the fixed-checkpoint _march_pfr_to_length() spatial march for any PFR subclass. Specialised stage networks (PFRWallProfileNet for the tube furnace, PFRHomogeneousShellNet / PFRThinShellNet for the shell models) wrap the same marcher with their wall-loss specifics; this class is the minimal fallback driver.

The network is Boulder-compatible: it exposes time and an advance() method, and wires into the NETWORK_CLASS attribute on each PFR subclass.

Parameters:

reactor – A PFR (or subclass) instance that has been fully configured (_meta populated by the STONE builder).

reactor#
property time: float#

Residence time [s] after the last advance.

advance(time=0.0)#

Run the spatial march and update the stage reactor outlet state.

The time argument is ignored (Boulder calls advance(1.0) by convention); the actual integration length is taken from reactor._meta['length'].