Skip to main content

Module tracking_loop

Module tracking_loop 

Source
Expand description

Tracking-loop loss-of-lock model: C/N₀ → lock/unlock with hysteresis, the time it takes to lose lock, and the spoof pull-in limits set by code and carrier offset rate.

The engine’s existing denial and capture criteria are ratios on received power: a jammer denies at J/S = 30 dB and a spoofer captures at J/S = 3 dB (crate::attack_surface), and a satellite is “lost” when its effective C/N₀ crosses a flat 25 dB-Hz line (crate::jamming::lock_status). Real loss of lock does not work that way. A tracking channel gives up when its loop can no longer hold the signal, and that depends on the loop noise bandwidth B_n, the predetection integration time T, the correlator spacing d, and the dynamics the loop is being asked to follow. This module builds that model and reports the denial radius it implies alongside the power-ratio radius — never in place of it — with the difference named (denial_radius_delta_km) so the two can be compared rather than quietly swapped.

Nothing here forks a second correlator or a second link budget. The loop primitives are crate::sdr (correlate, synth_if, CaCode) and the radiometry is crate::jamming (j_over_s_db, effective_cn0_dbhz, nominal_cn0_dbhz, q_factor).

§What is modelled

  1. Carrier-loop thermal jitter — the Costas 1σ phase jitter σ_PLL = √( (B_n/(C/N₀)) · (1 + 1/(2·T·C/N₀)) ) rad, the second factor being the squaring loss. The per-epoch (open-loop) discriminator jitter is the same expression with B_n → 1/(2T), and the two differ by exactly the loop-filter factor √(2·B_n·T).
  2. Code-loop thermal jitter — the non-coherent early/late 1σ code jitter σ_DLL = √( (d·B_n/(2·C/N₀)) · (1 + 2/((2−d)·T·C/N₀)) ) chips.
  3. The tracking-threshold rules — a Costas loop is held to 3σ_PLL + θ_e ≤ 45° (the “15-degree rule”: one sigma may not exceed a quarter of the 60° quarter-cycle budget), and a DLL to 3σ_DLL + lag ≤ d/2 chips, the discriminator’s linear half-range. Both allowances are inputs; the defaults are the stated rules.
  4. Dynamic stress — a second-order carrier loop carries a steady-state phase error θ_e = 360·ḟ/ω_n² degrees under a Doppler rate ḟ, with ω_n = B_n/0.53; a first-order code loop carries a ramp lag ẋ/(4·B_n) chips under a code slew ẋ. These are what a spoofer pushing on the loop actually has to stay inside.
  5. Hysteresis — a receiver that has lost lock must re-pull-in, and pull-in runs a wider loop than steady-state tracking. Since jitter grows as √B_n, the re-lock threshold is strictly above the drop threshold, and the width follows from the bandwidth ratio rather than being asserted: in the thermal-dominated limit it is 10·log₁₀(r) dB and in the squaring-loss-dominated limit 5·log₁₀(r) dB, so any computed hysteresis must lie between those two bounds.
  6. Time to lose lock — two distinct quantities, both reported. The declared loss of lock is when a two-threshold lock detector with confirmation dwells transitions, driven by a C/N₀ profile; the physical escape time is the mean time between cycle slips of the Costas loop, T̄ = π²·ρ'·I₀²(ρ')/(2·B_n) with ρ' = ρ/4 and loop SNR ρ = 1/σ_PLL² (Viterbi’s first-order result applied to the doubled phase 2φ a Costas loop tracks, whose slips are half cycles), reported in log₁₀ s because it spans many decades.

§Validated vs Modelled

Validated against the existing crate::sdr correlator stepped forward — a genuinely different route to the same numbers, not a restatement:

  • the open-loop Costas discriminator jitter including its squaring-loss term agrees with the measured jitter of crate::sdr::correlate on seeded synthetic IF to better than 2 % over 35–50 dB-Hz;
  • the closed-loop jitter reduction σ_closed = σ_meas·√(2·B_n·T) agrees with a stepped first-order Costas loop built from the same correlator to ~3 %;
  • the first-order code-loop ramp lag ẋ/(4·B_n) agrees with the stepped DLL to better than 0.1 %, with B_n = g·slope/(4T) the discrete loop’s equivalent bandwidth and slope = 1/(2−d) the ideal-triangle discriminator gain;
  • bessel_i0 agrees with the standard modified-Bessel values to < 2 × 10⁻⁶ relative.

