HOxCycle
Stratospheric HOx catalytic ozone destruction cycle (Seinfeld & Pandis 2006 Ch. 5, §5.4, Eqs. 5.23-5.28). Two-state ODE on the radicals OH and HO2 driven by reactions with O3, atomic O, and NO; HOx = OH + HO2 is exactly conserved by construction (D(OH) + D(HO2) ≡ 0). Migrated from GasChem.jl@8c12c048 src/StratosphericChemistry.jl:187 (`HOxCycle` @component). All rate coefficients are temperature-dependent Arrhenius forms k_X = k_X_A · exp(C_X / T) with the Seinfeld & Pandis bimolecular CGS pre-factors converted to SI via the cm³/molec/s × 1e-6 → m³/s factor; SI rates and SI number-density concentrations (m^-3) are preserved verbatim from upstream.
Description
Stratospheric HOx catalytic ozone destruction cycle (Seinfeld & Pandis 2006 Ch. 5, §5.4, Eqs. 5.23-5.28). Two-state ODE on the radicals OH and HO2 driven by reactions with O3, atomic O, and NO; HOx = OH + HO2 is exactly conserved by construction (D(OH) + D(HO2) ≡ 0). Migrated from GasChem.jl@8c12c048 src/StratosphericChemistry.jl:187 (HOxCycle @component). All rate coefficients are temperature-dependent Arrhenius forms k_X = k_X_A · exp(C_X / T) with the Seinfeld & Pandis bimolecular CGS pre-factors converted to SI via the cm³/molec/s × 1e-6 → m³/s factor; SI rates and SI number-density concentrations (m^-3) are preserved verbatim from upstream.
Reference
_source_ref: GasChem.jl@8c12c048482b515fb8eb2110bf8ab4b4f4e71309:src/StratosphericChemistry.jl:187
Upstream HOxCycle is an MTK @component declaring eight @constants (the four Arrhenius pre-factors k_OH_O3_A, k_HO2_O3_A, k_HO2_O_A, k_HO2_NO_A in m³/s and their four exponential temperature scales C_OH_O3, C_HO2_O3, C_HO2_O, C_HO2_NO in K), four @parameters (T, O, O3, NO) supplying the per-cell stratospheric environment, three time-indexed @variables (OH and HO2 with defaults at 1e12 and 1e13 m^-3 — the 1e6 and 1e7 molec/cm³ canonical mid-stratosphere values from S&P Page 161 — plus HOx for the algebraic family sum), and three equations (two ODEs and one algebraic constraint). Identical structure preserved here. Upstream structural test (test/StratosphericChemistry_test.jl ‘HOx Cycle Structure’, lines 61-69) asserts length(equations(sys)) == 3 and that OH(t), HO2(t), and HOx(t) appear in unknowns(sys) — matched here by the three entries in equations and the OH/HO2/HOx state declarations in variables.
The eight upstream @constants are encoded here as type: parameter with the upstream values as default, matching the convention used by sibling migrations in this rig (snow_thermal.esm encodes its C_liq, C_ice, λ_air, λ_ice_const, jordan_linear_coeff, jordan_quad_coeff @constants the same way; urban_radiation.esm encodes σ_SB, π_val, one_rad, one_wm2, zero_wm2 the same way; diameter_growth.esm encodes R_gas the same way). This keeps the migrated .esm’s parameter-and-default surface identical to the upstream’s symbolic @constants block — a downstream consumer that opts to expose the Arrhenius parameters (e.g. for tropospheric vs. stratospheric retuning, or for a sensitivity sweep over the temperature-sensitivity of OH+O3) reads them from parameter_overrides exactly as it would override T or O3.
Upstream rate-coefficient helper bindings (k_OH_O3 = k_OH_O3_A·exp(C_OH_O3/T) and the three sibling helpers k_HO2_O3, k_HO2_O, k_HO2_NO) are local Julia bindings expanded into the equation right-hand-sides — they are not user-visible MTK state variables (no @variables k_*) so the upstream unknowns(sys) returns exactly OH, HO2, HOx after mtkcompile. The migrated .esm preserves this by inlining the Arrhenius expression k_X_A * exp(C_X / T) directly into each of the four reaction-flux terms on the right-hand sides — bit-equivalent to writing the equations with the helper bindings, and matches the length(unknowns(sys)) == 3 invariant the upstream structural test enforces.
HOx conservation by construction: the two ODE right-hand-sides sum to identically zero — every term in D(OH) appears with the opposite sign in D(HO2) (OH→HO2 via OH+O3, HO2→OH via HO2+O, HO2+O3, HO2+NO). HOx = OH + HO2 is therefore an exact analytical invariant of the system regardless of solver and tolerance, and the algebraic equation HOx ~ OH + HO2 simply names this invariant for downstream observers. The hox_conservation_short_window and hox_conservation_long_window tests below pin HOx at the analytical 1.1e13 m^-3 (= 1e12 + 1e13, the sum of the OH and HO2 defaults) at every sampled time point — any drift away from this is a structural break in the migration, not a tolerance issue. The upstream regression test ‘HOx Conservation (HOx Cycle)’ (test/StratosphericChemistry_test.jl:317-337) asserts isapprox(HOx_init, HOx_final, rtol=1e-6) over a 1-second integration; the migrated assertions here are bit-tighter (rel=1e-8 model-level, with HOx pinned to the exact analytical sum) because the algebraic-state form HOx ~ OH + HO2 makes the conservation a constraint, not a numerical artifact.
Steady-state HO2/OH ratio (Eq. 5.28, S&P Page 161): under the model’s default O3, O, NO, T the bimolecular HO2-sink flux k_HO2_O·HO2·O + k_HO2_O3·HO2·O3 + k_HO2_NO·HO2·NO balances the OH-source flux k_OH_O3·OH·O3, giving a steady-state OH/HO2 ratio ≈ 0.18 (OH ≈ 1.689e12 m^-3, HO2 ≈ 9.311e12 m^-3 at t = 600 s, well-converged by t = 60 s). This is dominated by the HO2+NO recycle channel — the ’low_NO_perturbation’ test pins the much-lower OH steady state when NO drops 5 orders of magnitude (NO=1e10 m^-3) and the slower HO2+O3 and HO2+O channels carry the recycle alone. The ’low_O3_perturbation’ test pins the opposite limit (O3 dropped 2 orders of magnitude to 1e16 m^-3): the OH+O3 sink weakens, so HO2 drains into OH, and OH dominates HOx (OH ≈ 1.07e13, HO2 ≈ 2.7e11 at t=600s).
Unit balance (verified): each of the four flux terms in D(OH) and D(HO2) has units m³/s × m^-3 × m^-3 = 1/(m³·s) → m^-3/s, consistent with d[X]/dt for X in m^-3. The exponential factor exp(C/T) is dimensionless because the C constants carry unit K. The migrated .esm preserves the upstream units verbatim — the rate-coefficient pre-factors carry m^3/s, the temperature scales carry K, T carries K, and OH/HO2/O/O3/NO/HOx carry m^-3. SI throughout; no CGS↔SI conversion is performed at parse or runtime.
Observability classification: this is an active-ODE system (two state derivatives D(OH) and D(HO2) plus one algebraic HOx constraint), distinct from the observed-only convention used by sibling temps/urban_canopy components. Test scenarios sample at non-zero times (t = 60, 600, 1800, 3600 s for the headline default case) to exercise the integration path, not just the initial-condition assertion. Sample times are concentrated in [0, 600 s] because the system reaches numerical steady state by t ≈ 60 s — the t = 1800 s and t = 3600 s samples pin the steady-state hold rather than the transient.
Tolerance rationale (model-level rel=1e-6): two-state stiff ODE with widely separated radical lifetimes (~tens of seconds for OH/HO2 turnover under default stratospheric conditions, much shorter than the t = 60 s first non-trivial sample). The expected values pinned below are a Tsit5 reference at upstream Julia reltol=1e-10, abstol=1e-12; the ESS Python runner integrates the same RHS through LSODA (the earthsci_ast.simulation.simulate default), and the cross-integrator drift on the active-transient samples (most prominently OH at t=60 s, where the system is just settling into steady state) lands at ~2e-7 relative — well below 1e-6 but above the 1e-8 ceiling originally planned for an MTK-only Tsit5↔Rosenbrock23 comparison. Model-level rel=1e-6 matches the spec §6.6.4 default and leaves ~6× margin over the observed LSODA drift; it is still ~100× tighter than the convention used by the larger sibling SuperFast (gaschem/superfast.esm runs at rel=1e-4 because its 27-reaction radical pool compounds CSE-reorder noise). On hox_conservation_short_window rel=1e-6 is comfortably above floating-point round-off on the algebraic invariant HOx ~ OH + HO2 (~1e-16 ULP). Per-assertion tolerances are not loosened anywhere because every assertion samples at a time where the trajectory has either no integration drift (t=0) or has reached numerical steady state (t ≥ 60 s with mature-steady-state values at t ≥ 600 s).
Upstream test reproduction: the upstream structural test (length(equations(sys)) == 3 with OH/HO2/HOx state names) is reproduced by the test-level shape of the variables and equations blocks. The upstream integration test (‘HOx Cycle Integration’, lines 167-180: 1-hour Tsit5/Rosenbrock23 solve with default ICs and abstol=1e-8, reltol=1e-8 asserting sol.retcode == ReturnCode.Success and sol[compiled_sys.OH] .> 0 and sol[compiled_sys.HO2] .> 0) is reproduced by default_radical_steady_state_3600s, which pins not just positivity but the actual steady-state OH and HO2 values at five times across the window. The upstream conservation test (‘HOx Conservation (HOx Cycle)’, lines 317-337: 1-second Tsit5 solve at abstol=1e-12, reltol=1e-12 asserting isapprox(HOx_init, HOx_final, rtol=1e-6)) is reproduced and tightened by hox_conservation_short_window, which pins HOx to the exact analytical 1.1e13 m^-3 at four times in [0, 1] s. Parameter-sweep coverage (T, NO, O3) goes beyond the upstream test suite — there is no upstream parameter-sensitivity test for HOxCycle in particular, but the sweep mirrors the convention used by sibling gaschem migrations (superfast.esm has jNO2_photolysis_sweep, temperature_sweep, pressure_altitude_sweep examples)._
Variables
| Name | Units | Default | Description |
|---|---|---|---|
OH | m^-3 | 1000000000000.0 | Hydroxyl radical number density (1e6 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference). State variable: D(OH) = -k_OH_O3·OH·O3 + k_HO2_O·HO2·O + k_HO2_O3·HO2·O3 + k_HO2_NO·HO2·NO. |
HO2 | m^-3 | 10000000000000.0 | Hydroperoxyl radical number density (1e7 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference). State variable: D(HO2) = +k_OH_O3·OH·O3 - k_HO2_O·HO2·O - k_HO2_O3·HO2·O3 - k_HO2_NO·HO2·NO. |
Parameters
| Name | Units | Default | Description |
|---|---|---|---|
k_OH_O3_A | m^3/s | 1.7e-18 | Pre-factor for the OH + O3 → HO2 + O2 rate coefficient (S&P Page 161; 1.7e-12 cm^3/molec/s × 1e-6 → m^3/s). |
C_OH_O3 | K | -940.0 | Activation temperature for OH + O3 → HO2 + O2 (Arrhenius exponential scale, S&P Page 161). |
k_HO2_O3_A | m^3/s | 1e-20 | Pre-factor for the HO2 + O3 → OH + 2O2 rate coefficient (S&P Table B.1; 1e-14 cm^3/molec/s × 1e-6 → m^3/s). |
C_HO2_O3 | K | -490.0 | Activation temperature for HO2 + O3 → OH + 2O2 (Arrhenius exponential scale, S&P Table B.1). |
k_HO2_O_A | m^3/s | 3e-17 | Pre-factor for the HO2 + O → OH + O2 rate coefficient (S&P Page 161; 3e-11 cm^3/molec/s × 1e-6 → m^3/s). |
C_HO2_O | K | 200.0 | Activation temperature for HO2 + O → OH + O2 (Arrhenius exponential scale, S&P Page 161). |
k_HO2_NO_A | m^3/s | 3.5e-18 | Pre-factor for the HO2 + NO → NO2 + OH rate coefficient (S&P Page 158; 3.5e-12 cm^3/molec/s × 1e-6 → m^3/s). |
C_HO2_NO | K | 250.0 | Activation temperature for HO2 + NO → NO2 + OH (Arrhenius exponential scale, S&P Page 158). |
T | K | 227.0 | Local temperature for the Arrhenius rate evaluations (S&P mid-stratosphere reference, ~30 km). |
O | m^-3 | 10000000000000.0 | Atomic oxygen O(3P) number density (1e7 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference). |
O3 | m^-3 | 3e+18 | Ozone number density (3e12 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference). |
NO | m^-3 | 1000000000000000.0 | Nitric oxide number density (1e9 molec/cm^3 × 1e6 → m^-3; S&P Page 158 stratospheric reference). |
Observed
| Name | Units | Default | Description |
|---|---|---|---|
HOx | m^-3 | HOx odd-hydrogen family: algebraic sum HOx ~ OH + HO2 (S&P §5.4). Exactly conserved by construction since D(OH) + D(HO2) ≡ 0 (every reaction in this model only shuttles between OH and HO2). |
Observed expressions
$$ HOx = OH + HO2 $$HOx odd-hydrogen family: algebraic sum HOx ~ OH + HO2 (S&P §5.4). Exactly conserved by construction since D(OH) + D(HO2) ≡ 0 (every reaction in this model only shuttles between OH and HO2).
Equations
$$ \mathrm{D}\left(OH\right) = \left(-1.0\right) \cdot k_{OH\,O3\,A} \cdot e^{\frac{C_{OH\,O3}}{T}} \cdot OH \cdot O3 + k_{HO2\,O\,A} \cdot e^{\frac{C_{HO2\,O}}{T}} \cdot HO2 \cdot O + k_{HO2\,O3\,A} \cdot e^{\frac{C_{HO2\,O3}}{T}} \cdot HO2 \cdot O3 + k_{HO2\,NO\,A} \cdot e^{\frac{C_{HO2\,NO}}{T}} \cdot HO2 \cdot NO $$ $$ \mathrm{D}\left(HO2\right) = k_{OH\,O3\,A} \cdot e^{\frac{C_{OH\,O3}}{T}} \cdot OH \cdot O3 + \left(-1.0\right) \cdot k_{HO2\,O\,A} \cdot e^{\frac{C_{HO2\,O}}{T}} \cdot HO2 \cdot O + \left(-1.0\right) \cdot k_{HO2\,O3\,A} \cdot e^{\frac{C_{HO2\,O3}}{T}} \cdot HO2 \cdot O3 + \left(-1.0\right) \cdot k_{HO2\,NO\,A} \cdot e^{\frac{C_{HO2\,NO}}{T}} \cdot HO2 \cdot NO $$Analyses
default_run_3600s
One-hour integration under S&P Page 161 mid-stratosphere defaults (OH=1e12, HO2=1e13 m^-3, T=227 K, O=1e13, O3=3e18, NO=1e15 m^-3). Shows OH and HO2 approaching their bimolecular steady state on a ~tens-of-seconds timescale and holding for the rest of the hour. The OH curve illustrates the HO2 → OH recycle through HO2+NO (dominant), HO2+O, and HO2+O3.
Time span: 0.0 → 3600.0

