Conservation diagnostics
Mass- and energy-conservation ledgers, advanced step by step with record_substep!, and the sensible-energy log that the diagnostics keyword of solve records in extras.diagnostics_log. These names live in Pyrolysis.Diagnostics (also re-exported by Pyrolysis.Internal). See the Technical Reference's conservation-diagnostics chapter.
Pyrolysis.Diagnostics — Module
DiagnosticsConservation diagnostics.
MassConservationTracker— per-component mass ledger.EnergyConservationTracker— sensible-energy ledger of the temperature equation (E = Σ ρcp·T·Vover every component), balanced against the terms the discretized RHS assembles.DiagnosticsStateandrecord_substep!— advance both ledgers by one step's boundary fluxes and volumetric sources and log a row everystridesteps. The test suite drives them step by step to check conservation;solvedoes not.DiagnosticsCallback—DiscreteCallbackbuilder that runs arecord!(u, t)closure on every accepted step.solveuses it to logsnapshot_diagnostics_rows (the sensible energy at the step) when diagnostics are requested.
Pyrolysis.Diagnostics.DiagnosticsState — Type
DiagnosticsStateLedger state advanced by record_substep!. Holds:
- the running mass and energy trackers,
- a row log written every
striderecorded steps, - the time of the last recorded step (for the substep
dt).
Pyrolysis.Diagnostics.DiagnosticsState — Method
DiagnosticsState(mesh, material; stride=10) -> DiagnosticsStateInitialize the diagnostics state with fresh trackers seeded from the current mesh state (this becomes t=0 baseline).
Pyrolysis.Diagnostics.EnergyConservationTracker — Type
EnergyConservationTrackerSensible-energy ledger of the temperature equation. The stored quantity is
E(t) = Σ_i ρcp_i · T_i · V_i, ρcp = Σ_j ξ_j cp_j(T) (every component)and the ledger accumulates the terms the discretized RHS actually assembles:
ΔE(t) = ∫ q_cond_boundary · A · dt
+ ∫ Q_rxn dV · dt
+ ∫ S_conv dV · dtq_cond_boundary is the conducted boundary flux — the energy the boundary cell receives. The surface energy balance has no gas-enthalpy term, so it equals the applied boundary flux. The residual closes to roughly integrator tolerance × |E(0)| for constant composition and volume; with active reactions or a moving mesh it also carries the composition-change term ∫ T ∂(ρcp)/∂t dV dt and the volume term ∫ ρcp T ∂V/∂t dt, which the per-substep accumulation below does not reconstruct. NOTE: because it is built from the terms the RHS actually assembles, this ledger closes even if the RHS itself double-counts a mechanism — it audits discrete bookkeeping, not the physical model.
Fields
initial_energy::Float64—Σ ρcp · T · Vat t=0.cumulative_boundary_conduction::Float64—∫ q_cond_boundary · A · dt.cumulative_reaction_heat::Float64—∫ Q_rxn dV · dt.cumulative_advection_sink::Float64—∫ ∫ S_conv dV · dt.last_time::Float64— Time of last update [s].
Pyrolysis.Diagnostics.EnergyConservationTracker — Method
EnergyConservationTracker(mesh::Mesh1D, material)Initialize tracker with the current sensible energy.
Pyrolysis.Diagnostics.MassConservationTracker — Type
MassConservationTracker{NC}Tracks cumulative mass for each component throughout simulation.
Fields
initial_mass::NTuple{NC, Float64}: Initial mass per component [kg]cumulative_efflux::NTuple{NC, Float64}: Cumulative mass leaving domain [kg]last_time::Float64: Time of last update [s]
Pyrolysis.Diagnostics.MassConservationTracker — Method
MassConservationTracker(mesh::Mesh1D{NC}, material) where NCInitialize tracker with current domain mass.
Pyrolysis.Diagnostics.DiagnosticsCallback — Method
DiagnosticsCallback(record!)Construct a DiscreteCallback that fires every accepted step and runs record!(u, t) on the integrator's state u::Vector{Float64} and time t::Float64. It only observes the state, so it clears the derivative-discontinuity flag. Call sites supply a record! closure that has access to the live workspace and pushes a row to the log when appropriate; solve pushes snapshot_diagnostics_rows.
record! is held behind a fixed-signature FunctionWrapper, so the callback's type does not depend on the closure's and the integrator it joins keeps a problem-independent type.
Pyrolysis.Diagnostics.check_energy_conservation — Method
check_energy_conservation(tracker, mesh, material; tol=1e-6) -> NamedTupleCompute the energy-ledger residual E(t) − E(0) − Σ contributions.
Returns
NamedTuple with:
residual::Float64—E(t) − E(0) − Σ contributions[J].≈ 0to integrator tolerance for constant composition and volume.relative::Float64— relative magnitude vs|E(0)|.conserved::Bool—relative < tol.current_energy::Float64— current sensible heat [J].
Pyrolysis.Diagnostics.check_mass_conservation — Method
check_mass_conservation(tracker::MassConservationTracker{NC},
mesh::Mesh1D{NC}, material;
tol::Float64=1e-10) -> NamedTupleCheck mass conservation and return detailed diagnostics.
Conservation equation: mcurrent(t) + mescaped(t) = m_initial
Or equivalently: mcurrent(t) = minitial - m_escaped(t)
Arguments
tracker: Mass conservation trackermesh: Current mesh statematerial: Material definitiontol: Relative error tolerance (default: 1e-10)
ALE note
Every component rides the mesh (its only mesh-motion term is the dilation −ξθ, which conserves the cell mass ξV), so under ALE the mass of every component changes only through the transport flux J at the boundary faces — exactly what expected_mass integrates. A fully closed (impermeable) column conserves each component to integrator tolerance (V-CPL-03 P4).
Returns
NamedTuple with:
conserved::Bool: True if mass is conserved within tolerancecurrent_mass::NTuple{NC, Float64}: Current mass in domain [kg]expected_mass::NTuple{NC, Float64}: Expected mass (initial - efflux) [kg]relative_error::NTuple{NC, Float64}: Relative error per componentmax_error::Float64: Maximum relative error across components
Pyrolysis.Diagnostics.compute_domain_mass — Method
compute_domain_mass(mesh::Mesh1D{NC}, material) -> NTuple{NC, Float64}Compute total mass of each component in the domain.
Mass for component j: mⱼ = Σᵢ ξⱼ(i) × V(i)
Only includes active cells (excludes refined-away cells in AMR).
Pyrolysis.Diagnostics.compute_sensible_energy — Method
compute_sensible_energy(mesh::Mesh1D{NC}, material) -> Float64Sensible heat E = Σ_i ρcp_i · T_i · V_i with the mixture ρcp (every component, gas included — Properties.effective_heat_capacity). Only active cells (AMR-aware).
Pyrolysis.Diagnostics.energy_conservation_report — Method
energy_conservation_report(tracker::EnergyConservationTracker,
mesh::Mesh1D, material) -> StringGenerate a human-readable energy conservation report.
Useful for post-processing and debugging.
Pyrolysis.Diagnostics.mass_conservation_report — Method
mass_conservation_report(tracker::MassConservationTracker{NC},
mesh::Mesh1D{NC}, material) -> StringGenerate a human-readable mass conservation report.
Useful for post-processing and debugging.
Pyrolysis.Diagnostics.record_substep! — Method
record_substep!(state::DiagnosticsState, mesh, material;
q_cond_boundary, Q_rxn_total, S_conv_total,
J_boundary, A, t)Update the trackers with one substep's contributions and append a typed row to the log if step_counter % stride == 0.
Substep size dt = t − state.last_t is computed automatically.
Pyrolysis.Diagnostics.snapshot_diagnostics_row — Method
snapshot_diagnostics_row(t, mesh, material) -> DiagnosticsRowSnapshot at a single point: the sensible energy. The ledger fields (cumulative residual, mass error) are NaN; only record_substep! fills them. This is the row solve logs.
Pyrolysis.Diagnostics.update_energy_tracker! — Method
update_energy_tracker!(tracker; q_cond_boundary, Q_rxn_total,
S_conv_total, A, dt)Update tracker with one substep's contributions.
All *_total arguments are volume-integrals over the cell-source field (Σ_i field_i · V_i) computed from the active RHS state. q_cond_boundary is a face-flux value [W/m²] (multiplied by A and dt internally).
Keyword arguments
q_cond_boundary::Float64— net conducted boundary flux [W/m²], positive into slab.Q_rxn_total::Float64—∫ Q_rxn dV[W].S_conv_total::Float64—∫ S_conv dV[W] (typically negative).A::Float64— Cross-sectional area at this substep [m²].dt::Float64— Substep size [s].
Pyrolysis.Diagnostics.update_mass_tracker! — Method
update_mass_tracker!(tracker::MassConservationTracker{NC},
J_boundary::Matrix{Float64},
A::Float64, dt::Float64) where NCUpdate tracker with boundary mass fluxes.
Arguments
tracker: The mass conservation trackerJ_boundary: Boundary mass fluxes [NC × 2] for top/bottom boundaries [kg/(m²·s)]A: Cross-sectional area [m²]. Under variable cross-section, pass the trapezoidal-in-A average over the substep,A = 0.5 * (A_t_pre + A_t_post). With identity law / non-ALE,Ais constant and equalsmesh.cross_section_area_initial.dt: Time step [s]
Notes
- Convention: Positive J = flux into domain (inflow)
- Mass leaving = -J × A × dt (negative J = outflow)
- Typical case: Jtop < 0 (gas escaping), Jbottom = 0 (impermeable)