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.

source
TrackingLoops.CarrierRamp — Type
CarrierRamp

The 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.

source
TrackingLoops.NavFilterModel — Type
NavFilterModel

The navigation filter's process model F, Q for one navigation cycle, rebuilt in place when the cycle time changes (ensure_nav_filter_cycle_time!).

source
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 grid downconvert_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|² and N̂₀ 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.

source
TrackingLoops.ResidualPLL — Type
ResidualPLL

The 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.

source
TrackingLoops.VectorNavigation — Type
VectorNavigation

The 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.

source
Base.length — Method
length(estimator)

Number of observations in the window. Diagnostic only: it varies with the producer's dump cadence.

source
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).

source
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.

source
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.

source
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.

source
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).

source
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).

source
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.

source
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.

source
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.

source
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).

source
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.

source
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).

source
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.

source
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.

source
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.

source
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.

source