Combined System

Overview

The TroposphericChemistrySystem integrates the individual chemistry mechanisms from Chapter 6 into a comprehensive tropospheric chemistry diagnostic model. It couples:

  • OH production from O3 photolysis (Section 6.1, via OHProduction)
  • NOx photochemical cycle (Section 6.2, via NOxPhotochemistry)
  • CO oxidation and HOx cycling (Section 6.3, via COOxidation)

The systems are coupled through shared species (OH, HO2, NO, NO2, O3) and the combined system computes aggregate diagnostics including net O3 production, OPE, and HOx chain length.

Condition systems provide typical atmospheric conditions for different environments.

Reference: Seinfeld, J.H. and Pandis, S.N. (2006). Atmospheric Chemistry and Physics: From Air Pollution to Climate Change, 2nd Edition. John Wiley & Sons. Chapter 6.

GasChem.TroposphericChemistrySystemFunction
TroposphericChemistrySystem(; name)

Combined ModelingToolkit System for tropospheric chemistry diagnostics.

This system couples the OH production, NOx cycling, and CO/CH₄ oxidation mechanisms to create a comprehensive diagnostic model of tropospheric photochemistry.

Key coupling points:

  • OH is produced from O₃ photolysis and HO₂ + NO reaction
  • OH is consumed by CO, CH₄, and other species
  • HO₂ is produced from CO/VOC oxidation
  • HO₂ is lost to NO (producing OH) and self-reaction
  • NOx cycles between NO and NO₂ through O₃ and peroxy radicals
  • O₃ is produced when peroxy radicals oxidize NO to NO₂

Subsystem Composition:

  • oh: OHProduction subsystem (Section 6.1)
  • nox: NOxPhotochemistry subsystem (Section 6.2)
  • co: COOxidation subsystem (Section 6.3)

Input Variables

All species concentrations must be provided as inputs [m⁻³].

Diagnostic outputs:

  • PO3net: Net ozone production rate [m⁻³ s⁻¹]
  • OPE: Ozone production efficiency [dimensionless]
  • HOx: Total HOx (OH + HO₂) [m⁻³] # =========================================================================
  • chain_length: HOx chain length [dimensionless] # Subsystems (composed from individual component functions)
source
GasChem.TypicalConditionsFunction
TypicalConditions(; name)

ModelingToolkit System representing typical lower troposphere conditions.

All concentrations are parameters in SI units (m⁻³).

Based on values from Seinfeld & Pandis Chapter 6.

source
GasChem.UrbanConditionsFunction
UrbanConditions(; name)

ModelingToolkit System representing typical urban conditions with elevated NOx.

All concentrations are parameters in SI units (m⁻³).

source
GasChem.RemoteConditionsFunction
RemoteConditions(; name)

ModelingToolkit System representing typical remote/background conditions with low NOx.

All concentrations are parameters in SI units (m⁻³).

source
GasChem.get_conditions_dictFunction
get_conditions_dict(sys)

Helper to extract default parameter values from a conditions System as a Dict{Symbol,Float64}. This is a convenience function for use in tests and analysis code.

source

Implementation

State Variables

using DataFrames, ModelingToolkit, Symbolics, DynamicQuantities, GasChem