temperature_sensitivity_sweep
Stratospheric temperature sweep over 200 K - 280 K (cold polar vortex to warm sub-tropical mid-stratosphere). At each T the system is integrated for one hour to steady state. The four Arrhenius rate coefficients change exponentially with T, but their net effect on the OH/HO2 ratio is non-monotonic — at low T the HO2+O and HO2+NO channels (positive C) speed up while OH+O3 (negative C) slows down, shifting HOx toward OH. Mirrors the SuperFast temperature_sweep example structure.
Time span: 0.0 → 3600.0
Swept over: T


NO_recycle_sensitivity_sweep
NO-recycle sweep across the stratospheric NOx range (1e10 - 1e16 m^-3). The HO2+NO channel is the dominant HOx recycle pathway under typical mid-stratosphere conditions (S&P Eq. 5.28). Sweeping NO illustrates the transition between two regimes: at low NO, HO2+O3 and HO2+O carry the recycle alone and the steady-state OH/HO2 ratio is small; at high NO, HO2+NO dominates and the OH/HO2 ratio approaches a NO-independent limit.
Time span: 0.0 → 3600.0
Swept over: NO

Raw .esm
Raw .esm JSON (this component)
{
"reference": {
"notes": "source_ref: GasChem.jl@8c12c048482b515fb8eb2110bf8ab4b4f4e71309:src/StratosphericChemistry.jl:187\n\nUpstream `HOxCycle` is an MTK `@component` declaring eight `@constants` (the four Arrhenius pre-factors k_OH_O3_A, k_HO2_O3_A, k_HO2_O_A, k_HO2_NO_A in m³/s and their four exponential temperature scales C_OH_O3, C_HO2_O3, C_HO2_O, C_HO2_NO in K), four `@parameters` (T, O, O3, NO) supplying the per-cell stratospheric environment, three time-indexed `@variables` (OH and HO2 with defaults at 1e12 and 1e13 m^-3 — the 1e6 and 1e7 molec/cm³ canonical mid-stratosphere values from S&P Page 161 — plus HOx for the algebraic family sum), and three equations (two ODEs and one algebraic constraint). Identical structure preserved here. Upstream structural test (test/StratosphericChemistry_test.jl 'HOx Cycle Structure', lines 61-69) asserts `length(equations(sys)) == 3` and that OH(t), HO2(t), and HOx(t) appear in `unknowns(sys)` — matched here by the three entries in `equations` and the OH/HO2/HOx state declarations in `variables`.\n\nThe eight upstream `@constants` are encoded here as `type: parameter` with the upstream values as `default`, matching the convention used by sibling migrations in this rig (snow_thermal.esm encodes its C_liq, C_ice, λ_air, λ_ice_const, jordan_linear_coeff, jordan_quad_coeff `@constants` the same way; urban_radiation.esm encodes σ_SB, π_val, one_rad, one_wm2, zero_wm2 the same way; diameter_growth.esm encodes R_gas the same way). This keeps the migrated `.esm`'s parameter-and-default surface identical to the upstream's symbolic `@constants` block — a downstream consumer that opts to expose the Arrhenius parameters (e.g. for tropospheric vs. stratospheric retuning, or for a sensitivity sweep over the temperature-sensitivity of OH+O3) reads them from `parameter_overrides` exactly as it would override T or O3.\n\nUpstream rate-coefficient helper bindings (k_OH_O3 = k_OH_O3_A·exp(C_OH_O3/T) and the three sibling helpers k_HO2_O3, k_HO2_O, k_HO2_NO) are local Julia bindings expanded into the equation right-hand-sides — they are not user-visible MTK state variables (no `@variables k_*`) so the upstream `unknowns(sys)` returns exactly OH, HO2, HOx after `mtkcompile`. The migrated `.esm` preserves this by inlining the Arrhenius expression `k_X_A * exp(C_X / T)` directly into each of the four reaction-flux terms on the right-hand sides — bit-equivalent to writing the equations with the helper bindings, and matches the `length(unknowns(sys)) == 3` invariant the upstream structural test enforces.\n\nHOx conservation by construction: the two ODE right-hand-sides sum to identically zero — every term in D(OH) appears with the opposite sign in D(HO2) (OH→HO2 via OH+O3, HO2→OH via HO2+O, HO2+O3, HO2+NO). HOx = OH + HO2 is therefore an exact analytical invariant of the system regardless of solver and tolerance, and the algebraic equation `HOx ~ OH + HO2` simply names this invariant for downstream observers. The `hox_conservation_short_window` and `hox_conservation_long_window` tests below pin HOx at the analytical 1.1e13 m^-3 (= 1e12 + 1e13, the sum of the OH and HO2 defaults) at every sampled time point — any drift away from this is a structural break in the migration, not a tolerance issue. The upstream regression test 'HOx Conservation (HOx Cycle)' (test/StratosphericChemistry_test.jl:317-337) asserts `isapprox(HOx_init, HOx_final, rtol=1e-6)` over a 1-second integration; the migrated assertions here are bit-tighter (rel=1e-8 model-level, with HOx pinned to the exact analytical sum) because the algebraic-state form HOx ~ OH + HO2 makes the conservation a constraint, not a numerical artifact.\n\nSteady-state HO2/OH ratio (Eq. 5.28, S&P Page 161): under the model's default O3, O, NO, T the bimolecular HO2-sink flux k_HO2_O·HO2·O + k_HO2_O3·HO2·O3 + k_HO2_NO·HO2·NO balances the OH-source flux k_OH_O3·OH·O3, giving a steady-state OH/HO2 ratio ≈ 0.18 (OH ≈ 1.689e12 m^-3, HO2 ≈ 9.311e12 m^-3 at t = 600 s, well-converged by t = 60 s). This is dominated by the HO2+NO recycle channel — the 'low_NO_perturbation' test pins the much-lower OH steady state when NO drops 5 orders of magnitude (NO=1e10 m^-3) and the slower HO2+O3 and HO2+O channels carry the recycle alone. The 'low_O3_perturbation' test pins the opposite limit (O3 dropped 2 orders of magnitude to 1e16 m^-3): the OH+O3 sink weakens, so HO2 drains into OH, and OH dominates HOx (OH ≈ 1.07e13, HO2 ≈ 2.7e11 at t=600s).\n\nUnit balance (verified): each of the four flux terms in D(OH) and D(HO2) has units m³/s × m^-3 × m^-3 = 1/(m³·s) → m^-3/s, consistent with d[X]/dt for X in m^-3. The exponential factor exp(C/T) is dimensionless because the C constants carry unit K. The migrated `.esm` preserves the upstream units verbatim — the rate-coefficient pre-factors carry m^3/s, the temperature scales carry K, T carries K, and OH/HO2/O/O3/NO/HOx carry m^-3. SI throughout; no CGS↔SI conversion is performed at parse or runtime.\n\nObservability classification: this is an active-ODE system (two state derivatives D(OH) and D(HO2) plus one algebraic HOx constraint), distinct from the observed-only convention used by sibling temps/urban_canopy components. Test scenarios sample at non-zero times (t = 60, 600, 1800, 3600 s for the headline default case) to exercise the integration path, not just the initial-condition assertion. Sample times are concentrated in [0, 600 s] because the system reaches numerical steady state by t ≈ 60 s — the t = 1800 s and t = 3600 s samples pin the steady-state hold rather than the transient.\n\nTolerance rationale (model-level rel=1e-6): two-state stiff ODE with widely separated radical lifetimes (~tens of seconds for OH/HO2 turnover under default stratospheric conditions, much shorter than the t = 60 s first non-trivial sample). The expected values pinned below are a Tsit5 reference at upstream Julia `reltol=1e-10, abstol=1e-12`; the ESS Python runner integrates the same RHS through LSODA (the `earthsci_ast.simulation.simulate` default), and the cross-integrator drift on the active-transient samples (most prominently OH at t=60 s, where the system is just settling into steady state) lands at ~2e-7 relative — well below 1e-6 but above the 1e-8 ceiling originally planned for an MTK-only Tsit5↔Rosenbrock23 comparison. Model-level rel=1e-6 matches the spec §6.6.4 default and leaves ~6× margin over the observed LSODA drift; it is still ~100× tighter than the convention used by the larger sibling SuperFast (`gaschem/superfast.esm` runs at rel=1e-4 because its 27-reaction radical pool compounds CSE-reorder noise). On `hox_conservation_short_window` rel=1e-6 is comfortably above floating-point round-off on the algebraic invariant HOx ~ OH + HO2 (~1e-16 ULP). Per-assertion tolerances are not loosened anywhere because every assertion samples at a time where the trajectory has either no integration drift (t=0) or has reached numerical steady state (t ≥ 60 s with mature-steady-state values at t ≥ 600 s).\n\nUpstream test reproduction: the upstream structural test (`length(equations(sys)) == 3` with OH/HO2/HOx state names) is reproduced by the test-level shape of the `variables` and `equations` blocks. The upstream integration test ('HOx Cycle Integration', lines 167-180: 1-hour Tsit5/Rosenbrock23 solve with default ICs and `abstol=1e-8, reltol=1e-8` asserting `sol.retcode == ReturnCode.Success` and `sol[compiled_sys.OH] .> 0` and `sol[compiled_sys.HO2] .> 0`) is reproduced by `default_radical_steady_state_3600s`, which pins not just positivity but the actual steady-state OH and HO2 values at five times across the window. The upstream conservation test ('HOx Conservation (HOx Cycle)', lines 317-337: 1-second Tsit5 solve at `abstol=1e-12, reltol=1e-12` asserting `isapprox(HOx_init, HOx_final, rtol=1e-6)`) is reproduced and tightened by `hox_conservation_short_window`, which pins HOx to the exact analytical 1.1e13 m^-3 at four times in [0, 1] s. Parameter-sweep coverage (T, NO, O3) goes beyond the upstream test suite — there is no upstream parameter-sensitivity test for HOxCycle in particular, but the sweep mirrors the convention used by sibling gaschem migrations (superfast.esm has `jNO2_photolysis_sweep`, `temperature_sweep`, `pressure_altitude_sweep` examples)."
},
"tolerance": {
"rel": 1e-06
},
"variables": {
"k_OH_O3_A": {
"type": "parameter",
"units": "m^3/s",
"default": 1.7e-18,
"description": "Pre-factor for the OH + O3 → HO2 + O2 rate coefficient (S&P Page 161; 1.7e-12 cm^3/molec/s × 1e-6 → m^3/s)."
},
"C_OH_O3": {
"type": "parameter",
"units": "K",
"default": -940.0,
"description": "Activation temperature for OH + O3 → HO2 + O2 (Arrhenius exponential scale, S&P Page 161)."
},
"k_HO2_O3_A": {
"type": "parameter",
"units": "m^3/s",
"default": 1e-20,
"description": "Pre-factor for the HO2 + O3 → OH + 2O2 rate coefficient (S&P Table B.1; 1e-14 cm^3/molec/s × 1e-6 → m^3/s)."
},
"C_HO2_O3": {
"type": "parameter",
"units": "K",
"default": -490.0,
"description": "Activation temperature for HO2 + O3 → OH + 2O2 (Arrhenius exponential scale, S&P Table B.1)."
},
"k_HO2_O_A": {
"type": "parameter",
"units": "m^3/s",
"default": 3e-17,
"description": "Pre-factor for the HO2 + O → OH + O2 rate coefficient (S&P Page 161; 3e-11 cm^3/molec/s × 1e-6 → m^3/s)."
},
"C_HO2_O": {
"type": "parameter",
"units": "K",
"default": 200.0,
"description": "Activation temperature for HO2 + O → OH + O2 (Arrhenius exponential scale, S&P Page 161)."
},
"k_HO2_NO_A": {
"type": "parameter",
"units": "m^3/s",
"default": 3.5e-18,
"description": "Pre-factor for the HO2 + NO → NO2 + OH rate coefficient (S&P Page 158; 3.5e-12 cm^3/molec/s × 1e-6 → m^3/s)."
},
"C_HO2_NO": {
"type": "parameter",
"units": "K",
"default": 250.0,
"description": "Activation temperature for HO2 + NO → NO2 + OH (Arrhenius exponential scale, S&P Page 158)."
},
"T": {
"type": "parameter",
"units": "K",
"default": 227.0,
"description": "Local temperature for the Arrhenius rate evaluations (S&P mid-stratosphere reference, ~30 km)."
},
"O": {
"type": "parameter",
"units": "m^-3",
"default": 10000000000000.0,
"description": "Atomic oxygen O(3P) number density (1e7 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference)."
},
"O3": {
"type": "parameter",
"units": "m^-3",
"default": 3e+18,
"description": "Ozone number density (3e12 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference)."
},
"NO": {
"type": "parameter",
"units": "m^-3",
"default": 1000000000000000.0,
"description": "Nitric oxide number density (1e9 molec/cm^3 × 1e6 → m^-3; S&P Page 158 stratospheric reference)."
},
"OH": {
"type": "unknown",
"units": "m^-3",
"default": 1000000000000.0,
"description": "Hydroxyl radical number density (1e6 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference). State variable: D(OH) = -k_OH_O3·OH·O3 + k_HO2_O·HO2·O + k_HO2_O3·HO2·O3 + k_HO2_NO·HO2·NO."
},
"HO2": {
"type": "unknown",
"units": "m^-3",
"default": 10000000000000.0,
"description": "Hydroperoxyl radical number density (1e7 molec/cm^3 × 1e6 → m^-3; S&P Page 161 stratospheric reference). State variable: D(HO2) = +k_OH_O3·OH·O3 - k_HO2_O·HO2·O - k_HO2_O3·HO2·O3 - k_HO2_NO·HO2·NO."
},
"HOx": {
"type": "unknown",
"units": "m^-3",
"description": "HOx odd-hydrogen family: algebraic sum HOx ~ OH + HO2 (S&P §5.4). Exactly conserved by construction since D(OH) + D(HO2) ≡ 0 (every reaction in this model only shuttles between OH and HO2)."
}
},
"equations": [
{
"lhs": {
"op": "D",
"wrt": "t",
"args": [
"OH"
]
},
"rhs": {
"op": "+",
"args": [
{
"op": "*",
"args": [
-1.0,
"k_OH_O3_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_OH_O3",
"T"
]
}
]
},
"OH",
"O3"
]
},
{
"op": "*",
"args": [
"k_HO2_O_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_HO2_O",
"T"
]
}
]
},
"HO2",
"O"
]
},
{
"op": "*",
"args": [
"k_HO2_O3_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_HO2_O3",
"T"
]
}
]
},
"HO2",
"O3"
]
},
{
"op": "*",
"args": [
"k_HO2_NO_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_HO2_NO",
"T"
]
}
]
},
"HO2",
"NO"
]
}
]
}
},
{
"lhs": {
"op": "D",
"wrt": "t",
"args": [
"HO2"
]
},
"rhs": {
"op": "+",
"args": [
{
"op": "*",
"args": [
"k_OH_O3_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_OH_O3",
"T"
]
}
]
},
"OH",
"O3"
]
},
{
"op": "*",
"args": [
-1.0,
"k_HO2_O_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_HO2_O",
"T"
]
}
]
},
"HO2",
"O"
]
},
{
"op": "*",
"args": [
-1.0,
"k_HO2_O3_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_HO2_O3",
"T"
]
}
]
},
"HO2",
"O3"
]
},
{
"op": "*",
"args": [
-1.0,
"k_HO2_NO_A",
{
"op": "exp",
"args": [
{
"op": "/",
"args": [
"C_HO2_NO",
"T"
]
}
]
},
"HO2",
"NO"
]
}
]
}
},
{
"lhs": "HOx",
"rhs": {
"op": "+",
"args": [
"OH",
"HO2"
]
}
}
],
"tests": [
{
"id": "initial_conditions_t0",
"description": "At t=0 the solver must return the declared species defaults exactly. Pins OH at 1e12 m^-3 (1e6 molec/cm^3, S&P Page 161 reference), HO2 at 1e13 m^-3 (1e7 molec/cm^3, same reference), and HOx at the analytical sum 1.1e13 m^-3. Tolerance is tightened to rel=1e-10 / abs=1e-14 because at t=0 there is no integration drift — only parser→Float64 conversion and the first-step identity evaluation of the initial state. Mirrors the convention used by superfast.esm 'initial_conditions_t0'.",
"time_span": {
"start": 0.0,
"end": 1.0
},
"tolerance": {
"rel": 1e-10,
"abs": 1e-14
},
"assertions": [
{
"variable": "OH",
"time": 0.0,
"expected": 1000000000000.0
},
{
"variable": "HO2",
"time": 0.0,
"expected": 10000000000000.0
},
{
"variable": "HOx",
"time": 0.0,
"expected": 11000000000000.0
}
]
},
{
"id": "default_radical_steady_state_3600s",
"description": "Headline behavioral test: integrate the HOx cycle from the S&P Page 161 mid-stratosphere defaults (OH=1e12, HO2=1e13 m^-3; T=227 K; O=1e13, O3=3e18, NO=1e15 m^-3) for one hour. The bimolecular timescale is ~10 s under these conditions, so by t=60 s the system has reached numerical steady state and holds it through t=3600 s. The pinned values are a Tsit5 reference at reltol=1e-10, abstol=1e-12 — matched against the upstream Julia integration of the same component. The OH/HO2 ratio at steady state is 0.18, dominated by the HO2+NO recycle channel (S&P Eq. 5.28, Page 161). This is the migrated counterpart of the upstream 'HOx Cycle Integration' test (StratosphericChemistry_test.jl:167-180) — that test asserts only positivity at retcode=Success, this one pins the steady-state values.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"assertions": [
{
"variable": "OH",
"time": 0.0,
"expected": 1000000000000.0
},
{
"variable": "HO2",
"time": 0.0,
"expected": 10000000000000.0
},
{
"variable": "OH",
"time": 60.0,
"expected": 1686875662113.56
},
{
"variable": "HO2",
"time": 60.0,
"expected": 9313124337886.45
},
{
"variable": "OH",
"time": 600.0,
"expected": 1689067273084.782
},
{
"variable": "HO2",
"time": 600.0,
"expected": 9310932726915.219
},
{
"variable": "OH",
"time": 1800.0,
"expected": 1689067273132.326
},
{
"variable": "HO2",
"time": 1800.0,
"expected": 9310932726867.676
},
{
"variable": "OH",
"time": 3600.0,
"expected": 1689067273067.653
},
{
"variable": "HO2",
"time": 3600.0,
"expected": 9310932726932.348
},
{
"variable": "HOx",
"time": 60.0,
"expected": 11000000000000.0
},
{
"variable": "HOx",
"time": 3600.0,
"expected": 11000000000000.0
}
]
},
{
"id": "hox_conservation_short_window",
"description": "Short-window (1-second) integration that pins HOx = OH + HO2 to the analytical conservation value 1.1e13 m^-3 at four sample times spanning the early transient. HOx is exactly conserved by construction in this model (every reaction shuttles between OH and HO2 with no source or sink to either radical pair). The migrated counterpart of the upstream 'HOx Conservation (HOx Cycle)' test (StratosphericChemistry_test.jl:317-337) — that test asserts `isapprox(HOx_init, HOx_final, rtol=1e-6)`, this one tightens to the algebraic invariant. The transient OH and HO2 values are also pinned to confirm the system is actually evolving, not stuck at its initial state.",
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "HOx",
"time": 0.0,
"expected": 11000000000000.0
},
{
"variable": "HOx",
"time": 0.1,
"expected": 11000000000000.0
},
{
"variable": "HOx",
"time": 0.5,
"expected": 11000000000000.0
},
{
"variable": "HOx",
"time": 1.0,
"expected": 11000000000000.0
},
{
"variable": "OH",
"time": 0.1,
"expected": 1006572818695.378
},
{
"variable": "OH",
"time": 0.5,
"expected": 1032243082760.691
},
{
"variable": "OH",
"time": 1.0,
"expected": 1062977435638.981
},
{
"variable": "HO2",
"time": 1.0,
"expected": 9937022564361.02
}
]
},
{
"id": "low_temperature_perturbation",
"description": "Cold-stratosphere case: drop T from 227 K to 200 K. All four Arrhenius rates change exponentially; the dominant HO2-loss channels HO2+O (C=+200 K, faster at low T) and HO2+NO (C=+250 K, faster at low T) win out over the OH-loss channel OH+O3 (C=-940 K, much slower at low T) — so the steady-state shifts further toward OH (OH rises ~64% from 1.7e12 to 2.8e12 m^-3, HO2 falls correspondingly). HOx is still exactly 1.1e13. Pins the steady state at t=600 s and t=3600 s.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"parameter_overrides": {
"T": 200.0
},
"assertions": [
{
"variable": "OH",
"time": 600.0,
"expected": 2771091397174.361
},
{
"variable": "HO2",
"time": 600.0,
"expected": 8228908602825.635
},
{
"variable": "OH",
"time": 3600.0,
"expected": 2771091397118.811
},
{
"variable": "HO2",
"time": 3600.0,
"expected": 8228908602881.189
},
{
"variable": "HOx",
"time": 3600.0,
"expected": 11000000000000.0
}
]
},
{
"id": "high_temperature_perturbation",
"description": "Warm-stratosphere case: T raised from 227 K to 270 K. The HO2-loss channels weaken (positive C/T factors) while OH+O3 strengthens (negative C); the steady-state shifts toward HO2 (OH drops to ~9.2e11 m^-3, HO2 rises to ~1.01e13 m^-3). HOx still 1.1e13. Together with the low_temperature_perturbation case, brackets the dynamic range of the Arrhenius coefficients across the stratospheric temperature range.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"parameter_overrides": {
"T": 270.0
},
"assertions": [
{
"variable": "OH",
"time": 600.0,
"expected": 921831719842.094
},
{
"variable": "HO2",
"time": 600.0,
"expected": 10078168280157.9
},
{
"variable": "OH",
"time": 3600.0,
"expected": 921831719855.629
},
{
"variable": "HO2",
"time": 3600.0,
"expected": 10078168280144.37
},
{
"variable": "HOx",
"time": 3600.0,
"expected": 11000000000000.0
}
]
},
{
"id": "low_NO_perturbation",
"description": "Low-NO regime: drop NO from 1e15 to 1e10 m^-3 (five orders of magnitude — closer to a clean stratosphere away from the NOx peak). The HO2+NO recycle channel (the dominant pathway under default conditions) effectively shuts off; only HO2+O3 and HO2+O remain to convert HO2 → OH, so the OH/HO2 ratio drops: OH ≈ 5.4e11 m^-3 (down from 1.69e12), HO2 ≈ 1.05e13 m^-3. Verifies the model correctly responds to the NO-driven recycle channel — a key behavior tested by the OH/HO2 steady-state expression Eq. 5.28.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"parameter_overrides": {
"NO": 10000000000.0
},
"assertions": [
{
"variable": "OH",
"time": 600.0,
"expected": 540064787208.189
},
{
"variable": "HO2",
"time": 600.0,
"expected": 10459935212791.8
},
{
"variable": "OH",
"time": 3600.0,
"expected": 540064787182.561
},
{
"variable": "HO2",
"time": 3600.0,
"expected": 10459935212817.44
},
{
"variable": "HOx",
"time": 3600.0,
"expected": 11000000000000.0
}
]
},
{
"id": "low_O3_perturbation",
"description": "Low-O3 regime: drop O3 from 3e18 to 1e16 m^-3 (two orders of magnitude). The OH+O3 sink weakens dramatically, while the HO2 recycle channels (HO2+O, HO2+O3, HO2+NO) are only mildly affected (HO2+O3 weakens proportionally). HO2 drains into OH and OH becomes the dominant HOx species: OH ≈ 1.07e13, HO2 ≈ 2.68e11 m^-3 — the opposite limit from the default case. Demonstrates the model handles wide regime shifts cleanly.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"parameter_overrides": {
"O3": 1e+16
},
"assertions": [
{
"variable": "OH",
"time": 600.0,
"expected": 10732488172268.82
},
{
"variable": "HO2",
"time": 600.0,
"expected": 267511827731.158
},
{
"variable": "OH",
"time": 3600.0,
"expected": 10742105002837.69
},
{
"variable": "HO2",
"time": 3600.0,
"expected": 257894997162.3307
},
{
"variable": "HOx",
"time": 3600.0,
"expected": 11000000000000.0
}
]
},
{
"id": "equal_OH_HO2_initial",
"description": "Initial-condition sensitivity check: start the system at OH = HO2 = 5.5e12 m^-3 (so HOx = 1.1e13 same as the default case). The system should still relax to the same steady state as the default (OH ≈ 1.689e12, HO2 ≈ 9.311e12) because the steady-state ratio depends only on the rate coefficients and the per-cell environment T, O, O3, NO — not on the initial partition between OH and HO2. Verifies the initial-condition machinery works correctly and that the steady-state attractor is uniquely determined by the parameter set.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"initial_conditions": {
"OH": 5500000000000.0,
"HO2": 5500000000000.0
},
"assertions": [
{
"variable": "OH",
"time": 0.0,
"expected": 5500000000000.0
},
{
"variable": "HO2",
"time": 0.0,
"expected": 5500000000000.0
},
{
"variable": "OH",
"time": 600.0,
"expected": 1689067273141.99
},
{
"variable": "HO2",
"time": 600.0,
"expected": 9310932726858.0
},
{
"variable": "OH",
"time": 3600.0,
"expected": 1689067273107.957
},
{
"variable": "HO2",
"time": 3600.0,
"expected": 9310932726892.043
},
{
"variable": "HOx",
"time": 3600.0,
"expected": 11000000000000.0
}
]
}
],
"analyses": [
{
"id": "default_run_3600s",
"description": "One-hour integration under S&P Page 161 mid-stratosphere defaults (OH=1e12, HO2=1e13 m^-3, T=227 K, O=1e13, O3=3e18, NO=1e15 m^-3). Shows OH and HO2 approaching their bimolecular steady state on a ~tens-of-seconds timescale and holding for the rest of the hour. The OH curve illustrates the HO2 → OH recycle through HO2+NO (dominant), HO2+O, and HO2+O3.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"plots": [
{
"id": "OH_HO2_vs_time",
"type": "line",
"description": "OH and HO2 concentrations over one hour of HOxCycle integration.",
"x": {
"variable": "t",
"label": "time (s)"
},
"y": {
"variable": "OH",
"label": "OH (m^-3)"
}
}
]
},
{
"id": "temperature_sensitivity_sweep",
"description": "Stratospheric temperature sweep over 200 K - 280 K (cold polar vortex to warm sub-tropical mid-stratosphere). At each T the system is integrated for one hour to steady state. The four Arrhenius rate coefficients change exponentially with T, but their net effect on the OH/HO2 ratio is non-monotonic — at low T the HO2+O and HO2+NO channels (positive C) speed up while OH+O3 (negative C) slows down, shifting HOx toward OH. Mirrors the SuperFast temperature_sweep example structure.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"parameter_sweep": {
"type": "cartesian",
"dimensions": [
{
"parameter": "T",
"values": [
200.0,
210.0,
220.0,
230.0,
240.0,
250.0,
260.0,
270.0,
280.0
]
}
]
},
"plots": [
{
"id": "OH_steady_vs_T",
"type": "line",
"description": "Steady-state OH concentration vs. temperature.",
"x": {
"variable": "T",
"label": "Temperature (K)"
},
"y": {
"variable": "OH",
"label": "OH (m^-3)"
}
},
{
"id": "HO2_steady_vs_T",
"type": "line",
"description": "Steady-state HO2 concentration vs. temperature.",
"x": {
"variable": "T",
"label": "Temperature (K)"
},
"y": {
"variable": "HO2",
"label": "HO2 (m^-3)"
}
}
]
},
{
"id": "NO_recycle_sensitivity_sweep",
"description": "NO-recycle sweep across the stratospheric NOx range (1e10 - 1e16 m^-3). The HO2+NO channel is the dominant HOx recycle pathway under typical mid-stratosphere conditions (S&P Eq. 5.28). Sweeping NO illustrates the transition between two regimes: at low NO, HO2+O3 and HO2+O carry the recycle alone and the steady-state OH/HO2 ratio is small; at high NO, HO2+NO dominates and the OH/HO2 ratio approaches a NO-independent limit.",
"time_span": {
"start": 0.0,
"end": 3600.0
},
"parameter_sweep": {
"type": "cartesian",
"dimensions": [
{
"parameter": "NO",
"values": [
10000000000.0,
100000000000.0,
1000000000000.0,
10000000000000.0,
100000000000000.0,
1000000000000000.0,
1e+16
]
}
]
},
"plots": [
{
"id": "OH_steady_vs_NO",
"type": "line",
"description": "Steady-state OH concentration vs. NO number density.",
"x": {
"variable": "NO",
"label": "NO (m^-3)"
},
"y": {
"variable": "OH",
"label": "OH (m^-3)"
}
}
]
}
]
}