Internals
The unexported functions and types of the package. They are listed for those extending it — e.g. adding methods of despread_noise! for a new noise source — and for those reading its source. They are not part of the public API: they may change in any release.
TrackingLoops.CarrierLoopStaging — Type
CarrierLoopStaging(carrier_loop_filter_type)A scalar estimator's CarrierLoopStage and the phase-lock indicator that moves it on, read with carrier_loop_stage and phase_lock_indicator. CarrierLoopStaging(typeof(carrier_loop_filter)) starts it (FLL-assisted if the filter has an FLL path, else at the wide PLL); it then advances with every driver record. Not exported: the estimator states' keyword constructors start it; only code building a state positionally names it, as TrackingLoops.CarrierLoopStaging.
TrackingLoops.CarrierRamp — Type
CarrierRampThe carrier Doppler the navigation filter commands a member with: doppler (Hz) at time (s, records' grid) and its rate (Hz/s). Each record is commanded the ramp at the middle of the record it lands on.
TrackingLoops.NavFilterModel — Type
NavFilterModelThe navigation filter's process model F, Q for one navigation cycle, rebuilt in place when the cycle time changes (ensure_nav_filter_cycle_time!).
TrackingLoops.NoiseUpdateContext — Type
Per-call side information handed to update_noise!: what a software noise source may need and a Tracking.BandMeasurement does not carry.
Fields:
signal— the signal being measured (the estimator's key). Its code period sets the sub-integration length and its code family is what the reference despreads with, so the measurement carries that signal's spectral weighting.chunk_index— the index of the chunk being measured, on the same griddownconvert_and_correlate!uses.downconvert_and_correlator— the backend running this call. A software source must despread on it: the one- and two-bit accumulators are popcount counts, so another kernel would put|P|²andN̂₀on different scales and drop the quantisation loss from the measurement.
One struct, so adding a field later is not a signature change. It deliberately carries no satellite state; see CorrelatorNoiseEstimator.
TrackingLoops.ResidualPLL — Type
ResidualPLLThe residual PLL of a satellite in the vector loop (see VectorPLLAndDLL). Its fields are internal.
A PLL in software: it wipes each prompt with its own prediction of the incoming carrier phase against the NCO's, and the scalar PLL's third-order loop filter without FLL assist (ThirdOrderBilinearLF) closes it at bandwidth, capped like the scalar PLL's. It keeps
prompt_phase: the residual phase (cycles, unwrapped) it predicted at the latest prompt's centre and wiped that prompt with;carrier_freq_update: the loop filter's output (Hz), which the prediction runs at against the ramp until the next prompt;loop_filter: whose states estimate the incoming Doppler against the ramp (x1, Hz) and its rate against the ramp's (x2, Hz/s). A new ramp leaves these estimates where they are (_residual_pll_on_new_ramp).
Its lock is tested on the prompts it rotated (_with_lock_test), which also holds it at the ramp while the host's C/N₀ says there is no signal.
TrackingLoops.VectorNavigation — Type
VectorNavigationThe navigation engine of a VectorPLLAndDLL, shared by every satellite it steps: the slots, the bias layout, the navigation filter's model, state and covariance, and every buffer a cycle needs.
It holds the latest solution (pvt, whose containers every cycle reuses) and vector_loop_members (see vector_loop_members). pvt.sats carries only the members the update measured, so the keys missing from it are the members the filter coasted through an obscuration.
Base.length — Method
length(estimator)
Number of observations in the window. Diagnostic only: it varies with the producer's dump cadence.
TrackingLoops._advance_secondary_phase — Method
_advance_secondary_phase(
signal,
bit_buffer,
num_code_blocks
)
Advance a just-synced bit_buffer's secondary_phase by num_code_blocks (mod N); no-op without a secondary code.
The detector reports the phase for the block after the syncing record, but Tracking.jl's code-phase snap reads it only after the whole chunk, so every later record of the chunk (not overlay_wiped, see fold_record) must move it along, or the replica applies the wrong overlay chip (cf. JuliaGNSS/Tracking.jl#219).
TrackingLoops._cfar_decide — Method
_cfar_decide(
mean_bin_energy,
bin_energy_sum_of_squared_deviations,
num_blocks,
period,
confidence
)
Shared CFAR (constant-false-alarm-rate) decision of the soft sync detectors: find the maximum-energy hypothesis and its closest competitor in the PhaseAccumulators statistics and decide whether the peak is significant. period is both the blocks per bin and the number of hypotheses; confidence is the target 1 − P(false lock).
Returns (accepted, peak_index, peak_bin_count): whether the gap is significant (the caller still gates on the bin boundary), the 0-based winner (-1 if none) and its completed-bin count.
The true hypothesis keeps full coherent gain on every bin while wrong ones lose energy (straddling a bit transition, or failing to wipe the overlay), so the mean bin energy is the maximum-likelihood timing statistic. The noise scale is the winner's bin-to-bin energy variance, which captures thermal noise and slow drift (a within-bin residual would miss the drift and accept a small systematic asymmetry between hypotheses). The peak is accepted only when
z_score = energy_gap / standard_error ≥ t⁻¹(1 - (1 - confidence) / (period - 1); ν)with the standard error over the peak and runner-up bin counts, the false-alarm probability Bonferroni-split over the competitors, and _t_quantile as a small-sample penalty at ν = _bin_energy_variance_dof(peak_bin_count). A real peak's z_score grows like √bins, so the detector self-paces with C/N₀; a drift-only asymmetry stays bounded and never locks.
TrackingLoops._detect_bit_edge_cfar — Method
_detect_bit_edge_cfar(
accumulators,
blocks_per_bit,
confidence,
num_blocks
)
Soft CFAR bit-edge detector (see uses_soft_bit_edge_detection; GPS L1 C/A) running _cfar_decide over the edge phases 0:blocks_per_bit-1.
Fires only when the latest block ends the winning phase's bit, so the upcoming integration starts a fresh bit (rules out the off-by-one lock of JuliaGNSS/Tracking.jl#124). polarity is the sign of the last completed bin's sum.
TrackingLoops._detect_secondary_code_cfar — Method
_detect_secondary_code_cfar(
accumulators,
secondary_code_length,
confidence,
num_blocks
)
Soft CFAR secondary-code detector, the analog of _detect_bit_edge_cfar for short overlays (see uses_soft_secondary_code_detection). Unlike the hard _secondary_code_search it rejects the noise-driven false locks a short template match is prone to.
Fires only when the latest block ends the winning rotation's period, so the upcoming integration starts at secondary chip 0 and SyncResult.phase is 0. polarity is the sign of the winning period's overlay-wiped sum.
TrackingLoops._detect_secondary_code_sync — Method
_detect_secondary_code_sync(
signal,
prn,
code_block_bits,
num_code_blocks
)
Hard-path detector for secondary-coded signals: once N blocks are buffered, run _secondary_code_search against _packed_secondary_code allowing floor(tolerance × N) errors (get_bit_edge_or_secondary_code_tolerance); at the default, an exact match for a code shorter than 40 chips.
A new secondary-coded signal needs get_secondary_code, a get_code_block_buffer_type of at least N bits and a detect_bit_or_secondary_code_sync method delegating here (a soft-path signal reaches it only with its soft trait overridden).
TrackingLoops._detect_symbol_is_code_block_sync — Method
_detect_symbol_is_code_block_sync(_, _, _, _)
Detector for signals with no sub-block boundary to find: found = true from the first integration. Covers one symbol per code period (GPS L1C-D, L2CM, Galileo E1B / E6-B, BeiDou B2b-I / B1C-D; GNSSDecoder.jl resolves the ±1 polarity from the preamble) and Galileo E5a-QP (no data, no overlay; the lock only gates whole-code-cycle integration).
TrackingLoops._norm_quantile — Method
_norm_quantile(probability)
Standard-normal quantile Φ⁻¹(probability) = √2 · erfinv(2·probability − 1), the dof → ∞ anchor of _t_quantile. ±Inf at probability = 1 / 0.
TrackingLoops._packed_secondary_code — Function
The secondary code for prn packed into B in _secondary_code_search's order (bit i holds chip N - 1 - i). Specialize only for an overlay not reachable through get_secondary_code.
TrackingLoops._secondary_code_search — Method
_secondary_code_search(
received,
reference,
secondary_code_length,
max_errors
)
Hard-decision secondary-code rotation search (currently the 1800-chip overlay pilots GPS L1C-P and BeiDou B1C-P); locks within one secondary period.
received is the prompt-sign window, newest block in bit 0; reference is packed newest-first (bit i = chip N - 1 - i). Rotating the low N bits left by d finds the best Hamming match of either polarity; the upcoming integration's chip is mod(N - d, N) (Tracking.jl's _snap_code_phase_from_synced_signal anchor). No lock if the best distance exceeds max_errors.
TrackingLoops._t_quantile — Method
_t_quantile(probability, dof)
probability-quantile of a Student-t distribution with dof degrees of freedom. _cfar_decide uses it as a small-sample penalty on its threshold: its z-score is not exactly Student-t (χ² bin energies, correlated hypotheses), so the nominal dof (_bin_energy_variance_dof) is a heuristic. What matters is the shape: steep as dof → 1, the normal quantile as dof → ∞.
Hill's algorithm (Hill, G. W. (1970), Algorithm 396: Student's t-quantiles, Comm. ACM 13(10), 619–620) on the two-tailed probability: closed forms at dof = 1, 2, otherwise a series in the normal deviate or (deep tail) in the probability. Accuracy ≲ 2e-4 relative (worst at dof ≈ 3–10). Used instead of SpecialFunctions.beta_inc_inv, which allocates over part of the dof range and is 10–170× slower: it runs every pre-sync block, which must not allocate (checked in test/bit_buffer.jl).
TrackingLoops._update_phase_accumulators! — Method
_update_phase_accumulators!(
accumulators,
prompt,
block_index,
blocks_per_bit
)
Fold the prompt of the block at 0-based block_index into the PhaseAccumulators of a blocks_per_bit-phase bit-edge search. Phase phase's bins start at block phase; the prompt joins every open bin, and the one phase whose bin ends here folds |bin_sum|² into its Welford statistics and records its polarity.
TrackingLoops._update_secondary_accumulators! — Method
_update_secondary_accumulators!(
accumulators,
prompt,
block_index,
secondary_code_length,
signal,
prn
)
The secondary-code analog of _update_phase_accumulators!: rotation d ∈ 0:N-1 has N-block bins starting at block d, and block i is multiplied by secondary chip mod(i - d, N) (the overlay the post-sync replica applies) before summing, so the correct rotation adds coherently. Chip 0 is thus the physical secondary chip 0, the rotation the hard _secondary_code_search locks (up to a polarity the energy ignores).
TrackingLoops.buffer — Method
buffer(
signal,
prn,
bit_buffer,
integrated_code_blocks,
prompt
)
Advance the bit buffer by one record of integrated_code_blocks blocks.
Post-sync, one soft bit is emitted each time the block count reaches a bit. A record carrying the count past the boundary (only from a non-bit-aligned external producer) drops sync instead (JuliaGNSS/Tracking.jl#238): its energy belongs to two bits, and the equality test would never be met again. The detector then re-runs from clean accumulators and the caller falls back to single-block records; the decoded soft_bits are kept. Silent; apply_record reports it as overshoot.
TrackingLoops.despread_noise! — Function
despread_noise!(backend, estimator, measurement, first_sample, last_sample, context)The software fill path of a CorrelatorNoiseEstimator: despread the reference over the slice with backend's kernel and append the observations. Implemented by Tracking.jl for its backends.
TrackingLoops.fold_record — Method
fold_record(signal, prn, bit_buffer, cn0_estimator, post_corr_filter, output,
sampling_frequency, noise_density, noise_density_ready,
driver_carrier_phase;
overlay_wiped = has_bit_or_secondary_code_been_found(bit_buffer))
-> (bit_buffer, cn0_estimator, post_corr_filter, prompt, filtered_correlator,
bit_block_count, integrated_code_blocks, overshoot, carrier_phase_polarity)Apply one completed record to a signal component's bare state: normalize the raw correlator by sample count and code amplitude, update and apply the post-correlation filter, advance the C/N₀ estimator and the bit buffer.
The bit buffer is credited with the blocks actually integrated (calc_num_code_blocks_for_bit_buffer), recovered from the sample count. Everything here is Tracking's per-record arithmetic, in the same order. carrier_phase_polarity is the sign of the record's prompt where the replica wiped every sign modulation off it (a dataless signal with a secondary code, overlay wiped), else 0; a LoopRecord carries it to the carrier discriminators.
overshoot, overlay_wiped and the pre-sync contract are as for apply_record.
TrackingLoops.is_zero — Method
is_zero(correlator)
Is zero correlator
TrackingLoops.wrap_phase_error — Method
wrap_phase_error(phase_error; polarity = 0)Fold a carrier-phase error in cycles into the range of pll_disc with the same polarity: ±1/4 cycle for the Costas PLL (polarity = 0; a bit flip turns the prompt by half a cycle), ±1/2 for the four-quadrant one. Exact inside it.