sys = TroposphericChemistrySystem()
vars = unknowns(sys)
DataFrame(
    :Name => [string(Symbolics.tosymbol(v, escape = false)) for v in vars],
    :Units => [dimension(ModelingToolkit.get_unit(v)) for v in vars],
    :Description => [ModelingToolkit.getdescription(v) for v in vars]
)
61×3 DataFrame
RowNameUnitsDescription
StringDimensio…String
1oh₊O3m⁻³Ozone concentration
2O3m⁻³Ozone
3oh₊H2Om⁻³Water vapor concentration
4H2Om⁻³Water vapor
5oh₊Mm⁻³Total air number density
6Mm⁻³Total air density
7nox₊NOm⁻³NO concentration
8NOm⁻³Nitric oxide
9nox₊NO2m⁻³NO₂ concentration
10NO2m⁻³Nitrogen dioxide
11nox₊O3m⁻³O₃ concentration
12nox₊O2m⁻³O₂ concentration
13O2m⁻³Molecular oxygen
14nox₊Mm⁻³Total air number density
15co₊COm⁻³CO concentration
16COm⁻³Carbon monoxide
17co₊OHm⁻³OH concentration
18OHm⁻³Hydroxyl radical
19co₊HO2m⁻³HO₂ concentration
20HO2m⁻³Hydroperoxy radical
21co₊NOm⁻³NO concentration
22co₊NO2m⁻³NO₂ concentration
23co₊O3m⁻³O₃ concentration
24NOxm⁻³Total NOx
25HOxm⁻³Total HOx
26RO2m⁻³Total organic peroxy radicals
27CH3O2m⁻³Methylperoxy radical
28P_O3_totalm⁻³ s⁻¹Total O₃ production
29L_O3_totalm⁻³ s⁻¹Total O₃ loss
30P_O3_netm⁻³ s⁻¹Net O₃ tendency
31L_NOxm⁻³ s⁻¹NOx loss rate
32OPEOzone production efficiency (dimensionless)
33chain_lengthHOx chain length (dimensionless)
34co₊chain_lengthHOx chain length (dimensionless)
35oh₊O1Dm⁻³O(¹D) steady-state concentration
36oh₊O3m⁻³Ozone concentration
37oh₊Mm⁻³Total air number density
38oh₊H2Om⁻³Water vapor concentration
39oh₊ε_OHOH yield fraction (dimensionless)
40oh₊P_OHm⁻³ s⁻¹OH production rate
41nox₊Om⁻³O atom concentration
42nox₊NO2m⁻³NO₂ concentration
43nox₊O2m⁻³O₂ concentration
44nox₊Mm⁻³Total air number density
45nox₊O3_pssm⁻³Photostationary state O₃
46nox₊NOm⁻³NO concentration
47nox₊ΦPhotostationary state parameter (dimensionless)
48nox₊O3m⁻³O₃ concentration
49nox₊P_O3m⁻³ s⁻¹Net O₃ production rate
50co₊L_OHm⁻³ s⁻¹OH loss rate
51co₊OHm⁻³OH concentration
52co₊COm⁻³CO concentration
53co₊NO2m⁻³NO₂ concentration
54co₊O3m⁻³O₃ concentration
55co₊L_HO2m⁻³ s⁻¹HO₂ loss rate
56co₊HO2m⁻³HO₂ concentration
57co₊NOm⁻³NO concentration
58co₊L_HOxm⁻³ s⁻¹HOx loss rate
59co₊HO2_ssm⁻³HO₂ steady-state (high NOx)
60co₊P_O3m⁻³ s⁻¹Net O₃ production rate
61co₊chain_lengthHOx chain length (dimensionless)

Parameters

params = parameters(sys)
DataFrame(
    :Name => [string(Symbolics.tosymbol(p, escape = false)) for p in params],
    :Units => [dimension(ModelingToolkit.get_unit(p)) for p in params],
    :Description => [ModelingToolkit.getdescription(p) for p in params]
)
18×3 DataFrame
RowNameUnitsDescription
StringDimensio…String
1co₊k_HO2_NOm³ s⁻¹HO₂ + NO rate constant (8.1e-12 cm³/molec/s)
2k_CH3O2_NOm³ s⁻¹CH₃O₂ + NO rate constant (7.7e-12 cm³/molec/s)
3nox₊k_NO_O3m³ s⁻¹NO + O₃ → NO₂ rate (1.9e-14 cm³/molec/s, p. 211)
4co₊k_HO2_O3m³ s⁻¹HO₂ + O₃ rate constant (2.0e-15 cm³/molec/s)
5co₊k_OH_O3m³ s⁻¹OH + O₃ rate constant (7.3e-14 cm³/molec/s)
6co₊k_OH_NO2m³ s⁻¹OH + NO₂ + M rate constant (1.0e-11 cm³/molec/s)
7oh₊j_O3s⁻¹O₃ photolysis rate producing O(¹D) at surface, solar zenith 0°
8oh₊k3_O2m³ s⁻¹O(¹D) + O₂ quenching rate (4.0e-11 cm³/molec/s)
9oh₊f_O2Fraction of air that is O₂ (dimensionless)
10oh₊k3_N2m³ s⁻¹O(¹D) + N₂ quenching rate (2.6e-11 cm³/molec/s)
11oh₊f_N2Fraction of air that is N₂ (dimensionless)
12oh₊k4m³ s⁻¹O(¹D) + H₂O → 2OH rate (2.2e-10 cm³/molec/s)
13oh₊twoStoichiometric factor: 2 OH per O(¹D)+H₂O reaction (dimensionless)
14nox₊j_NO2s⁻¹NO₂ photolysis rate
15nox₊k_O_O2_Mm⁶ s⁻¹O + O₂ + M → O₃ rate (6.0e-34 cm⁶/molec²/s)
16co₊k_CO_OHm³ s⁻¹CO + OH rate constant (2.4e-13 cm³/molec/s)
17co₊k_HO2_HO2m³ s⁻¹HO₂ + HO₂ rate constant (2.9e-12 cm³/molec/s)
18co₊twoStoichiometric factor for HO₂ self-reaction (dimensionless)

