Files
gps/soc/software/gps_measurement_python/test_measurement_model.py
T

183 lines
6.1 KiB
Python

from __future__ import annotations
import unittest
import numpy as np
from gps_measurement_python.code_smoothing_model import run_smoothing_trial
from gps_measurement_python.firmware_wls_model import (
SigmaInputs,
SigmaMethod,
firmware_sigma_m,
)
from gps_measurement_python.measurement_scenarios import (
run_scenario,
scenario_matrix,
)
from gps_measurement_python.rtl_dll_model import (
CodeLoopFilterRtl,
quantized_code_epoch_measurement,
rtl_discriminator,
synthesize_dll_window,
)
class RtlDllModelTests(unittest.TestCase):
def test_discriminator_zero_and_q13_symmetry(self):
self.assertEqual(rtl_discriminator(0.0)[0], 0)
positive = rtl_discriminator(0.05)[1]
negative = rtl_discriminator(-0.05)[1]
self.assertAlmostEqual(positive, -negative, places=6)
self.assertLess(positive, 0.0)
def test_code_loop_integrator_accumulates_constant_error(self):
loop = CodeLoopFilterRtl()
first = loop.update(0.01)
twentieth = first
for _ in range(19):
twentieth = loop.update(0.01)
self.assertGreater(first, 0.0)
self.assertGreater(twentieth, first)
def test_dll_window_detects_excess_variance(self):
rng = np.random.default_rng(42)
nominal = synthesize_dll_window(
rng,
cn0_dbhz=45.0,
jitter_multiplier=1.0,
sample_count=4000,
)
noisy = synthesize_dll_window(
rng,
cn0_dbhz=45.0,
jitter_multiplier=3.0,
sample_count=4000,
)
self.assertAlmostEqual(nominal.variance_ratio, 1.0, delta=0.10)
self.assertAlmostEqual(noisy.variance_ratio, 9.0, delta=0.70)
def test_abs_sample_remainder_reconstructs_subsample_epoch(self):
for true_sample in (
12_345_678.314159,
12_345_678.0000001,
12_345_678.9999999,
12_345_678.0,
):
raw, remainder, corrected = quantized_code_epoch_measurement(true_sample)
self.assertEqual(raw, int(np.ceil(true_sample)))
self.assertLessEqual(
abs(corrected - true_sample),
0.5 / 1.023e6 + 1.0e-12,
)
self.assertGreaterEqual(remainder, 0)
class FirmwareSigmaTests(unittest.TestCase):
@staticmethod
def inputs(ratio: float = 1.0, mean_z: float = 0.0) -> SigmaInputs:
return SigmaInputs(
ura_index=np.asarray([0]),
elevation_deg=np.asarray([90.0]),
cn0_dbhz=np.asarray([45.0]),
dll_variance_ratio=np.asarray([ratio]),
dll_mean_z=np.asarray([mean_z]),
actual_jitter_multiplier=np.asarray([np.sqrt(ratio)]),
)
def test_current_matches_c_formula(self):
sigma = firmware_sigma_m(self.inputs(), SigmaMethod.CURRENT)[0]
expected = np.sqrt(2.4**2 + 3.0**2 + 3.0**2)
self.assertAlmostEqual(sigma, expected, places=12)
def test_excess_method_does_not_double_count_nominal_dll_noise(self):
current = firmware_sigma_m(self.inputs(), SigmaMethod.CURRENT)[0]
excess = firmware_sigma_m(self.inputs(), SigmaMethod.DLL_EXCESS)[0]
naive = firmware_sigma_m(self.inputs(), SigmaMethod.NAIVE_ADD_DLL)[0]
self.assertAlmostEqual(excess, current, places=12)
self.assertGreater(naive, current)
def test_excess_method_inflates_hidden_jitter(self):
current = firmware_sigma_m(self.inputs(9.0), SigmaMethod.CURRENT)[0]
excess = firmware_sigma_m(self.inputs(9.0), SigmaMethod.DLL_EXCESS)[0]
self.assertGreater(excess, 1.8 * current)
class ScenarioTests(unittest.TestCase):
def test_hidden_jitter_dynamic_sigma_improves_seeded_case(self):
scenario = next(
item
for item in scenario_matrix()
if item.name == "one_channel_hidden_jitter"
)
samples = run_scenario(
scenario,
17,
(SigmaMethod.CURRENT, SigmaMethod.DLL_EXCESS),
)
current = np.asarray(
[
item.position_error_3d_m
for item in samples
if item.method == SigmaMethod.CURRENT and item.time_s >= 5.0
]
)
dynamic = np.asarray(
[
item.position_error_3d_m
for item in samples
if item.method == SigmaMethod.DLL_EXCESS and item.time_s >= 5.0
]
)
self.assertLess(
np.sqrt(np.mean(dynamic * dynamic)),
0.90 * np.sqrt(np.mean(current * current)),
)
def test_stable_multipath_is_not_solved_by_variance_only(self):
scenario = next(
item
for item in scenario_matrix()
if item.name == "stable_multipath_bias"
)
samples = run_scenario(
scenario,
23,
(SigmaMethod.CURRENT, SigmaMethod.DLL_EXCESS),
)
current = np.asarray(
[
item.position_error_3d_m
for item in samples
if item.method == SigmaMethod.CURRENT and item.time_s >= 5.0
]
)
dynamic = np.asarray(
[
item.position_error_3d_m
for item in samples
if item.method == SigmaMethod.DLL_EXCESS and item.time_s >= 5.0
]
)
relative_difference = abs(
np.sqrt(np.mean(dynamic * dynamic))
/ np.sqrt(np.mean(current * current))
- 1.0
)
self.assertLess(relative_difference, 0.08)
def test_doppler_prediction_avoids_blind_iir_dynamic_lag(self):
result = run_smoothing_trial(31, doppler_cfo_mps=0.0)
self.assertLess(result.doppler_smoothed_rmse_m, result.raw_rmse_m)
self.assertLess(
result.doppler_peak_dynamic_error_m,
0.10 * result.scalar_iir_peak_dynamic_error_m,
)
self.assertGreater(
result.firmware_iir_doppler_peak_dynamic_error_m,
result.doppler_peak_dynamic_error_m,
)
if __name__ == "__main__":
unittest.main()