ublox-gps-data-processing/experiments/FINDINGS.md
= 6f85a6f1ed Add GCGC virtual-reference-station solution, VBS exploration suite, and report improvements
Pipeline (process_gps.py):
- New standard solution ppk_vrs: a virtual reference station synthesized at
  the capture position from the GCGC master station the CORS stage already
  fetched (zero baseline). Gated on GCGC availability; --no-vrs to disable.
  Engine validated in experiments/ (gates G1/G2a) and benchmarked equivalent
  to Trimble Pivot's commercial VRS on ground truth.
- Track output now selects among PPK-family solutions by measured dispersion,
  not nominal baseline (protects against poor network-edge VRS data).
- Warm-up exclusion: first 5 minutes (--warmup-min) removed from all quality
  metrics and figures; prominent note in both reports; data outputs unchanged.
- Professional report language: abbreviations expanded on first use, solution
  methods defined, no shorthand in tables or figure labels; fig5 reframed as
  "self-reported precision (uncalibrated) vs measured dispersion" with the
  optimism factor annotated.
- Local-base robustness: refuse to combine base files at different positions;
  RTCM logs and Data Shop RINEX accepted for the parallel GCGC method.

Streaming (stream_gps.py):
- Credentials via gitignored gcgc.env (template gcgc.env.example), env vars
  take precedence; RTCM correction stream recorded to stream_<ts>.rtcm3 for
  zero-baseline post-processing; live GGA uplink and sea dynamic model remain
  the buoy defaults; warm-up excluded from session statistics.

Experiments (new):
- vbs_synth.py: geometric virtual-base synthesis engine (RINEX 2.11 patcher,
  SP3 orbits, light-time + Earth-rotation per position, clock-robust).
- vbs_iono.py: carrier-leveled slant-ionosphere estimation + station-network
  interpolation with leave-one-out validation (median 7 cm; 1.2 cm / 10 km
  growth; DCBs as daily constants via median polish).
- vbs_compare.py: observation-domain comparison of commercial VRS files vs
  synthesized bases (ambiguity-detrended correction content).
- FINDINGS.md: gate results, commercial benchmark, offshore analysis, and
  promotion rationale.

Docs: README validation-results section explaining why the synthesized VRS
(kinematic) is the preferred solution for buoy deployments; VRS orders and
experiment data moved under gitignored experiments/data/.
2026-07-27 21:50:10 -04:00

11 KiB
Raw Blame History

VBS Exploration Findings — Session 1 (2026-07-27)