Equations

eqs = equations(sys)

\[ \begin{align} \mathtt{oh.O3}\left( t \right) &= \mathtt{O3}\left( t \right) \\ \mathtt{oh.H2O}\left( t \right) &= \mathtt{H2O}\left( t \right) \\ \mathtt{oh.M}\left( t \right) &= M\left( t \right) \\ \mathtt{nox.NO}\left( t \right) &= \mathtt{NO}\left( t \right) \\ \mathtt{nox.NO2}\left( t \right) &= \mathtt{NO2}\left( t \right) \\ \mathtt{nox.O3}\left( t \right) &= \mathtt{O3}\left( t \right) \\ \mathtt{nox.O2}\left( t \right) &= \mathtt{O2}\left( t \right) \\ \mathtt{nox.M}\left( t \right) &= M\left( t \right) \\ \mathtt{co.CO}\left( t \right) &= \mathtt{CO}\left( t \right) \\ \mathtt{co.OH}\left( t \right) &= \mathtt{OH}\left( t \right) \\ \mathtt{co.HO2}\left( t \right) &= \mathtt{HO2}\left( t \right) \\ \mathtt{co.NO}\left( t \right) &= \mathtt{NO}\left( t \right) \\ \mathtt{co.NO2}\left( t \right) &= \mathtt{NO2}\left( t \right) \\ \mathtt{co.O3}\left( t \right) &= \mathtt{O3}\left( t \right) \\ \mathtt{NOx}\left( t \right) &= \mathtt{NO}\left( t \right) + \mathtt{NO2}\left( t \right) \\ \mathtt{HOx}\left( t \right) &= \mathtt{HO2}\left( t \right) + \mathtt{OH}\left( t \right) \\ \mathtt{RO2}\left( t \right) &= \mathtt{CH3O2}\left( t \right) \\ \mathtt{P\_O3\_total}\left( t \right) &= \mathtt{co.k\_HO2\_NO} ~ \mathtt{NO}\left( t \right) ~ \mathtt{HO2}\left( t \right) + \mathtt{k\_CH3O2\_NO} ~ \mathtt{NO}\left( t \right) ~ \mathtt{CH3O2}\left( t \right) \\ \mathtt{L\_O3\_total}\left( t \right) &= \mathtt{co.k\_HO2\_O3} ~ \mathtt{HO2}\left( t \right) ~ \mathtt{O3}\left( t \right) + \mathtt{co.k\_OH\_O3} ~ \mathtt{OH}\left( t \right) ~ \mathtt{O3}\left( t \right) + \mathtt{nox.k\_NO\_O3} ~ \mathtt{NO}\left( t \right) ~ \mathtt{O3}\left( t \right) \\ \mathtt{P\_O3\_net}\left( t \right) &= \mathtt{P\_O3\_total}\left( t \right) - \mathtt{L\_O3\_total}\left( t \right) \\ \mathtt{L\_NOx}\left( t \right) &= \mathtt{co.k\_OH\_NO2} ~ \mathtt{NO2}\left( t \right) ~ \mathtt{OH}\left( t \right) \\ \mathtt{OPE}\left( t \right) &= \frac{\mathtt{P\_O3\_total}\left( t \right)}{\mathtt{L\_NOx}\left( t \right)} \\ \mathtt{chain\_length}\left( t \right) &= \mathtt{co.chain\_length}\left( t \right) \\ \mathtt{oh.O1D}\left( t \right) &= \frac{\mathtt{oh.j\_O3} ~ \mathtt{oh.O3}\left( t \right)}{\mathtt{oh.k4} ~ \mathtt{oh.H2O}\left( t \right) + \left( \mathtt{oh.f\_N2} ~ \mathtt{oh.k3\_N2} + \mathtt{oh.f\_O2} ~ \mathtt{oh.k3\_O2} \right) ~ \mathtt{oh.M}\left( t \right)} \\ \mathtt{oh.\varepsilon\_OH}\left( t \right) &= \frac{\mathtt{oh.k4} ~ \mathtt{oh.two} ~ \mathtt{oh.H2O}\left( t \right)}{\mathtt{oh.k4} ~ \mathtt{oh.H2O}\left( t \right) + \left( \mathtt{oh.f\_N2} ~ \mathtt{oh.k3\_N2} + \mathtt{oh.f\_O2} ~ \mathtt{oh.k3\_O2} \right) ~ \mathtt{oh.M}\left( t \right)} \\ \mathtt{oh.P\_OH}\left( t \right) &= \mathtt{oh.j\_O3} ~ \mathtt{oh.\varepsilon\_OH}\left( t \right) ~ \mathtt{oh.O3}\left( t \right) \\ \mathtt{nox.O}\left( t \right) &= \frac{\mathtt{nox.j\_NO2} ~ \mathtt{nox.NO2}\left( t \right)}{\mathtt{nox.k\_O\_O2\_M} ~ \mathtt{nox.O2}\left( t \right) ~ \mathtt{nox.M}\left( t \right)} \\ \mathtt{nox.O3\_pss}\left( t \right) &= \frac{\mathtt{nox.j\_NO2} ~ \mathtt{nox.NO2}\left( t \right)}{\mathtt{nox.k\_NO\_O3} ~ \mathtt{nox.NO}\left( t \right)} \\ \mathtt{nox.\Phi}\left( t \right) &= \frac{\mathtt{nox.j\_NO2} ~ \mathtt{nox.NO2}\left( t \right)}{\mathtt{nox.k\_NO\_O3} ~ \mathtt{nox.O3}\left( t \right) ~ \mathtt{nox.NO}\left( t \right)} \\ \mathtt{nox.P\_O3}\left( t \right) &= \mathtt{nox.j\_NO2} ~ \mathtt{nox.NO2}\left( t \right) - \mathtt{nox.k\_NO\_O3} ~ \mathtt{nox.O3}\left( t \right) ~ \mathtt{nox.NO}\left( t \right) \\ \mathtt{co.L\_OH}\left( t \right) &= \mathtt{co.k\_CO\_OH} ~ \mathtt{co.CO}\left( t \right) ~ \mathtt{co.OH}\left( t \right) + \mathtt{co.k\_OH\_NO2} ~ \mathtt{co.OH}\left( t \right) ~ \mathtt{co.NO2}\left( t \right) + \mathtt{co.k\_OH\_O3} ~ \mathtt{co.O3}\left( t \right) ~ \mathtt{co.OH}\left( t \right) \\ \mathtt{co.L\_HO2}\left( t \right) &= \mathtt{co.k\_HO2\_NO} ~ \mathtt{co.HO2}\left( t \right) ~ \mathtt{co.NO}\left( t \right) + \mathtt{co.k\_HO2\_O3} ~ \mathtt{co.HO2}\left( t \right) ~ \mathtt{co.O3}\left( t \right) + \left( \mathtt{co.HO2}\left( t \right) \right)^{2} ~ \mathtt{co.k\_HO2\_HO2} ~ \mathtt{co.two} \\ \mathtt{co.L\_HOx}\left( t \right) &= \mathtt{co.k\_OH\_NO2} ~ \mathtt{co.OH}\left( t \right) ~ \mathtt{co.NO2}\left( t \right) + \left( \mathtt{co.HO2}\left( t \right) \right)^{2} ~ \mathtt{co.k\_HO2\_HO2} ~ \mathtt{co.two} \\ \mathtt{co.HO2\_ss}\left( t \right) &= \frac{\mathtt{co.k\_CO\_OH} ~ \mathtt{co.CO}\left( t \right) ~ \mathtt{co.OH}\left( t \right)}{\mathtt{co.k\_HO2\_NO} ~ \mathtt{co.NO}\left( t \right)} \\ \mathtt{co.P\_O3}\left( t \right) &= \mathtt{co.k\_HO2\_NO} ~ \mathtt{co.HO2}\left( t \right) ~ \mathtt{co.NO}\left( t \right) - \mathtt{co.k\_HO2\_O3} ~ \mathtt{co.HO2}\left( t \right) ~ \mathtt{co.O3}\left( t \right) - \mathtt{co.k\_OH\_O3} ~ \mathtt{co.O3}\left( t \right) ~ \mathtt{co.OH}\left( t \right) \\ \mathtt{co.chain\_length}\left( t \right) &= \frac{\mathtt{co.k\_HO2\_NO} ~ \mathtt{co.HO2}\left( t \right) ~ \mathtt{co.NO}\left( t \right)}{\mathtt{co.L\_HOx}\left( t \right)} \end{align} \]

