CLAS Public Validation Datasets¶
Current
developstatus (2026-06-19): the current CLI no longer exposes the historical--claslib-parityentry point. Native CLAS runs usegnss ppp --clas-osrdirectly, orgnss clas-ppp --profile clas, which forwards--clas-osrafter expanding QZSS L6/Compact SSR inputs. The iter55 millimetre-level table below is retained as historical reference; the active default-flip decision remains the A5 STOP described in clas_dd_filter_a5.md.
This page records the public CLAS datasets used for the native CLASNAT parity checks. It separates accepted regression gates from public exploratory runs that are useful for robustness work but are not yet millimetre-level passes.
Public Source¶
The current CLAS parity data comes from the public CLASLIB repository:
https://github.com/QZSS-Strategy-Office/claslib.git
The local validation copy used during iter55 was:
Its Git remote was confirmed as:
origin https://github.com/QZSS-Strategy-Office/claslib.git
The files below are from that repository's data/ directory.
Accepted Regression Gate¶
| Case | Public files | Window | Native mode | Acceptance status |
|---|---|---|---|---|
| 2019-08-27, QZSS CLAS sample | 0627239Q.obs, sept_2019239.nav, 2019239Q.l6 |
2019-08-27, 16:00 GPST window | Historical iter55 --claslib-parity native CLASNAT path |
Historical accepted gate. Iter55 measured 0.00556 m RMS 3D against the CLASLIB trajectory over the last 100 epochs of a 2000-epoch run, and 0.01014 m RMS 3D over the last 100 epochs of a 300-epoch smoke window. |
This is the dataset behind the historical --claslib-parity mm-level
regression. It is public because the input files are shipped in the public
QZSS-Strategy-Office/claslib repository, not because they are private local
capture files.
The same 2000-epoch iter55 run measured 0.00251 m RMS 3D against the fixed
ECEF reference (-3957235.3717, 3310368.2257, 3737529.7179) over its last 100
epochs.
Public Exploratory Runs¶
| Case | Public files | What was checked | Result |
|---|---|---|---|
| 2018-11-25, CLASLIB bundled sample | 0161329A.obs, tskc2018329.nav, 2018328X_329A.l6 |
CLASLIB reference generation and 300-epoch native CLASNAT run | CLASLIB reference solved, but native CLASNAT stayed in fallback status for the checked window. Direct trajectory diff against CLASLIB was about 1.336 m RMS 3D over the last 100 aligned epochs, so this is not an accepted <20 mm gate. |
| 2019-12-15, CLASLIB bundled ST12 sample | 0627349AB.bnx, sept_2019349.nav, 2019349B.l6 |
CLASLIB reference from BINEX, then convbin to RINEX 3.02 with a GNSS signal mask before native CLASNAT |
CLASLIB reference solved, but native CLASNAT did not reach the mm gate in the checked window. This remains an input-format and robustness probe, not an accuracy gate. |
No additional bundled CLASLIB public sample reached <20 mm native CLASNAT RMS
in iter55. Those probes are still useful because they confirm that the
additional samples are public and reproducible, and they expose the next
robustness gaps without weakening the accepted 2019-08-27 gate.
Reproduce The Current Native CLAS Run¶
Clone CLASLIB and point the commands at its public data/ directory:
git clone https://github.com/QZSS-Strategy-Office/claslib.git /tmp/claslib
DATA=/tmp/claslib/data
Build the current native PPP binary:
cmake -S . -B build
cmake --build build --target gnss_ppp -j4
Run the current CLAS wrapper. --profile clas expands the raw QZSS L6 file and
forwards the resulting sampled corrections to gnss ppp --clas-osr:
python3 apps/gnss.py clas-ppp \
--profile clas \
--obs "$DATA/0627239Q.obs" \
--nav "$DATA/sept_2019239.nav" \
--qzss-l6 "$DATA/2019239Q.l6" \
--qzss-gps-week 2068 \
--out /tmp/gnsspp_2019239.pos \
--summary-json /tmp/gnsspp_2019239_summary.json \
--max-epochs 300
gnss clas-ppp also forwards --antex <antennas.atx> and
--receiver-antenna-type <type> to the underlying gnss ppp run for
receiver/satellite antenna parity probes. The public 0627239Q.obs header does
not declare a receiver antenna type, so A4b receiver-antenna probes should pass
the native ANTEX antenna key explicitly, for example
--receiver-antenna-type "TRM59800.80 NONE". The CLASLIB ANTEX file uses
CRLF line endings, so the native ANTEX loader trims trailing carriage returns
before matching labels. Set GNSS_PPP_CLAS_RX_ANTENNA=1 to materialize those
receiver terms into the CLAS OSR receiver_antenna_m component and the
aggregate PRC/CPC terms. The default path leaves them unchanged while A4b
parity is measured. The A4b probe also enables
GNSS_PPP_CLAS_ATMOS_GRID_MATRIX=1 and
GNSS_PPP_CLAS_ATMOS_LIFECYCLE=1 so native STEC/trop materialization follows
the CLASLIB-style grid selection and regenerated atmosphere-token lifecycle.
It also sets GNSS_PPP_CLAS_TROP_CLIMATOLOGY=1, which limits the CLAS grid-trop
hydro/wet zenith-baseline change to this A4b parity lane while default CLAS
output keeps the legacy constants.
For the A4b GPS L2W identity probe, enable the diagnostic gates and write a native zero-difference code component dump:
GNSS_PPP_CLAS_DD_FILTER=1 \
GNSS_PPP_CLAS_CODE_ROW_PARITY=bias,full-prc \
GNSS_PPP_CLAS_RX_ANTENNA=1 \
GNSS_PPP_CLAS_ATMOS_GRID_MATRIX=1 \
GNSS_PPP_CLAS_ATMOS_LIFECYCLE=1 \
GNSS_PPP_CLAS_TROP_CLIMATOLOGY=1 \
GNSS_PPP_CLAS_CODE_DUMP=/tmp/clas_a4b_native_code_dump.csv \
python3 apps/gnss.py clas-ppp \
--profile clas \
--obs "$DATA/0627239Q.obs" \
--nav "$DATA/sept_2019239.nav" \
--qzss-l6 "$DATA/2019239Q.l6" \
--qzss-gps-week 2068 \
--antex "$DATA/igs14_L5copy.atx" \
--clas-atmos-selection freshness-first \
--receiver-antenna-type "TRM59800.80 NONE" \
--compact-code-bias-composition-policy base-only-if-present \
--compact-code-bias-bank-policy delayed-15s-bank \
--compact-bias-row-materialization selected-satellite-base-extend \
--out /tmp/gnsspp_clas_a4b_native.pos \
--summary-json /tmp/gnsspp_clas_a4b_native_summary.json \
--max-epochs 300
Self-diff the dump to inspect identity provenance before comparing against a CLASLIB-side component dump:
python3 scripts/analysis/clas_zd_component_diff.py \
/tmp/clas_a4b_native_code_dump.csv \
/tmp/clas_a4b_native_code_dump.csv \
--base-label native \
--candidate-label native \
--row-type code \
--json-out /tmp/clas_a4b_native_code_selfdiff.json \
--details-csv /tmp/clas_a4b_native_code_selfdiff.csv
python3 - <<'PY'
import json
payload = json.load(open("/tmp/clas_a4b_native_code_selfdiff.json"))
print(json.dumps(payload["identity_provenance"], indent=2, sort_keys=True))
PY
Disposable CLASLIB dumps usually use numeric sys/prn/RTKLIB code fields
instead of native G14/C2W-style row keys. Normalize those dumps before using
them as the oracle side of the component diff:
python3 scripts/analysis/claslib_zd_component_export.py \
/tmp/claslib_zd_dump.csv \
--output /tmp/claslib_zd_dump.normalized.csv \
--stage-label post
python3 scripts/analysis/clas_zd_component_diff.py \
/tmp/claslib_zd_dump.normalized.csv \
/tmp/clas_a4b_native_code_dump.csv \
--base-label claslib \
--candidate-label native \
--stage post \
--row-type code \
--sat G14 \
--freq 1 \
--rinex-code C2W \
--duplicate-policy mean \
--json-out /tmp/clas_a4b_native_vs_claslib_code.json \
--details-csv /tmp/clas_a4b_native_vs_claslib_code.csv
Unmodified CLASLIB rnx2rtkp can also write an .osr file when run with its
OSR output option. That file carries OSRRES(ch*) rows with tow, sys,
prn, and L1/L2/L5 component columns, but no GPS week or exact observation
code. For the current A4b GPS L2W probe, the exporter maps GPS slot 2 to
C2W/L2W; provide the GPS week explicitly so the diff key is not ambiguous.
The normalized iono_scaled_m field is reconstructed from the CLASLIB PRC
closure instead of the raw .osr iono display column, so it matches the
correction actually applied in PRC1/PRC2/PRC5. iono_l1_m is the
frequency-slot-specific L1-equivalent of that reconstructed value, not a copy
of the L1 slot's display or closure value. The normalized stec_tecu field is
derived from that L1-equivalent delay with the GPS L1 TECU-to-meter factor.
The exporter also writes claslib_iono_source=prc_closure,
claslib_raw_iono_l1_m, and claslib_raw_stec_tecu, so raw CLASLIB .osr
display ionosphere can be inspected separately from the effective
PRC-closure ionosphere used by the default diff. This lets the diff separate
TECU materialization drift from frequency-scaling drift:
python3 scripts/analysis/claslib_zd_component_export.py \
/tmp/claslib_solution.osr \
--output /tmp/claslib_solution.osr.normalized.csv \
--stage-label post \
--gps-week 2068
On the 300-epoch 2019 sample smoke, current develop produces 5,350 native code
rows. The GPS L2W slice has 300 rows, all with exact bias identity and exact
observation matches, and zero observation-family or code-bias fallback rows.
Use base-only-if-present with the latest preceding base bank for this A4b
probe: the subtype-6 network code-bias rows are replacement rows for the
subtype-4 base values, not additive deltas. On the same 300-epoch window, the
direct network policy produced periodic G14/C2W code_bias_m outliers such as
-0.70 m where CLASLIB reports +0.76 m. The A4b policy above restores those
rows to the base value and reduces the G14/C2W code_bias_m diff from
mean_abs=0.1795 m, rms=0.4000 m, max_abs=1.4600 m to
mean_abs=0.1319 m, rms=0.3029 m, max_abs=0.7800 m.
The row materialization option also extends the latest base code-bias bank onto
phase-only subtype-6 network rows. Without it, the 300-epoch G14/C2W smoke has
49/300 native rows with code_bias_present=0 and code_bias_m=0; with it, the
same slice has 0/300 missing code-bias rows. On the short CLASLIB ZD oracle
window bundled with this probe, that phase-only-row extension reduces the
G14/C2W code_bias_m diff from mean_abs=0.0764 m, rms=0.2295 m,
max_abs=0.7600 m to mean_abs=0.0091 m, rms=0.0135 m, max_abs=0.0200 m, and
the aggregate PRC diff from mean_abs=0.0915 m, rms=0.2369 m,
max_abs=0.7819 m to mean_abs=0.0243 m, rms=0.0266 m, max_abs=0.0419 m. The
gated receiver-antenna materialization keeps the exact GPS L2W row on CLASLIB's
receiver-antenna slot; on the same 300-epoch G14/C2W slice this reduces
receiver_antenna_m from mean_abs=0.0249 m to mean_abs=0.0021 m. The
gated trop-climatology baseline then reduces trop_correction_m from
mean_abs=0.0283 m, rms=0.0284 m, max_abs=0.0289 m to
mean_abs=0.0011 m, rms=0.0012 m, max_abs=0.0017 m. Aggregate PRC mean/RMS
also improves from mean_abs=0.0292 m, rms=0.0324 m to
mean_abs=0.0146 m, rms=0.0212 m; the remaining max_abs=0.0695 m is an
iono/code-bias dominated row, not a trop regression. The remaining top-row
deltas are therefore dominated by ionosphere and residual code-bias lifecycle
terms rather than the 30-second code-bias sign flip, phase-only code-bias gaps,
receiver-antenna slot mismatch, or fixed trop zenith constants.
The diff JSON also includes top_component_deltas, the largest individual
component deltas across all matched rows, and top_row_component_breakdowns,
which groups all component deltas for the same ZD key. Use those fields to
distinguish the global largest component from the most unbalanced row before
changing the gated correction model. The optional CI summary schema
ci_optional_clas_zd_component_diff.v7 and later summaries lift both the global top delta, the
leading row-breakdown component, and a per-component max-delta map into summary
metrics, so release artifacts show the next single-component target without
manually opening the full diff JSON. Schema
ci_optional_clas_zd_component_diff.v11 additionally surfaces top-row values
for highlighted components. Present-on-both components, such as stec_tecu,
iono_l1_m, iono_scaled_m, code_bias_m, and trop_correction_m, carry
CLASLIB/native/delta values in the CI summary; present-on-one-side components
still expose the native atmosphere, clock, and code-bias reference epochs when
the CLASLIB export cannot provide comparable fields. Schema
ci_optional_clas_zd_component_diff.v12 also highlights CLAS atmosphere
network/grid provenance when both normalized sides provide those columns;
ci_optional_clas_zd_component_diff.v13 adds native code-bias reference TOW.
CI can regenerate the native side of this A4b probe without a pre-staged native CSV:
python3 scripts/ci/run_clas_a4b_native_selfdiff.py
The wrapper sparse-checks out the public CLASLIB data/ directory at the pinned
GNSSPP_CLAS_A4B_CLASLIB_REF, runs the native command above, and self-diffs the
G14/C2W code rows. It emits
ci_clas_a4b_native_selfdiff_summary.json, native_code_dump.csv,
native_code_dump_summary.json, native_summary.json, and
selfdiff.{json,csv}. The wrapper first validates the native dump with
clas_zd_component_summary.v2; the CI summary schema is
ci_clas_a4b_native_selfdiff.v3. The summary uses status: passed only when
the native run reaches the configured epoch count, the native dump summary
passes and covers the dump rows, the dump summary independently verifies GPS
L2W exact observation/bias identity with zero fallback rows, and the self-diff has no
unmatched rows or component deltas. Set
GNSSPP_CLAS_A4B_DATA_ROOT to use a pre-existing CLASLIB data checkout, or
GNSSPP_CLAS_A4B_AUTO_FETCH=0 to record blocked_infrastructure instead of
fetching public data. CI treats that blocked state as a failure by default; set
GNSSPP_CLAS_A4B_FAIL_ON_BLOCKED=0 only for diagnostic-only local runs.
The optional CLAS ZD oracle/native diff runs after this native self-diff in CI.
CI also builds pinned CLASLIB as a test-only tool, runs unmodified rnx2rtkp -s
on the same public A4b window, normalizes its .osr file, and uploads
ci_claslib_osr_zd_export_summary.json, claslib.nmea.osr,
claslib_osr.normalized.csv, and claslib_osr_summary.json:
python3 scripts/ci/run_claslib_osr_zd_export.py
When GNSSPP_CLAS_ZD_CANDIDATE_CSV is unset, the workflow sets
GNSSPP_CLAS_ZD_NATIVE_CSV=output/clas_a4b_native_selfdiff/native_code_dump.csv
so the generated public-data native dump becomes the candidate side. When
GNSSPP_CLAS_ZD_BASE_CSV is unset, the workflow uses
output/claslib_osr_zd_export/claslib_osr.normalized.csv as the CLASLIB base
side and defaults the optional diff filter to post/code/G14/f1/C2W;
explicit workflow variables still override those defaults. The normalized
CLASLIB CSV includes test-only grid provenance derived from clas_grid.def and
the CLASLIB .osr receiver latitude/longitude, and the runner fails if GPS L2W
rows are missing those provenance columns.
When GNSSPP_CLAS_ZD_COMPONENTS is unset, the remote diff compares the
component-level fields prc_m, prc_component_sum_m,
prc_closure_residual_m, stec_tecu, iono_l1_m, iono_l1_from_stec_m,
iono_l1_stec_closure_residual_m, iono_scaled_m, iono_scale,
iono_scaled_closure_residual_m, trop_correction_m, code_bias_m,
receiver_antenna_m, relativity_m, atmos_ref_tow, clock_ref_tow,
code_bias_ref_tow, and atmos_clock_gap_s, plus CLAS grid id/count/weight
provenance.
atmos_grid_distance_m remains available for explicit diagnostic runs, but it
is not part of the default component list because receiver-coordinate
differences can otherwise dominate the top-row summary without representing a
correction-value regression.
The derived prc_closure_residual_m value is computed by the diff tool as
PRC - (trop + relativity + receiver antenna + scaled iono + code bias), so it
diagnoses PRC convention/rounding gaps without changing either input CSV schema.
The derived iono_l1_stec_closure_residual_m is
iono_l1_m - stec_tecu * GPS_L1_TECU_TO_METERS, which separates TECU-to-meter
unit mistakes from upstream STEC materialization mismatches. The derived
iono_scaled_closure_residual_m is iono_scaled_m - iono_scale * iono_l1_m,
which separates frequency-scaling mistakes from upstream STEC/L1-delay
mismatches. The aggregate applied_pr_corr_m remains available for
explicit diagnostic runs, but the default sign-off points the top-row evidence
at the underlying ZD correction component.
The GitHub step summary highlights stec_tecu, iono_l1_m,
iono_scaled_m, iono_l1_from_stec_m,
iono_l1_stec_closure_residual_m, iono_scaled_closure_residual_m,
code_bias_m, trop_correction_m, and prc_closure_residual_m from the
per-component max-delta map, and reports missing counts for highlighted
atmosphere-reference fields when CLASLIB cannot provide a comparable value.
The native clas_zd_component_summary.v2 snapshot also reports numeric
min/max stats for atmos_ref_tow, clock_ref_tow, code_bias_ref_tow,
atmos_clock_gap_s, atmos_lifecycle_tow, atmos_selected_satellite_count,
atmos_valid_grid_count, atmos_stec_grid_value_count, and
atmos_selected_grid_stec_value_count, so reviewers can see which lifecycle
bank was materialized before opening the raw CSV. For the leading-row
breakdown, v11 summaries also include CLASLIB/native/delta values for
highlighted present components and the native value of each highlighted missing
field, which ties the largest PRC/iono/trop delta back to the selected
reference epoch.
For the G14/C2W code-bias timing issue, native SSR interpolation forbids future
code-bias samples and the A4b L6 expansion uses delayed-15s-bank so base-bank
lookup uses the latest bank effective at tow - 15 s. Native therefore no
longer applies the 230430 bank to TOW 230426..230429, and it holds the
previous bank through the first half of each 30-second bank. On the CLASLIB
.osr slice this moves G14/C2W code_bias_m from mean_abs=0.00857 m,
rms=0.01309 m to mean_abs=0.00800 m, rms=0.01265 m, with mismatch count
moving from 120/280 to 112/280 common rows. The L6 expansion then emits delayed
base-bank refresh rows for selected subtype-6 network replacements, moving the
same G14/C2W slice to 16/280 mismatches with mean_abs=0.00114 m and
rms=0.00478 m; aggregate PRC moved to mean_abs=0.00879 m and
rms=0.01481 m. Current develop regenerates the same public-data slice with
zero code_bias_m delta on all 280 common G14/C2W rows, including the former
5-second boundary mismatches. CI hard-gates this component at 0.0 m while
the remaining ionosphere and aggregate PRC differences stay diagnostic. The CLASLIB .osr
snapshot summaries also include claslib_raw_iono_l1_m and
claslib_raw_stec_tecu numeric stats
as explicit diagnostics; they are intentionally outside the default native diff
component set.
If either generated side is missing, the optional diff reports
blocked_infrastructure, not a passing sign-off.
Build with CLASLIB linked only when you need oracle-backed unit tests:
cmake -S . -B build-claslib \
-DCLASLIB_PARITY_LINK=ON \
-DCLASLIB_ROOT_DIR=/tmp/claslib
cmake --build build-claslib --target run_tests -j4
Run the oracle helper parity tests when that optional build is available:
./build-claslib/tests/run_tests --gtest_filter='*ClasnatParity*'
Expected helper coverage after iter55 is 17 tests, including filter,
lambda, tropmodel, and stec_grid_data.