Repository navigation
feat(urbannav): run the filter on Tunnel-1 with the receiver's own fixes and outage (T2-0b) - #130
Merged
Merged
Conversation
…xes and outage (WB-7) navkit urbannav run: real F9P fixes and the real tunnel outage, scored horizontally and vertically, with settings declared in code before the first run. A second variant (per-fix receiver sigma) was added after the first result and is labelled as not blind. Also: GST reader, NovAtel attitude convention (checked against the data), run_sequence gnss_override and detail_out, page section, ADR-0026 evidence, work-breakdown status. The filter is wrong and overconfident through the real outage and loses the GNSS channel for good. One sequence, one receiver. Not field performance. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The committed numbers were produced before the attitude reset Jacobian fix (T1-1). Regenerated with navkit urbannav run twice; the two outputs are byte-identical. Only the generated blocks and the CSV change. The prose on the page is not edited here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…'s report (T2-0b) Add three diagnostic variants that replace part of the filter's attitude with the reference's inside the outage (oracle_tilt, oracle_yaw, oracle_attitude). They are labelled ORACLE DIAGNOSTIC, not a filter, in the code, the CSV rows and the page. The filter's code is not changed: the propagate method is wrapped for the length of a run and restored on exit. Add the roll, pitch and yaw error at the start and end of the outage, the first-return NIS, the fixes after the outage, and the reader's counts (epochs not passed on, unusable variances, variances below the filter's floor). A fix with a negative, NaN or infinite variance is dropped by the run before it reaches the filter. Add the inverse of rpy_to_rotation. The new page test is added to the documentation tests step. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…it the dataset (T2-0b) The page now states that the filter is already inconsistent with GNSS available, that the outage error is driven by tilt error inside the outage (oracle rows), that a link between the two is not shown, and that the lost channel follows from the position error. The cause of the tilt growth is marked HYPOTHESIS and not tested. The clock sentence now says no offset was applied. The CSV is regenerated (two runs, byte-identical). The README credits the UrbanNav authors with the citation they ask for. ADR-0026 gets context and options; the decision section stays blank. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| k = min(int(np.searchsorted(seq.imu.t - t0, t)), len(seq.imu.t) - 1) | ||
| if t < 10.0 - 1e-9: | ||
| assert (r, p, h) == pytest.approx((own_r, own_p, own_h), abs=1e-6) | ||
| elif 10.0 + 1e-6 < t < 20.0 - 1e-6: |
| orig = ErrorStateKalmanFilter._propagate | ||
| with pytest.raises(RuntimeError), ur.oracle_attitude(seq, (1.0, 2.0), "oracle_tilt"): | ||
| raise RuntimeError("boom") | ||
| assert ErrorStateKalmanFilter._propagate is orig |
| def test_the_oracle_patch_is_removed_even_when_the_run_fails(tmp_path): | ||
| ee.write_fixture(tmp_path, "MH_01_easy", duration_s=20.0, seed=1) | ||
| seq = ee.load_sequence(tmp_path, "MH_01_easy") | ||
| orig = ErrorStateKalmanFilter._propagate |
…xercises it (T2-0b) Write a small sequence in the publisher's layout from code with made-up numbers (a vehicle on a straight road, a 1 Hz reference, a 20 Hz IMU, an NMEA file with a gap as the outage, and a gyroscope bias that drifts the roll) and run build, run_once for all five variants, and main on it. The tests assert behaviour: the outage holds no fix, the reader's counts, the labels, a tilt oracle beating the baseline on a known tilt drift, unusable variances dropped before the filter, a deterministic CSV, and the documented messages for a missing path. They do not skip without the dataset. Fix found by the fixture: one NaN or infinite GST sigma made the declared horizontal sigma NaN, because it was averaged before the unusable fixes were dropped. Averages now use finite values only. The real sequence has none, so its numbers are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment on lines
+83
to
+84
| "UTCTime Week GPSTime Latitude Longitude H-Ell VelBdyX VelBdyY VelBdyZ AccBdyX AccBdyY AccBdyZ " | ||
| "Roll Pitch Heading Q", |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #109
Closes #95
By the maintainer's instruction this closes #95. The README credit it asks for is in. The Deep-Urban-1 rows and the INSPVAX decision it also lists are not done here, so they need a follow-up issue.
All of this is one sequence (Tunnel-1) and one receiver (u-blox F9P). Not field performance. Branch from
origin/mainat 5f5abef, in a fresh clone.Commits
9c14892the stranded commit (a23512c), cherry-picked with its author.feb8c0fthe CSV and the generated page tables regenerated on current main.7599935oracle diagnostics, tilt and NIS rows, the reader's report, and the page test added to the CI documentation step.ecc52c8the page findings, the README credit, ADR-0026 context and options.ad4cbf8a synthetic Tunnel-1 sequence written by the tests, so CI runs the code that otherwise needs the dataset (see below), and a fix it found.Conflict resolutions (step 1)
git cherry-pick --no-commit a23512cconflicted only in the generated count markers:CONSTRAINTS.md(two markers),CONTRIBUTING.md,ROADMAP.mdanddocs/data/metrics.json. I took main's version of all four (git checkout --ours) and did not edit them by hand.metrics.py --writeregenerated them at the end (1565tests collected). ADR-0026 merged without a conflict. Its decision section is the blank placeholder on main and is unchanged ("To be written by the maintainer.").Step 2: the committed CSV did not reproduce
navkit urbannav runon main did not give the committeddocs/data/urbannav_run.csv. First differing row: line 2,tunnel1,declared,0,hor_rmse_m, committed 4878, reproduced 4944; 160 of 250 data rows differ.Cause: the commit was written before T1-1 (
b8cb8e8, the sign of the attitude reset Jacobian). Evidence, declared seed 0:a23512cas committeda23512cplus only the T1-1 source diffThe run reads three named files (
tunnel1/UrbanNav_tunnel_GT_raw.txt,xsense_imu_tunnel_1.csv,20210518.tunnel.cht.ublox.f9p.nmea). There is no glob, so it does not depend on what else is on disk. The CSV and the page tables were regenerated, and the run was done twice with byte-identical output (SHA-25625c8cee4930c54d11e608254528e75a3ab37c598cddf7352de99be1915c88168for both).Investigation (steps 3 and the follow-up), seed 0, current main
Scratch scripts, not in the repository, are at the end of this description. Everything labelled "scratch" is not blind, one sequence, not a fix.
Sanity checks (a to g)
Attitude error and what explains the position error (declared; Euler angles in the dataset's convention; estimate minus reference)
Position error at the end predicted from the attitude error (scratch): tilt term (g times the tilt error, integrated twice) 3885 m; yaw term (speed times yaw error, integrated) 147 m; start error plus start velocity error 118 m; sum 3660 m against 3726 m actual; the tilt term alone points the same way as the error (cosine 0.998). Per-fix gives the same picture (tilt 3706 m, yaw 91 m, actual 3574 m). For scale: a constant 1 degree tilt over 151 s gives 1951 m.
Counterfactuals (now in the code as oracle variants, generated on the page)
Sensitivity runs (scratch). Not blind, one sequence, not a fix. Not on the page.
The x3 white-noise result makes the pre-outage state worse while it shrinks the outage error. It is tuning on one sequence, not a finding. Lever arm: the mean fix residual in the body frame (38 moving fixes within 5 m of the reference) is 1.1 m forward and 0.2 m right, consistent with the antenna offset the page names (not corrected). It is too small to explain a median error of several metres or a NEES of 10. The gyro bias the filter holds at the outage start is within about 5e-5 rad/s of its start guess; the bias inferred from the 1 Hz reference differs on one axis by about 4.5e-4 rad/s, and starting from the inferred biases still ended in a kilometre-scale error. So the tilt growth is not explained. The page says so and marks the weak-observability reason HYPOTHESIS.
Steps 4 and 5
Variants.
DECLAREDin the code lists the settings with the first variant fixed before the first run andvariant: per_fixmarked "added AFTER the first run ... Not blind". Inrun_oncethe two differ only bygnss_per_fix_covariance. Both are on the page.Interface after #129. Of the 250 valid fixes none has a NaN, negative, zero or below-floor variance. The receiver sigma ranges from 0.70 to 31 m; every fix has a matching GST sentence, so the scalar fallback is never used. The floor (0.01 m) therefore changed nothing, and no fix would raise. The reader does not pass on 151 receiver epochs (quality 0 or no position). The run now drops a fix with an unusable variance before the filter sees it, reports the count, and reports the epochs not passed on and the fixes below the floor. All three are generated rows (151, 0, 0 here) with tests that they match the data (skipped when the data is absent).
What changed on the page because of the checks
Tests (written first)
Euler inverse round trip; each oracle mode overwrites only the named attitude only inside the outage and the patch is removed on exit and on error; unknown mode refused; variance report and the drop mask; epochs not passed on; page labels (ORACLE DIAGNOSTIC, HYPOTHESIS, the missing link); claim tests on the CSV (tilt grows, reference tilt removes most of the error and reference yaw does not, oracles equal the declared filter before the outage, every fix after the outage is over the gate, the CSV holds aggregates only). Data-dependent tests skip without the dataset: counts match the data, the oracle is deterministic, the committed numbers reproduce.
tests/test_urbannav_run.pyis added to the CI documentation step because the guard test required it.Coverage: why CI failed the first time, and what changed
The first CI run failed only at the recorded-coverage step (
coverage is 92.32%, metrics.json records 94.31%). The new run code (build,run_once,main, the oracle wrapper) ran only with the dataset, which CI does not have. The ratchet itself passed. I did not re-record the figure or widen the allowed gap. Insteadtests/test_urbannav_synthetic.pywrites a small Tunnel-1-shaped sequence from code (made-up numbers on a made-up spot, a straight drive, a 1 Hz reference, a 20 Hz IMU, an NMEA file with RMC, GGA and GST sentences and a 40 s gap as the outage, a gyroscope bias that drifts the roll from just before the outage). Nothing in it is a row of the real dataset and no data file is committed. The tests do not skip without the dataset, and the whole file takes about 8 s.What the new tests assert (not just execute): the outage has no offered fix and the reader's not-valid count equals the epochs written without a fix; all five variants run and give the documented quantities, CSV columns and labels, and the oracle rows are labelled ORACLE DIAGNOSTIC; on the known roll drift the tilt oracle and the attitude oracle end the outage under half the declared filter's error while the yaw oracle does not change it, and the tilt oracle removes the roll error; a fix with a NaN or infinite variance is dropped before the filter, counted, and no
ValueErrorreaches the caller (a negative variance is covered by the mask test, since a GST sigma is squared); a sigma below the floor is counted and kept;mainwrites the documented CSV and two runs give the same SHA-256;main --from-csv --pagefills the blocks; a missing path and a missing NMEA file give the documentederror:message and no traceback; too few finite sigmas, and an oracle without an outage, are refused. Mutation check: disabling the oracle's overwrite fails 5 tests, and the NaN-sigma test failed first against the old code.The fixture found a real defect: one NaN or infinite GST sigma made the declared horizontal sigma NaN, because the average was taken before the unusable fixes were dropped. The average now uses finite values only. The real sequence has no such sigma, so its numbers and CSV are unchanged.
Measured locally in a scratch venv outside the repository (
coverage7.16.2,PYTHONPATH=src, dataset hidden, the UrbanNav, EuRoC and recorded-orientation test files):src/navkit/urbannav_run.pysrc/navkit/urbannav_data.pysrc/navkit/euroc_eval.pyThe missing lines are error branches and the real-receiver cases, which I did not chase. Against
origin/mainwith the same files (hidden data):euroc_eval.py420 statements and 86 missed,urbannav_data.py243 and 19. So this branch adds 354 statements and 374 hit lines.Expected total.
docs/data/metrics.jsonon main records 10033 of 10638 lines. coverage.py counts lines differently fromscripts/coverage_report.py, so I calibrated: for the tree CI measured at 92.32%, the same estimate gives 93.05%, an offset of 0.73 points. Now: (10033 + 374) / (10638 + 354) = 94.68% by the raw estimate, about 93.95% after the offset. The recorded figure is 94.31% and the allowed gap is 0.5, so I expect the check to pass with about 0.36 points to spare, which is thin. If CI still disagrees I will report the measured gap and the missing lines, and not widen the tolerance or re-record.Checks
ruff check,ruff format --check,mypy src,check_doc_tables.py,metrics.py --check,check_links.pypass (1586tests collected). Full suite:1563 passed, 2 xfailed in 666.20s (0:11:06)(the dataset read through a read-only symlink to the old checkout'sdata/raw, not committed)1555 passed, 29 skipped, 2 xfailed in 594.34s (0:09:54). The real-data tests skip with a reason, nothing fails, and the synthetic tests runThe coverage ratchet is left to CI. Nothing under
src/navkit/estimatorschanged. No dataset file is committed.Scratch scripts for the investigation
investigate.py (modes base, cf, shift, sens)
bias_cf.py and bw.py
🤖 Generated with Claude Code