Give the TOA comparison the clock state its own bias needs - #88
Merged
Conversation
`run_inline_comparison` injects a shared 1.5 m receiver clock bias into the TOA pseudoranges -- correctly, to show TDOA differencing it away -- and then solved them with a position-only (x, y) state. A common bias is unobservable to that state: no position makes four uniformly inflated ranges consistent, so the residual never reached `tol` and the solve was discarded. The figure's "Convergence Success Rate" panel therefore read 2-5 of 100 for TOA at every noise level, beside a median-error table where TOA was among the best. Both cannot be true of a method, and the panel was the one lying -- it reported the survival rate of a model mismatch. The tell was the noiseless row. TOA printed 0.153 m of median error at zero measurement noise where TDOA and AOA both printed 0.000. A method solving perfect data is exact, so a nonzero number there is never noise. That also corrects the issue's reading that "the 2-4 % that do converge are accurate": they are not. The survivors are the geometries where the bias could be partly absorbed *into the position*, which makes them the least-inaccurate rather than the accurate ones -- and it rules out relabelling the panel as a fix, because the median column is wrong too. The fix was already exported. `toa_solve_with_clock_bias` is Eqs. (4.24)-(4.26), the chapter's own joint position-and-clock estimate, in `core/rf/positioning.py` and in `core.rf`'s `__all__`. The example injected a clock bias and then declined to use the solver written for it, one import away. With the state as (x, y, c*dt): 100/100 converge at every level, the bias returns as +1.500 m on noiseless data, and the medians go 0.153 -> 0.000, 0.177 -> 0.044, 0.211 -> 0.152, so TOA now tracks TDOA at the cost of the extra unknown. That is the comparison Chapter 4 exists to make. The RSS branch keeps `iterative_ls`. It reaches the same class through `rss_to_distance` and carries no clock bias, so it has nothing to estimate. Fixing it made TOA invisible, which is the same defect mirrored: three methods then sit on 100 % and four solid lines at one value show only the last drawn, so a panel that used to say "TOA barely converges" would have said "TOA is missing". Each method now has its own dash pattern; no value is nudged. The chapter README pins this example's stdout, so that guard went red on the old numbers exactly as intended and the transcript is updated from a real run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 #85.
run_inline_comparisoninjects a shared 1.5 m receiver clock bias into the TOA pseudoranges — correctly, to show TDOA differencing it away — then solved them with a position-only(x, y)state. A common bias is unobservable to that state, so the residual never reachedtoland the solve was discarded.Confirmed, and one correction to the issue
Measured convergence, holding everything else fixed:
The bias is the entire cause.
The issue says "the 2–4 % that do converge are accurate". They are not. TOA printed 0.153 m of median error at zero measurement noise, where TDOA and AOA both printed 0.000. A method solving perfect data is exact, so a nonzero number there is never noise. The survivors are the geometries where the bias could be partly absorbed into the position — the least-inaccurate rather than the accurate ones.
That matters for the choice of fix: it rules out option 3 (relabel the panel), because the median column is contaminated too, not just the convergence panel.
Option 1, cheaper than the issue estimates
toa_solve_with_clock_biasis Eqs. (4.24)-(4.26), already incore/rf/positioning.pyand already incore.rf's__all__. The example injected a clock bias and then declined to use the solver written for it, one import away.Summary-table medians go 0.153 → 0.000, 0.177 → 0.044, 0.211 → 0.152. TOA now tracks TDOA at the cost of the extra unknown, which is the comparison Chapter 4 exists to make: TOA carries the clock, TDOA differences it away.
The RSS branch keeps
iterative_ls— it reaches the same class throughrss_to_distanceand carries no clock bias, so it has nothing to estimate. (The issue lists:482as a position-only site; it is, but correctly so.)The fix introduced a mirrored defect, also fixed
Three methods then sit on 100 %, and four solid lines at one value show only the last one drawn — the panel that used to say "TOA barely converges" would have said "TOA is missing", which is the same failure this file warns about at
:81. Each method now has its own dash pattern. No value is nudged.Guard behaviour
The chapter README pins this example's stdout, so
test_readme_example_output.pywent red on the old numbers exactly as designed. The transcript is updated from a real run and passes.Verification
44 passed across
tests/ch4_rf_point_positioningand the lint ratchet; README transcript guard passes; pyflakes clean. Figure regenerated and opened.🤖 Generated with Claude Code