Chapter 4: sweep the initial guess, and find the residual behind it - #92
Merged
Conversation
When an iterative positioning solve fails, the reflex is to blame the starting
point. example_initial_guess_basin.py holds the geometry at the well-behaved
square, fixes one target, sets the measurement noise to zero, and sweeps the
initial guess over 1681 seeds twice - changing nothing but the space the
residual is formed in.
The example was written expecting the wrapped-angle form to remove the basin
outright and the sweep said otherwise, so the docstring, the README and the
tests all state the narrower claim it can actually support. Over 1681 seeds:
tan(psi) wrap(angle)
seeds that fail 785 341
quiet: stalled or plausible 263 0
loud: walked off past 100 m 522 341
failures claiming converged 305 196
So it is 2.3x fewer failures, and underneath that a sharper statement: the
wrapped-angle form removes the quiet class completely - the failures that look
like answers - while a seed far outside the room still walks off under either
parameterisation, and 196 of those still set converged=True. Fixing the
residual makes the solver honest, not safe, which is why solve_batch's four
conditions are not optional either way. One traced seed at (-4.5, -2.0) walked
to 9.4e11 m in 18 iterations with the flag set; that run is the fourth panel.
Two details worth keeping:
- The target sits off the seed lattice at (3.2, 6.8). On a lattice point one
of the 1681 seeds IS the answer, so the solver cannot move and scores as a
stall - a classification artifact, and the rule against seeding a solver
with the ground truth arrived at by accident. An assert now makes the
collision impossible rather than subtracting it afterwards.
- Classification goes through core.rf.solve_batch rather than being
re-derived, and the failure modes are drawn as their own bar panel.
Also adds tests/ch4_rf_point_positioning/test_initial_guess_basin.py, which
pins both halves of the claim so the caption cannot drift to "fixed", and a
guard that fails if the two parameterisations ever perform the same - at which
point the example has stopped demonstrating anything.
Notebook: new Part 7, "When the solver lies" - the collinear geometry from
compare_geometries() where TOA and TDOA fail 100/100 with a GDOP of 1.43, then
this sweep. Parts 1-6 all report the accuracy of the solves that worked.
The cell appended an error only `if info['converged']`, wrapped each solve in `except: pass`, and reported RMSE, mean and a "Success %" over whatever survived. That is the shape 030-figures-and-claims.mdc exists to stop: the solves that silently returned nonsense are averaged in, the ones that honestly refused disappear from the denominator, and a solve that converges at 1e11 m counts as a success. Now on core.rf.solve_batch - the four conditions applied once - reporting the median beside the failure count, with the failure rate drawn as its own panel because an accuracy plot cannot say "this did not work". The seed stays the beacon centroid, which was already right. Every method reports 0/100 on this geometry, which is the honest result and the right setup for Part 7: the same code and the same noise, with the beacons moved into a line, fails 100/100.
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.
When an iterative positioning solve fails, the reflex is to blame the starting point. This adds
ch4_rf_point_positioning/example_initial_guess_basin.py, which holds the geometry at the well-behaved square array, fixes one target, sets the measurement noise to zero, and sweeps the initial guess over 1681 seeds — twice, changing nothing but the space the residual is formed in.residual="tan"residual="angle"converged=TrueThe example was written expecting the wrapped-angle form to remove the basin outright, and the sweep said otherwise. The docstring, the README section and the tests all state the narrower claim it can actually support: 2.3× fewer failures, and underneath that, the quiet class goes to zero — the failures that look like answers. A seed far outside the room still walks off under either parameterisation, and 196 of those still set the convergence flag.
So fixing the residual makes the solver honest, not safe, which is why
core.rf.solve_batch's four conditions are not optional either way. One traced seed at (−4.5, −2.0) walked to 9.4 × 10¹¹ m in 18 iterations withconverged=True; that run is the fourth panel of the figure.Two details worth keeping
solve_batchscores it — correctly, by its own definition — as a stall. That is a classification artifact, and it is also the rule against seeding a solver with the ground truth, arrived at by accident. Anassertinseed_grid()now makes the collision impossible rather than subtracting it afterwards.core.rf.solve_batchrather than being re-derived, and the failure modes are drawn as their own bar panel, per030-figures-and-claims.mdc.Also in this PR
tests/ch4_rf_point_positioning/test_initial_guess_basin.py— pins both halves of the claim, so the caption cannot drift to "fixed", plus a guard that fails if the two parameterisations ever perform the same, at which point the example has stopped demonstrating anything. Companion to the existingtest_aoa_initialisation_basin.py, which pins the same behaviour from a single cold start; this one sweeps the floor.compare_geometries(), where TOA and TDOA fail 100/100 while GDOP calls the array fine at 1.43 against 1.02 for the square; then this sweep. Three "Try it" edits at the end.python -m tools.chapter_dependencies).Checks
tests/ch4_rf_point_positioningand the example-level meta tests: 368 passed (one genuine failure found and fixed — the example importedcorewithout putting the repo root onsys.pathfirst, per issue Examples do not put the repo root on sys.path, so a fresh clone fails on 'import core' #86).tests/docs: 507 passed, after regenerating the Architecture section and listing the new example in File Structure.ruff check: clean.