Skip to main content

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}