Exploration of DIY virtual-base-station synthesis from GCGC/NGS multi-station data, targeting offshore (extrapolation) PPK for buoy deployments. Full design

  • gate definitions: see the approved plan; scripts: vbs_synth.py, vbs_iono.py. Data: NGS CORS 30 s dailies, DOY 204 (2026-07-23) — MSIN, MARY, SBCH, COVG, MSEV (+ ALDI/ALFO/AL90/AL92/ALMJ downloaded for geometry D, unused so far); ESA rapid SP3 (GPS+GLONASS — Galileo pending a multi-GNSS SP3 source, e.g. ESA finals ~2 weeks post-date); NGS brdc RINEX 2 nav (demo5 rnx2rtkp did NOT parse BKG's RINEX 3.05 mixed nav — use NGS brdc .26n/.26g).

Gate G1 — geometric synthesis engine: GREEN

Check Result
G1a identity (zero displacement) PASS — 44,884 surviving records bit-identical (max diff 0.000, flags preserved)
G1b round-trip MSIN→MARY site→back PASS — max diff 0.001 (= 1 LSB of RINEX quantization)
G1c clock perturbation PASS at physical magnitude (see below)
G1d A→B truth (MARY as rover, 43.7 km) PASS — VBS-at-MARY base reproduces the direct MSIN-base solution (mean 3D diff 13 cm = float noise); truth errors statistically identical (horiz RMS 0.634 vs 0.639 m; mean N/E offset actually slightly smaller for VBS)

G1d detail (4 h window 1620 UT, GPS+GLO, 30 s, broadcast nav, kinematic): direct fix rate 37.4% with 69 wrong fixes (Q=1 & >10 cm truth error); VBS 43.7% with 31 wrong fixes. Wrong fixes are present in BOTH — confirming the plan's central honesty rule: never score on fix rate; score truth error under Q=1. 44 km daytime solar-max AR is marginal, as expected.

The clock findings (validates the timing concern raised in review)

  • A 1 ms injected clock offset broke the solution (0.63 m shifts, AR lost) — but diagnosis showed the test was unphysical, not the engine: shifting observables without shifting the sampling instant creates an inconsistent receiver. RTKLIB derives the base clock from pseudoranges and time-shifts satellite positions accordingly — so timestamp↔pseudorange self-consistency in synthesized files is mandatory (our engine preserves the master's real, self-consistent clock, hence G1d passing).
  • At the physically representative 1 µs (steered CORS clocks): both-float epochs agree to max 4.7 mm / mean 0.8 mm → the engine is clock-robust. All larger deviations (≤12 cm) came from 18/481 epochs where a borderline AR validation decision flipped — chaotic threshold sensitivity inherent to marginal AR, present under any tiny perturbation, not a synthesis defect.

Gate G2a — iono LIM cross-validation: GREEN (GPS-only)

Carrier-leveled geometry-free slant iono (arc-leveled to code), 5-min bins, 20° mask, per-satellite planar fit (LIM) across 4 stations, leave-one-out:

Held-out Centroid dist median|r| 95%|r|
MSIN 18 km 0.050 m 0.158 m
MARY 52 km 0.050 m 0.124 m
SBCH 65 km 0.057 m 0.185 m
COVG 38 km 0.100 m 0.267 m
MSEV 135 km (true extrapolation) 0.191 m 0.551 m
  • Overall median 0.070 m ≤ 0.10 m gate → PASS.
  • Residual-growth slope: 1.2 cm per 10 km — matches the literature band (12 cm/10 km) used in the design review. Offshore implication: ~1015 cm slant-iono prediction error at 60 km beyond the network edge — supportive of dm-level (not cm-level) VBS improvement targets for extrapolated points.

Two instructive failures on the way (both diagnosed + fixed)

  1. v1 estimator failed the gate by 10x (median 0.71.4 m; COVG 4.66.9 m). Cause: per-bin free per-station bias parameters are near-degenerate with a satellite-common gradient in a 4-station fit — extrapolated planes explode. Fix: estimate station biases (DCBs) as daily constants via median polish (hardware DCBs are stable), then fit per-bin planes with biases fixed → residuals collapsed 20x. Lesson recorded for Phase 2b: DCB handling is the conditioning issue in small-network LIM.
  2. P1/C1 column trap: MSEV logs GPS code in C1 with a blank P1 column — column-level fallback silently produced a GLONASS-only station. Value-level P1→C1 / P2→C2 fallback required. (GLONASS remains excluded from the iono fit for now: per-slot inter-channel code biases need per-(station,slot) bias terms — queued for Phase 2b if GPS-only corrections prove insufficient.)

Pivot VRS benchmark (2026-07-27, orders V304-V307) — the G0 question answered

GCGC's portal DOES offer VRS orders, and accepted both offshore points (25 km and 60 km beyond the network edge) without complaint — 1 s files, zero missing epochs, positions declared exactly as requested (NAD83(2011); note +1.4 m vs ITRF ellipsoidal height in this region). Files in experiments/data/orders/ (V304 = capture site 50 min; V305 = MARY site 4 h; V306/V307 = offshore probes 4 h).

MARY truth benchmark (rover = MARY 30 s, 16-20 UT, GPS+GLO, broadcast nav; truth = MARY's published coordinates, frame-matched per leg):

Base Fix rate Wrong fixes Truth horiz RMS Fixed-only RMS / CEP95
Pivot VRS at MARY (0 km) 41.4% 45 0.548 m 0.078 / 0.129 m
DIY VBS at MARY (0 km, geometric-only) 43.7% 31 0.634 m 0.075 / 0.149 m
Direct MSIN (44 km) 37.4% 69 0.639 m 0.136 / 0.298 m

Findings:

  1. Commercial Pivot VRS ~= DIY geometric-only VBS at this site/day — the network's atmospheric corrections added no measurable advantage over pure geometric displacement (single quiet-ish day, GPS+GLO, 30 s caveats apply). Both zero-baseline methods beat the 44 km direct base when fixed (7.5-7.8 cm vs 13.6 cm) and roughly halve wrong fixes.
  2. Wrong-fix rates are high for ALL methods (~20-35% of Q=1 epochs) — processing-config improvements (fix-and-hold, mask, L5) are currently a bigger lever than base choice; reinforces truth-error-under-fix scoring.
  3. Capture-site leg (V304 via the production pipeline): ppk_gcgc ran automatically (base 0.0 km) but stayed float with ratio ~1.1 and worse scatter than the MSEV solution (CEP50 1.10 vs 0.44 m), plus a ~3.5 m unexplained height offset beyond the ~1.4 m datum difference — float-bias behavior consistent with the u-blox rover's GAL/BDS signals being unusable against a G+R-only 2.11 base plus VRS correction noise; needs a dedicated look (multi-frequency conf, -f 3, RINEX 3.04 VRS re-order at the site).
  4. Offshore V306/V307 files are in hand for the Phase 2b/3 obs-domain comparison (Pivot's extrapolated corrections vs our LIM extrapolation).

Obs-domain analysis: how much correction does Pivot actually embed? (vbs_compare.py)

DIY geometric VBS synthesized at each Pivot file's exact declared position (same NAD83 frame/master), observations differenced satellite-by-satellite, DD'd against the highest satellite, per-satellite ambiguity constants removed. What remains = network correction content relative to pure geometry (+ master noise; 95% tails include uncleaned re-levelings):

VRS point Displacement L1 DD variation median / 95% P1 code DD median
V304 capture site (edge) 34 km 0.026 / 0.163 m 0.278 m
V305 MARY (in-network) 29 km 0.064 / 2.127 m 0.369 m
V306 25 km offshore 75 km 0.063 / 0.914 m 0.446 m
V307 60 km offshore 92 km 0.065 / 1.020 m 0.517 m

Three independent measurements now agree: (1) LOOCV says LIM-interpolable iono differences are 5-10 cm median in-network, growing 1.2 cm/10 km; (2) the MARY truth benchmark says that correction content is too small to change positioning outcomes on this day (Pivot ~= DIY); (3) the obs-domain analysis says Pivot embeds a median of only ~3-7 cm of correction beyond pure geometry, roughly flat out to 92 km displacement (code DD grows mildly with distance, dominated by master code noise). Consistent conclusion for buoys: at these distances/conditions, a geometric virtual base + our own LIM layer is competitive with the commercial product; the decisive factors are processing config (AR strategy, multi-frequency) and disturbed-day behavior (untested).

Capture-site V304 anomaly RESOLVED (2026-07-27, promotion session): the production ppk_vrs stage (our engine, same MSEV master, same zero baseline, same rover) is clean - matches direct-MSEV to 3 cm and slightly beats its scatter (CEP50 0.42 vs 0.44 m). The ~5 m bias + 3x noise is therefore in Pivot's V304 file itself (network-edge synthesis at Hattiesburg where MSHT is decommissioned), not in the rover pairing. Method note: the obs-domain DD comparison could not see this because per-satellite ambiguity detrending also absorbs constant biases - it measures time-varying content only. The RINEX 3.04 re-order of the capture-site VRS is now optional curiosity, not a blocker. Fix-and-hold truth test at MARY: +10% fix rate on the Pivot leg only, wrong-fix counts UNCHANGED on all legs - wrong fixes are a validation-threshold problem (30 s epochs, ratio 3.0), not an AR-strategy problem; the next real levers are multi-frequency processing and stricter/partial validation.

Promotion (2026-07-27, user decision)

The DIY VRS is promoted into process_gps.py as the standard GCGC-provided solution ppk_vrs: auto-synthesized at the capture position from the NGS stage's master whenever that master is a GCGC station (agency header / MS station check), ESA orbits (FIN>RAP, ~1 day lag, "pending" until published), frame-consistent ITRF, zero nominal baseline. Reported alongside all other solutions with its own color/validation; --no-vrs disables. Track output now selects among PPK-family solutions by empirical scatter, protecting against poor network-edge VRS data (exactly the V304 case). Caveats carried with the promotion: single-day validation, GPS+GLO only until multi-GNSS orbits are available for the day (Galileo joins via ESA finals), disturbed-iono behavior untested (Phase 3).

Status vs plan

  • G0: partially done — station set + data availability verified (NGS path); USER ACTION open: check the GCGC portal (rtn.usm.edu) "new order" screen for a "Virtual Reference Station" order type, and whether it permits offshore points; GCGC Data Shop 1 s downloads not yet needed but will be for Phase 3's disturbed-day / 1 Hz legs.
  • G1: GREEN (all four checks).
  • G2a: GREEN (GPS-only, one quiet day). Pending for robustness: ≥2 more days incl. a disturbed day (Phase 3 requirement, not a G2a blocker).
  • Next (Phase 2b): apply LIM corrections in vbs_synth (--iono hook exists in design): sign-correct code/phase application, (f_L1/f)² scaling, phase-continuity audit (<λ/4 steps), then geometry B/C/D truth runs with the wrong-fix-rate scoring, day/night splits, and the 060 km offshore sweep.

Reproduction

# G1 (identity/round-trip/clock/truth) — see session commands; engine:
venv/bin/python experiments/vbs_synth.py experiments/data/204/msin2040.26o \
  --xyz <MARY_XYZ> --master-xyz <MSIN_XYZ> \
  --sp3 experiments/data/204/ESA0OPSRAP_20262040000_01D_05M_ORB.SP3 \
  --nav experiments/data/204/BRDC00WRD_R_20262040000_01D_MN.rnx \
  -o experiments/data/204/vbs_at_mary.obs
# G2a:
venv/bin/python experiments/vbs_iono.py --stations msin:... mary:... sbch:... \
  covg:... msev:... --dir experiments/data/204 --sp3 ... --nav ... --gps-only

(ITRF2020 station coordinates epoch-propagated via process_gps.cors_station_xyz.)