Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,49 @@ three -- `(position, bias, info)` -- inside a `try/except Exception: pass`, and
got a confident `0/100 converged`. That is the issue's own defect one level up:
a blanket except turning a caller error into a result about the callee. Fourth
harness in this file to report the thing it could not read as broken.
## Every example now bootstraps its own sys.path, and the sweep took three tries

The trap at the top of this file -- `python chX/example.py` resolving `core` to
the main checkout -- is closed at the source now: all 38 examples insert the
repository root before their first `core` import, matching what
`ch5_fingerprinting/example_classification` and nine of twelve `scripts/`
generators already did. `tests/test_examples_import_this_checkout.py` holds it,
and checks *order* rather than presence, because a bootstrap below the import
changes nothing.

Two things worth knowing before repeating this kind of sweep.

**Three of the three bugs in the sweep script were found by pyflakes, none by
reading.** Each was a plausible-looking way to locate an insertion point:

1. `ast.walk` to decide whether `sys` was already imported -- it counts an
`import sys` inside a *function*, so four files got a module-level
`sys.path.insert` with no `sys` bound.
2. Finding the stdlib group with `line.startswith(("import ", "from "))` --
which matched a line of *module docstring prose* beginning "from range
measurements ...", and wrote `import sys` inside the docstring.
3. Treating any module-level import of a name as sufficient -- ch7's pose-graph
example imports `pathlib.Path` three lines *below* its first `core` import,
so the name existed but not yet at the point the bootstrap runs.

All three produce files that `compileall` accepts. The rule from the earlier
lint sweep holds and is worth restating in the stronger form: **a tool that
locates Python by line prefix cannot tell an import from prose that starts like
one, and `ast.walk` cannot tell module scope from function scope.** Use
`tree.body`, and compare line numbers.

**The import-order ratchet fired, correctly, and fixing it improved the
baseline.** Inserting `import sys` at the top of a stdlib group is unsorted, so
I001 went 113 -> 147. `ruff --select I001 --fix` over the chapter directories
cleared 63, including 29 that predated this change, leaving **84** -- so the
baseline moved down, not up. E402 does *not* fire: ruff exempts imports that
follow a `sys.path` manipulation, which is what makes this idiom viable at all.

Verification was the cheap strong one: `--help` for all 38 examples, captured
before and diffed after, **byte-identical both times** -- once after the
insertions and again after ruff reordered the imports. `--help` exits during
argument parsing but only after every module-level import has run, so it tests
exactly what this change touches, in about a second per example.