Typical Atmospheric Conditions

The module provides condition systems for three atmospheric environments:

using DataFrames

conditions = [
    ("Background", get_conditions_dict(TypicalConditions())),
    ("Urban", get_conditions_dict(UrbanConditions())),
    ("Remote", get_conditions_dict(RemoteConditions()))
]

# Show key species for each condition
species = [:O3, :NO, :NO2, :CO, :OH, :HO2]
header = vcat([:Species, :Units], [Symbol(c[1]) for c in conditions])

rows = []
for s in species
    ppb_factor = 2.5e16  # m⁻³ per ppb at STP
    vals = [c[2][s] for c in conditions]
    ppb_vals = vals ./ ppb_factor
    push!(rows,
        (
            Species = string(s),
            Units = "ppb",
            Background = round(ppb_vals[1], sigdigits = 3),
            Urban = round(ppb_vals[2], sigdigits = 3),
            Remote = round(ppb_vals[3], sigdigits = 3)
        ))
end
DataFrame(rows)
6×5 DataFrame
RowSpeciesUnitsBackgroundUrbanRemote
StringStringFloat64Float64Float64
1O3ppb40.080.030.0
2NOppb0.110.00.01
3NO2ppb1.030.00.02
4COppb100.02000.080.0
5OHppb4.0e-52.0e-54.0e-5
6HO2ppb0.0040.0020.008

