1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// SPDX-License-Identifier: AGPL-3.0-only
//! Klobuchar broadcast-ionosphere reference test (external oracle).
//!
//! kshana's `klobuchar_delay_m` (the IS-GPS-200 §20.3.3.5.2.5 single-frequency
//! ionosphere model) is checked against an **independent third-party
//! implementation**: RTKLIB's `ionmodel` (tomojitakasu/RTKLIB, `src/rtkcmn.c`),
//! the de-facto open GNSS reference, compiled from source and run on the inputs
//! below. Reproducing RTKLIB's slant L1 delay across elevation, azimuth,
//! local-time and two coefficient sets makes the model externally validated, not
//! merely self-consistent. The reference numbers are the RTKLIB outputs to six
//! decimals; kshana matches to far better than the 1e-4 m gate.
use kshana::gnss_sim::{klobuchar_delay_m, KlobucharCoeffs};
const D2R: f64 = std::f64::consts::PI / 180.0;
const TOL_M: f64 = 1e-4;
/// (lat°, lon°, elevation°, azimuth°, GPS seconds-of-day, RTKLIB delay [m]).
struct Case(f64, f64, f64, f64, f64, f64);
fn check(coeffs: &KlobucharCoeffs, cases: &[Case], label: &str) {
for (i, x) in cases.iter().enumerate() {
let got = klobuchar_delay_m(coeffs, x.0 * D2R, x.1 * D2R, x.2 * D2R, x.3 * D2R, x.4);
let d = (got - x.5).abs();
assert!(
d <= TOL_M,
"{label}[{i}]: kshana {got:.6} m vs RTKLIB ionmodel {:.6} m (|Δ|={d:.2e} > {TOL_M:.0e})",
x.5
);
}
}
#[test]
fn klobuchar_matches_rtklib_ionmodel_with_kshana_default_coeffs() {
// kshana's KlobucharCoeffs::default() — the IS-GPS-200 worked-example set.
check(
&KlobucharCoeffs::default(),
&[
Case(40.0, -100.0, 45.0, 0.0, 50400.0, 5.232845),
Case(40.0, -100.0, 80.0, 0.0, 50400.0, 4.196824),
Case(40.0, -100.0, 10.0, 0.0, 50400.0, 7.888022),
Case(0.0, 0.0, 90.0, 0.0, 7200.0, 1.499610),
],
"kshana-default",
);
}
#[test]
fn klobuchar_matches_rtklib_ionmodel_with_rtklib_default_coeffs() {
// RTKLIB's own built-in default broadcast coefficients.
let coeffs = KlobucharCoeffs {
alpha: [0.1118e-7, -0.7451e-8, -0.5961e-7, 0.1192e-6],
beta: [0.1167e6, -0.2294e6, -0.1311e6, 0.1049e7],
};
check(
&coeffs,
&[
Case(40.0, 260.0, 20.0, 210.0, 518400.0, 6.127760),
Case(40.0, 260.0, 80.0, 210.0, 518400.0, 2.606222),
Case(40.0, 260.0, 5.0, 210.0, 518400.0, 9.339666),
Case(40.0, 260.0, 45.0, 45.0, 7200.0, 2.025446),
Case(0.0, 0.0, 30.0, 90.0, 43200.0, 8.167701),
Case(-35.0, 150.0, 25.0, 300.0, 64800.0, 2.933828),
],
"rtklib-default",
);
}