GNSS Carrier-Phase Smoothing (gnc-autonomy/navigation/gnss-carrier-smoothing)
Use when the task is smoothing GNSS code pseudoranges with
carrier-phase delta ranges as a measurement preprocessor for
positioning. The code pseudorange is low-pass filtered at a smoothing
time constant tau while the precise carrier delta range carries the
smoothed range between epochs, cutting the raw code noise by about an
order of magnitude while a code-carrier ionospheric divergence monitor
alarms the slowly growing error that a linear ionosphere induces. This
leaf implements the first-order Hatch recursion, its exact
steady-state noise closed forms, and the divergence monitor in pure
Python, stdlib only. It pairs with gnc-autonomy/navigation/
gnss-pseudorange-positioning (the single-epoch snapshot fix that
consumes the smoothed ranges), gnss-raim-fde for integrity on the raw
measurement set, and kalman-filter-design as the alternative
estimation route with a dynamics model. RTCA DO-229 MOPS
carrier-smoothing and divergence concepts appear here in paraphrased
summary form only. Continuous carrier phase between epochs is assumed;
cycle-slip repair and integer ambiguity resolution are out of scope.
Domain quick reference
- Hatch gain: alpha = T / tau in (0, 1), so the recursion pole
(1 - alpha) lies in (0, 1) and the variance relaxation e-folds in
about tau/2 seconds (50 s at tau = 100 s). A time constant at or
below the update interval (alpha >= 1) means no smoothing.
- Recursion: s_0 = c_0; for k >= 1,
s_k = alphac_k + (1 - alpha)(s_(k-1) + (phi_k - phi_(k-1))).
The code is low-pass filtered while the carrier delta range bridges
epochs, so a slowly growing code bias is smoothed out over tau while
the fast, precise carrier motion is followed.
- Exact code-only steady-state std: sigma_smoothed =
sigma_codesqrt(alpha/(2 - alpha)). An input fed with weight w to a
pole-(1-alpha) recursion contributes w^2/(alpha(2 - alpha)) times
its variance, giving the code term alpha^2/(alpha*(2 - alpha)) and
the carrier term (1 - alpha)^2/(alpha*(2 - alpha)).
- Textbook limit: sigma_code/sqrt(2tau/T) = sigma_codesqrt(alpha/2),
the tau >> T small-alpha form, with the exact identity
approx = exact*sqrt((2 - alpha)/2) and relative gap
(exact - approx)/exact = alpha/4 + alpha^2/32 + alpha^3/128 + ...,
about 0.0025 at alpha = 0.01.
- Carrier delta-range term std:
(1 - alpha)sigma_carrier/sqrt(alpha(2 - alpha)). The two noise
inputs are independent per epoch, so the total smoothed variance is
the exact sum of the code-term and carrier-term variances and the
total std is sqrt(code-term variance + carrier-term variance).
- Improvement factor: sigma_code / total_std (10.025 at the defaults).
- Ionospheric divergence: the code is delayed by +I while the carrier
is advanced by -I, so the code-carrier difference D = code - phi =
2I grows at rate dD/dt = 2dI/dt. A Hatch filter lags a linear
ramp: at steady state the smoothed-minus-code error is
-rate*(tau - T) = -2*(dI/dt)(tau - T), the classic code-carrier
divergence error whose tau >> T form is -2(dI/dt)*tau.
- Units are SI throughout (m, s, m/s). Deterministic recursion,
closed-form noise and slope fits, stdlib math only.
Workflow
- Set the smoothing configuration: choose the smoothing time
constant tau, the update interval T and the raw noise sigmas, and
confirm 0 < T < tau with alpha_from_time_constant so alpha = T/tau
lies in (0, 1). The defaults are tau = 100 s, T = 1 s,
sigma_code = 0.3 m, sigma_carrier = 0.003 m, window 60, threshold
1.0 m.
- Run the Hatch recursion traverse over the code-carrier stream:
run_hatch_smoother seeds the first epoch with the raw code and each
later epoch applies hatch_update with the carrier delta range
phi_k - phi_(k-1). On a diverging ionosphere the early smoothed
values sink below the raw code, the expected lag signature.
- Form the noise-reduction verdict with code_noise_std_smoothed and
noise_reduction_verdict: the exact code-only std against the
sigma_code/sqrt(2*tau/T) textbook limit and their identity, the
carrier delta-range term, the total std and the improvement factor
over the raw code.
- Run the code-carrier divergence monitor: iono_divergence_rate fits
the least-squares slope of the code-carrier difference over the
trailing window (divided by T into m/s), smoothed_iono_bias
predicts the steady-state smoothed-minus-code bias -rate*(tau - T),
and divergence_check raises the alarm when the predicted bias
exceeds the threshold.
- Gate the smoothed range for positioning: release the smoothed range
time series with the noise-reduction verdict and the divergence
alarm, and confirm the deterministic checks with the contract test
(see Behavior contract below).
Worked example
Receiver at a fixed 20000000.0 m range, tau = 100 s, T = 1 s,
sigma_code = 0.3 m, sigma_carrier = 0.003 m, divergence window 60
epochs, alarm threshold 1.0 m. All values are real module outputs of
this leaf (deterministic, offline).
- Configuration: alpha = T/tau = 0.010000; pole 1 - alpha = 0.990000;
variance relaxation e-folding about tau/2 = 50.0 s.
- Noise-reduction verdict (noise_reduction_verdict):
- exact code-only std = sigma_code*sqrt(alpha/(2 - alpha)) =
0.021266 m;
- textbook limit sigma_code/sqrt(2*tau/T) = 0.3/sqrt(200) =
0.021213 m, relative gap 0.002503 = alpha/4 + alpha^2/32 +
alpha^3/128 (identity asserted to 1e-8);
- carrier delta-range term = 0.021054 m, comparable to the code term
because the recursion integrates many millimeter increments;
- total smoothed std = 0.029925 m (variance is the exact sum of the
code term and the carrier term, asserted to 1e-15);
- improvement factor = sigma_code/total_std = 10.025: the smoothed
range is about ten times quieter than the raw code.
- Empirical confirmation over 38000 settled epochs: code-only replay
(Random(42), perfect carrier) gives empirical std 0.021043 m against
the closed form 0.021266 m (within 0.002 m and 5%); code plus
carrier replay (Random(7)) gives 0.027221 m against 0.029925 m
(relative difference 0.0904, within the 15% autocorrelation budget,
effective samples about N/(2*tau/T) ~ 190).
- Ionospheric ramp at v = 0.02 m/s (divergence rate dD/dt = 2v = 0.04
m/s, noise free): closed-form steady-state smoothed-minus-code bias
smoothed_iono_bias(0.04, 100, 1) = -3.9600 m = -(2v)(tau - T), and
the empirical mean over the last 2000 of 4000 epochs is -3.9600 m
(matched within 0.1%). The smoothed range lags the growing code
delay by about 4 m at this time constant.
- Early epochs of that ramp (raw code 20000000.02 m, carrier delta
-0.0200 m per epoch): smoothed 20000000.000000 (seed),
19999999.980400, 19999999.961196, 19999999.942384,
19999999.923960 m: the recursion sinks below the code while the
ionosphere climbs.
- Divergence monitor on noisy code-carrier differences (code noise
0.3 m on the diffs, Random(123)):
- quiet ionosphere v = 0.001 m/s: estimated rate -0.0001 m/s,
predicted smoothed bias +0.0075 m, alarm False (the noise floor of
the 60 s slope fit stays far below the 1.0 m threshold);
- ramp v = 0.02 m/s: estimated rate 0.0427 m/s (near the true
0.04), predicted smoothed bias -4.2256 m, alarm True.
Verification
- Confirm alpha_from_time_constant(100, 1) = 0.01 and the pole
0.99 in (0, 1).
- Confirm code_noise_std_smoothed(0.01, 0.3) = 0.021266 m and the
identity approx = 0.021213 = exact*sqrt(1.99/2) to 1e-12.
- Confirm the verdict: carrier_term_std 0.021054 m, total_std 0.029925
m, improvement_factor 10.025; total variance equals the exact sum of
the code and carrier term variances to 1e-15.
- Confirm run_hatch_smoother on the noise-free ramp reproduces the
five early smoothed values above within 1e-6.
- Confirm smoothed_iono_bias(0.04, 100, 1) = -3.96 m and the
divergence_check replayed verdicts (quiet no alarm, ramp alarms).
- Confirm every non-physical input raises ValueError: tau <= 0, T <=
0, T >= tau, alpha outside (0, 1), sigma_code or sigma_carrier at 0
or negative, window below 2 or above the epoch count, non-finite
measurements and differences, threshold at 0 or negative, unequal or
empty epoch lists.
- Run the deterministic contract test offline: python3
scripts/test_gnss_carrier_smoothing.py (34 tests).
Related leaves
- gnc-autonomy/navigation/gnss-pseudorange-positioning: the
single-epoch snapshot fix that consumes the smoothed pseudoranges
(its scope explicitly excludes smoothing).
- gnc-autonomy/navigation/gnss-raim-fde: integrity fault detection and
exclusion on the raw measurement set, the snapshot guard around the
positioning solution.
- gnc-autonomy/navigation/kalman-filter-design: the scalar
predict/correct estimator with a dynamics model and covariance
tuning, the alternative to the measurement-domain Hatch recursion.
- gnc-autonomy/navigation/dilution-of-precision: geometry quality
reads that complement the range-quality verdict.
- gnc-autonomy/navigation/ins-gnss-integrated-filter: the loosely
coupled integration that consumes gated GNSS range updates.
Pitfalls
- Reporting the textbook limit as the exact reduction: at alpha =
0.01 the exact code-only std is 0.021266 m while
sigma_code/sqrt(2*tau/T) = 0.021213 m, a 0.25% relative gap that
grows with alpha/4; use the exact closed form and quote the limit
only as the tau >> T approximation.
- Dropping the carrier delta-range term: the carrier increments are
millimeter-level per epoch but the recursion integrates them, so
their term (0.021054 m) rivals the code term (0.021266 m) at the
defaults; the total smoothed std is 0.029925 m, not the code-only
0.021266 m.
- Forgetting the divergence monitor before release: a linear
ionosphere with dI/dt = 0.02 m/s leaves a steady smoothed-minus-code
bias of about -4 m at tau = 100 s, enough to corrupt the position
fix even though the smoothed range looks quiet.
- Running the recursion without validating the configuration: alpha
outside (0, 1), T >= tau, or an empty or unmatched code-carrier
stream raises ValueError rather than producing a plausible-looking
series.
- Assuming continuous carrier phase: the recursion needs an unbroken
carrier arc between epochs; cycle-slip repair and integer ambiguity
resolution are out of scope for this leaf.
Behavior contract (gate 3)
Run the deterministic contract test (stdlib unittest, offline, exit 0):
python3 scripts/test_gnss_carrier_smoothing.py
The test covers the worked-example contract (alpha 0.01, code-only std
0.021266 m, total std 0.029925 m, improvement factor 10.025, early
ramp epochs), the closed-form identities (textbook limit identity to
1e-12, variance sum to 1e-15, relative-gap expansion to 1e-8), the
empirical replays (Random(42) code-only within 0.002 m and 5%,
Random(7) code plus carrier within 15%, ionospheric ramp bias within
0.1%), the divergence monitor replay (quiet no alarm, ramp rate near
0.04 m/s with alarm), deterministic replay, and ValueError rejection of
every non-physical input.
Compliance
- Standards referenced, not reproduced: RTCA DO-229 MOPS is the GNSS
airborne equipment standard whose carrier-smoothing and divergence
monitoring concepts this leaf paraphrases in summary form per
standards-map.yaml; no MOPS text is reproduced verbatim.
- compliance: STANDARDS-REF, gated: false.
1---2name: gnss-carrier-smoothing3description: Use when you must smooth GNSS code pseudoranges with carrier-phase delta ranges before positioning: run the first-order Hatch recursion at a smoothing time constant, carry the smoothed range between epochs on the precise carrier increments, and monitor the code-carrier ionospheric divergence whose trailing-window slope fit predicts the smoothed-minus-code bias that would alarm a diverging range. Computes the noise-reduction verdict from the exact steady-state code-noise closed form sigma_code*sqrt(alpha/(2 - alpha)), its sigma_code/sqrt(2*tau/T) textbook limit and the carrier delta-range term. Produces the smoothed range time series, the verdict (code-only std, quoted limit, carrier term, total std, improvement factor) and the divergence alarm that gate the range before it feeds positioning. Trigger: carrier-phase smoothing, code-carrier smoothing, hatch recursion, smoothing time constant, code-carrier divergence, ionospheric divergence, smoothed range noise reduction.4license: Apache-2.05---67# GNSS Carrier-Phase Smoothing (gnc-autonomy/navigation/gnss-carrier-smoothing)89Use when the task is smoothing GNSS code pseudoranges with10carrier-phase delta ranges as a measurement preprocessor for11positioning. The code pseudorange is low-pass filtered at a smoothing12time constant tau while the precise carrier delta range carries the13smoothed range between epochs, cutting the raw code noise by about an14order of magnitude while a code-carrier ionospheric divergence monitor15alarms the slowly growing error that a linear ionosphere induces. This16leaf implements the first-order Hatch recursion, its exact17steady-state noise closed forms, and the divergence monitor in pure18Python, stdlib only. It pairs with gnc-autonomy/navigation/19gnss-pseudorange-positioning (the single-epoch snapshot fix that20consumes the smoothed ranges), gnss-raim-fde for integrity on the raw21measurement set, and kalman-filter-design as the alternative22estimation route with a dynamics model. RTCA DO-229 MOPS23carrier-smoothing and divergence concepts appear here in paraphrased24summary form only. Continuous carrier phase between epochs is assumed;25cycle-slip repair and integer ambiguity resolution are out of scope.2627## Domain quick reference2829- Hatch gain: alpha = T / tau in (0, 1), so the recursion pole30 (1 - alpha) lies in (0, 1) and the variance relaxation e-folds in31 about tau/2 seconds (50 s at tau = 100 s). A time constant at or32 below the update interval (alpha >= 1) means no smoothing.33- Recursion: s_0 = c_0; for k >= 1,34 s_k = alpha*c_k + (1 - alpha)*(s_(k-1) + (phi_k - phi_(k-1))).35 The code is low-pass filtered while the carrier delta range bridges36 epochs, so a slowly growing code bias is smoothed out over tau while37 the fast, precise carrier motion is followed.38- Exact code-only steady-state std: sigma_smoothed =39 sigma_code*sqrt(alpha/(2 - alpha)). An input fed with weight w to a40 pole-(1-alpha) recursion contributes w^2/(alpha*(2 - alpha)) times41 its variance, giving the code term alpha^2/(alpha*(2 - alpha)) and42 the carrier term (1 - alpha)^2/(alpha*(2 - alpha)).43- Textbook limit: sigma_code/sqrt(2*tau/T) = sigma_code*sqrt(alpha/2),44 the tau >> T small-alpha form, with the exact identity45 approx = exact*sqrt((2 - alpha)/2) and relative gap46 (exact - approx)/exact = alpha/4 + alpha^2/32 + alpha^3/128 + ...,47 about 0.0025 at alpha = 0.01.48- Carrier delta-range term std:49 (1 - alpha)*sigma_carrier/sqrt(alpha*(2 - alpha)). The two noise50 inputs are independent per epoch, so the total smoothed variance is51 the exact sum of the code-term and carrier-term variances and the52 total std is sqrt(code-term variance + carrier-term variance).53- Improvement factor: sigma_code / total_std (10.025 at the defaults).54- Ionospheric divergence: the code is delayed by +I while the carrier55 is advanced by -I, so the code-carrier difference D = code - phi =56 2*I grows at rate dD/dt = 2*dI/dt. A Hatch filter lags a linear57 ramp: at steady state the smoothed-minus-code error is58 -rate*(tau - T) = -2*(dI/dt)*(tau - T), the classic code-carrier59 divergence error whose tau >> T form is -2*(dI/dt)*tau.60- Units are SI throughout (m, s, m/s). Deterministic recursion,61 closed-form noise and slope fits, stdlib math only.6263## Workflow64651. Set the smoothing configuration: choose the smoothing time66 constant tau, the update interval T and the raw noise sigmas, and67 confirm 0 < T < tau with alpha_from_time_constant so alpha = T/tau68 lies in (0, 1). The defaults are tau = 100 s, T = 1 s,69 sigma_code = 0.3 m, sigma_carrier = 0.003 m, window 60, threshold70 1.0 m.712. Run the Hatch recursion traverse over the code-carrier stream:72 run_hatch_smoother seeds the first epoch with the raw code and each73 later epoch applies hatch_update with the carrier delta range74 phi_k - phi_(k-1). On a diverging ionosphere the early smoothed75 values sink below the raw code, the expected lag signature.763. Form the noise-reduction verdict with code_noise_std_smoothed and77 noise_reduction_verdict: the exact code-only std against the78 sigma_code/sqrt(2*tau/T) textbook limit and their identity, the79 carrier delta-range term, the total std and the improvement factor80 over the raw code.814. Run the code-carrier divergence monitor: iono_divergence_rate fits82 the least-squares slope of the code-carrier difference over the83 trailing window (divided by T into m/s), smoothed_iono_bias84 predicts the steady-state smoothed-minus-code bias -rate*(tau - T),85 and divergence_check raises the alarm when the predicted bias86 exceeds the threshold.875. Gate the smoothed range for positioning: release the smoothed range88 time series with the noise-reduction verdict and the divergence89 alarm, and confirm the deterministic checks with the contract test90 (see Behavior contract below).9192## Worked example9394Receiver at a fixed 20000000.0 m range, tau = 100 s, T = 1 s,95sigma_code = 0.3 m, sigma_carrier = 0.003 m, divergence window 6096epochs, alarm threshold 1.0 m. All values are real module outputs of97this leaf (deterministic, offline).9899- Configuration: alpha = T/tau = 0.010000; pole 1 - alpha = 0.990000;100 variance relaxation e-folding about tau/2 = 50.0 s.101- Noise-reduction verdict (noise_reduction_verdict):102 - exact code-only std = sigma_code*sqrt(alpha/(2 - alpha)) =103 0.021266 m;104 - textbook limit sigma_code/sqrt(2*tau/T) = 0.3/sqrt(200) =105 0.021213 m, relative gap 0.002503 = alpha/4 + alpha^2/32 +106 alpha^3/128 (identity asserted to 1e-8);107 - carrier delta-range term = 0.021054 m, comparable to the code term108 because the recursion integrates many millimeter increments;109 - total smoothed std = 0.029925 m (variance is the exact sum of the110 code term and the carrier term, asserted to 1e-15);111 - improvement factor = sigma_code/total_std = 10.025: the smoothed112 range is about ten times quieter than the raw code.113- Empirical confirmation over 38000 settled epochs: code-only replay114 (Random(42), perfect carrier) gives empirical std 0.021043 m against115 the closed form 0.021266 m (within 0.002 m and 5%); code plus116 carrier replay (Random(7)) gives 0.027221 m against 0.029925 m117 (relative difference 0.0904, within the 15% autocorrelation budget,118 effective samples about N/(2*tau/T) ~ 190).119- Ionospheric ramp at v = 0.02 m/s (divergence rate dD/dt = 2*v = 0.04120 m/s, noise free): closed-form steady-state smoothed-minus-code bias121 smoothed_iono_bias(0.04, 100, 1) = -3.9600 m = -(2v)*(tau - T), and122 the empirical mean over the last 2000 of 4000 epochs is -3.9600 m123 (matched within 0.1%). The smoothed range lags the growing code124 delay by about 4 m at this time constant.125- Early epochs of that ramp (raw code 20000000.02 m, carrier delta126 -0.0200 m per epoch): smoothed 20000000.000000 (seed),127 19999999.980400, 19999999.961196, 19999999.942384,128 19999999.923960 m: the recursion sinks below the code while the129 ionosphere climbs.130- Divergence monitor on noisy code-carrier differences (code noise131 0.3 m on the diffs, Random(123)):132 - quiet ionosphere v = 0.001 m/s: estimated rate -0.0001 m/s,133 predicted smoothed bias +0.0075 m, alarm False (the noise floor of134 the 60 s slope fit stays far below the 1.0 m threshold);135 - ramp v = 0.02 m/s: estimated rate 0.0427 m/s (near the true136 0.04), predicted smoothed bias -4.2256 m, alarm True.137138## Verification139140- Confirm alpha_from_time_constant(100, 1) = 0.01 and the pole141 0.99 in (0, 1).142- Confirm code_noise_std_smoothed(0.01, 0.3) = 0.021266 m and the143 identity approx = 0.021213 = exact*sqrt(1.99/2) to 1e-12.144- Confirm the verdict: carrier_term_std 0.021054 m, total_std 0.029925145 m, improvement_factor 10.025; total variance equals the exact sum of146 the code and carrier term variances to 1e-15.147- Confirm run_hatch_smoother on the noise-free ramp reproduces the148 five early smoothed values above within 1e-6.149- Confirm smoothed_iono_bias(0.04, 100, 1) = -3.96 m and the150 divergence_check replayed verdicts (quiet no alarm, ramp alarms).151- Confirm every non-physical input raises ValueError: tau <= 0, T <=152 0, T >= tau, alpha outside (0, 1), sigma_code or sigma_carrier at 0153 or negative, window below 2 or above the epoch count, non-finite154 measurements and differences, threshold at 0 or negative, unequal or155 empty epoch lists.156- Run the deterministic contract test offline: python3157 scripts/test_gnss_carrier_smoothing.py (34 tests).158159## Related leaves160161- gnc-autonomy/navigation/gnss-pseudorange-positioning: the162 single-epoch snapshot fix that consumes the smoothed pseudoranges163 (its scope explicitly excludes smoothing).164- gnc-autonomy/navigation/gnss-raim-fde: integrity fault detection and165 exclusion on the raw measurement set, the snapshot guard around the166 positioning solution.167- gnc-autonomy/navigation/kalman-filter-design: the scalar168 predict/correct estimator with a dynamics model and covariance169 tuning, the alternative to the measurement-domain Hatch recursion.170- gnc-autonomy/navigation/dilution-of-precision: geometry quality171 reads that complement the range-quality verdict.172- gnc-autonomy/navigation/ins-gnss-integrated-filter: the loosely173 coupled integration that consumes gated GNSS range updates.174175## Pitfalls176177- Reporting the textbook limit as the exact reduction: at alpha =178 0.01 the exact code-only std is 0.021266 m while179 sigma_code/sqrt(2*tau/T) = 0.021213 m, a 0.25% relative gap that180 grows with alpha/4; use the exact closed form and quote the limit181 only as the tau >> T approximation.182- Dropping the carrier delta-range term: the carrier increments are183 millimeter-level per epoch but the recursion integrates them, so184 their term (0.021054 m) rivals the code term (0.021266 m) at the185 defaults; the total smoothed std is 0.029925 m, not the code-only186 0.021266 m.187- Forgetting the divergence monitor before release: a linear188 ionosphere with dI/dt = 0.02 m/s leaves a steady smoothed-minus-code189 bias of about -4 m at tau = 100 s, enough to corrupt the position190 fix even though the smoothed range looks quiet.191- Running the recursion without validating the configuration: alpha192 outside (0, 1), T >= tau, or an empty or unmatched code-carrier193 stream raises ValueError rather than producing a plausible-looking194 series.195- Assuming continuous carrier phase: the recursion needs an unbroken196 carrier arc between epochs; cycle-slip repair and integer ambiguity197 resolution are out of scope for this leaf.198199## Behavior contract (gate 3)200201Run the deterministic contract test (stdlib unittest, offline, exit 0):202203 python3 scripts/test_gnss_carrier_smoothing.py204205The test covers the worked-example contract (alpha 0.01, code-only std2060.021266 m, total std 0.029925 m, improvement factor 10.025, early207ramp epochs), the closed-form identities (textbook limit identity to2081e-12, variance sum to 1e-15, relative-gap expansion to 1e-8), the209empirical replays (Random(42) code-only within 0.002 m and 5%,210Random(7) code plus carrier within 15%, ionospheric ramp bias within2110.1%), the divergence monitor replay (quiet no alarm, ramp rate near2120.04 m/s with alarm), deterministic replay, and ValueError rejection of213every non-physical input.214215## Compliance216217- Standards referenced, not reproduced: RTCA DO-229 MOPS is the GNSS218 airborne equipment standard whose carrier-smoothing and divergence219 monitoring concepts this leaf paraphrases in summary form per220 standards-map.yaml; no MOPS text is reproduced verbatim.221- compliance: STANDARDS-REF, gated: false.