Vector tracking
In scalar tracking every satellite closes its own code and carrier loops from its own discriminators. In vector tracking a navigation filter closes them all at once: each satellite's accumulated DLL and FLL discriminators become pseudorange and pseudorange-rate measurements, the filter fuses them into a position, velocity and clock state and sets every satellite's NCOs from it (the carrier to the predicted Doppler, the code by a correction). Weak or briefly obscured satellites are carried through by the common solution instead of losing lock on their own.
The filter is an unscented Kalman filter over position, velocity, one clock bias per GNSS time system (all driven by the one oscillator's drift) and one inter-frequency bias per band beyond the reference band. It follows the bias model of PositionVelocityTime.jl: which biases a cycle can determine is decided per cycle, and clocks collapse onto a hub system's through the broadcast offset (GGTO, BGTO) when the measurements cannot separate them. With use_pseudorange_rates = true (the default) it also measures the pseudorange rates (VDFLL); with false it is a VDLL, and the carrier NCOs' Doppler comes from the code measurements alone.
One estimator does it all
VectorPLLAndDLL is a Doppler estimator like any other: a host builds each satellite's state with init_estimator_state and steps it with step_loop on every record of the satellite. Everything vector tracking needs happens inside that step, from what the records carry. All satellites stepped with one estimator share its navigation engine, which
- decodes each satellite's navigation data from the soft bits its records carry;
- snapshots each satellite at every navigation epoch (every
vt_update_intervalon the records' time grid): code phase from the last data-symbol edge, decoder, Dopplers, C/N₀ and the discriminators accumulated since the last epoch; - runs the epoch's navigation cycle on the record that completes the last satellite's snapshot: the scalar PVT until the filter is seeded, a filter iteration after that;
- leaves the cycle's decisions (admission, release, commands) for each satellite to take up on its next record, sized for where its command lands.
estimator = VectorPLLAndDLL(GPSL1CA(), GalileoE1C() => GalileoE1B()) # owns decoders, filter and PVT
ts = TrackState(; signal = GPSL1CA(), doppler_estimator = estimator)
for chunk in chunks
track!(chunk, ts, fs) # decodes, solves, steers
end
navigation_solution(estimator) # the PVTSolution; navigation_status(estimator)Several constellations or bands share one filter: list every ranging signal at construction. Each satellite runs the scalar loop inner until the filter takes it over, and again once the filter lets it go. In the vector loop the code filter is frozen, the filter sets the replica, and the measurements are accumulated for the filter (see The carrier in the vector loop).
Pilot + data pairs
A signal that carries navigation data is ranged on and decoded alike. A pilot carries none, so it is given with its data component, driver => decoding signal: the satellite ranges on the pilot, whose prompt has no data-bit transitions, and decodes the data component (Galileo GalileoE1C() => GalileoE1B(), GPS GPSL5Q() => GPSL5I(), GPSL1C_P() => GPSL1C_D(), GPSL2CL() => GPSL2CM(), BeiDou BeiDouB1C_P() => BeiDouB1C_D()).
- The driver closes the loops: its records carry the discriminators, the replica's code phase and the C/N₀ the measurements are weighted by.
- The decoding signal feeds the decoder: its records carry its prompts and where its bit clock stands. For a plain signal these are the driver's own records.
At an epoch the transmit time is the data symbols the decoding signal has counted plus the driver's code phase within the current symbol, as PositionVelocityTime builds it for a pair. The two codes are aligned at the satellite, so their lengths may differ (L2 CL is 1.5 s, L2 CM 20 ms); the chip rates must agree. A satellite is in lock when the driver's C/N₀ clears lock_cn0_threshold and the decoding signal holds its bit sync; a data component too weak to decode never completes its decoder, so the satellite never becomes ready for the PVT. The solution, the reports and the release reasons are keyed by the driver.
The records of the two arrive independently and in any order: a satellite's snapshot of an epoch is complete once a record of each has crossed it, and the cycle waits for that as for a late satellite.
What the records must carry
The engine runs no bit clock and no C/N₀ estimator of its own. The host holds a SignalLoopState per signal of every satellite (bit clock, C/N₀ estimator, prompt filter), advances it with apply_record on every record, and builds the record from it:
loop, prompt, filtered, blocks, overshoot =
apply_record(loop, signal, prn, output, fs, noise_density, noise_density_ready, driver_phase)
record = LoopRecord(signal, filtered, output, loop, fs; prn)
state, carrier_doppler, code_doppler = step_loop(estimator, state, record, words, landing_sample)The record summarises that state: whether the bit clock holds the sync and whether this record found or lost it, the code blocks into the current symbol, exactly the soft bits this record added (so the host may drain the bit buffer whenever it likes), and the host's C/N₀ estimator (noise-referenced by default, which reads -Inf dB-Hz, out of lock, until the host has a noise density). Beyond that, every LoopRecord handed to the estimator must carry what only the correlator knows:
prn: the satellite;code_phase: the replica's code phase in chips at the record's end, from theCorrelatorOutput. The end sample alone pins it only to within one sample (some 75 m at 4 MHz). Only its part past the nearest code-block boundary is read, so any wrap convention works; without it (NaN) the record is taken to end on a block boundary;- a common time grid:
sample_index / sampling_frequencymust be the time since one origin shared by all satellites. A host whose correlator restarts its sample count passes the offset assample_offsetwhen it builds the record.
NO_LANDING_SAMPLE still means the command computed from a record acts from the record's end; a hardware host passes the landing sample (see Under a hardware NCO delay).
The lifecycle of a satellite
Vector tracking needs a decoded navigation message and a first position fix, so every satellite starts on its scalar loop:
- A satellite registers with the engine on its first record. Until vector tracking runs, each cycle solves the scalar PVT over the satellites in lock (C/N₀ at least
lock_cn0_threshold, bit sync found), decoded for positioning and healthy. - The first fix seeds the filter with its position and clock biases and the covariance its geometry gives them, so a fix of poor geometry (high DOP) starts the filter as uncertain as it is. The fix's satellites are added to the vector loop and take their first commands, computed at the seeded state, on their next record. In the loop the filter sets a satellite's carrier NCO and its residual PLL follows the carrier, whatever Carrier-loop staging stage its scalar loop had reached.
- While it runs, a satellite is admitted once it is decoded, healthy, in lock and 1° (
ADMISSION_ELEVATION) above the horizon. A member out of lock (C/N₀ or bit sync) stays in the loop, unmeasured, and is predicted through the outage. A member that misses an epoch sits that cycle out. - A member is released when it is no longer eligible (
VT_INELIGIBLE; this includes a satellite without a record for two cycles, whose slot is freed) or drops below the horizon (VT_BELOW_HORIZON). Its scalar loop is re-seeded at the vector loop's estimate of the Doppler and its rate (see The carrier in the vector loop) and its carrier-loop staging restarts, so it pulls in afresh, FLL-assisted at the wide bandwidth. - After
insufficient_meas_timeoutof unsolvable cycles, or with no member left, every satellite in the loop, members that missed the epoch included, is released (VT_FALLBACK) and the scalar PVT runs again until a fresh fix seeds the filter anew.
A re-acquired satellite (a fresh state on a PRN seen before) gets its old slot back: its decoder restarts its sync but keeps its data, so it is ready again after the next subframe rather than a whole frame once the host's bit clock has found the bit edges again.
Stepping the satellites
A cycle runs once every satellite that can still complete its epoch's snapshot has, and a satellite that has gone two cycles without a record of one of its signals is dropped, so a host should step every signal of every satellite at least once per half cycle (Tracking.jl's track! does, for any chunk shorter than that). A satellite that falls behind holds a cycle up only until the others reach the next epoch; then the cycle runs without it. A satellite already past an epoch when it becomes pending (stepped earlier in the chunk than the record that ran the previous cycle, as after a re-registration) sits that epoch's cycle out; the next pending epoch is the first no satellite is past. A host never has to hold a record back or order its channels: each record is stepped as it arrives.
The carrier in the vector loop
The navigation filter sets a member's carrier NCO directly: no loop filter and no phase discriminator sit between them. So the FLL measures the incoming carrier against a replica nothing else moves, and a residual PLL per satellite follows the carrier on the prompts the NCO leaves, for the data.
- The filter sets the carrier NCO. Each record is commanded the filter's predicted Doppler at the middle of the record its command lands on: a ramp evaluated from the latest cycle's state at the landing and one cycle later (velocity, acceleration where the motion model has one, clock drift and the satellite's motion). The code is commanded from that Doppler, carrier-aided, plus the filter's code correction.
- A residual PLL follows the carrier. Per satellite, a third-order software loop estimates the residual phase, Doppler and Doppler rate against the filter's ramp from the prompts and the words the NCO ran, Costas or four-quadrant by the record's
carrier_phase_polarity. It never steers the NCO, and neither the command nor a new ramp moves its estimate of the incoming Doppler. Its only outputs are the phase the decoded prompts are rotated by, and its lock. - The rate measurement is the replica's Doppler at the epoch plus the mean FLL discriminator over the cycle: the replica follows the filter's ramp, so its rise to the epoch matches the incoming carrier's, and no loop's jitter is in its word. The FLL compares consecutive prompts, so it needs no phase lock, and the measurements telescope across cycles (lag-1 correlation −½ without an NCO delay, an integrated error bounded by the boundary prompts' noise).
- The engine decodes the prompts it rotated. The prompts turn at the filter's Doppler error, so the engine rotates each of the decoding signal's prompts onto the incoming carrier by the residual PLL's phase (and a data component's carrier-phase offset from its pilot), sums them over each data symbol the host's bit clock counts and decodes the sum's real part. A plain signal's prompt is rotated by the residual PLL's prediction for its record, which that prompt did not correct; a data component's records, which a host folds after its pilot's, take the phase estimated at the pilot's record of the same span. The host's bit buffer still keeps the bit clock (it counts blocks once synced), but its soft bits are not decoded while the satellite is a member, so the host's prompts need no rotation. Its C/N₀ estimator should read power only, as the default noise-referenced and the moment-based ones do: a coherent one (
NWPRCN0Estimator) reads the turning prompts as a small loss. - Lock. A member is measured while the host's C/N₀ clears
lock_cn0_thresholdand its decoding signal holds the bit sync, as for any satellite. The residual PLL's own phase lock (phase_locked) reads the indicator on the prompts it rotated, with the C/N₀ those moments read no more than 6 dB belowlock_cn0_threshold. While the host's C/N₀ is belowlock_cn0_threshold(no signal) it holds its residual at the filter's Doppler, so it does not integrate a fade's noise into tens of hertz and pulls in again from the filter's prediction once the signal is back; its own moments read up to the threshold on bare noise, so they do not decide that. With the signal there, in phase lock or not, its loop runs on and pulls in by itself. - Carrier phase. The replica's carrier phase is the NCO's, which the filter sets, so it is not the incoming carrier's: a host that measures the carrier phase (RINEX
L) addscarrier_phase_correction, the residual PLL's predicted residual at the sample it reads the replica at, whole cycles included (zero out of the vector loop and for the scalar loops). It steps by half a cycle where a pilot turns the discriminator four-quadrant and the residual PLL moves to its frame, and by the residual on a release. - Hand-over. A satellite is added at its scalar loop's word, which its residual PLL starts from. It is released into its scalar loop at the residual PLL's estimate of the incoming Doppler and its rate (the ramp's plus the residual PLL's), which the third-order carrier filter starts from; out of lock, at the Doppler and rate the filter last commanded. Its staging restarts.
The residual PLL's bandwidth is VectorPLLAndDLL's residual_pll_loop_filter_bandwidth, 10 Hz by default (default_residual_pll_loop_filter_bandwidth), capped like the scalar PLL's against the record's integration time. At 10 Hz its thermal jitter is √(10/18) = 0.75 times the 18 Hz PLL's.
In the simulation of test/vector_residual_pll.jl (synthetic GPS L1 C/A and Galileo E1 satellites, no oscillator phase noise):
| phase jitter of the residual PLL's prediction at 40 dB-Hz, vs. the 18 Hz scalar PLL | 0.77 (L1 C/A), 0.75 (E1) |
| FLL rate residuals, Galileo E1 at 37–45 dB-Hz (Costas) and 34 dB-Hz (four-quadrant), GPS L1 C/A at 45 dB-Hz | σ/√R 0.92–1.08, lag-1 −0.36 to −0.62, at most 4.7σ, integrated error below 3.5 cm |
| 0.5 g / 1 g manoeuvre at 40 dB-Hz | in lock throughout, residual Doppler up to 4.0 / 7.6 Hz, phase error up to 10° / 21° |
Driving each member's PLL through its FLL branch instead, which leaves the PLL's integrators in charge of the replica's frequency, gave 1.47 (L1 C/A) and 1.08 (E1) times the scalar PLL's prompt-phase jitter.
Not done yet: a receiver-clock phase common to all satellites, estimated at the record rate (vector phase lock), and a carrier-phase output for RTK.
Under a hardware NCO delay
With a hardware correlator, the command computed from a record takes effect only when it lands at the NCO. Pass the landing sample to step_loop and the channel's NCOTimeline as its words: each satellite then sizes its commands for the moment they land, against the replica the timeline predicts there, and a released satellite's scalar loop takes over from that replica. The landing may lie up to 2.5 cycles after the epoch.
Reading the results
Nothing is logged. navigation_solution is the latest solution (the scalar PVT's before the filter is seeded) and navigation_status the NavigationStatus of the latest cycle: its events and the filter's position and clock uncertainties (also position_uncertainty and clock_uncertainty). release_reason says whether and why the latest cycle released a satellite, and vector_loop_members reports every member, measured and coasted. navigation_cycle counts the cycles, so a consumer polling it after each step reads every solution exactly once, and navigation_epoch is the moment the latest solution describes, on the records' time grid. satellite_report hands out what the engine knows of a satellite (decoder, bit sync, C/N₀, lock, PVT readiness, membership), so a receiver need not decode or estimate the C/N₀ a second time.
Scalar loops answer these with nothing, so a host can ask whichever estimator it was given:
cycle = navigation_cycle(estimator)
if cycle != last_cycle # `nothing` for a scalar loop: never a new solution
last_cycle = cycle
pvt = navigation_solution(estimator) # position, velocity, time, DOP, …
report = satellite_report(estimator, GPSL1CA(), prn)
endThe returned objects are the estimator's own and reused by the next cycle or call: copy out what is needed later.
Storage
All storage (decoders and the filter's buffers) is allocated at construction for max_satellites_per_signal satellites per ranging signal; a dropped satellite leaves its slot to the next one, and only past that capacity does a group grow. Once warm, records and cycles allocate nothing, and the estimator compiles with juliac --trim=safe.
Known limits
- A pair ranges on the driver alone: the data component's discriminators are not combined with the pilot's.
- The residual PLL is capped like the scalar PLL against the record's integration time: at 20 ms records its 10 Hz runs at 4.5 Hz.
- On 1 ms records (GPS L1 C/A) the FLL's rate measurement breaks down below about 40 dB-Hz: a phase difference between two prompts wraps at the Costas discriminator's ±90° often enough to break the telescoping, and the residuals reach three times
√R. - Membership follows the host's C/N₀, which lags a sudden blockage: a cycle in which the signal is suddenly gone can still be measured with noise-only FLL discriminators.
- HardwareLoopCore does not yet report the records'
prnandcode_phase.
The estimator
TrackingLoops.SatVectorPLLAndDLL — Type
SatVectorPLLAndDLLPer-satellite state of a VectorPLLAndDLL, built by init_estimator_state and advanced by step_loop. Its fields are internal and may change in any release. Inspect it with phase_lock_indicator, phase_locked and carrier_loop_stage; satellite_report has what the navigation engine knows of the satellite.
TrackingLoops.VectorPLLAndDLL — Type
VectorPLLAndDLL(signals...; inner = ConventionalAssistedPLLAndDLL(),
filter_config = NavigationFilterConfig(), vt_update_interval = 100ms,
lock_cn0_threshold = 30dBHz,
residual_pll_loop_filter_bandwidth = default_residual_pll_loop_filter_bandwidth(),
max_satellites_per_signal = 16,
approximate_year = year(now(UTC)),
enable_ionospheric_correction = true,
enable_tropospheric_correction = true)Vector-tracking Doppler estimator for the ranging signals: each a signal that carries navigation data (GPSL1CA()) or a pilot + data pair driver => decoding_signal (GalileoE1C() => GalileoE1B(); see Pilot + data pairs). A decoding signal must carry data and have its driver's chip rate. No signal may be given twice.
All satellites stepped with this estimator share one navigation engine which, inside step_loop, decodes the navigation data, solves the scalar PVT, seeds the navigation filter from its first fix and then runs one filter cycle every vt_update_interval, taking satellites over and handing them back. A host needs no vector-specific code: it builds a satellite's state with init_estimator_state for its driver and steps it with every record of the satellite. Read the results, keyed by the driver, with navigation_solution, navigation_status, release_reason, vector_loop_members, position_uncertainty and clock_uncertainty.
The engine reads the host's bit clock and C/N₀ estimator from the records (see What the records must carry); the C/N₀, read at each navigation epoch for the lock and the measurement weights, is capped at 80 dB-Hz. A satellite is in lock when the driver's C/N₀ is at least lock_cn0_threshold and the decoding signal holds its bit sync. A record without a PRN, or of a driver not among signals, throws an ArgumentError; any other passenger is ignored, also the decoding signal of a pair that does not drive this satellite. Step every signal of every satellite at least once per vt_update_interval / 2 (see Stepping the satellites).
Every driver record runs inner until the filter takes the satellite over, and again once it is released. In the vector loop (see The carrier in the vector loop):
- the navigation filter alone sets the carrier NCO, to its predicted Doppler at the middle of the record the command lands on;
- a residual PLL (
TrackingLoops.ResidualPLL) follows the carrier on the prompts the NCO leaves, without steering it; the scalar loop, its staging included, stands still.phase_lock_indicatorandphase_lockedare the residual PLL's, andcarrier_loop_stagereportsVECTOR_LOOP; - the code filter is frozen and the filter's code correction replaces its output:
code_doppler = init_code_doppler + code_freq_update + (carrier_doppler - init_carrier_doppler) · code_center_frequency_ratio; - the DLL output (chips) and the FLL discriminator (Hz) are accumulated for the filter;
- the engine decodes the decoding signal's prompts rotated by the residual PLL's phase, not the host's soft bits. The host's C/N₀ estimator should read power only: a coherent one such as
NWPRCN0Estimatorreads the turning prompts as a small loss.
A satellite is added at its scalar loop's Doppler and released at its residual PLL's estimate of the incoming Doppler and its rate (out of lock, at the filter's), into a restarted staging, FLL-assisted again.
residual_pll_loop_filter_bandwidth is the residual PLL's noise bandwidth, capped like the scalar PLL's (effective_carrier_loop_filter_bandwidth). Under the default motion_model_order = 2 the prediction does not carry the user's acceleration, so the residual PLL follows a manoeuvre by itself: a third-order loop's error to a jerk j is j / (λ ω₀³) cycles (ω₀ = 1.2 B_L), 5.4° at 10 Hz for the 2.5 m/s³ of a 0.5 g manoeuvre built up over two seconds, 43° at 5 Hz. Narrower suits a gentler platform or motion_model_order = 3.
The latest cycle's commands are taken up on the next driver record, sized for where they land: at landing_sample, or at the record's end under NO_LANDING_SAMPLE. A record of any other signal returns the command in force there.
inner must have an FLL-assisted carrier filter, which pulls a released satellite in again: a ConventionalPLLAndDLL{ThirdOrderAssistedBilinearLF} (what ConventionalAssistedPLLAndDLL builds) or an NCOReferencedPLLAndDLL. Anything else throws an ArgumentError, as does a dataless signal given alone (pair a pilot such as GPS L1C-P or Galileo E1C with its data component). filter_config = nothing only ever solves the scalar PVT.
Storage is allocated for max_satellites_per_signal satellites per ranging signal (a pilot => data pair is one) and grows only past that; a dropped satellite leaves its storage to the next one.
TrackingLoops.init_estimator_state — Method
init_estimator_state(estimator::VectorPLLAndDLL, driver_signal, carrier_doppler, code_doppler)The inner loop's state with the vector interface empty (out of the vector loop, nothing accumulated, no commands) and the residual PLL at the estimator's residual_pll_loop_filter_bandwidth. The satellite registers with the navigation engine on its first record.
TrackingLoops.reset_estimator_state — Method
reset_estimator_state(estimator::VectorPLLAndDLL, state, carrier_doppler, code_doppler)Re-seed the inner loop from the converged Dopplers, zero the accumulators and drop the code correction, keeping in_vector_loop, the carrier ramp, the residual PLL and the satellite's place in the navigation engine. The converged Dopplers already contain the code correction, so keeping it would apply it twice; the replica was still steered by it, so the correction history and landing lead are kept. The carrier ramp replaces the inner loop's carrier word rather than adding to it, so it stays.
TrackingLoops.step_loop — Method
step_loop(estimator::VectorPLLAndDLL, state, record::LoopRecord, words, landing_sample)
-> (state, carrier_doppler, code_doppler)One record of a satellite through the vector loop (see VectorPLLAndDLL). The satellite registers with the navigation engine on its first record, of either signal. A record of its
- driver takes up the latest cycle's admission, release and commands, snapshots the replica, Dopplers, accumulated discriminators and C/N₀ when it crosses a navigation epoch, and runs the loop;
- decoding signal feeds its new soft bits to the decoder and notes where the bit clock stands (the snapshot's symbol count); it returns the command in force and the state unchanged;
- plain signal does both; any other passenger does nothing, as in a scalar loop.
A satellite's snapshot of an epoch is complete once a record of each signal has crossed it, in either order; the record completing the last satellite's snapshot runs that epoch's cycle. Out of the vector loop the driver's Dopplers equal step_loop(estimator.inner, …)'s.
TrackingLoops.default_residual_pll_loop_filter_bandwidth — Method
default_residual_pll_loop_filter_bandwidth()
Default noise bandwidth of the residual PLL that follows each satellite's carrier in the vector loop (10 Hz, VectorPLLAndDLL's residual_pll_loop_filter_bandwidth), capped like the scalar PLL's by effective_carrier_loop_filter_bandwidth.
The navigation cycle
TrackingLoops.NavigationStatus — Type
NavigationStatusWhat the latest navigation cycle of a VectorPLLAndDLL did (see navigation_status).
running: vector tracking is running after this cycle;position_std,clock_std: the filter's 1σ 3-D position and primary-clock uncertainties (NaNunless the filter ran or fell back this cycle); a degenerate geometry shows here, not in the starvation timer;time_with_insufficient_meas: how long the filter has coasted on epochs it could not solve, paid back at half rate otherwise, and checked againstinsufficient_meas_timeout; nonzero means the loop is on its way out;num_members: satellites in the vector loop after this cycle;- the events
seeded(a fresh scalar fix seeded the filter),fell_back(vector tracking stopped) andreleased(some satellite was released; seerelease_reason).
The per-member report of the latest cycle is vector_loop_members.
TrackingLoops.SatelliteReport — Type
SatelliteReportWhat a VectorPLLAndDLL knows of one satellite (see satellite_report), so a consumer need not decode or estimate C/N₀ again:
prn, andtracked: whether it is stepped now (falseafter two cycles without a record; the rest then describes it as it was);decoder: its navigation-message decoder up to the last record of its decoding signal. It is the estimator's, to read only: resetting or decoding into it (e.g.reset_decoder_state!,decode!) changes the estimator's decoder. It shares buffers with the running decoder, socopyit to keep it past the next record; once the slot is freed they go to the next satellite on it, so a consumer that needs a decoder of its own builds a fresh one;bit_synced: whether the decoding signal's bit clock held the bit sync at that signal's last record;- at its latest snapshot (
epoch, on the records' time grid,nothingbefore the first):cn0_dbhz(the driver's, from the host's C/N₀ estimator),in_lock(decoding signal synced and the driver's C/N₀ at least the lock threshold) andpvt_ready(in lock, decoded for positioning and healthy); in_vector_loopandrelease_reason: the latest cycle's decision.
The report is the estimator's own object, refreshed in place by every satellite_report call (no allocation): copy out what is needed beyond the next call.
TrackingLoops.VTReleaseReason — Type
VTReleaseReasonWhy the latest navigation cycle handed a satellite back to its scalar loop (see release_reason):
VT_NOT_RELEASED: it was not;VT_INELIGIBLE: no longer tracked (no record for two navigation cycles), decoded for positioning, or healthy;VT_BELOW_HORIZON: below the horizon at the solution the cycle starts from, so not measured in that cycle; re-admitted onlyADMISSION_ELEVATION(1°) above it (hysteresis);VT_FALLBACK: vector tracking stopped (starvation timeout, or no member left).
A receiver that forces released satellites out of lock does so for the first two, not for a fallback.
TrackingLoops.clock_uncertainty — Method
clock_uncertainty(estimator::VectorPLLAndDLL)The navigation filter's 1σ uncertainty (m) of the solution's reference clock bias, once seeded.
TrackingLoops.navigation_solution — Method
navigation_solution(estimator::VectorPLLAndDLL) -> PVTSolutionThe latest navigation solution: the scalar PVT's until the filter is seeded, the filter's after. The next cycle reuses its containers; copy out what is needed.
TrackingLoops.navigation_status — Method
navigation_status(estimator::VectorPLLAndDLL) -> NavigationStatusWhat the latest navigation cycle did (see NavigationStatus).
TrackingLoops.position_uncertainty — Method
position_uncertainty(estimator::VectorPLLAndDLL)The navigation filter's 1σ 3-D position uncertainty (m), once seeded.
TrackingLoops.release_reason — Method
release_reason(estimator::VectorPLLAndDLL, signal, prn) -> VTReleaseReasonWhether and why the latest navigation cycle released satellite prn of signal from the vector loop.
TrackingLoops.vector_loop_members — Method
vector_loop_members(estimator::VectorPLLAndDLL)The latest cycle's per-member report: every member of the vector loop, measured and coasted, keyed (signal_id, prn) like the solution's sats, each with the satellite position, transmit time and post-fit residuals. Empty while the scalar solve is in control.
TrackingLoops.NavigationFilterConfig — Type
NavigationFilterConfig(; use_pseudorange_rates = true,
motion_model_order = 2, clock_model_order = 2,
acceleration_noise_std = 5.0m/s^2,
h0 = 2e-19, hm2 = 2e-20,
ifb_noise_density = 0.01m/sqrt(1.0s),
insufficient_meas_timeout = 10.0s)Configuration of the vector-tracking navigation filter of a VectorPLLAndDLL. The defaults suit a vehicle with a consumer-grade front end.
Fields
use_pseudorange_rates:true(VDFLL) also fuses the FLL pseudorange rates; withfalse(VDLL) only pseudoranges enter, and the carrier Doppler the NCOs are set to comes from the code measurements alone.motion_model_order: per-axis states —1position,2+ velocity,3+ acceleration.clock_model_order:1clock biases only (one per GNSS time system),2biases + one common drift.acceleration_noise_std: how hard the platform manoeuvres.5 m/s²suits road vehicles; ~1 m/s²pedestrians or ships, tens ofm/s²aircraft. It means the same at every order: orders 1 and 3 drive a velocity (σ_a·τ) or jerk (σ_a/τ) noise over aτ = 2 smanoeuvre (MANOEUVRE_TIME).h0,hm2: oscillator Allan-variance coefficients $h_0$ (s) and $h_{-2}$ (1/s); $h_0 = 2τσ_y²(τ)$ at short and $h_{-2} = 3σ_y²(τ)/(2π²τ)$ at long averaging times. The defaults model a TCXO; an OCXO is far tighter, a bare crystal looser.ifb_noise_density: random-walk density of the inter-frequency biases (m/√s), covering the front end's thermal drift: by default a bias wanders ~0.01 m(1σ) in one second. Raise it for bands that are less thermally coupled, lower it for a calibrated front end.insufficient_meas_timeout: how long the filter may coast on unsolvable epochs (too few measurements or distinct satellites for the unknowns) before falling back to scalar tracking. Solution uncertainty is reported, not policed: seeNavigationStatus'sposition_std.