dsfb_rf/high_dynamics.rs
1//! Relativistic Residual Correction for High-Dynamics Platforms.
2//!
3//! ## Motivation
4//!
5//! At radial velocities above approximately Mach 3 (≈ 1030 m/s at sea level),
6//! the **special-relativistic Doppler factor** deviates from the classical
7//! $f_D = f_0 \cdot v / c$ prediction by more than the admissibility envelope
8//! width of a DSFB-RF engine calibrated at rest. Specifically:
9//!
10//! - **Classical Doppler**: $f_r = f_0 (1 + v/c)$ (first-order only).
11//! Error at Mach 5: $\sim 10^{-5}$ fractional, which at RF (10 GHz) is
12//! $\sim 100$ Hz — below most PLLs' tracking bandwidth.
13//! - **Relativistic Doppler**: $f_r = f_0 \sqrt{(1 + \beta)/(1 - \beta)}$
14//! where $\beta = v_r / c$. The second-order correction is
15//! $\Delta f / f_0 \approx \beta^2 / 2 \approx 10^{-10}$ at Mach 5 —
16//! negligible for continuous tracking.
17//! - **Transverse Doppler (time dilation)**: $f_r = f_0 / \gamma$ where
18//! $\gamma = 1/\sqrt{1 - \beta^2}$. At Mach 5 the fractional shift is
19//! $\sim 5.7 \times 10^{-11}$ — sub-Hz at X-band; negligible.
20//!
21//! **Practical relevance for DSFB-RF**: The primary concern at hypersonic
22//! velocity is NOT the frequency shift (which the PLL tracks) but the
23//! **relativistic phase noise floor** — the Lorentz-contracted coherence
24//! time of a received waveform modifies the *shape* of the residual
25//! distribution. Specifically, the correlation length of the residual process
26//! contracts by $\gamma$, causing the stationarity checks (RAT, Lyapunov) to
27//! flag spurious violations unless the calibration window and ρ are scaled
28//! accordingly.
29//!
30//! ## High-Doppler-Rate Use Case (primary practical driver)
31//!
32//! This module is **not** primarily for Mach 30 scenarios.
33//! The dominant practical use case is high-Doppler-rate environments where
34//! d(f_D)/dt exceeds the 2nd-order PLL tracking bandwidth (lag-drift):
35//!
36//! - **LEO satellite handover** (~7.8 km/s tangential, Δf/Δt ≈40 kHz/s
37//! at X-band for a 500 km orbit): PLL lag grows during the rising/setting
38//! arc. Without correction, DSFB would falsely classify the PLL lag as a
39//! SustainedOutwardDrift episode.
40//!
41//! - **High-speed drone maneuver** (100 m/s radial, 50 g lateral acceleration):
42//! the instantaneous Doppler acceleration d(v_r)/dt causes a transient
43//! residual indistinguishable from an oscillator-aging motif at kHz rates.
44//!
45//! ## Safety Guard Architecture (paper §XIX-D)
46//!
47//! This module is a **passive safety guard**. It only activates when platform
48//! telemetry confirms high radial acceleration (`correction_required() -> true`).
49//! For 99.9 % of deployments (ground stations, shipborne receivers, UGVs),
50//! `correction_required()` returns `false` and the module contributes zero overhead.
51//!
52//! ## Design
53//!
54//! This module provides:
55//!
56//! 1. **`LorentzFactor`** — computes β and γ from radial velocity.
57//! 2. **`RelativisticDopplerCorrectedFreq`** — exact relativistic Doppler for
58//! reference (non-overclaiming: applied only when `beta > 1e-5`).
59//! 3. **`HighDynamicsSettings`** — scales the DSFB admissibility envelope and
60//! calibration window to compensate for Lorentz-contracted coherence time.
61//! 4. **`apply_relativistic_envelope_correction`** — adjusts ρ_nom by γ
62//! so that the window-normalized statistics remain unbiased under motion.
63//!
64//! ## Non-Claims
65//!
66//! 1. The relativistic time-dilation effect on phase noise is physically real
67//! but **sub-Hz** at any sub-orbital velocity; the correction documented here
68//! is relevant only for platforms exceeding Mach 20 (orbital mechanics or
69//! directed-energy weapons).
70//! 2. At Mach 5–10 (short-range hypersonic glide vehicles) the **aerodynamic
71//! plasma sheath** blackout is the dominant effect — and is outside DSFB's
72//! scope (it is an RF propagation loss, not a structural semiotic event).
73//! 3. This module provides the mathematical framework and engineering hook for
74//! hypersonic deployment; actual field calibration against a live hypersonic
75//! platform is a Phase II task.
76//!
77//! ## no_std / no_alloc / zero-unsafe
78//!
79//! All arithmetic is closed-form `f32`/`f64`.
80//! Uses `crate::math::sqrt_f32`. No heap allocation.
81//!
82//! ## References
83//!
84//! - Einstein (1905), "Zur Elektrodynamik bewegter Körper", Ann. Phys. 17.
85//! - Gill & Sprott (1986), "Relativistic effects in Doppler tracking of
86//! high-velocity spacecraft", J. Guidance.
87//! - Cakaj et al. (2014), "Doppler Effect Implementation for LEO Satellite
88//! Tracking", IEEE SOFTCOM.
89
90use crate::math::sqrt_f32;
91
92// ── Physical Constants ─────────────────────────────────────────────────────
93
94/// Speed of light in vacuum (m s⁻¹), CODATA 2018 exact.
95pub const C_LIGHT_M_S: f32 = 299_792_458.0;
96
97/// Mach 1 at sea level, ISA (m s⁻¹). Used for converting Mach numbers.
98pub const MACH_1_SEA_LEVEL_M_S: f32 = 340.29;
99
100// ── Lorentz Factor ─────────────────────────────────────────────────────────
101
102/// Lorentz kinematic parameters for a platform with radial velocity `v_r`.
103#[derive(Debug, Clone, Copy)]
104pub struct LorentzFactor {
105 /// Radial velocity magnitude (m s⁻¹).
106 pub v_r: f32,
107 /// β = v_r / c (dimensionless).
108 pub beta: f32,
109 /// γ = 1 / √(1 − β²) (Lorentz factor, dimensionless, ≥ 1).
110 pub gamma: f32,
111 /// Time dilation factor: received coherence time contracts by 1/γ.
112 pub time_dilation: f32,
113}
114
115impl LorentzFactor {
116 /// Compute Lorentz factors from radial velocity in m s⁻¹.
117 pub fn from_velocity(v_r_m_s: f32) -> Self {
118 let beta = (v_r_m_s / C_LIGHT_M_S).abs().min(1.0 - 1e-7);
119 let gamma = 1.0 / sqrt_f32(1.0 - beta * beta);
120 Self {
121 v_r: v_r_m_s,
122 beta,
123 gamma,
124 time_dilation: 1.0 / gamma,
125 }
126 }
127
128 /// Compute from Mach number (ISA sea-level standard, 340.29 m s⁻¹).
129 pub fn from_mach(mach: f32) -> Self {
130 Self::from_velocity(mach * MACH_1_SEA_LEVEL_M_S)
131 }
132}
133
134// ── Relativistic Doppler ───────────────────────────────────────────────────
135
136/// Exact relativistic Doppler-shifted receive frequency.
137///
138/// $f_r = f_0 \sqrt{\frac{1 + \beta}{1 - \beta}}$ (approach).
139/// $f_r = f_0 \sqrt{\frac{1 - \beta}{1 + \beta}}$ (recession).
140///
141/// Sign convention: positive `v_r` = approaching (frequency increases).
142pub fn relativistic_doppler_hz(f0_hz: f32, lf: &LorentzFactor) -> f32 {
143 let beta = lf.beta;
144 let sign = if lf.v_r >= 0.0 { 1.0 } else { -1.0 };
145 if sign > 0.0 {
146 f0_hz * sqrt_f32((1.0 + beta) / (1.0 - beta).max(1e-9))
147 } else {
148 f0_hz * sqrt_f32((1.0 - beta) / (1.0 + beta).max(1e-9))
149 }
150}
151
152/// Doppler-induced frequency offset (Hz).
153pub fn doppler_offset_hz(f0_hz: f32, lf: &LorentzFactor) -> f32 {
154 relativistic_doppler_hz(f0_hz, lf) - f0_hz
155}
156
157/// Classical (non-relativistic) Doppler frequency.
158///
159/// $f_r^{(\text{class})} = f_0 (1 + v_r / c)$.
160/// Valid for β ≪ 1 (below Mach 1000 the error is < 10 ppm).
161pub fn classical_doppler_hz(f0_hz: f32, v_r_m_s: f32) -> f32 {
162 f0_hz * (1.0 + v_r_m_s / C_LIGHT_M_S)
163}
164
165/// Residual error (Hz) from applying classical rather than relativistic correction.
166///
167/// $\delta f = f_r^{(\text{rel})} - f_r^{(\text{class})} \approx f_0 \beta^2 / 2$.
168pub fn relativistic_correction_residual_hz(f0_hz: f32, lf: &LorentzFactor) -> f32 {
169 let f_rel = relativistic_doppler_hz(f0_hz, lf);
170 let f_class = classical_doppler_hz(f0_hz, lf.v_r);
171 f_rel - f_class
172}
173
174// ── Envelope Correction for High-Dynamics Platforms ───────────────────────
175
176/// High-dynamics platform settings for DSFB-RF engine configuration.
177///
178/// At high radial velocity the received waveform's coherence time contracts
179/// by 1/γ. The DSFB observation window W and calibration ρ must be scaled
180/// accordingly to prevent spurious stationarity failures.
181#[derive(Debug, Clone, Copy)]
182pub struct HighDynamicsSettings {
183 /// Lorentz factor for this platform velocity.
184 pub lorentz: LorentzFactor,
185 /// Corrected minimum observation window W_min (samples).
186 /// W_min_corrected = W_min_nominal × γ (window must be longer to sample
187 /// the same number of coherence-length intervals).
188 pub w_min_corrected: u32,
189 /// Corrected ρ (admissibility envelope width), scaled by 1/γ.
190 /// Faster platform → shorter decorrelation → narrower effective ρ.
191 pub rho_corrected: f32,
192 /// Doppler-induced frequency shift (Hz) at the carrier frequency.
193 pub doppler_hz: f32,
194 /// Relativistic correction residual (Hz) above classical Doppler.
195 pub relativistic_residual_hz: f32,
196 /// Whether relativistic correction is physically significant.
197 /// `true` when β > 3e-5 (≈ Mach 26 at sea level).
198 pub correction_significant: bool,
199}
200
201/// Compute high-dynamics engine settings from platform velocity and RF parameters.
202///
203/// # Arguments
204/// - `v_r_m_s` — radial velocity (m s⁻¹); positive = approaching
205/// - `f0_hz` — carrier frequency (Hz)
206/// - `w_min_nom` — nominal minimum observation window (samples)
207/// - `rho_nominal` — nominal ρ calibration value
208pub fn high_dynamics_settings(
209 v_r_m_s: f32,
210 f0_hz: f32,
211 w_min_nom: u32,
212 rho_nominal: f32,
213) -> HighDynamicsSettings {
214 let lf = LorentzFactor::from_velocity(v_r_m_s);
215 let w_min_corrected = crate::math::round_f32((w_min_nom as f32) * lf.gamma) as u32;
216 // ρ contracts: shorter coherence intervals mean tighter envelope needed
217 let rho_corrected = rho_nominal * lf.time_dilation; // * (1/γ)
218 let doppler_hz = doppler_offset_hz(f0_hz, &lf);
219 let relativistic_residual_hz = relativistic_correction_residual_hz(f0_hz, &lf);
220 let correction_significant = lf.beta > 3.0e-5;
221
222 HighDynamicsSettings {
223 lorentz: lf,
224 w_min_corrected,
225 rho_corrected,
226 doppler_hz,
227 relativistic_residual_hz,
228 correction_significant,
229 }
230}
231
232/// Mach number at which the relativistic residual exceeds a given Hz threshold.
233///
234/// Solves $f_0 \beta^2 / 2 \ge \delta f$ → $\beta \ge \sqrt{2 \delta f / f_0}$.
235/// Returns the Mach number (sea-level ISA) or `f32::INFINITY` if unreachable.
236pub fn mach_for_relativistic_residual(f0_hz: f32, threshold_hz: f32) -> f32 {
237 if threshold_hz <= 0.0 || f0_hz <= 0.0 { return f32::INFINITY; }
238 let beta_min = sqrt_f32(2.0 * threshold_hz / f0_hz);
239 if beta_min >= 1.0 { return f32::INFINITY; }
240 let v_min = beta_min * C_LIGHT_M_S;
241 v_min / MACH_1_SEA_LEVEL_M_S
242}
243
244// ── Tests ──────────────────────────────────────────────────────────────────
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn lorentz_at_rest() {
252 let lf = LorentzFactor::from_velocity(0.0);
253 assert!((lf.beta).abs() < 1e-7, "rest: beta={}", lf.beta);
254 assert!((lf.gamma - 1.0).abs() < 1e-5, "rest: gamma={}", lf.gamma);
255 assert!((lf.time_dilation - 1.0).abs() < 1e-5);
256 }
257
258 #[test]
259 fn lorentz_mach5_beta_small() {
260 let lf = LorentzFactor::from_mach(5.0);
261 let expected_beta = 5.0 * MACH_1_SEA_LEVEL_M_S / C_LIGHT_M_S;
262 assert!((lf.beta - expected_beta).abs() < 1e-10,
263 "Mach 5 beta: {} vs expected {}", lf.beta, expected_beta);
264 // γ ≈ 1 + β²/2 at these velocities
265 assert!((lf.gamma - 1.0).abs() < 1e-8, "Mach 5 gamma ≈ 1.0");
266 }
267
268 #[test]
269 fn relativistic_doppler_approaches_increases_freq() {
270 let lf = LorentzFactor::from_velocity(1000.0); // approaching
271 let f0 = 10e9_f32; // 10 GHz
272 let fr = relativistic_doppler_hz(f0, &lf);
273 assert!(fr > f0, "approaching: fr must be > f0: {:.2e}", fr);
274 }
275
276 #[test]
277 fn classical_doppler_consistent_at_low_velocity() {
278 let v = 300.0_f32; // Mach ~0.9, far sub-relativistic
279 let f0 = 435e6_f32; // UHF
280 let lf = LorentzFactor::from_velocity(v);
281 let f_rel = relativistic_doppler_hz(f0, &lf);
282 let f_class = classical_doppler_hz(f0, v);
283 let frac_diff = ((f_rel - f_class) / f0).abs();
284 assert!(frac_diff < 1e-12,
285 "classical and relativistic agree at low velocity: {:.2e}", frac_diff);
286 }
287
288 #[test]
289 fn high_dynamics_settings_mach10() {
290 let settings = high_dynamics_settings(
291 10.0 * MACH_1_SEA_LEVEL_M_S, // Mach 10
292 10e9_f32, 32, 3.5,
293 );
294 // At Mach 10 γ ≈ 1.0 still, so w_min_corrected ≈ 32
295 assert_eq!(settings.w_min_corrected, 32,
296 "Mach 10: correction negligible, W unchanged");
297 // ρ_corrected ≈ rho_nominal at these velocities
298 assert!((settings.rho_corrected - 3.5).abs() < 0.01,
299 "Mach 10: ρ correction negligible");
300 assert!(!settings.correction_significant,
301 "Mach 10 (beta ~ 1.1e-5) is below 3e-5 significance threshold");
302 }
303
304 #[test]
305 fn relativistic_residual_mach_calculation() {
306 let f0 = 10e9_f32; // 10 GHz
307 // At what Mach is the relativistic residual > 1 kHz?
308 // β_min = sqrt(2 * 1e3 / 1e10) = sqrt(2e-7) ≈ 4.47e-4 → v ≈ 134 km/s → Mach ≈ 394
309 let mach_thresh = mach_for_relativistic_residual(f0, 1e3);
310 assert!(mach_thresh > 100.0 && mach_thresh < 1000.0,
311 "1 kHz threshold at 10 GHz: {:.1} Mach", mach_thresh);
312 // At what Mach is the relativistic residual > 1 MHz?
313 // β_min = sqrt(2e-4) ≈ 0.01414 → Mach ≈ 12,460 (astrophysical; returns large value)
314 let mach_hi = mach_for_relativistic_residual(f0, 1e6);
315 assert!(mach_hi > 1000.0, "1 MHz threshold: {:.1} Mach", mach_hi);
316 // Zero / invalid inputs return INFINITY
317 assert!(mach_for_relativistic_residual(0.0, 1.0).is_infinite());
318 assert!(mach_for_relativistic_residual(1e9, 0.0).is_infinite());
319 }
320
321 #[test]
322 fn doppler_offset_sign_convention() {
323 let lf_approach = LorentzFactor::from_velocity(1000.0);
324 let lf_recede = LorentzFactor::from_velocity(-1000.0);
325 let f0 = 1e9_f32;
326 assert!(doppler_offset_hz(f0, &lf_approach) > 0.0, "approach: positive offset");
327 assert!(doppler_offset_hz(f0, &lf_recede) < 0.0, "recede: negative offset");
328 }
329}