Analysis

O3 Production in Different NOx Regimes

The combined system captures the transition between NOx-limited and VOC-limited regimes. In the NOx-limited regime (remote conditions), O3 production increases linearly with NOx. In the VOC-limited regime (urban conditions), adding more NOx can actually decrease O3.

This analysis uses the TroposphericChemistrySystem to compute how net O3 production and OPE vary across a range of NOx levels. The HO2 concentration is estimated from steady-state approximations (Eqs. 6.13 and 6.18), and the resulting concentrations are fed into the compiled system to compute the diagnostics.

using Plots, NonlinearSolve

# Compile the TroposphericChemistrySystem with all species as inputs
sys_nns = ModelingToolkit.toggle_namespacing(sys, false)
input_vars = [sys_nns.O3, sys_nns.NO, sys_nns.NO2, sys_nns.OH, sys_nns.HO2,
    sys_nns.CO, sys_nns.CH3O2, sys_nns.H2O, sys_nns.M, sys_nns.O2]
compiled = mtkcompile(sys; inputs = input_vars)

# Extract rate constants from the system parameters for HO2 estimation
k_HO2_NO_val = Float64(ModelingToolkit.getdefault(sys.co.k_HO2_NO))
k_HO2_HO2_val = Float64(ModelingToolkit.getdefault(sys.co.k_HO2_HO2))
k_CO_OH_val = Float64(ModelingToolkit.getdefault(sys.co.k_CO_OH))

# Fixed conditions (background, SI: m⁻³)
CO_val = 2.5e18     # 100 ppb
O3_val = 1e18       # 40 ppb
OH_val = 1e12       # typical daytime
CH3O2_val = 1e14    # typical
H2O_val = 4e23
M_val = 2.5e25
O2_val = 5.25e24
P_HOx_est = 1e12    # m⁻³/s (for HO2 estimation from Eq. 6.13)

# Vary NO from 10 ppt to 100 ppb
NO_ppb = 10 .^ range(-2, 2, length = 300)
NO_vals = NO_ppb .* 2.5e16  # m⁻³
NO2_vals = 2 .* NO_vals     # assume NO2/NO ratio ~ 2

# Estimate HO2 from steady state (Eqs. 6.13 and 6.18)
HO2_high_NOx = k_CO_OH_val .* CO_val .* OH_val ./ (k_HO2_NO_val .* NO_vals)
HO2_low_NOx = sqrt(P_HOx_est / (2 * k_HO2_HO2_val))
HO2_vals = min.(HO2_high_NOx, HO2_low_NOx)