Modelled — the default loop parameters (10 Hz carrier / 1 Hz code noise bandwidth, 1 ms predetection integration, 0.5 chip spacing), the 2× pull-in bandwidth ratio, the confirmation dwell lengths, the representative jammer power and antenna gains, and the C/N₀ profile are all representative band figures, not a datasheet for any receiver. Oscillator (Allan-deviation) and vibration jitter are not modelled: the carrier budget here is thermal plus dynamic stress only, and a real receiver adds those two terms, which can only make the threshold worse. Front-end bandwidth limiting, multipath, AGC dynamics, data-bit-transition losses, and aiding from an inertial or clock reference are likewise out of scope and stated rather than silently included. The slip time is Viterbi’s first-order expression; a second-order loop slips more often (GNSS-SDR 0.0.19’s second-order Costas loop slipped about ten times more often near the threshold in tests/tracking_loop_gnss_sdr_oracle.rs).

References: Kaplan & Hegarty, Understanding GPS/GNSS (3rd ed.), ch. 8 (carrier and code tracking loops, thermal jitter, the tracking-threshold rules, the second-order B_n = 0.53·ω_n relation); A. J. Viterbi, Principles of Coherent Communication (1966) and F. M. Gardner, Phaselock Techniques (3rd ed.), ch. 9 (mean time between cycle slips); Abramowitz & Stegun §9.8 (the I₀ polynomial approximations).

Structs§

DenialLink
The one-jammer link whose radius both denial criteria are evaluated on. Every leg is evaluated with the existing crate::jamming functions; nothing here re-derives the radiometry.
LockDetector
A two-threshold lock detector with confirmation dwells — the mechanism that makes lock/unlock hysteretic rather than a single line a noisy C/N₀ estimate chatters across. Lock is dropped after drop_confirm_epochs consecutive epochs below drop_cn0_dbhz, and regained after relock_confirm_epochs consecutive epochs at or above the strictly higher relock_cn0_dbhz.
LockHistory
The outcome of running the hysteretic detector over the C/N₀ profile.
LoopConfig
The loop geometry a threshold calculation needs.
LoopThresholds
The C/N₀ thresholds a loop configuration implies, with the binding loop named.
PullInCell
One cell of the spoof pull-in map: whether a spoofer slewing the victim’s code at code_slew_chips_per_s and its carrier at doppler_rate_hz_per_s stays inside both loops’ following limits.
TrackingLoopScenario
A tracking-loop loss-of-lock scenario. Every field is defaulted, so a body of only kind = "tracking-loop" reproduces the reference configuration.

Enums§

LockState
Whether the receiver currently declares the channel locked.

Constants§

CN0_SEARCH_MAX_DBHZ
Highest C/N₀ (dB-Hz) considered when solving a tracking threshold.
CN0_SEARCH_MIN_DBHZ
Lowest C/N₀ (dB-Hz) considered when solving a tracking threshold.
COSTAS_THRESHOLD_DEG
The Costas-loop carrier tracking-threshold allowance (degrees) applied to 3σ + θ_e. This is the “15-degree rule” stated as its three-sigma form: one sigma of phase jitter may not exceed 15°, i.e. three sigma may not exceed 45°.
MAX_AXIS_LEN
Hard cap on the length of any input axis (the spoof pull-in grid, the jitter table).
MAX_DETECTOR_STEPS
Hard cap on the number of lock-detector steps a single run may take, so no input can turn the state machine into an unbounded loop.
RADIUS_BRACKET_MAX_M
Upper bound on the search bracket (m) used when inverting a link for a radius.
RADIUS_BRACKET_MIN_M
Lower bound on the search bracket (m) used when inverting a link for a radius.
SECOND_ORDER_BN_OVER_WN
The ratio B_n/ω_n for the standard second-order loop (damping ζ = 1/√2), as used to convert a stated loop noise bandwidth into the natural frequency that sets dynamic stress. Kaplan & Hegarty, ch. 8.

Functions§

