use super::C_M_S;
use crate::portable_math::PortableFloat;
pub const K_IONO: f64 = 40.3;
pub const TECU: f64 = 1.0e16;
pub fn group_delay_m(stec_el_m2: f64, f_hz: f64) -> f64 {
K_IONO * stec_el_m2 / (f_hz * f_hz)
}
pub fn stec_from_klobuchar(
coeffs: &crate::gnss_sim::KlobucharCoeffs,
lat_rad: f64,
lon_rad: f64,
el_rad: f64,
az_rad: f64,
gps_sod: f64,
) -> f64 {
let d = crate::gnss_sim::klobuchar_delay_m(coeffs, lat_rad, lon_rad, el_rad, az_rad, gps_sod);
d * crate::gnss_sim::L1_HZ * crate::gnss_sim::L1_HZ / K_IONO
}
pub fn single_layer_mapping(el_rad: f64, re_m: f64, shell_m: f64) -> f64 {
let x = re_m * el_rad.pcos() / (re_m + shell_m);
1.0 / (1.0 - x * x).max(1e-9).sqrt()
}
fn erfc(x: f64) -> f64 {
1.0 - crate::detection::erf(x)
}
pub fn fraction_below(h_m: f64, peak_m: f64, scale_m: f64) -> f64 {
let z = (h_m - peak_m) / scale_m.max(1.0);
let x = 0.5 * (-z).pexp();
erfc(x.sqrt()).clamp(0.0, 1.0)
}
pub fn iono_free_coefficients(f1_hz: f64, f2_hz: f64) -> (f64, f64) {
let (a, b) = (f1_hz * f1_hz, f2_hz * f2_hz);
(a / (a - b), -b / (a - b))
}
pub fn geometry_free_stec_tecu(
p1_m: f64,
p2_m: f64,
f1_hz: f64,
f2_hz: f64,
dcb_p1_minus_p2_m: f64,
) -> f64 {
let (a, b) = (f1_hz * f1_hz, f2_hz * f2_hz);
((p2_m - p1_m) + dcb_p1_minus_p2_m) * a * b / (40.3 * (a - b)) / TECU
}
pub fn iono_free_noise_amplification(f1_hz: f64, f2_hz: f64, sigma1: f64, sigma2: f64) -> f64 {
let (a1, a2) = iono_free_coefficients(f1_hz, f2_hz);
(a1 * a1 * sigma1 * sigma1 + a2 * a2 * sigma2 * sigma2).sqrt()
}
pub fn dll_code_noise_m(
chip_rate_hz: f64,
cn0_dbhz: f64,
bl_hz: f64,
d_chips: f64,
t_s: f64,
) -> f64 {
let cn0 = 10f64.ppowf(cn0_dbhz / 10.0);
let var_chips = bl_hz * d_chips / (2.0 * cn0) * (1.0 + 2.0 / (t_s * cn0 * (2.0 - d_chips)));
var_chips.sqrt() * C_M_S / chip_rate_hz
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delay_scales_as_one_over_f_squared() {
let stec = 50.0 * TECU;
let d1 = group_delay_m(stec, 1.2e9);
let d2 = group_delay_m(stec, 2.4e9);
assert!((d1 / d2 - 4.0).abs() < 1e-12);
assert!((group_delay_m(TECU, crate::gnss_sim::L1_HZ) - 0.162_376).abs() < 1e-5);
}
#[test]
fn chapman_fraction_is_half_at_about_the_peak_and_tends_to_one_high_up() {
let f_peak = fraction_below(350e3, 350e3, 100e3);
assert!((f_peak - erfc(0.5f64.sqrt())).abs() < 1e-6);
assert!(fraction_below(20_000e3, 350e3, 100e3) > 0.999);
assert!(fraction_below(0.0, 350e3, 100e3) < 1e-6);
let leo = fraction_below(510e3, 350e3, 100e3);
assert!((0.5..0.8).contains(&leo), "{leo}");
}
#[test]
fn iono_free_combination_removes_the_first_order_delay() {
let (f1, f2) = (1575.42e6, 1176.45e6);
let (a1, a2) = iono_free_coefficients(f1, f2);
assert!((a1 + a2 - 1.0).abs() < 1e-12);
let stec = 30.0 * TECU;
let resid = a1 * group_delay_m(stec, f1) + a2 * group_delay_m(stec, f2);
assert!(resid.abs() < 1e-9);
}
#[test]
fn code_noise_falls_with_cn0_and_chip_rate() {
let a = dll_code_noise_m(1.023e6, 45.0, 1.0, 0.5, 0.02);
let b = dll_code_noise_m(1.023e6, 55.0, 1.0, 0.5, 0.02);
let c = dll_code_noise_m(10.23e6, 45.0, 1.0, 0.5, 0.02);
assert!(b < a && c < a);
assert!((a / c - 10.0).abs() < 1e-9);
assert!((0.3..2.0).contains(&a), "{a}");
}
}