deep_causality_physics/constants/hypersonic.rs
1/*
2 * SPDX-License-Identifier: MIT
3 * Copyright (c) 2023 - 2026. The DeepCausality Authors and Contributors. All Rights Reserved.
4 */
5
6//! Hypersonic reacting-air / Park two-temperature model coefficients for the
7//! Gap-2 (Tier-A) plasma-blackout slice.
8//!
9//! Primary sources (PDFs in `deep_causality_physics/papers/`):
10//! * Gupta, Yos, Thompson & Lee, "A Review of Reaction Rates and Thermodynamic
11//! and Transport Properties for an 11-Species Air Model for Chemical and
12//! Thermal Nonequilibrium Calculations to 30000 K," NASA RP-1232 (1990).
13//! `papers/gupta_1990_nasa_rp1232.pdf` — Table II (reaction rates), eq. 3a/5b.
14//! * Millikan & White, "Systematics of Vibrational Relaxation," J. Chem. Phys.
15//! 39, 3209 (1963) — the τ_vt correlation.
16//! * Park, "Nonequilibrium Hypersonic Aerothermodynamics," Wiley (1990) — the
17//! two-temperature model and the high-temperature vibrational limiting
18//! correction.
19//! * Park, "Review of Chemical-Kinetic Problems of Future NASA Missions, I:
20//! Earth Entries," J. Thermophys. Heat Transfer 7(3):385 (1993).
21//!
22//! Note on rate-set sensitivity: published air-chemistry rate sets disagree on
23//! the associative-ionization pre-exponential/exponent (Gupta RP-1232:
24//! Cf = 9.03e9, η = 0.5, θd = 32,400 K; Park lineage: ~8.8e8, η ≈ 0.5,
25//! θd = 31,900 K; Dunn–Kang: 5.3e12, η = 0, θd = 17,778 K). The Tier-A slice
26//! uses the **RP-1232 primary-table values** (verified from the downloaded PDF)
27//! and the verification tolerance is set wide enough to absorb this rate-set
28//! sensitivity (see `add-park2t-blackout-tier-a`).
29
30// ─────────────────────────────────────────────────────────────────────────
31// Associative ionization N + O ⇌ NO⁺ + e⁻ (the dominant low-velocity channel).
32// Arrhenius forward rate k_f = Cf · T^η · exp(−θd / T), controlled by the
33// heavy-particle translational temperature T.
34// Source: NASA RP-1232 (Gupta et al. 1990), Table II, reaction 7.
35// ─────────────────────────────────────────────────────────────────────────
36
37/// Pre-exponential factor `Cf` for N + O ⇌ NO⁺ + e⁻. Unit: cm³·mol⁻¹·s⁻¹.
38pub const PARK_NO_IONIZATION_PREFACTOR: f64 = 9.03e9;
39
40/// Temperature exponent `η` for N + O ⇌ NO⁺ + e⁻ (dimensionless).
41pub const PARK_NO_IONIZATION_EXPONENT: f64 = 0.5;
42
43/// Characteristic (activation) temperature `θd` for N + O ⇌ NO⁺ + e⁻. Unit: K.
44pub const PARK_NO_IONIZATION_ACTIVATION_TEMP: f64 = 32_400.0;
45
46/// First ionization energy of NO (the dominant air ionization channel), used by
47/// the Saha-equilibrium target. Unit: eV. (NO → NO⁺ + e⁻; ≈ 9.26 eV.)
48pub const NO_IONIZATION_ENERGY_EV: f64 = 9.26;
49
50// ─────────────────────────────────────────────────────────────────────────
51// Millikan–White vibrational relaxation correlation:
52// τ_sr · P = exp[ A_sr · (T^(−1/3) − B · μ_sr^(1/4)) − C ] (P in atm, τ in s)
53// with A_sr = MW_A_COEFFICIENT · μ_sr^(1/2) · θ_v^(4/3), μ_sr in amu, θ_v in K.
54// The natural-log constants below are the base-10 originals (5.0e-4, 0.015, 8.00)
55// converted via ×ln(10): 5.0e-4·ln10 = 1.16e-3 and 8.00·ln10 = 18.42.
56// Source: Millikan & White (1963); Park (1990) rearrangement.
57// ─────────────────────────────────────────────────────────────────────────
58
59/// Millikan–White `A_sr` prefactor coefficient (natural-log form). Combined with
60/// `μ_sr^(1/2) · θ_v^(4/3)` to give `A_sr`. Unit: amu⁻¹ᐟ² · K⁻⁴ᐟ³ (dimensional bookkeeping).
61pub const MILLIKAN_WHITE_A_COEFFICIENT: f64 = 1.16e-3;
62
63/// Millikan–White reduced-mass offset `B` in `(T^(−1/3) − B·μ^(1/4))`. Dimensionless-ish (amu⁻¹ᐟ⁴).
64pub const MILLIKAN_WHITE_MU_OFFSET: f64 = 0.015;
65
66/// Millikan–White additive log constant `C` (natural-log form, = 8.00·ln 10).
67pub const MILLIKAN_WHITE_LOG_OFFSET: f64 = 18.42;
68
69// ─────────────────────────────────────────────────────────────────────────
70// Park (1990) high-temperature limiting vibrational relaxation, applied as
71// τ_park = 1 / (σ_v · c̄ · N), σ_v = σ_ref · (T_ref / T)²
72// to correct the Millikan–White under-prediction above ~8000 K.
73// ─────────────────────────────────────────────────────────────────────────
74
75/// Park limiting vibrational cross-section reference `σ_ref`. Unit: m².
76pub const PARK_LIMITING_CROSS_SECTION: f64 = 1.0e-21;
77
78/// Park limiting-cross-section reference temperature `T_ref`. Unit: K.
79pub const PARK_LIMITING_REFERENCE_TEMP: f64 = 50_000.0;
80
81// ─────────────────────────────────────────────────────────────────────────
82// Characteristic vibrational temperatures θ_v of the principal air species,
83// used by the Millikan–White A_sr term. θ_v = h·c·ω_e / k_B.
84// Source: Park (1990); standard spectroscopic constants (Vincenti & Kruger).
85// ─────────────────────────────────────────────────────────────────────────
86
87/// Characteristic vibrational temperature of N₂. Unit: K.
88pub const THETA_VIB_N2: f64 = 3_393.0;
89
90/// Characteristic vibrational temperature of O₂. Unit: K.
91pub const THETA_VIB_O2: f64 = 2_273.0;
92
93/// Characteristic vibrational temperature of NO. Unit: K.
94pub const THETA_VIB_NO: f64 = 2_739.0;
95
96// ─────────────────────────────────────────────────────────────────────────
97// Finite-rate ionization network (RP-1232 Table II, page 46). Table II pairs
98// each forward rate with its backward rate; the source's eq. (5a) is the
99// detailed-balance relation k_b = k_f / K_eq, so K_eq = k_f / k_b from one
100// table row. The source states the pairs are valid for flight velocities up
101// to about 8 km/s. All rates are Arrhenius forms k = Cf · T^η · exp(−θ/T) in
102// cm³·mol⁻¹·s⁻¹ (two-body) or cm⁶·mol⁻²·s⁻¹ (three-body); the third-body
103// concentration multiplies the three-body forms at the call site.
104// Source: Gupta, Yos, Thompson & Lee, NASA RP-1232 (1990), Table II,
105// verified from `papers/gupta_1990_nasa_rp1232.pdf` (rendered page 46).
106// ─────────────────────────────────────────────────────────────────────────
107
108// Reaction 7 backward: NO⁺ + e⁻ → N + O (dissociative recombination), the
109// two-body reverse of the shipped associative-ionization channel. Rated at
110// the electron temperature in the two-temperature model.
111
112/// Pre-exponential factor for NO⁺ + e⁻ → N + O. Unit: cm³·mol⁻¹·s⁻¹.
113pub const RP1232_NO_DR_PREFACTOR: f64 = 1.80e19;
114
115/// Temperature exponent for NO⁺ + e⁻ → N + O (dimensionless).
116pub const RP1232_NO_DR_EXPONENT: f64 = -1.0;
117
118/// Activation temperature for NO⁺ + e⁻ → N + O (barrier-free). Unit: K.
119pub const RP1232_NO_DR_ACTIVATION_TEMP: f64 = 0.0;
120
121// Reaction 8 forward: O + e⁻ → O⁺ + e⁻ + e⁻ (electron-impact ionization).
122// Table II states the central value with a ±33 percent spread; the source
123// notes (page 10) these rates come from expansion-flow data and tend to be
124// lower than compressive-flow data. Both are absorbed by the validation band.
125
126/// Pre-exponential factor for O + e⁻ → O⁺ + 2e⁻. Unit: cm³·mol⁻¹·s⁻¹.
127pub const RP1232_EI_O_PREFACTOR: f64 = 3.6e31;
128
129/// Temperature exponent for O + e⁻ → O⁺ + 2e⁻ (dimensionless).
130pub const RP1232_EI_O_EXPONENT: f64 = -2.91;
131
132/// Activation temperature for O + e⁻ → O⁺ + 2e⁻. Unit: K.
133pub const RP1232_EI_O_ACTIVATION_TEMP: f64 = 1.58e5;
134
135// Reaction 9 forward: N + e⁻ → N⁺ + e⁻ + e⁻ (electron-impact ionization).
136
137/// Pre-exponential factor for N + e⁻ → N⁺ + 2e⁻. Unit: cm³·mol⁻¹·s⁻¹.
138pub const RP1232_EI_N_PREFACTOR: f64 = 1.1e32;
139
140/// Temperature exponent for N + e⁻ → N⁺ + 2e⁻ (dimensionless). Table II
141/// states −3.14; written as a quotient because the raw literal trips
142/// `clippy::approx_constant` (it is a temperature exponent, not π).
143pub const RP1232_EI_N_EXPONENT: f64 = -314.0 / 100.0;
144
145/// Activation temperature for N + e⁻ → N⁺ + 2e⁻. Unit: K.
146pub const RP1232_EI_N_ACTIVATION_TEMP: f64 = 1.69e5;
147
148// Reaction 1: O₂ + M ⇌ 2O + M (dissociation forward, three-body
149// recombination backward). Forward in cm³·mol⁻¹·s⁻¹ (after the third-body
150// concentration multiplies once), backward in cm⁶·mol⁻²·s⁻¹.
151
152/// Pre-exponential factor for O₂ + M → 2O + M. Unit: cm³·mol⁻¹·s⁻¹.
153pub const RP1232_O2_DISS_PREFACTOR: f64 = 3.61e18;
154
155/// Temperature exponent for O₂ + M → 2O + M (dimensionless).
156pub const RP1232_O2_DISS_EXPONENT: f64 = -1.0;
157
158/// Activation temperature for O₂ + M → 2O + M. Unit: K.
159pub const RP1232_O2_DISS_ACTIVATION_TEMP: f64 = 5.94e4;
160
161/// Pre-exponential factor for 2O + M → O₂ + M. Unit: cm⁶·mol⁻²·s⁻¹.
162pub const RP1232_O2_RECOMB_PREFACTOR: f64 = 3.01e15;
163
164/// Temperature exponent for 2O + M → O₂ + M (dimensionless).
165pub const RP1232_O2_RECOMB_EXPONENT: f64 = -0.5;
166
167// Reaction 2: N₂ + M ⇌ 2N + M.
168
169/// Pre-exponential factor for N₂ + M → 2N + M. Unit: cm³·mol⁻¹·s⁻¹.
170pub const RP1232_N2_DISS_PREFACTOR: f64 = 1.92e17;
171
172/// Temperature exponent for N₂ + M → 2N + M (dimensionless).
173pub const RP1232_N2_DISS_EXPONENT: f64 = -0.5;
174
175/// Activation temperature for N₂ + M → 2N + M. Unit: K.
176pub const RP1232_N2_DISS_ACTIVATION_TEMP: f64 = 1.131e5;
177
178/// Pre-exponential factor for 2N + M → N₂ + M. Unit: cm⁶·mol⁻²·s⁻¹.
179pub const RP1232_N2_RECOMB_PREFACTOR: f64 = 1.09e16;
180
181/// Temperature exponent for 2N + M → N₂ + M (dimensionless).
182pub const RP1232_N2_RECOMB_EXPONENT: f64 = -0.5;
183
184// ─────────────────────────────────────────────────────────────────────────
185// Standard-air elemental composition for the atom-pool closure (mole
186// fractions of the undissociated diatomics; trace species folded into N₂).
187// Source: U.S. Standard Atmosphere 1976 (N₂ 0.78084, O₂ 0.20946; the ~1
188// percent Ar and trace gases are folded into the inert N₂ share here).
189// ─────────────────────────────────────────────────────────────────────────
190
191/// Mole fraction of N₂ in undissociated standard air (traces folded in).
192pub const AIR_N2_MOLE_FRACTION: f64 = 0.79;
193
194/// Mole fraction of O₂ in undissociated standard air.
195pub const AIR_O2_MOLE_FRACTION: f64 = 0.21;
196
197// ─────────────────────────────────────────────────────────────────────────
198// Real-field accessors for the finite-rate network coefficients, following
199// the house mechanism (see `constants/condensed.rs`): each `f64` constant
200// has a companion function returning it at the target precision `R`.
201// ─────────────────────────────────────────────────────────────────────────
202
203use deep_causality_algebra::RealField;
204use deep_causality_num::FromPrimitive;
205
206/// Returns [`RP1232_NO_DR_PREFACTOR`] at the target real-field precision `R`.
207#[inline]
208pub fn rp1232_no_dr_prefactor<R: RealField + FromPrimitive>() -> R {
209 crate::constants::real_from_f64(RP1232_NO_DR_PREFACTOR)
210}
211
212/// Returns [`RP1232_NO_DR_EXPONENT`] at the target real-field precision `R`.
213#[inline]
214pub fn rp1232_no_dr_exponent<R: RealField + FromPrimitive>() -> R {
215 crate::constants::real_from_f64(RP1232_NO_DR_EXPONENT)
216}
217
218/// Returns [`RP1232_NO_DR_ACTIVATION_TEMP`] at the target real-field precision `R`.
219#[inline]
220pub fn rp1232_no_dr_activation_temp<R: RealField + FromPrimitive>() -> R {
221 crate::constants::real_from_f64(RP1232_NO_DR_ACTIVATION_TEMP)
222}
223
224/// Returns [`RP1232_EI_O_PREFACTOR`] at the target real-field precision `R`.
225#[inline]
226pub fn rp1232_ei_o_prefactor<R: RealField + FromPrimitive>() -> R {
227 crate::constants::real_from_f64(RP1232_EI_O_PREFACTOR)
228}
229
230/// Returns [`RP1232_EI_O_EXPONENT`] at the target real-field precision `R`.
231#[inline]
232pub fn rp1232_ei_o_exponent<R: RealField + FromPrimitive>() -> R {
233 crate::constants::real_from_f64(RP1232_EI_O_EXPONENT)
234}
235
236/// Returns [`RP1232_EI_O_ACTIVATION_TEMP`] at the target real-field precision `R`.
237#[inline]
238pub fn rp1232_ei_o_activation_temp<R: RealField + FromPrimitive>() -> R {
239 crate::constants::real_from_f64(RP1232_EI_O_ACTIVATION_TEMP)
240}
241
242/// Returns [`RP1232_EI_N_PREFACTOR`] at the target real-field precision `R`.
243#[inline]
244pub fn rp1232_ei_n_prefactor<R: RealField + FromPrimitive>() -> R {
245 crate::constants::real_from_f64(RP1232_EI_N_PREFACTOR)
246}
247
248/// Returns [`RP1232_EI_N_EXPONENT`] at the target real-field precision `R`.
249#[inline]
250pub fn rp1232_ei_n_exponent<R: RealField + FromPrimitive>() -> R {
251 crate::constants::real_from_f64(RP1232_EI_N_EXPONENT)
252}
253
254/// Returns [`RP1232_EI_N_ACTIVATION_TEMP`] at the target real-field precision `R`.
255#[inline]
256pub fn rp1232_ei_n_activation_temp<R: RealField + FromPrimitive>() -> R {
257 crate::constants::real_from_f64(RP1232_EI_N_ACTIVATION_TEMP)
258}
259
260/// Returns [`RP1232_O2_DISS_PREFACTOR`] at the target real-field precision `R`.
261#[inline]
262pub fn rp1232_o2_diss_prefactor<R: RealField + FromPrimitive>() -> R {
263 crate::constants::real_from_f64(RP1232_O2_DISS_PREFACTOR)
264}
265
266/// Returns [`RP1232_O2_DISS_EXPONENT`] at the target real-field precision `R`.
267#[inline]
268pub fn rp1232_o2_diss_exponent<R: RealField + FromPrimitive>() -> R {
269 crate::constants::real_from_f64(RP1232_O2_DISS_EXPONENT)
270}
271
272/// Returns [`RP1232_O2_DISS_ACTIVATION_TEMP`] at the target real-field precision `R`.
273#[inline]
274pub fn rp1232_o2_diss_activation_temp<R: RealField + FromPrimitive>() -> R {
275 crate::constants::real_from_f64(RP1232_O2_DISS_ACTIVATION_TEMP)
276}
277
278/// Returns [`RP1232_O2_RECOMB_PREFACTOR`] at the target real-field precision `R`.
279#[inline]
280pub fn rp1232_o2_recomb_prefactor<R: RealField + FromPrimitive>() -> R {
281 crate::constants::real_from_f64(RP1232_O2_RECOMB_PREFACTOR)
282}
283
284/// Returns [`RP1232_O2_RECOMB_EXPONENT`] at the target real-field precision `R`.
285#[inline]
286pub fn rp1232_o2_recomb_exponent<R: RealField + FromPrimitive>() -> R {
287 crate::constants::real_from_f64(RP1232_O2_RECOMB_EXPONENT)
288}
289
290/// Returns [`RP1232_N2_DISS_PREFACTOR`] at the target real-field precision `R`.
291#[inline]
292pub fn rp1232_n2_diss_prefactor<R: RealField + FromPrimitive>() -> R {
293 crate::constants::real_from_f64(RP1232_N2_DISS_PREFACTOR)
294}
295
296/// Returns [`RP1232_N2_DISS_EXPONENT`] at the target real-field precision `R`.
297#[inline]
298pub fn rp1232_n2_diss_exponent<R: RealField + FromPrimitive>() -> R {
299 crate::constants::real_from_f64(RP1232_N2_DISS_EXPONENT)
300}
301
302/// Returns [`RP1232_N2_DISS_ACTIVATION_TEMP`] at the target real-field precision `R`.
303#[inline]
304pub fn rp1232_n2_diss_activation_temp<R: RealField + FromPrimitive>() -> R {
305 crate::constants::real_from_f64(RP1232_N2_DISS_ACTIVATION_TEMP)
306}
307
308/// Returns [`RP1232_N2_RECOMB_PREFACTOR`] at the target real-field precision `R`.
309#[inline]
310pub fn rp1232_n2_recomb_prefactor<R: RealField + FromPrimitive>() -> R {
311 crate::constants::real_from_f64(RP1232_N2_RECOMB_PREFACTOR)
312}
313
314/// Returns [`RP1232_N2_RECOMB_EXPONENT`] at the target real-field precision `R`.
315#[inline]
316pub fn rp1232_n2_recomb_exponent<R: RealField + FromPrimitive>() -> R {
317 crate::constants::real_from_f64(RP1232_N2_RECOMB_EXPONENT)
318}
319
320/// Returns [`AIR_N2_MOLE_FRACTION`] at the target real-field precision `R`.
321#[inline]
322pub fn air_n2_mole_fraction<R: RealField + FromPrimitive>() -> R {
323 crate::constants::real_from_f64(AIR_N2_MOLE_FRACTION)
324}
325
326/// Returns [`AIR_O2_MOLE_FRACTION`] at the target real-field precision `R`.
327#[inline]
328pub fn air_o2_mole_fraction<R: RealField + FromPrimitive>() -> R {
329 crate::constants::real_from_f64(AIR_O2_MOLE_FRACTION)
330}
331
332/// Returns [`PARK_NO_IONIZATION_PREFACTOR`] at the target real-field precision `R`.
333#[inline]
334pub fn park_no_ionization_prefactor<R: RealField + FromPrimitive>() -> R {
335 crate::constants::real_from_f64(PARK_NO_IONIZATION_PREFACTOR)
336}
337
338/// Returns [`PARK_NO_IONIZATION_EXPONENT`] at the target real-field precision `R`.
339#[inline]
340pub fn park_no_ionization_exponent<R: RealField + FromPrimitive>() -> R {
341 crate::constants::real_from_f64(PARK_NO_IONIZATION_EXPONENT)
342}
343
344/// Returns [`PARK_NO_IONIZATION_ACTIVATION_TEMP`] at the target real-field precision `R`.
345#[inline]
346pub fn park_no_ionization_activation_temp<R: RealField + FromPrimitive>() -> R {
347 crate::constants::real_from_f64(PARK_NO_IONIZATION_ACTIVATION_TEMP)
348}
349
350// Reaction 6: N₂ + O ⇌ NO + N (Zeldovich exchange), the low-activation
351// N-atom production path that feeds associative ionization before direct
352// N₂ dissociation wakes up. Source: RP-1232 Table II, reaction 6 (verified
353// from the rendered page 46).
354
355/// Pre-exponential factor for N₂ + O → NO + N. Unit: cm³·mol⁻¹·s⁻¹.
356pub const RP1232_ZELDOVICH_PREFACTOR: f64 = 6.75e13;
357
358/// Temperature exponent for N₂ + O → NO + N (dimensionless).
359pub const RP1232_ZELDOVICH_EXPONENT: f64 = 0.0;
360
361/// Activation temperature for N₂ + O → NO + N. Unit: K.
362pub const RP1232_ZELDOVICH_ACTIVATION_TEMP: f64 = 3.75e4;
363
364/// Park's classic controlling-temperature exponent for **dissociation**,
365/// `T_q = T^q · T_v^(1−q)` with `q = 0.7` (Park 1990; the geometric mean
366/// `q = 0.5` is the alternative). The controlling-temperature choice is the
367/// largest closure divergence among production codes (DPLR/LAURA/US3D); this
368/// model adopts the Park lineage's own published exponent for the Park rate
369/// set. The *ionization* controller keeps the calibrated geometric mean.
370pub const PARK_DISSOCIATION_Q: f64 = 0.7;
371
372/// Returns [`RP1232_ZELDOVICH_PREFACTOR`] at the target real-field precision `R`.
373#[inline]
374pub fn rp1232_zeldovich_prefactor<R: RealField + FromPrimitive>() -> R {
375 crate::constants::real_from_f64(RP1232_ZELDOVICH_PREFACTOR)
376}
377
378/// Returns [`RP1232_ZELDOVICH_EXPONENT`] at the target real-field precision `R`.
379#[inline]
380pub fn rp1232_zeldovich_exponent<R: RealField + FromPrimitive>() -> R {
381 crate::constants::real_from_f64(RP1232_ZELDOVICH_EXPONENT)
382}
383
384/// Returns [`RP1232_ZELDOVICH_ACTIVATION_TEMP`] at the target real-field precision `R`.
385#[inline]
386pub fn rp1232_zeldovich_activation_temp<R: RealField + FromPrimitive>() -> R {
387 crate::constants::real_from_f64(RP1232_ZELDOVICH_ACTIVATION_TEMP)
388}
389
390/// Returns [`PARK_DISSOCIATION_Q`] at the target real-field precision `R`.
391#[inline]
392pub fn park_dissociation_q<R: RealField + FromPrimitive>() -> R {
393 crate::constants::real_from_f64(PARK_DISSOCIATION_Q)
394}