bessel_i0
Modified Bessel function of the first kind, order zero. Abramowitz & Stegun §9.8 polynomial approximations: 9.8.1 for |x| ≤ 3.75 (|ε| < 1.6 × 10⁻⁷) and 9.8.2 for |x| > 3.75 (|ε| < 1.9 × 10⁻⁷ on the scaled form). Overflows to infinity above x ≈ 713; use ln_bessel_i0 where that matters.
carrier_tracking_threshold_cn0_dbhz
The carrier-loop tracking threshold (dB-Hz): the lowest C/N₀ at which 3·σ_PLL + θ_e still fits inside allowance_deg, for a loop of bandwidth bn_hz, integration t_s, under a Doppler rate doppler_rate_hz_per_s.
code_tracking_threshold_cn0_dbhz
The code-loop tracking threshold (dB-Hz): the lowest C/N₀ at which 3·σ_DLL + lag still fits inside allowance_chips, for a DLL of bandwidth bn_hz, integration t_s, correlator spacing spacing_chips, under a code slew of code_slew_chips_per_s. None when the slew lag alone exceeds the allowance.
dll_measurement_jitter_chips
Per-epoch (open-loop) code-error jitter (chips) of a non-coherent early/late discriminator at correlator spacing d: σ² = (d/(4·T·C/N₀)) · (1 + 2/((2−d)·T·C/N₀)).
dll_ramp_lag_chips
Steady-state code lag (chips) a first-order code loop of noise bandwidth bn_hz carries under a code slew of code_slew_chips_per_s: lag = ẋ/(4·B_n).
dll_thermal_jitter_chips
Closed-loop code thermal jitter (chips) for a non-coherent early/late DLL: σ_DLL² = (d·B_n/(2·C/N₀)) · (1 + 2/((2−d)·T·C/N₀)).
early_late_amplitude_discriminator_slope
The linear-region gain (chips of discriminator output per chip of code error) of the normalised early-minus-late amplitude discriminator ½·(|E|−|L|)/(|E|+|L|) for an ideal triangular autocorrelation at spacing d: 1/(2−d). This is the constant that maps the per-epoch gain of the discrete loop in crate::sdr::track onto an equivalent loop noise bandwidth.
equivalent_first_order_bandwidth_hz
The equivalent one-sided loop noise bandwidth (Hz) of a discrete first-order loop that applies gain × discriminator once per t_s: B_n = g·k/(4·T), where k is the discriminator’s linear-region slope. A first-order loop with this bandwidth carries the ramp lag dll_ramp_lag_chips returns, which is how the two are cross-checked against the crate::sdr correlator.
ln_bessel_i0
Natural logarithm of bessel_i0, stable for arbitrarily large arguments: for x > 3.75 the A&S 9.8.2 scaled form gives ln I₀ = x − ½·ln x + ln(poly(3.75/x)), so a loop SNR of several hundred (where I₀ itself overflows a float) is still reportable. Needed because the mean time between cycle slips grows like e^{2ρ}.
log10_mean_time_to_cycle_slip_s
Mean time between Costas carrier slips (half cycles), reported as log₁₀(seconds): T̄ = π²·ρ'·I₀²(ρ')/(2·B_n) with ρ' = ρ/4 and loop SNR ρ = 1/σ_PLL² (Viterbi’s first-order, sinusoidal-phase-detector result; Gardner, Phaselock Techniques, ch. 9).
max_code_slew_at_cn0_chips_per_s
The largest code slew (chips/s) a DLL can be dragged at at signal strength cn0_dbhz: the ramp lag may only use whatever 3σ_DLL leaves of allowance_chips. Zero when the jitter already fills the budget. Reduces to max_code_slew_chips_per_s as the jitter goes to zero, and is the ceiling the pull-in map’s cells are decided against, so the two can never disagree.
max_code_slew_chips_per_s
The largest code slew (chips/s) a noiseless first-order DLL of bandwidth bn_hz can be dragged at before its ramp lag leaves the discriminator’s linear half-range allowance_chips: ẋ_max = 4·B_n·allowance. A spoofer slewing faster than this does not capture a locked loop — it outruns it, and the victim falls back onto the authentic peak (or loses lock) instead of following the lie.
max_doppler_rate_hz_per_s
The largest carrier Doppler rate (Hz/s) a second-order loop of bandwidth bn_hz can be dragged at, at signal strength cn0_dbhz: the dynamic stress must fit in whatever the jitter leaves of allowance_deg. Zero when the jitter already fills the budget.
pll_dynamic_stress_deg
Steady-state carrier phase error (degrees) a second-order loop of noise bandwidth bn_hz carries under a Doppler rate of doppler_rate_hz_per_s: θ_e = 360·ḟ/ω_n² with ω_n = B_n/0.53. This is the dynamic-stress term that adds to 3σ in the 45° carrier budget, and it is what limits how fast a spoofer may slew a victim’s carrier before the victim’s loop simply falls off the back.
pll_measurement_jitter_rad
Per-epoch (open-loop) Costas discriminator phase jitter (rad) at cn0_dbhz for a predetection integration time t_s: σ² = (1/(2·C/N₀·T)) · (1 + 1/(2·T·C/N₀)). The first factor is the post-correlation phase noise of a coherent integration; the second is the squaring loss a Costas discriminator pays for being insensitive to the data-bit sign.
pll_thermal_jitter_rad
Closed-loop Costas carrier thermal jitter (rad): σ_PLL² = (B_n/(C/N₀)) · (1 + 1/(2·T·C/N₀)).
pull_in_cell
Evaluate one spoof pull-in cell for a loop configuration at signal strength cn0_dbhz.
run_lock_history
Drive detector over the down-then-up C/N₀ ramp at the loop’s own epoch rate and report when it declared loss of lock and when it recovered. Deterministic.