Verification¶
Verification asks whether the solver solves its equations correctly; Validation asks whether those are the right equations for real columns. Skarstrom's verification tests compare the solver with closed-form solutions of the governing equations, conservation identities and observed orders of convergence, so a failure means a defect, not a modelling choice. They live in core/test/validation/ and run with the rest of the test suite (ctest -R "^(AdiabaticAdsorption|AdvectionScheme(Conservation|Order|Robustness)|Analytic[A-Za-z]+|BlakeKozenyPressureDrop|ClosedBedEnergyTest|CompressorWork|CSSAcceleration|CyclicSteadyState|DispersionMassBalance|EquilibriumShock|ErgunPressureDrop|ErgunVelocity|ExtendedLangmuirTest|InletTemperatureProfile|Invariants|IsothermalMode|Ldf[A-Za-z]+|LtneRelaxationTest|NumericalVerification|PhysicsParitySerialisation|RobustnessGuards|ThermodynamicConsistency|DesorptionSimpleWave|CompetitiveLangmuirFronts|BlowdownCompositionPath|SkarstromCycleEquilibriumTheory|ThermalFronts|UnitOperations|MetamorphicRelations|MixedLayers|ParameterLimits|DegenerateMixtures|ThermodynamicBound|ExtremeConditions|CycleKpiGridConvergence|IntegratorTolerance|CSSConvergence|ManufacturedSolutions|ValveBedPressure|ExtendedLangmuirFreundlich(Test)?)\.|RobustnessFuzz|CompetitiveSips[A-Za-z]+\.", one alternative per test suite).
The rest of this page is the suite's README.
Analytical verification suite¶
Tests in this directory compare the solver against expected values derived from physics, not against previously-recorded output. There are no golden files: if a test here fails, either the solver is wrong or the derivation in the test comment is wrong, and both are checkable by reading.
This complements the existing coverage rather than duplicating it:
| Layer | Where | What it proves |
|---|---|---|
| Unit tests | test/unit/ |
Individual components behave as specified |
| Integration tests | test/integration/ |
Cycles run, and mass is conserved |
| Paper validation | test/papers/ |
Results match published experiments |
| Analytical validation | test/validation/ |
Results match closed-form solutions of the governing equations |
Design rules¶
- The reference is written from the physics, not from
core/src. The helpers invalidation_helpers.hre-derive cylinder geometry, energy inventories and heat fluxes from textbook forms. Do not refactor them to share code with the solver — that would make the tests self-confirming. - Collapse the PDE to something solvable. Most energy tests seal both ends of the bed so that convection and axial conduction vanish identically and every cell obeys the same ODE. The spatial discretisation then cannot influence the answer, so any discrepancy is a defect in the physics.
- Assert that the test can fail. Each test contains a guard confirming the
effect it targets is actually present and large (
ASSERT_GT(gas_share, 0.1),ASSERT_LT(dU_bed, -100.0),EXPECT_TRUE(flow_reversed), and so on). A validation test that passes because the term under test is negligible is worse than no test. - Tolerances come from the derivation. Where a closed form is only
asymptotically exact, the tolerance is computed from the neglected term
(the Henry-limit tests scale theirs by
b*p; the invariant tests set theirs from the relevant equation'sabstol) rather than picked to make the test pass. - Prefer asserting a rate over asserting a number. Several tests check the
observed order of convergence — that halving
dxhalves the error, or that the polytropic work approaches the isothermal limit linearly inn-1. That is a far stronger statement than any single tolerance, and it cannot be satisfied by accident.
Files¶
| File | Covers |
|---|---|
validation_helpers.h |
Independent reference implementations: energy inventories, cylinder areas, moments |
breakthrough_case.h |
One shared, deliberately plain isothermal breakthrough case |
psa_cycle_case.h |
One shared minimal O2/N2 pressurise/feed/blowdown cycle on a 13X bed |
test_energy_balance.cpp |
Closed and open-system energy balances, wall/ambient coupling, cyclic steady state |
test_analytic_transport.cpp |
Residence time, pressure drop, breakthrough time, valve blowdown |
test_analytic_dispersion.cpp |
Physical axial dispersion: inert outlet curve against Ogata-Banks |
test_retarded_dispersion.cpp |
Linear-isotherm advection-dispersion: front retarded by R, width by R^2 |
test_dispersion_mass_balance.cpp |
Component balance with dispersion and Danckwerts inlets, per step and at CSS |
test_advection_schemes.cpp |
Upwind order on a smooth solution, WENO / van Leer less diffusive, conservation, sum y = 1 |
test_equilibrium_shock.cpp |
Langmuir shock front speed against the equilibrium-theory secant balance |
test_ldf_kinetics.cpp |
LDF relaxation at rate k, first- and second-order rate laws |
test_extended_langmuir.cpp |
Extended (competitive) Langmuir loadings, single-component limit, selectivity |
test_thermodynamic_consistency.cpp |
Isosteric heat vs van 't Hoff, Henry limits, monotonicity |
test_adiabatic_adsorption.cpp |
Adiabatic breakthrough temperature plateau against the energy balance |
test_ltne_relaxation.cpp |
Two-temperature (LTNE) gas-solid relaxation rate, bed-wall energy conservation |
test_physics_parity.cpp |
Ergun / Blake-Kozeny pressure drop, isothermal mode, inlet temperature ramps, serialisation |
test_compressor_work.cpp |
Polytropic work, isothermal and reversible-adiabatic limits |
test_numerical_verification.cpp |
Order of accuracy, tolerance independence, scale invariance, flow symmetry |
test_cyclic_steady_state.cpp |
End-of-cycle profile converges to a cyclic steady state |
test_css_acceleration.cpp |
CSS acceleration reduces the cycles to CSS with each advection scheme, and halves them on a deck whose approach to CSS is one slow mode (same KPIs); the jump on exact geometric sequences (exact, limited, skipped); reseeded ramps start at the step start |
test_invariants.cpp |
Maximum principle, composition and loading bounds |
test_robustness_guards.cpp |
Steep fronts and thin walls stay finite and bounded |
test_robustness_fuzz.cpp |
Randomised physical decks converge or fail cleanly, never silently NaN |
test_extreme_conditions.cpp |
Extreme pressure ratios, near-zero flow, very short / long steps, high temperature: converge or fail cleanly |
test_thermodynamic_bound.cpp |
Separation energy at CSS against the reversible pump work, the bed exergy change and the minimum work |
test_parameter_limits.cpp |
Rates of approach to parameter limits (k_LDF, dH, wall, Langmuir b, pressure ratio, trace feed); degenerate mixtures |
test_metamorphic_relations.cpp |
Equivalent decks give the same solution: component order, identical layers, split steps, units, cycle phase at CSS |
test_unit_operations.cpp |
Constant-speed evacuation, two-bed pressure equalisation at CSS, buffer-tank fill, without and with Henry adsorption |
test_thermal_fronts.cpp |
Inert-bed thermal front speed; steady exponential temperature profile with wall losses |
test_blowdown_composition_path.cpp |
Blowdown of a binary on linear isotherms: equilibrium-theory composition path y(P), departure scaling with the LDF lag |
test_skarstrom_cycle_equilibrium_theory.cpp |
Full four-step Skarstrom cycle on linear isotherms: light-product purity and recovery and the heavy-product composition against the Knaebel-Hill equilibrium theory, first-order approach under refinement |
test_competitive_langmuir_fronts.cpp |
Binary competitive (constant separation factor) Langmuir: roll-up plateau and both front times against equilibrium theory |
test_desorption_simple_wave.cpp |
Langmuir desorption: outlet times of the simple wave against the method of characteristics, first-order convergence and extrapolation |
test_ldf_thomas.cpp |
LDF-limited linear-isotherm front: whole outlet curve against the Anzelius/Thomas solution, with the exact upwind error |
test_mixed_layers.cpp |
Mixed (multi-solid) layers: identical-copy and inert-diluent blends equal pure layers, two-adsorbent stoichiometric time, mole and energy balance closure |
psa_cycle_case.h |
The shared PSA cycle (pressurise, feed, blowdown) and its O2 product KPIs |
test_grid_convergence.cpp |
Cycle-KPI grid convergence at CSS: monotone convergence, observed order, GCI, all three schemes |
test_integrator_tolerance.cpp |
Error against reltol (BDF slope, abstol floor) on a breakthrough and a cycle; max_step independence |
test_css_convergence.cpp |
Geometric approach to CSS, KPI error left by the stopping rule, Aitken lands on the same KPIs |
test_manufactured_solutions.cpp |
Method of manufactured solutions: observed order of the whole discretisation (all terms, Langmuir, time-dependent) for upwind, WENO, van Leer, isothermal and with the energy balance |
test_extended_langmuir_freundlich.cpp |
Extended Langmuir-Freundlich (toPSAil form): competitive loadings, pure and IAST forms, trace components against the closed form down to y = 1e-5 |
test_valve_bed_pressure.cpp |
Valve law read at the bed-end face or the end cell (settings.valve_bed_pressure), unconnected and connected |
test_competitive_sips_trace_loading.cpp |
Competitive Sips kinds (extended_sips_2, arrhenius_sips) and extended_langmuir_freundlich: trace-component loadings against the published isotherms down to y = 1e-5, the documented continuation below, single-component and competitive paths agreeing, a trace front's first moment |
test_energy_balance.cpp¶
The energy equation had no conservation check before this suite; the mass balance had two.
QuiescentClosedBedIsExactlyPreserved— a closed, adiabatic bed with no source has zero right-hand side. The exact solution is the initial condition. Catches spurious source terms.-
ElectricHeatingGivesAnalyticTemperatureRamp— closed adiabatic bed with a known volumetric heat input. Because the vessel is rigid and sealed, the molar density is a constant of the motion anddP/dt = C R dT/dt, which turns theCpin the temperature equation intoCv:T(t) = T0 + Q t / ( et C0 Cv + rho_b Cps ), P(t) = C0 R T(t)
A straight line with an analytically known slope. This pins the accumulation
term, the P-V work term and the ideal-gas coupling. The test also asserts the
simulated slope is inconsistent with the Cp (no work term) prediction, so
it cannot pass for the wrong reason.
- HeatDrivenDesorptionMatchesLumpedModel — the same bed, now with a Henry
loading and a heater that drives desorption. The spatially uniform PDE system
reduces exactly to three coupled ODEs in (T, C, q), which the test integrates
independently with RK4 at dt = 1e-3. Exercises the heat-of-adsorption source,
the gas/adsorbed-phase mole exchange and the LDF kinetics simultaneously. The
test asserts the latent heat is at least 20 % of the energy input.
- ClosedBedWallAndAmbientEnergyBalanceCloses — first-law bookkeeping over
bed + wall: dU_bed + dU_wall + Q_ambient = 0, plus the requirement that the
bed-to-wall exchange is a pure transfer. This is the test that found the wall
flux-normalisation bug described below.
- WallAmbientCoolingMatchesTwoBodyExponentialSolution — conservation says
the books balance; it does not say the wall responds at the right rate. With
the bed uniform and the wall axially insulated, bed + wall is a two-state
linear system whose solution is a sum of two exponentials with analytically
known eigenvalues. The test asserts the fast (bed/wall equilibration) and slow
(loss to ambient) modes are separated by at least 5x, so a wrong wall heat
capacity or area cannot hide.
- OpenSystemEnergyBalanceConvergesUnderGridRefinement — with flow the
control volume is open: dU = int (n_in h_in - n_out h_out) dt. This is not
expected to hold exactly on a coarse grid, because the temperature equation
discretises convection in non-conservative form and neighbouring cells' fluxes
do not telescope. For a single-component isobaric feed the two forms differ by
T d(rho Cp u)/dz, which vanishes identically, so the residual is pure
discretisation error. The test asserts it converges at first order — the
measured sequence is 1.92 %, 0.95 %, 0.47 %, 0.23 % over 20/40/80/160 cells.
- SealedThermalCycleEnergyBalanceClosesAtCyclicSteadyState — at CSS the net
energy accumulation over a cycle must be zero: whatever the heater puts in
must leave through the wall. The mass balance has had this as a convergence
criterion for a while; the energy balance had no equivalent. A sealed bed is
used so the cycle carries no flow enthalpy and the balance is exact rather
than discretisation-limited.
test_analytic_transport.cpp¶
InertTracerMeanResidenceTimeMatchesVoidVolume— the first moment of a step-change breakthrough curve equalsN_void / n_dotexactly, and is invariant to dispersion. Physical dispersion, numerical smearing from the upwind scheme and cell count all change the shape of the curve; none of them move its centre of mass. Sharp test of void volume, area and the velocity/flow coupling; immune to everything a shape comparison would trip on.SteadyFlowPressureDropMatchesCarmanKozeny— the only quantitative test of the momentum equation. For isothermal compressible Darcy flow,P_in^2 - P_out^2 = 2 N L mu R T / KwithK = eb^3 dp^2 / (180 (1-eb)^2). Every other test in the suite is satisfied by any velocity field that conserves mass.LinearIsothermBreakthroughTimeMatchesCapacity— the mass-balance tests confirm that everything fed in is accounted for; they cannot see when it arrives. The stoichiometric timet_st = V P [ et/(R T) + rho_b k exp(Q/RT) ] / n_dotis the column capacity divided by the feed rate, and like the inert moment it is invariant to dispersion and to mass-transfer resistance. Theexp(Q/RT)factor is what separates it from the inert case, so a sign error in the van 't Hoff exponent shows up here and nowhere else.ValveBlowdownFollowsAnalyticExponentialDecay— the valve law is linear in the pressure difference, so discharging a rigid isothermal vessel gives a first-order decay withtau = V_void * 1e5 / (R T cv). Exercises the valve boundary condition, the coupling between a boundary molar flow and the interior pressure field, and the accumulation term under a 5:1 pressure swing. The test asserts the run really was isothermal before applying the closed form.
test_thermodynamic_consistency.cpp¶
The heat of adsorption is a separate user input from the isotherm
parameters, and nothing in the solver forces them to agree. Thermodynamics does:
q_st = R T^2 (d ln P / d T)_q. These tests evaluate that derivative numerically
from the isotherm functions and check it against the parameter a user would
reach for. No solver run, so they are near-free.
- Henry and single-site Langmuir:
q_stequals the fitted energy parameter exactly, at every coverage. - Dual-site Langmuir:
q_stis loading-dependent, bounded by the two site energies, decreasing as the strong site saturates, and tends to the Henry-weighted mean at zero coverage. - Concentration-basis Langmuir:
q_st = q1 + R T, notq1. Worth knowing — at 300 K that is an extra 2.5 kJ/mol, roughly 8 % of a typical CO2 heat of adsorption, and it lands straight in the energy balance if the heat of adsorption field is filled in with the fittedq1. - Low-pressure Henry limits and monotonicity/saturation bounds.
test_compressor_work.cpp¶
Compression duty is the largest operating cost in most PSA plants, so the power
model feeds every economic KPI. The polytropic work has two limits worth
pinning: n -> 1 must recover the isothermal work n_dot R T ln(P2/P1) (both
the prefactor and the bracket diverge, so this is a genuine L'Hopital limit),
and n = gamma must give the reversible adiabatic work, which must equal the
enthalpy rise n_dot Cp (T2 - T1) computed from the model's own outlet
temperature. Also checks that CalculateMolarFlow inverts CalculatePower.
test_numerical_verification.cpp¶
Verification rather than validation: not "does it solve the right equations?" but "does it solve the equations right?". These catch defects that are present on every grid and are therefore invisible to any fixed-grid comparison.
UpwindSchemeConvergesAtFirstOrder— with dispersion off and no adsorption the exact breakthrough curve is a step with zero variance, so the variance of the simulated curve is a direct measure of numerical diffusion (D_num = u dx / 2) with a known exact value of zero. Halvingdxmust halve it. The test measures the observed order and requires1.0 +/- 0.2, while separately requiring the first moment not to drift (it is fixed by the column capacity and must be grid-invariant).SolutionIsIndependentOfIntegratorTolerance— looseningreltolfrom 1e-9 to 1e-4 must move the outlet composition by far less than the discretisation error. If it does not, every result in the suite is quietly tolerance-dependent.ResultsAreInvariantUnderColumnScaling— the equations see the cross-section only through the superficial velocity. Doubling the diameter while quadrupling the feed rate must reproduce the profiles cell-for-cell. Catches an area, volume or diameter that has leaked into the physics with the wrong power.TopFedBedMirrorsBottomFedBed— a uniform bed has no preferred direction, so a top-fed run must be the mirror image of a bottom-fed one. This is the sharpest available test of the upwinding, which switches branch on the sign of the velocity; an asymmetry there is invisible in every co-current test.
test_invariants.cpp¶
Properties the exact solution has for structural reasons, asserted over every cell and every logged instant rather than through one scalar functional. They catch localised misbehaviour — an overshoot at a front, a negative loading in a cell the front has not reached — that an integral comparison averages away.
TemperatureObeysDiscreteMaximumPrinciple— a scalar convected with no source can never leave the envelope of its initial and boundary values. Upwind preserves this; an unlimited higher-order scheme does not, and the symptom is a small local overshoot that no energy balance would see. Run both ways (hot feed into a cold bed and vice versa).MoleFractionsAndLoadingsStayBounded—0 <= y <= 1,sum y = 1,q >= 0, andqbelow the equilibrium value at the feed partial pressure.BoundsHoldThroughAPressureSwingCycle— the same bounds through a pressurise/feed/blowdown cycle with two competing adsorbates, which reverses the flow. Reversal is where boundedness is most likely to break. The test asserts the flow genuinely reverses.
test_extreme_conditions.cpp¶
Targeted corners an operator reaches by turning one knob to an extreme on an ordinary deck (the fuzz samples at random): pressure ratio 500 (50 bar to 0.1 bar) and 1.0001, blowdown to 1 mbar, feeds of 1e-9 and 1e-10 mol/s, steps of 1 ms and of 1e5 s (which packs ~10^4 bar into the closed bed), a 1e6 s breakthrough with no step cap, a bed at 600, 900 and 1200 K (above the 650 K end of the viscosity and conductivity fits) and the PSA cycle at 700 K. No accuracy is claimed. Each run must converge or fail cleanly (a negative IDA code or an exception, within tightened work caps), every logged state must be finite, and a converged run must be physical: P > 0, T > 0, 0 <= y <= 1 and q >= 0 to their absolute tolerance, sum y = 1 to 1e-3 (the fuzz bound; measured worst 4e-6, at 1 mbar). Each corner has a guard that it really is extreme (the pressure or temperature reached). All corners converge today.
test_thermodynamic_bound.cpp¶
At cyclic steady state the separation energy must be at least the minimum (reversible) work of producing the product streams from the feed. For a plant at CSS exchanging heat with surroundings at T0 and handling ideal gases at T0 (the decks are isothermal), the second law gives W >= sum_out n b - sum_in n b, with b = R T0 [sum y ln y + ln(P/P0)]; with all streams at P0 this is the textbook minimum work of separation. The test checks it as a chain of separately meaningful inequalities on the last cycle:
W_electric >= W_rev >= dB_bed >= W_min
W_electric is the engine's reported energy, the integral of every endpoint's logged pump power (the E_total of the energy KPI; polytropic, eta = 0.7, n = 1.4); W_rev the reversible isothermal work of the same pumps on the same logged flows (pump model consistency); dB_bed the exergy change of the instantaneous streams at the bed ends (the bed's own second law: dispersion, pressure drop and finite-rate adsorption can only destroy exergy); W_min the mixed products at their delivery pressures (the ideal-mixing bound). The slack for the not-quite-CSS cycle is R T0 (max |ln y| + max |ln P/P0|) times the measured component imbalance (< 0.15 J).
VacuumSwingEnergyExceedsMinimumSeparationWork— feed and product at 1 bar, blowdown to 0.2 bar through a vacuum pump: 2486 J >= 1513 J >= 1245 J >= 172 J (pure separation work); second-law efficiency 6.9 %.PressureSwingEnergyExceedsMinimumWork— 1 -> 3 bar with compressors, light product delivered at 3 bar (its pressure exergy counts): 15004 J >= 9050 J >= 8691 J >= 5851 J (of which 266 J separation).
test_parameter_limits.cpp¶
Limits as a parameter goes to zero or infinity, asserted through the RATE of approach over a geometric sequence (observed order, tolerance from the next term of the expansion), plus two degenerate mixtures.
LdfFrontApproachesEquilibriumTheoryAsOneOverK— linear isotherm, no dispersion. The cumulants of the semi-discrete column are exact: the mean is t_st whatever k is, and the variance is 2 (L/u_s) H / k plus a k-free upwind part, so var(k) - var(2k) = (L/u_s) H / k. Measured: mean constant to 1e-3, order in 1/k 1.002, and the size within 1.1-1.3 % at y = 0.005, 0.5-0.6 % at y = 0.0025; the error is O(y) (the uptake changes the gas velocity) and its extrapolation to y = 0 leaves 0.04 %.VanishingLdfCoefficientGivesAnInertTracer— k -> 0: the outlet curve approaches the inert tracer's at order 0.98-0.99 in k, and the deficit behind the front, 1 - exp(-k H L/u_s), gives deficit/k = 120.775 s extrapolated to k = 0 against H L/u_s = 120.766 s.AdiabaticRunApproachesIsothermalAsHeatOfAdsorptionVanishes— the temperature rise and the outlet-curve shift are O(dH) (order 1.000 for dH = 10, 5, 2.5 kJ/mol); at dH = 0 the run matches the isothermal mode to 1.3e-5 y_feed, 1.3 % of the smallest dH effect.WallAndBedBecomeIsothermalAsHeatCapacityAndExchangeGrow— the wall's excursion falls as 1/C (order 0.998, 1.000 per decade of wall heat capacity); with that wall, the bed's falls as 1/h_i once the bed-wall time constant is below the uptake time (order 0.99, 1.00 from h_i = 1e5 W/m2K).LangmuirRunReducesToHenryRunAsAffinityVanishes— b -> 0 with m b fixed: the simulated outlet curve approaches the Henry run's at order 1.00 in b' p.PressureSwingSeparationVanishesAsThePressureRatioGoesToOne— at CSS the heavy product's composition deviation is O(s) (order 0.92, 0.90) and the light product's enrichment O(s^2) (order 1.91, 1.87) for P_high/P_low = 1 + s, s = 0.1 .. 0.025; with no swing the enrichment is 2.3e-7 (the feed step's own pressure drop).TraceFeedReducesToTheSingleComponentLimit— a trace of O2 in N2 on a competitive Langmuir: the carrier's loading tends to the pure-N2 run's at order 1.00 in y, the normalised trace curve to a limit at order ~0.95, and that limit's first moment (50.524 s, extrapolated) to linear chromatography with the trace's Henry constant at the carrier loading (50.507 s).DegenerateMixtures.IdenticalComponentsKeepTheirFeedRatio— O2 split into O2 and Ar with identical isotherm, kinetics and heat of adsorption, 2:1, through three PSA cycles: y_O2 = 2 y_Ar to 1.3e-10 and q_O2 = 2 q_Ar to 1.2e-12 mol/kg in every cell at every instant (tolerance one error-test weight), while the pair is separated from N2 (y_O2 0.04 .. 0.34).DegenerateMixtures.ZeroAffinityComponentBehavesAsAnInert— a Henry constant of zero (and a 40 kJ/mol heat of adsorption that must do nothing) reproduces the inert tracer to 3e-11, with the loading at 3e-19 mol/kg.
test_metamorphic_relations.cpp¶
Pairs of runs that pose the same physical problem in two ways and must give
the same answer, compared field by field (pressure, temperature, every mole
fraction and loading, every cell, the end of every step). No reference
solution is needed. The tolerance comes from the integrator: two equivalent
runs are two adaptive-step integrations of one semi-discrete system, each
within its global error E of it, so they may differ by 2E (E measured in the
test by tightening reltol 100x) plus one error-test weight w = reltol |x| +
atol (atol fixed in the code, docs/numerics.md).
ComponentOrderDoesNotChangeTheSolution— reversing the fluid's component list on the shared PSA cycle (two adsorbates, heat of adsorption, wall exchange, flow reversal). Measured: y differs by 1.4e-7 to 2.7e-7, against a tolerance of 1.1e-6 to 1.6e-6 (E = 3.5e-7).IdenticalLayersEqualOneLayer— the packing split into two and four identical layers (dispersion, heat of adsorption, bed-wall exchange and harmonic-mean face permeability all on, front across the interfaces). Bitwise identical on Linux.SplitStepEqualsSingleStep— a step cut into two halves with the same boundary conditions. Approximate by construction: every step restarts the integrator (first order, initial step, re-solved algebraic state), which may move a single unknown by up to sqrt(N) w (the WRMS error test over N unknowns), so the tolerance is 2E + sqrt(N) w. Measured on y_O2: 3.7e-8 (0.6 w); on a longer run it followed w down to the absolute-tolerance floor as reltol was tightened, the signature of an integrator effect.InputsInOtherUnitsGiveTheSameSolution— the same deck with its inputs in cm, mm, bar, mbar, degC, min, g/cm3, kJ/(kg K) and mol/h. The converted values equal the SI ones to 1e-14 and the solutions agree (y: 6.9e-8, E = 6.5e-8).RotatedCycleReachesTheSameCyclicSteadyState— the engine solves one bed, so the beds of a symmetric multi-bed cycle are that bed at other phases and are identical by construction; the testable consequence is that the CSS cannot depend on which step is listed first. An isothermal pressurise / feed / blowdown / purge cycle, the purge fed with the feed's product through a connection, is started two steps later: listed before the feed, the purge then reads the PREVIOUS cycle's feed instead of the same cycle's. After 30 cycles the two CSS agree to 9.4e-8 in y (tolerance: twice each run's own cycle-to-cycle change, 1.7e-7), while their first cycles differ by 0.21.
Not testable as a relation: "one bed = two beds in series of half length". Other beds exist only through connections that replay a partner step's previous run, so two beds working in series at the same time cannot be written as a deck that should equal one long bed. The layer split is the exact statement the engine can express (a series connection with no dead volume).
test_unit_operations.cpp¶
The unit operations between and around beds, each reduced to a lumped ODE: the
bed is isothermal, the pressure drop negligible, and with the same Henry slope
on both gases the inventory is V a P, a = eps/(R T) + rho_b K, whatever the
composition.
ConstantSpeedEvacuationDecaysExponentially: a volumetric-flow outlet (constant pump speedS) givesP = P0 exp(-t/tau_S),tau_S = V a R T / S. Fitted over three time constants: 52.366 s against 52.360 s inert and 247.03 s against 246.96 s with adsorption, which stretches the decay by the Henry retardation factor (4.7). About 0.03 s.TwoBedEqualisationConservesMolesAndDecaysAtTwiceTheRate: equalisation through a linear valve, as two connected steps of the single-bed model (pressurise, ED, blow down, EU) run to the cyclic steady state (30 cycles). The mole balance keepsP_ED + P_EU = P_H + P_L(to 1.2e-5 inert, 2.7e-5 with adsorption) and the difference decays asexp(-t/tau_E),tau_E = 1e5 V a / (2 cv): fitted 31.699 s against 31.699 s inert, 149.53 s against 149.51 s with adsorption. The factor 2 is the signature of two beds exchanging gas rather than one venting to a fixed pressure. About 2.8 s.TwoBedEqualisationWithLangmuirConservesTheInventory: the same with a Langmuir isotherm (K P = 0.5 .. 1.5). The pressures no longer add up (the sum falls 9.0 % short, the guard); what is conserved is the inventoryn(P_ED) + n(P_EU) = n(P_H) + n(P_L),n = eps P/(R T) + rho_b q*(P), measured to 1.1e-4 (tolerance 2e-3, the LDF lag). About 2.3 s.BufferTankFillDeliversTheVentedMoles: a fill step drawing 60 % of a vent step's moles from a buffer tank raises the pressure by 60 % of the vent's pressure drop (103 760 Pa against 103 759 Pa inert; 103 766 against 103 757 with adsorption), linearly in time. About 0.04 s.
test_thermal_fronts.cpp¶
Convective heat transport in a flowing bed, which the sealed-bed energy tests cannot see.
InertBedFrontTravelsAtEnergyBalanceSpeed: a 10 K step in feed temperature into an inert, adiabatic bed. At constant pressure the gas internal energy per volumeeps Cv P/Ris constant, so the energy and mole jump conditions givew = G1 Cp / (rho_b Cps + eps C1 Cp). The outlet temperature's first moment is 347.54 s againstL/w = 347.63s (2.5e-4); without the gas heat-capacity term it would be 323.1 s, so the test sees that term (7 % of the total) with a 1e-3 tolerance. The jump conditions make the characteristic speeds on both sides equal tow(a contact discontinuity), so the smeared front still travels atw. About 0.1 s.SteadyFlowWithWallLossGivesExponentialProfile: hot gas through a bed losing heat through the bed-wall and wall-ambient films in series reachesT(z) = T_amb + (T_in - T_amb) exp(-lambda z),lambda = U pi d_i / (n_dot Cp),1/(U pi d_i) = 1/(h_i pi d_i) + 1/(h_o pi d_o)(lambda L = 1.34). On 25, 50 and 100 cells the profile and outlet errors fall at first order (outlet 9.3e-3, 4.7e-3, 2.35e-3), and the finest outlet error equals the leading upwind termtheta (lambda L)(lambda dx)/2 = 2.352e-3(measured 2.345e-3). About 0.5 s.
test_blowdown_composition_path.cpp¶
A pressure-change step. A uniform bed of a binary on linear isotherms
(K_A = 8 K_B, isothermal) blows down through a valve with the other end
closed. In local equilibrium the composition stays uniform and the outflow
carries it, so d(a_A P y)/y = d(a_B P (1-y))/(1-y) with
a_i = eps/(R T) + rho_b K_i, which integrates to the Knaebel-Hill path
P/P0 = (y0/y)^(1/(1-beta)) ((1-y)/(1-y0))^(beta/(1-beta)),
beta = a_B/a_A. The path depends on the isotherm slopes only, not on the
valve or the time.
FollowsEquilibriumTheoryPath: blowing down 5 -> 1 bar enriches the strong component from 0.30 to 0.7615. The largest departure ofln Pfrom the path is 7.1e-4 atk = 5 1/sand 3.6e-4 atk = 10 1/s: it is the LDF lag, halving with1/k(ratio 2.00), and its Richardson extrapolation tok -> infvanishes. The final composition matches the path at the final pressure, and the composition stays uniform to 1.3e-5. Guard:beta < 0.3, so a model without the adsorbed phase (beta = 1,yconstant) fails by 0.46 iny. About 0.4 s.
test_skarstrom_cycle_equilibrium_theory.cpp¶
A full PSA cycle against the equilibrium theory of Chan, Hill & Wong (1981)
and Knaebel & Hill (1985) (also Ruthven, Farooq & Knaebel 1994, ch. 4). The
blowdown test's binary (beta = a_B/a_A = 0.251, isothermal, linear
isotherms, 5 mm particles so the pressure drop is about 20 Pa) runs the
four-step Skarstrom cycle with pressurisation by light product: pressurise
1 -> 5 bar from the top, feed y_F = 0.3 at 5 bar until the shock (speed
beta_A v_F) reaches the top, blow down to 1 bar from the bottom, purge
from the top with light product at 1 bar. Started from pure B at 1 bar, the
state a sufficient purge leaves, the cycle is at its cyclic steady state from
the first cycle. With the purge-to-feed ratio of actual volumes
gamma >= 1 and Pi = P_H/P_L the light product is pure B and
recovery = (1 - beta)(1 - 1/(Pi (1 - y_F))) - (gamma - 1)/(Pi (1 - y_F)),
and the heavy product (blowdown + purge effluent) has
y_W = P_H y_F / (P_H (beta + (1 - beta) y_F) + P_L (gamma - beta)). The gas
for pressurisation and purge is supplied as pure B, which is what the
theory's product is, and the net light product is counted as the theory
counts it.
PurityAndRecoveryMatchKnaebelHill: atgamma = 1and1.5on 50 cells (k = 20 1/s), the purity is 0.9954 and 0.9956 (theory 1), the recovery 0.53375 and 0.39095 (theory 0.53503 and 0.39217) and the heavy product 0.4759 and 0.4103 (theory 0.4796 and 0.4135). The gap is the smeared feed shock leaking A as it reaches the top; tolerances are regression bounds over it (purity 6e-3, recovery 2e-3, heavy product 5e-3). The purge term, the drop in recovery between the two purge ratios, is 0.142793 against 0.142857 (tolerance 5e-4). All the A fed leaves over the cycle (to 2e-5), and the KPIs agree between the last two of three cycles. Guards:beta < 0.3, so a model without the adsorbed phase (recovery 0) fails; the purge term is many times the recovery tolerance. About 1.4 s.GapVanishesUnderRefinement(slow tier): refining the grid and the LDF rate together (100/40, 200/80, 400/160 cells / 1/s) halves the gap at each level (purity 2.30e-3, 1.14e-3, 5.67e-4; recovery 6.35e-4, 3.15e-4, 1.56e-4), so the cycle converges to the theory at first order, the order of the captured shock (the1/kpart is a few per cent of the gap). The Richardson extrapolation of the two finest levels gives purity 1.00001, recovery 0.535030 (theory 0.535027) and heavy product 0.479638 (theory 0.479631), within a fifth of the finest gap. About 16 s.
test_competitive_langmuir_fronts.cpp¶
Competitive adsorption fronts. Two trace adsorbates on an extended Langmuir
isotherm with a common capacity (constant separation factor alpha = 3) are
fed into a clean bed. Equilibrium theory (Rhee, Aris & Amundson 1970; Ruthven
1984 ch. 9) gives two shocks with the weak component alone, ROLLED UP above
its feed level, between them. The coherence condition on the slow front (both
components' jumps give the same speed) fixes the plateau as the positive root
of alpha x^2 - [(alpha-1) b0 - alpha + D_f] x - (alpha-1) b0 = 0
(x = K_B p_B*, b0 = K_B p_B0, D_f = 1 + K_A p_A0 + K_B p_B0), and the
jump conditions give the two front times.
TwoFrontsAndRollUpMatchEquilibriumTheory: on 200 cells the plateau of the weak component is1.5363e-3against1.5352e-3(a 1.54x roll-up; error 0.073 %), the fast front's first moment is 312.39 s against 312.40 s (3e-5), and the slow front is 532.60 s in both the strong component and the weak one's drop from the plateau, against 532.72 s (2.3e-4). Tolerancey_A + y_B = 2e-3relative, anO(y)bound on the sorption effect, which was measured by varying the feed level (plateau error about0.35 (y_A + y_B)). The quantities are converged in everything numerical: 10x inreltol,max_step0.1 to 2 s, 100 to 400 cells and LDF 5 to 20 1/s move them by < 1.5e-5. First moments are mass balances, so the numerical and LDF smearing of the shocks does not move them; the partial integrals interpolate at the window ends, because the solver logs at irregular instants. Guards: the roll-up exceeds 1.4x and shows in the simulated outlet, and A is absent from the plateau. About 3.4 s.
test_desorption_simple_wave.cpp¶
The unfavourable direction of the Langmuir front that test_equilibrium_shock
pins: a bed saturated at a trace level (K p0 = 3) purged with clean carrier.
The front spreads into a simple wave in which each concentration travels at
u / (1 + (rho_b/eps) f'(c)), so the outlet sees c at
t(c) = (L/u) (1 + M / (1 + K p)^2) (method of characteristics; de Vault 1943,
Ruthven 1984 ch. 8), and the whole wave probes the isotherm slope rather than
its secant.
LangmuirDesorptionFollowsCharacteristics: on 100 and 200 cells the outlet times of the levelsc/c0 = 0.6, 0.5, 0.4converge at first order (measured 0.97, 1.00, 0.98) tot(c), and the Richardson-extrapolated times agree witht(c)to 3e-4, 1e-5 and 1.2e-4 (tolerance 1e-3: LDF lag1/(k t), theO(y0)sorption effect and the mole-fractionabstol). The sorption effect was measured:1.3e-3oft(c)aty0 = 1e-3,1.3e-4aty0 = 1e-4, which is why the test usesy0 = 1e-4. Guard: the wave spreads over 450 s, more than 5x its head time, andf'(c)varies 16-fold across it. About 0.5 s.
test_extended_langmuir_freundlich.cpp¶
The extended Langmuir-Freundlich isotherm with T-dependent parameters (Yang
1987; toPSAil), q_i = (k1 + k2 T) z_i / (1 + sum z_j),
z_i = k3 exp(k4/T) (P y_i / P_ref)^{n_i}, 1/n_i = k5 + k6/T, with the
H2/CO parameters of toPSAil's case_study_4.0, against the closed form written
in the test.
CompetitiveLoadingsMatchClosedFormAt303K/At343K: loadings seeded in a closed uniform bed (the solver's path) match to 1e-6 at 11.15 bar, 303 K (H2 0.0468, CO 0.795 mol/kg) and 5 bar, 343 K. Guards: CO cuts the H2 loading to 0.66 of its single-component value; at 343 K, freezing q_s and 1/n at their 303 K values (k2 = k6 = 0, the only case extended Sips 2 can represent) moves the CO loading by more than 1 %.PureEvaluationAndIastMappingMatchClosedForm:EvalIsothermand the IAST Sips term within 1e-12 (measured 9e-16 and 4e-16), at 273–373 K and 1 kPa–20 bar.TraceComponentKeepsItsLoading: the pure-component form of CO at 11 bar, 303 K matches the closed form to 1e-12 at y = 1e-2 down to the continuation threshold y_0 = 1e-5, and is exactly 0 at y = 0. Guard: the former step y^2 / (y^2 + 1e-10) moved it by 1e-4 at 1e-3, 1 % at 1e-4 and 20 % at 2e-5. The competitive path is intest_competitive_sips_trace_loading.cpp. All four take about 0.1 s.
test_valve_bed_pressure.cpp¶
Steady flow through an inert isothermal bed: a set molar flow F enters at the
bottom and leaves through a linear valve, so at steady state the bed side of
the valve law is exactly P_down + 1e5 F / Cv whatever the bed's resistance.
Fine particles on a 10-cell grid make the end cell sit about 1.8 kPa above the
face, so reading the wrong pressure fails by three orders of magnitude more
than the 1 Pa tolerance.
SteadyThroughFlowReadsTheChosenPressure: withfacethe face pressure is 200000 Pa (end cell 201834); withend_cellthe end cell is 200000 Pa (face 198149); outflow equalsFto 1e-6. About 0.1 s.ConnectedValvesReadBothEndCells: a donor step (fed atF, valve out) connected to a receiver step (valve in, bottom vented through a pressure boundary), at the cyclic steady state (18 cycles, end pressure moves < 0.01 Pa per cycle): the face pressures (face setting) or the end-cell pressures (end-cell setting) are1e5 F / Cvapart to 1 Pa. Guard: the end-cell and face differences across the valve differ by 3.8 kPa. About 3.5 s.
test_ldf_thomas.cpp¶
A front shaped by the LDF rate rather than by dispersion: a Henry isotherm with
k = 0.1 1/s (12 transfer units), dispersion off, isothermal, compared point
by point with the Anzelius/Thomas closed form
c/c0 = J(x, tau) = 1 - int_0^x exp(-tau - s) I0(2 sqrt(tau s)) ds
(Ruthven 1984, ch. 8), which the test evaluates by quadrature with its own
Bessel series. The upwind scheme makes the column n tanks in series, whose
transfer function (1 + (dx/u) g(s))^(-L/dx) is known exactly, so the grid
error is not a nuisance to be tolerated but a second prediction.
ReferenceSolutionSatisfiesItsIdentities: the two integral forms ofJagree,J(a,b) + J(b,a) = 1 + exp(-a-b) I0(2 sqrt(ab)), the analytic second derivatives match finite differences, and the cumulants arexand2x. No solver run.LinearIsothermOutletCurveMatchesThomasSolution: on 50, 100 and 200 cells, (1) the outlet variance minus the exact upwind termL dx R^2 / u^2equals the Thomas variance2 (R-1) L / (u k)(measured 2415.5 vs 2416.2 s^2, 0.03 %, on every grid); (2)max |F - J|falls at first order (0.0246, 0.0128, 0.0065); (3) after subtracting the closed-form leading error(L dx / 2) d2F/dz2, the residual falls at second order (2.2e-3, 6.1e-4, 1.7e-4; orders 1.89, 1.84) and is 2.6 % of the error on the finest grid. Guard: a Gaussian curve with the same mean and variance misses the simulated one by 0.042, 250x the finest residual, so the asymmetric Thomas shape is what is being matched. About 1.3 s.
test_mixed_layers.cpp¶
Solved tests for a layer packed with several solids at set mass fractions
w_k. The model gives solid k its own loading at its own bulk density
rho_b,k = (1 - eb) rho_mix w_k, with rho_mix = 1 / sum_k (w_k / rho_k);
the particle voidage and the conductivity mix on the solid-volume fractions
phi_k = (w_k / rho_k) rho_mix, the heat capacity on w_k; all solids in a
cell share the gas and one solid temperature. The tests re-derive these rules
and check the solved model against them, with heats of adsorption, wall
exchange, dispersion and a conductivity-dependent heat dispersion
(Yagi-Kunii) on. The two metamorphic tests use the tolerance of
test_metamorphic_relations.cpp (2E + one error-test weight, E measured by
tightening reltol 100x).
IdenticalCopyBlendEqualsPureLayer: A blended with an identical copy A' at 50/50 and 25/75 gives the pure-A solution in every field and cell (pressure, gas, solid and wall temperatures, mole fractions, loadings), in LTE and LTNE. Measured: y_O2 within 1.1e-7 (tolerance 3.6e-7), T within 4.8e-7 K.InertDiluentEqualsPureLayerAtBlendProperties: A (60 % by mass) blended with an inert solid of another density, voidage, heat capacity and conductivity gives the solution of pure A set by hand to the blend's properties: densityw_A rho_mix(A's bulk density unchanged), voidage and conductivity volume-weighted, heat capacity(w_A cp_A + w_I cp_I) / w_A(the inert's added per kg of A). The blend's logged loading (per kg of all solid) is divided byw_A. Run at reltol 1e-7: at 1e-6 the y_O2 discrepancy (1.9e-7) is 0.75 of its bound, and it falls with reltol to the absolute-tolerance floor, so it is the integrator's. Measured: y_O2 within 5.3e-8 (tolerance 1.3e-7). Each wrong rule fails it: an arithmetic-mean density (y_O2 off by 0.038), a mass-weighted voidage (0.0014), no inert heat capacity (0.77 K), a mass-weighted conductivity (q_O2 off by 2.6e-6, 2.5 to 4.5 times the tolerance; T by 1.8e-4 K).LinearIsothermBreakthroughTimeIsMassWeightedCapacity: a dilute, isothermal tracer on Henry isotherms in a 30/70 blend of two adsorbents (1000 and 2500 kg/m3). The flux-weighted first moment of the outlet curve ist_st = V P [et/(R T) + (1 - eb) rho_mix sum_k w_k K_k] / n_dot. Measured: 130.06 s against 130.04 s (+1.6e-4; tolerance 0.2 %, set by the asserted pressure drop below 0.05 % and the quadrature of the outlet flux). An arithmetic-mean density would give +14 %, a mass-weighted voidage -1.9 %, either pure solid more than 20 %.MoleBalanceClosesOnMixedBed: a non-isothermal breakthrough on two different adsorbents and an inert (45/35/20). Per component, the moles the boundary streams log equal the change of the gas inventory plus the adsorbed inventorysum_i V_i (1 - eb) rho_mix q_i(the loggedq = sum_k w_k q_k), in LTE and LTNE. Measured residual: 2.6e-7 (N2), 6e-8 (O2), tolerance 1e-5.-
EnergyBalanceClosesForHeatDrivenDesorptionFromTwoSolids: a closed, adiabatic, heated bed of two Henry adsorbents with different capacities, heats, LDF rates, densities and heat capacities. The bed is uniform, so it reduces to ODEs in(T, C, q_1, q_2), which the test integrates with RK4. Each solid's latent heat is at least 10 % of the heater input (14 % and 13 %). Measured: T, P and the loading agree to 6e-10 of their change over the run, tolerance 1e-6. -
SolidAtZeroMassFractionTakesNoPart: pure A against A with a strongly adsorbing B atw = 0in the composition (it has no bulk density, no solid-volume fraction and no heat capacity share). Same solution in every field; the deck checks accept the layer and the run-start warnings name B. Measured: y_O2 within 1.2e-7 (tolerance 3.6e-7). AllInertLayerHoldsNothingAndBalances: a layer of an inert solid alone (no isotherm, so every component gets the Inert one) is a valid deck with no warnings. O2 breaks through, every loading stays at round-off (below 1e-12 mol/kg, about 1e-24 measured) and the mole balance closes on the gas alone. Measured residual: 1.0e-6 (N2), 5.8e-6 (O2), tolerance 1e-5.SplittingALayerIntoTwoOfTheSameBlendChangesNothing: a 70/30 blend of A and an inert in one 30-cell layer against the same blend split into two 15-cell layers (separate adsorbent objects). The interface face poses the same equations as an interior face, so the solutions agree; measured: bit for bit in every field.MoleBalanceClosesAcrossAnInterfaceBetweenBlends: a weak bottom layer (40 % A, 60 % inert) under a strong top layer (70 % B, 30 % A), so density, voidage, capacity, heats and conductivity all jump at the interface. Per component, the streams equal the gas and adsorbed inventories of both layers, each with its own blend, in LTE and LTNE. Guard: the O2 front has crossed into the top layer. Measured residual: 1.2e-7 (N2), 1.1e-7 (O2), tolerance 1e-5.
The whole suite takes about 2 s. No defect was found, at the layer interior, at the interface between layers or in the mixing rules.
test_manufactured_solutions.cpp¶
The method of manufactured solutions (MMS): the order of accuracy of the
whole discretisation with every term of the model active at once. The tests
above switch terms off to reach a closed form; here the solution is chosen
instead, substituted into the continuous equations of the documented model
(overall and component balances with dispersion, LDF kinetics on a single-
site Langmuir isotherm, Kozeny-Carman momentum, and in the thermal case the
LTE energy balance with conduction, compression work and heat of
adsorption), and what does not vanish is handed to the solver as a source
through the verification hook UserData::manufactured_sources_
(adsim/equations/manufactured_sources.h: non-serialised, not reachable from
a deck, Python or the web; empty, it leaves every residual bit-identical).
The chosen fields are then the exact solution, and the difference is the
discretisation error alone.
The solution, with zeta = z/L and g(t) = 1 - exp(-t/tau), is smooth and time-dependent (component B inert):
P = P_L + g [dP (1 - zeta) + a sin(pi zeta)]
y_A = y0 + b g (1 - cos(pi zeta)) / 2
u = u_in [1 + c g sin(pi zeta / 2)]
q_A = q*(P_L y0) + d g cos(pi zeta / 2)
T = T0 + e g (1 - cos(pi zeta)) / 2 (thermal case)
It meets the constant boundary data a deck can express: the uniform initial state at t = 0 (loading at equilibrium with the initial gas), a velocity inlet with dy/dz = dT/dz = 0 there (so the Danckwerts inlet holds with the constant feed y0, T0) and a pressure outlet with zero gradients (no dispersive or conductive flux crosses a bed-end face). The sources are derived by hand in the test from the product rule on C = P/(RT) and re-derived by central finite differences of the fields; the two agree to 1e-7 of the largest term of each balance. Errors are measured at t = 2 tau, in the transient, on 20/40/80/160 cells.
SourcesMatchFiniteDifferencesAndEveryTermIsActive— the derivation check, and the guards: in each gas balance every term (accumulation, convection, dispersion, adsorption) is at least 15 % of the largest, both momentum terms exceed 1e4 Pa/m, the Langmuir K p reaches 2-3 (well past the Henry limit), and each energy term other than the (solid-dominated) accumulation would move the temperature by at least 5e-3 K over the run if dropped.ZeroSourcesAreBitIdentical— a run with every source returning zero equals a run without the hook bit for bit. (Separately,bench_solverlogs ofCasas2012_Fig4_50_50_15bar_25CandWard2022_Q100ccmare byte-identical between the hook build and main's residual code, with no measurable change in time per residual.)UpwindOrder,WenoOrder,VanLeerOrder— isothermal. The dispersion, Darcy and face-pressure terms are central, O(dz^2); the convective face is upwind (O(dz)), WENO or van Leer. Measured orders of the L2 error (y, P, q at cell centres, u at faces), the max norm of y and the outlet cell:
| Scheme | y | P | q | u | max | outlet cell | 160-cell L2 of y |
|---|---|---|---|---|---|---|---|
| upwind | 0.97-0.99 | 0.98-1.00 | 0.97-0.99 | 0.98-0.99 | 0.95-0.99 | 0.95-0.99 | 8.5e-5 |
| WENO | 2.04-2.13 | 1.81-2.41 | 2.05-2.28 | 2.45-2.51 | 1.68-1.94 | 1.48-1.72 | 7.0e-7 |
| van Leer | 1.99-2.03 | 1.77-1.92 | 1.96-2.05 | 2.04-2.21 | 1.72-1.90 | 1.72-1.90 | 9.5e-7 |
On 320 and 640 cells every field tends to 1.00 (upwind) and 2 (WENO
1.88-2.23, van Leer 1.97-2.03). The WENO interior error (0.25 L to 0.75 L)
converges at 2.55-2.78 on these grids, the third-order reconstruction
showing before the O(dz^2) central terms take over (2.24 on 640 cells),
and is 1.7 to 4 times below van Leer's. The bed-end faces take the upwind
value in every scheme, which is first order in general (the reason the
outlet-curve orders of test_advection_schemes.cpp are capped at 1); the
Danckwerts conditions force dy/dz = 0 at both ends of this solution, so
there the upwind face is second order and the outlet-cell order rises to 2
as well (1.88 WENO, 1.97 van Leer on 640 cells). The assertions take the
two finest pairs with bounds from these numbers (upwind 0.93-1.07; WENO
1.7-2.7, interior above 2.3; van Leer 1.75-2.3). A deliberate 5 % error in
the dispersion source stalls WENO and van Leer at 9.3e-5 on 160 cells
(130 times the correct error) and fails the order checks.
- ThermalOrder — non-isothermal (LTE, adiabatic bed, constant gas and
adsorbed-phase heat capacities, constant conductivity, heat of adsorption
20 kJ/mol). The energy balance's convective face temperature is
reconstructed with the same scheme as the mole fractions, so T converges
at 0.97-0.99 (upwind), 2.06-2.25 (WENO) and 1.95-2.03 (van Leer) on 20 to
160 cells, and at 1.00, 1.99 and 1.99 from 320 to 640 cells. The other
fields keep their isothermal orders (WENO y 2.08-2.17, van Leer
1.99-2.04). Errors at 160 cells: T 1.6e-5 (WENO) and 2.1e-5 (van Leer)
against 1.7e-3 K (upwind), y 7.2e-7 and 9.7e-7 against 9.0e-5. The
assertions: every field within the isothermal bounds (outlet and max norm
above 1.7), T order 0.9-1.1 (upwind), 1.85-2.5 (WENO), 1.85-2.3 (van
Leer), and the high-order T errors below 5e-5 K and 3 % of upwind's.
Guard: while the face temperature was the upwind value under every scheme
(before the change, see the changelog), T converged at 0.97-1.05 for all
three and pulled the WENO / van Leer y order down to 1.31 / 1.49 between
80 and 160 cells (about 1 on 640), with T errors of 1.6e-4 K at 160 cells.
What the hook covers: the overall and component balances, the loading rate of each component on every adsorbent, the momentum residual as a pressure- gradient source on every Darcy face and on the interior faces of an Ergun layer, and the LTE energy balance. Not covered: the bed-end faces of an Ergun layer (the boundary conditions impose the Ergun law there, so a manufactured solution must satisfy it exactly at the ends), faces next to void cells, LTNE and the wall. All six tests together take about 4 s (Release).
Convergence of cycle results¶
test_grid_convergence.cpp, test_integrator_tolerance.cpp and
test_css_convergence.cpp. The tests above verify the discretisation on
single breakthrough curves. What a user reports is a cycle KPI at cyclic steady state, which carries three
numerical errors at once: the grid, the integrator tolerance and the distance
from CSS. These files measure each one on the shared PSA cycle
(psa_cycle_case.h, with the O2 product KPIs of AddO2ProductKpis: purity,
recovery and the specific energy of a feed compressor). The full grid study
of the example decks, and what each needs, is on the
Numerics page.
CycleKpiGridConvergence.*— the isothermal cycle at CSS on 10, 20, 40 and 80 cells with each advection scheme. The successive differences must keep one sign and shrink (monotone convergence), and the observed order from the three finest grids (Richardson) must lie in[1, 2]for upwind: with errora h + b h^2, same-signed terms put the observed order between the formal order and one more (measured 1.54 to 1.56; offline on 80 to 640 cells it falls to 1.37 and 1.26). WENO and van Leer must converge faster than upwind (larger order, smaller difference at every grid; measured 2.40 to 2.50). Their asymptotic order is capped at 1 by the first-order bed-end faces (test_advection_schemes.cpp), but the KPIs are read at the feed end, where the face carries the feed exactly, and at the product end, where the gradient is small, so that term has a small coefficient. Finally Roache's grid convergence index (safety factor 1.25) of the 80-cell upwind KPI must be a true error band: it must contain the WENO and van Leer 80-cell values, which are ten times closer to the limit (upwind purity 0.39572 +- 0.00046, WENO 0.39611, van Leer 0.39611).IntegratorTolerance.*— IDA's error-per-step control of a BDF method of order k gives a global error~ tol^(k/(k+1)), so the observed slope of log(error) against log(reltol) must lie in[0.5, 1]: measured 0.68 on a breakthrough (1e-3 to 1e-7, against a 1e-12 reference) and 0.77 on the cycle's end-of-cycle loadings. Belowreltol |y| = abstolthe error stops falling: in a dilute breakthrough the loading error stays at 0.75 to 1.4 x abstol (1e-8 mol/kg) from reltol 1e-8 to 1e-10. The cycle KPIs converge more slowly (recovery slope 0.42): they integrate the logged boundary flows by the trapezoid rule, and a step that opens a pressure boundary drives a flow transient on the pressure diffusion time of one cell (~2e-5 s), resolved only by IDA's own steps. At the default reltol 1e-6 the recovery error is 6e-5 after two cycles, below the 2.4e-4 change from 20 to 40 cells.max_stepcaps at or above IDA's own steps leave a run bit-for-bit the same; a 0.1 s cap (2 to 2.3 x the steps) moves profiles and KPIs by less than the tolerance error of a 100 x looser reltol. Decks that setmax_stepdo it for robustness, not accuracy (Vysyaraju 2024: IDA stalled at its minimum step with a 1 s cap; 0.5 s and reltol 1e-7 at 1 s agree to 0.01 points).CSSConvergence.*— linearised at its fixed point the end-of-cycle map contracts by its slowest eigenvalue rho, so the state change decays geometrically, every KPI converges at the same rate, and the error left when the stopping rule fires at a state changeDelta <= epsisg Delta rho / (1 - rho)(g the KPI change per unit state change). On the isothermal cycle (rho = 0.106) the successive ratios agree with the fitted rate within 10 % over five decades, the KPI error is a fixed multiple of the state change, and each decade of state tolerance costs one cycle and buys a decade of KPI accuracy. With the energy balance on (rho = 0.966, a slow thermal mode) the CSS recovery fitted to cycles 40-79 predicts the KPI of cycles 99 to 139 within the noise, and the default rule (state tolerance 2e-5) stops at cycle 108 with a recovery error of 6.11e-5 against 6.21e-5 predicted (purity 7.20e-5 against 7.29e-5), inside a tolerance of 3 standard deviations computed from the noise measured in the run (11 % and 4 %). Aitken acceleration stops at cycle 83 on KPIs 2.6e-5 (recovery) and 4.0e-5 (purity) from that CSS value, inside the error the rule allows a plain run. The KPIs carry cycle-to-cycle noise that depends on the integrator's step sequence, and so on the platform; the tests measure it in the run and fit every CSS value over many cycles (an Aitken extrapolation from three cycles amplifies the noise five-fold: on macOS that moved f* by 15 % of the error under test).
test_competitive_sips_trace_loading.cpp¶
The competitive Sips kinds continue each term z_i = (b_i P y_i)^{s_i} below y_0 = 1e-5 by a quadratic in y (docs/isotherms.md), and use the published form unchanged above it (WP19-1: they used to multiply every term by y^2 / (y^2 + 1e-6), halving the loading at y = 1e-3). extended_langmuir_freundlich continues the power of z_i = b_i (P y_i / P_ref)^{n_i} the same way (it used a softplus and the step y^2 / (y^2 + 1e-10): -1e-4 at y = 1e-3, -1 % at 1e-4, -20 % at 2e-5).
- Seeded loadings of a trace CO2 (Casas 2012 arrhenius_sips at 15 bar with y_H2 = 0.5; Streb 2021 extended_sips_2, s = 0.425, at 10 bar with y_CH4 = 0.5) in a closed uniform bed, at y = 0.1 to 2e-5, against the closed form written from the papers' equations: tolerance 1e-6 relative (same algebra, round-off only); measured below 1e-15. Below y_0 (5e-6, 1e-7) the documented continuation to 1e-6, and exactly 0 at y = 0. Guard: the former step moved each loading above y_0 by at least 1e-4 (99x the tolerance); on main it gave -0.96 % at 1e-2, -49.9 % at 1e-3, -99 % at 1e-4. extended_langmuir_freundlich: trace CO (toPSAil case_study_4.0, n = 0.72) at 11 bar with y_H2 = 0.5, same tolerances, measured below 1e-15; its former step is guarded at y <= 1e-3 (closed form before the fix: -9.9e-5 at 1e-3, -0.99 % at 1e-4, -20 % at 2e-5), while 1e-1 and 1e-2 check exactness only.
- Single-component agreement (all three kinds, y = 1e-2, 2e-5, 5e-6):
with the competitor absent,
EvalIsotherm(the Python and fitter path) matches the closed form to 1e-12 and the solver's competitive loading to 1e-6; measured equal to the printed digits. - Trace breakthrough (y_f = 1e-3 CO2 in helium, arrhenius_sips, 1 bar, isothermal): the first moment of the outlet curve equals the equilibrium-theory time (L/u)(1 + (rho_b/eps) q*(y_f)/c_f) (a mass balance, exact however smeared the front), tolerance 2 y_f for the sorption effect; measured -1.1e-4. Guard: the former step gives a 49 % earlier front (measured on main: 543 s against 1062 s). The same run on extended_langmuir_freundlich (CO, y_f = 1e-4, tolerance 2e-4): measured -1.1e-4; the former step would give a front 0.95 % early (747 s against 754 s), 47x the tolerance. About 1 s each.