bloc.reactors.plug_flow#
Plug-flow reactor (PFR) engines and wall-model subclasses.
Classes#
Plug Flow Reactor (PFR) engine — closed Lagrangian parcel march. |
|
PFR engine with a prescribed wall-temperature profile. |
|
PFR engine with homogeneous-shell radial heat loss. |
|
PFR engine with thin-shell local-temperature radial heat loss. |
|
PFR engine with a prescribed GAS temperature profile T_gas(x). |
|
Unified network driver for any |
Module Contents#
- class bloc.reactors.plug_flow.PFR(gas, *args, **kwargs)#
Bases:
cantera.ExtensibleIdealGasConstPressureMoleReactorPlug 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:
A fresh carrier reactor is built by
build_closed_carrier_net()from the stage reactor’s mechanism (_mech_source) and type.The carrier is marched from inlet to outlet.
The outlet TPX is copied back to the stage reactor.
Subclasses override
heat_flux_factory()and/orSOLVER_MODEto express the wall heat-transfer hypothesis:PFRWallProfile— prescribedT_wall(x)with forced-convection + grey-gas radiation, adaptivenet.stepmarch.PFRHomogeneousShell— radial insulation stack, spatial-mean temperature, Forward-Backward Sweep.PFRThinShell— radial insulation stack, localT(z), single forward pass.
Why not
ct.FlowReactor?#Cantera’s
FlowReactoris a steady-state plug-flow model that is integrated byReactorNetover 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 aFlowReactormust run alone in its network — it cannot share aReactorNetwith 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_factoryand full compatibility with Boulder’s time-based stage networks.See also: https://cantera.org/dev/python/zerodim.html#cantera.FlowReactor
- param gas:
Cantera
Solutionat the initial thermodynamic state.
Notes
_metamust 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
ybefore 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
Nonefor 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:
PFRPFR 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 setsreactor.T_wall_Kandreactor.x_position; theafter_evalhook 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
PFRHomogeneousShellthick-shell radial loss.
PFRThinShellthin-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
ybefore 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
Nonefor 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:
PFRPFR 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_kWis converged by a Forward-Backward Sweep (FBS): an adiabatic predictor pass gives the mean T, which yields a firstPhi_kWestimate; the corrector repeats with the updated wall flux until|ΔΦ| / Φ < 1 %.The heat loss enters the Cantera ODE via a
ct.Wall(area = 1 m²) whoseheat_fluxcallable distributesPhi_kWuniformly 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
PFRThinShellinstead.
See also
PFRWallProfileprescribed wall-temperature profile.
PFRThinShellthin-shell local-T radial loss.
- SOLVER_MODE: ClassVar[str] = 'fixed_checkpoint'#
- thermal_resistance_stack()#
Build the radial-conduction + external-convection resistance list.
Uses
_metageometry 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, includingheat_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_lengthin thexextra 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
xextra column (axial position [m]).- Returns:
Length-weighted mean temperature in °C. Falls back to the unweighted mean only if
states.xis 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
PFRHomogeneousShellNetand delegates to its_spatial_ode_pass. This is the single-shot entry point for standalone use and tests; the FBS convergence loop inPFRHomogeneousShellNet.advancecalls_spatial_ode_passdirectly.- 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]) andx(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
ybefore 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
Nonefor 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:
PFRPFR 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
PFRWallProfileprescribed wall-temperature profile.
PFRHomogeneousShellthick-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
PFRThinShellNetand runs oneadvancepass.- Returns:
Spatial profile with extra columns
tandx.- Return type:
ct.SolutionArray
- NETWORK_CLASS: ClassVar[type | None] = None#
- before_update_state(y)#
Inject a pending initial state into CVODE’s
ybefore 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
Nonefor 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:
PFRPFR 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 toT_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()withenergy_off=True; the temperature is re-imposed before each checkpoint advance so the stepwise-constant history converges to the continuous profile asn_pointsgrows. This is the closed-parcel / OO counterpart of the proceduralsolve_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
PFRWallProfileprescribed 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
ybefore 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
Nonefor 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
PFRsubclass.A generic single-pass driver that runs the fixed-checkpoint
_march_pfr_to_length()spatial march for anyPFRsubclass. Specialised stage networks (PFRWallProfileNetfor the tube furnace,PFRHomogeneousShellNet/PFRThinShellNetfor 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
timeand anadvance()method, and wires into theNETWORK_CLASSattribute on eachPFRsubclass.- Parameters:
reactor – A
PFR(or subclass) instance that has been fully configured (_metapopulated 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
timeargument is ignored (Boulder callsadvance(1.0)by convention); the actual integration length is taken fromreactor._meta['length'].