CN0 Estimator
The CN0 (Carrier-to-Noise density ratio) estimator provides a measure of signal quality in dB-Hz. Each TrackedSignal on a TrackedSat holds its own CN0 estimator, so a multi-signal satellite produces one CN0 value per signal.
Default Estimator
The default CN0 estimator is the Moments Method, implemented as MomentsCN0Estimator. It buffers prompt correlation values and estimates CN0 from the first and second moments of their magnitudes. The buffer holds num_prompts_for_cn0_estimation prompts (default 100, ~100 ms for L1 C/A) — the estimator returns 0.0 dB-Hz until at least one prompt has been pushed in, and the estimate sharpens as the buffer fills.
Tracking.MomentsCN0Estimator — Type
MomentsCN0Estimator to estimate the CN0
Tracking.update — Method
update(estimator, prompt)
Buffers the prompts such that they can be used to estimate the CN0. Returns a new estimator with the latest prompt added (immutable update). This is the extension point for custom AbstractCN0Estimators.
You can shrink or grow the buffer by building the TrackedSat yourself and handing it to add_satellite!'s escape-hatch overload:
julia> using Tracking, GNSSSignals
julia> using Tracking: Hz
julia> track_state = TrackState(; signal = GPSL1CA());
julia> sat = TrackedSat(GPSL1CA(), 1, 50.0, 1000.0Hz;
num_prompts_for_cn0_estimation = 200);
julia> track_state = add_satellite!(track_state, :default, sat);
julia> get_prn(track_state, 1)
1What the estimator returns
The estimator's output depends on what's been pushed into its buffer:
- Before any
trackcall has completed an integration — the buffer is empty andestimate_cn0returns0.0 dB-Hz. This is the value seen immediately afteradd_satellite!. - Driving a noise-free signal through
track— the noise-moment estimate goes to zero, so the C/N₀ ratio diverges and the result isInf dB-Hz. The Quick start shows this behavior. - Driving a noisy signal — the estimate converges to the underlying CN0 as more prompts fill the buffer. Real-world signals typically land in 30–50 dB-Hz; values below ~25 dB-Hz are unusable for conventional tracking.
The doctest below builds a noisy L1 C/A signal at a known 45 dB-Hz CN0 and drives 25 1-ms tracking cycles through it. The estimator's output converges to the input within a few dB:
julia> using Tracking, GNSSSignals, Random
julia> using Tracking: Hz
julia> using GNSSSignals: gen_code, get_code_frequency
julia> function run_cn0_demo()
sys = GPSL1CA()
fs = 4e6Hz
num_samples = 4000 # 1 ms at 4 MHz
prn = 1
cn0_db_hz = 45.0
sigma = sqrt((fs/Hz) / 10^(cn0_db_hz/10) / 2)
code_freq = get_code_frequency(sys)
rng = MersenneTwister(0)
track_state = TrackState(; signal = GPSL1CA())
track_state = add_satellite!(track_state; prn, code_phase = 0.0, carrier_doppler = 0.0Hz)
for _ in 1:25
clean = gen_code(num_samples, sys, prn, fs, code_freq, 0.0)
noise = sigma .* randn(rng, ComplexF64, num_samples)
track_state = track(clean .+ noise, track_state, fs)
end
estimate_cn0(track_state, 1)
end;
julia> run_cn0_demo() # converges toward the 45 dB-Hz input as the buffer fills
49.1 dB-HzThe 1-σ noise amplitude is derived from the target CN0 by inverting C/N₀ = signal_power / (σ² / Fs). With unit-amplitude code samples this collapses to σ² = Fs / 10^(CN0_dB/10), split evenly across the complex sample's real and imaginary parts. The seeded MersenneTwister(0) keeps the doctest deterministic; the bounds above (40–50 dB-Hz) leave room for the natural spread of the moment estimator with a 25-prompt fill of the default 100-sample buffer.
Tracking.estimate_cn0 — Function
estimate_cn0(estimator, integration_time)
Estimates the CN0 based on the struct MomentsCN0Estimator.
Custom CN0 Estimators
Tracking.AbstractCN0Estimator — Type
Abstract supertype for CN0 (carrier-to-noise-density ratio) estimators. Each TrackedSignal holds one estimator instance. Custom estimators subtype this and implement Tracking.update and estimate_cn0.
You can implement your own estimator by creating a subtype of AbstractCN0Estimator and implementing:
Tracking.update(cn0_estimator::MyCN0Estimator, prompt)— return a new estimator with the latest prompt added (immutable update).estimate_cn0(cn0_estimator::MyCN0Estimator, integration_time)— return the CN0 estimate as adB-Hzquantity.
To plug your custom estimator into a satellite, build a TrackedSignal with your cn0_estimator, splice it into a fresh TrackedSat, and add it via the escape-hatch overload — see the TrackedSat docstring for the full constructor surface.