fix(gps): adapt Doppler bandwidth from board validation

This commit is contained in:
2026-07-26 03:15:52 +03:00
parent 23650be635
commit 156cfc8b77
24 changed files with 592 additions and 49 deletions
@@ -59,6 +59,90 @@ def noisy_observations(
class AdaptivePpsTests(unittest.TestCase):
def test_board_calibrated_doppler_predictor_reduces_pps_error(self):
"""Genesys2 WLS statistics: predictor-on must beat fallback-only."""
quality_metrics = QualityMetrics(
1.50, 2.92, 15.87, 40.3, 34.8, 0.35, 0.23, 7
)
aided_phase_rms = []
fallback_phase_rms = []
aided_edge_jitter = []
fallback_edge_jitter = []
for seed in range(8):
rng = random.Random(7300 + seed)
observations = []
for update in range(900):
time_s = 0.2 * update
long_omega = 2.0 * math.pi / 75.0
short_omega = 2.0 * math.pi / 19.0
varying_drift_mps = (
5.0 * math.sin(long_omega * time_s)
+ 2.0 * math.sin(short_omega * time_s)
)
varying_bias_m = (
5.0 * (1.0 - math.cos(long_omega * time_s)) / long_omega
+ 2.0 * (1.0 - math.cos(short_omega * time_s))
/ short_omega
)
# 15.87 m / c = 52.94 ns; 3.47 m/s is the measured
# board standard deviation of the reference bias drift.
noise_s = rng.gauss(0.0, 15.87 / 299_792_458.0)
if update % 113 == 0 and update != 0:
noise_s += rng.choice((-1.0, 1.0)) * 120.0e-9
observations.append(
Observation(
time_s,
noise_s,
varying_drift_mps + rng.gauss(0.0, 3.47),
quality_metrics,
True,
-291.75,
1,
varying_bias_m,
)
)
aided = run_observations(
observations,
finish_time_s=180.0,
oscillator_ppm=1.592,
)
fallback = run_observations(
[
Observation(
item.time_s,
item.timing_error_s,
item.clock_drift_error_mps,
item.quality,
False,
item.reference_range_rate_mps,
item.reference_id,
item.reference_datum_offset_m,
)
for item in observations
],
finish_time_s=180.0,
oscillator_ppm=1.592,
)
aided_phase_rms.append(aided.phase_rms(after_s=60.0))
fallback_phase_rms.append(fallback.phase_rms(after_s=60.0))
aided_edge_jitter.append(
aided.one_second_edge_jitter_rms(after_s=60.0)
)
fallback_edge_jitter.append(
fallback.one_second_edge_jitter_rms(after_s=60.0)
)
self.assertEqual(aided.unlock_events, 0)
self.assertLess(
sum(aided_phase_rms) / len(aided_phase_rms),
0.35 * sum(fallback_phase_rms) / len(fallback_phase_rms),
)
self.assertLess(
sum(aided_edge_jitter) / len(aided_edge_jitter),
0.70 * sum(fallback_edge_jitter) / len(fallback_edge_jitter),
)
self.assertLess(max(aided_phase_rms), 30.0e-9)
def test_quality_hysteresis_rejects_metric_chatter(self):
selector = QualitySelector()
for _ in range(7):