# Solve the combined system for each NO level
prob = NonlinearProblem(compiled,
    Dict(compiled.O3 => O3_val, compiled.NO => NO_vals[1], compiled.NO2 => NO2_vals[1],
        compiled.OH => OH_val, compiled.HO2 => HO2_vals[1], compiled.CO => CO_val,
        compiled.CH3O2 => CH3O2_val, compiled.H2O => H2O_val, compiled.M => M_val,
        compiled.O2 => O2_val);
    build_initializeprob = false)

P_O3_net_vals = Float64[]
OPE_result = Float64[]
for i in eachindex(NO_ppb)
    newprob = remake(prob,
        p = [compiled.NO => NO_vals[i], compiled.NO2 => NO2_vals[i],
            compiled.HO2 => HO2_vals[i]])
    sol = solve(newprob)
    push!(P_O3_net_vals, sol[compiled.P_O3_net])
    push!(OPE_result, sol[compiled.OPE])
end

p1 = plot(NO_ppb, P_O3_net_vals ./ 1e12,
    xlabel = "NO (ppb)",
    ylabel = "Net P(O₃) (10¹² m⁻³ s⁻¹)",
    title = "Net O₃ Production vs NOx",
    xscale = :log10, linewidth = 2,
    label = "P(O₃)_net", legend = :topleft)
vline!([0.1], linestyle = :dash, color = :gray, label = "Background NO")
vline!([10.0], linestyle = :dash, color = :red, label = "Urban NO")

p2 = plot(NO_ppb, OPE_result,
    xlabel = "NO (ppb)",
    ylabel = "OPE (mol O₃ / mol NOx)",
    title = "Ozone Production Efficiency",
    xscale = :log10, yscale = :log10, linewidth = 2,
    label = "OPE", legend = :topright, ylims = (0.1, 1000))
hline!([3.0], linestyle = :dot, color = :red, label = "Urban OPE ~ 1-3")
hline!([15.0], linestyle = :dot, color = :blue, label = "Remote OPE ~ 10-30")

plot(p1, p2, layout = (1, 2), size = (900, 400))
"/home/runner/work/GasChem.jl/GasChem.jl/docs/build/combined_o3_regimes.svg"

O3 production in different NOx regimes

The left panel shows that net O3 production peaks at intermediate NOx levels and decreases at very high NOx due to O3 titration by NO and reduced HO2 concentrations. The right panel shows that OPE decreases monotonically with increasing NOx, from OPE > 10 in remote conditions to OPE of 1-3 in urban environments, consistent with Section 6.3 of Seinfeld & Pandis.

Comparison of Atmospheric Conditions

This table computes key diagnostics for each of the three standard atmospheric environments by compiling and solving the TroposphericChemistrySystem with the conditions from each environment.

environments = [
    ("Background", get_conditions_dict(TypicalConditions())),
    ("Urban", get_conditions_dict(UrbanConditions())),
    ("Remote", get_conditions_dict(RemoteConditions()))
]

results = []
for (name, cond) in environments
    env_prob = remake(prob,
        p = [compiled.O3 => cond[:O3], compiled.NO => cond[:NO],
            compiled.NO2 => cond[:NO2], compiled.OH => cond[:OH],
            compiled.HO2 => cond[:HO2], compiled.CO => cond[:CO],
            compiled.CH3O2 => cond[:CH3O2], compiled.H2O => cond[:H2O],
            compiled.M => cond[:M], compiled.O2 => cond[:O2]])
    sol = solve(env_prob)

    push!(results,
        (
            Environment = name,
            NO_ppb = round(cond[:NO] / 2.5e16, sigdigits = 3),
            O3_ppb = round(cond[:O3] / 2.5e16, sigdigits = 3),
            P_O3 = round(sol[compiled.P_O3_total], sigdigits = 3),
            OPE = round(sol[compiled.OPE], sigdigits = 3),
            Chain_Length = round(sol[compiled.chain_length], sigdigits = 3)
        ))
end

DataFrame(results)
3×6 DataFrame
RowEnvironmentNO_ppbO3_ppbP_O3OPEChain_Length
StringFloat64Float64Float64Float64Float64
1Background0.140.03.95e1215.86.57
2Urban10.080.01.98e1452.726.9
3Remote0.0130.07.9e11158.01.71

This table shows how the key photochemical diagnostics vary across different atmospheric environments, illustrating the transition from NOx-limited (remote, high OPE and long chain length) to VOC-limited (urban, low OPE and short chain length) regimes discussed in Chapter 6.