## A scratch probe in the scratchpad imports the *other* checkout

Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,17 @@ python -m ch6_dead_reckoning.example_comparison
```

`python -m` puts the repository root on `sys.path`, so these run straight from
a fresh clone even before step 4 above. The script form —
`python <chapter>/<example>.py` — puts the *script's* directory there instead,
so `core` is only importable once the package is installed. That is why every
command in this repository is written as `python -m`.
a fresh clone even before step 4 above, and it is the form every command in
this repository is written in.

The script form — `python <chapter>/<example>.py` — puts the *script's*
directory there instead, so `core` would not be importable from a fresh clone.
Each example now adds the repository root itself before importing `core`, so
that form works too. It is worth knowing why the line is there: without it, on
a machine that has ever installed this package, `import core` does not fail —
it quietly resolves to **whichever checkout the install points at**, and the
example runs to completion against a different copy of the library. The error,
when there is one, names a directory you have never heard of.

Examples find their datasets from any working directory, so `cd`-ing into a
chapter folder first is fine. Every example takes `--help`, which prints what
Expand Down
8 changes: 8 additions & 0 deletions ch2_coords/example_attitude_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@
"""

import argparse
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.coords import (
enu_to_ned,
euler_to_rotation_matrix,
Expand Down
10 changes: 9 additions & 1 deletion ch2_coords/example_coordinate_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@

import argparse
import json
import sys
from pathlib import Path

import numpy as np

from core.utils import angle_diff, resolve_data_path
# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.coords import (
ecef_to_enu,
ecef_to_llh,
Expand All @@ -41,6 +48,7 @@
quat_to_rotation_matrix,
rotation_matrix_to_euler,
)
from core.utils import angle_diff, resolve_data_path


def load_dataset(data_dir: str) -> dict:
Expand Down
25 changes: 17 additions & 8 deletions ch3_estimators/example_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,35 @@
import argparse
import contextlib
import io
import sys
import time
from pathlib import Path

import numpy as np
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.estimators import (
ExtendedKalmanFilter,
Factor,
FactorGraph,
ParticleFilter,
UnscentedKalmanFilter,
)
from core.eval import (
plot_error_cdf,
plot_error_magnitude_time,
plot_trajectory_2d,
save_figure,
show_figures_if_requested,
)
from core.estimators import (
ExtendedKalmanFilter,
UnscentedKalmanFilter,
ParticleFilter,
Factor,
FactorGraph,
)


def setup_scenario(seed=42):
Expand Down
11 changes: 10 additions & 1 deletion ch3_estimators/example_ekf_range_bearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@

import argparse
import json
import sys
from pathlib import Path
from typing import Dict

import numpy as np
import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.estimators import ExtendedKalmanFilter
from core.eval import save_figure, show_figures_if_requested
from core.utils import angle_diff, resolve_data_path
Expand Down
10 changes: 9 additions & 1 deletion ch3_estimators/example_iekf_range_bearing.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,18 @@
"""

import argparse
import sys
from pathlib import Path

import numpy as np
import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.estimators import ExtendedKalmanFilter, IteratedExtendedKalmanFilter
from core.eval import (
Expand Down
11 changes: 10 additions & 1 deletion ch3_estimators/example_kalman_1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@
"""

import argparse
import sys
from pathlib import Path

import numpy as np
import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.estimators import KalmanFilter
from core.eval import save_figure, show_figures_if_requested

Expand Down
19 changes: 14 additions & 5 deletions ch3_estimators/example_least_squares.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,27 @@
"""

import argparse
import numpy as np
import matplotlib.pyplot as plt
import sys
from pathlib import Path

from core.eval import save_figure, show_figures_if_requested
import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.estimators import (
linear_least_squares,
weighted_least_squares,
gauss_newton,
levenberg_marquardt,
linear_least_squares,
robust_gauss_newton,
weighted_least_squares,
)
from core.eval import save_figure, show_figures_if_requested


def setup_positioning_scenario():
Expand Down
15 changes: 14 additions & 1 deletion ch3_estimators/example_particle_bimodal.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,26 @@
"""

import argparse
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.estimators import ParticleFilter
from core.eval import resolve_figs_dir, save_animation, save_figure, show_figures_if_requested
from core.eval import (
resolve_figs_dir,
save_animation,
save_figure,
show_figures_if_requested,
)

FIGS_DIR = Path(__file__).parent / "figs"

Expand Down
8 changes: 8 additions & 0 deletions ch4_rf_point_positioning/example_aoa_positioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@
"""

import argparse
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.eval import save_figure, show_figures_if_requested
from core.rf import (
AOAPositioner,
Expand Down
8 changes: 8 additions & 0 deletions ch4_rf_point_positioning/example_comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import argparse
import json
import sys
import time
from functools import partial
from pathlib import Path
Expand All @@ -33,6 +34,13 @@
import numpy as np
from tqdm import tqdm

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.eval import save_figure, show_figures_if_requested
from core.rf import (
DIVERGENCE_M,
Expand Down
15 changes: 14 additions & 1 deletion ch4_rf_point_positioning/example_dop_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,26 @@
"""

import argparse
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Ellipse

from core.eval import resolve_figs_dir, save_animation, save_figure, show_figures_if_requested
# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.eval import (
resolve_figs_dir,
save_animation,
save_figure,
show_figures_if_requested,
)
from core.rf.dop import compute_dop, compute_geometry_matrix
from core.rf.positioning import TOAPositioner

Expand Down
10 changes: 8 additions & 2 deletions ch4_rf_point_positioning/example_tdoa_positioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@
"""

import argparse
import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

# `core` must come from this checkout. Running this file as a script puts
# its *chapter* directory on sys.path[0], not the repository root, so
# without this line `import core` silently resolves to whatever else is
# installed -- another clone, a stale editable install -- or fails outright
# on a fresh one. See issue #86.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from core.eval import save_figure, show_figures_if_requested
from core.rf import (
TDOAPositioner,
Expand All @@ -29,8 +37,6 @@
toa_fang_solver,
)



# Seed for the Monte Carlo in Demo 2.
SEED = 42

Expand Down
Loading
Loading