Skip to content

feat: within, near, subgraph, coordinates and an optional k-d tree - #60

Merged
petercorke merged 4 commits into
mainfrom
feat/spatial-queries
Oct 12, 2026
Merged

petercorke merged 4 commits into
mainfrom
feat/spatial-queries

Conversation

@petercorke

Copy link
Copy Markdown
Owner

Spatial queries on embedded graphs, built on one primitive.

New

  • g.coordinates(): the (dim, n) matrix of vertex coordinates, one column per vertex in insertion order (NaN for a vertex without one), a view of a row-per-vertex array so each coordinate is contiguous. A snapshot, not cached.
  • g.within(box): vertices inside an n-d box given as a 2 × n array of lower and upper corner. Closed box, graph order.
  • g.near(coord, radius): [(vertex, distance), …] within a distance, nearest first, using the graph's metric; vectorized for the built-in L1, L2 and SE2Metric, a loop for a metric of your own.
  • g.subgraph(vertices): the induced subgraph, a new independent graph of the same kind with the edges that have both ends among the vertices (edges crossing the border are dropped; parallel edges and loops kept). Takes vertices, names, or a boolean mask of length n (an integer vector is refused: it could be indices). Reuses the flat description that pickling uses, so classes, names, coordinates, costs, user attributes, metric and order are kept.
  • g.build_kdtree(): optional scipy cKDTree that closest and near use for L1, L2 and SE2Metric (the angle is a periodic dimension, scaled by the weight; tested against brute force at four weights). scipy is imported only inside the method and is not in pyproject.toml. The index is dropped when a vertex is added or removed or coord is assigned (= and +=, via Vertex.__setattr__), so a stale index is never used; a coordinate edited in place (v.coord[0] = x) is not seen, which is documented and pinned by a test. It is not copied or pickled.

validate(embedded=False)

Now also checks that coordinates are finite 1-D numeric vectors, all of one length when dim was not given; embedded=True also reports vertices without a coordinate.

Notes

  • spatialmath-python, a dependency, installs scipy and imports scipy.spatial itself, so scipy is always present in practice; the lazy import and the absence from pyproject.toml are about pgraph not relying on it. A test checks that no pgraph module imports scipy at module level.
  • Tests skip the k-d tree cases if scipy is missing. The "scipy missing" error path is tested regardless, by patching the import.
  • Docs: the new methods are in analysis.rst; the docs build with no warnings.

Stacked on #58 (SE2Metric), which is merged, so this is relative to main. 731 tests pass, mypy clean.

🤖 Generated with Claude Code

petercorke and others added 2 commits October 13, 2026 09:05
within(box) lists the vertices inside an n-dimensional box given as a 2 x n
array of lower and upper corner, near(coord, radius) those within a distance
of a point by the graph's metric with their distances, nearest first, and
subgraph(vertices) is the graph induced by a set of vertices, a new graph of
the same kind in which every edge with an end outside is dropped.

subgraph reuses the flat description that pickling uses, so classes, names,
coordinates, costs, user attributes, metric and the order of vertices and
edges are kept, parallel edges and loops included, and the result shares
nothing with the original.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
coordinates() is the matrix of vertex coordinates, one column per vertex in
insertion order (NaN for a vertex with none), a view of a row-per-vertex array.
within() and near() are now computed on it with numpy, for the built-in
metrics, instead of vertex by vertex.

build_kdtree() builds an optional scipy cKDTree that closest() and near()
use for the built-in L1, L2 and SE2 (with its weight, using a periodic
dimension for the angle).  scipy is imported only inside the method and is
not added to pyproject.toml.  The index is dropped when a vertex is added or
removed or a coord is assigned (Vertex.__setattr__), so a stale index is never
used; a coordinate edited in place is documented as not seen.  It is not
copied or pickled.

subgraph() also takes a boolean mask of length n.  An integer vector is
refused, since it could be indices.

validate() now checks that coordinates are finite 1-D numeric vectors and,
when dim was not given, all of one length, and with embedded=True that every
vertex has one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 146 complexity · 0 duplication

Metric Results
Complexity 146
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.18%. Comparing base (d33a0dc) to head (3973f82).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #60      +/-   ##
==========================================
+ Coverage   96.71%   97.18%   +0.46%     
==========================================
  Files           3        3              
  Lines        1371     1562     +191     
==========================================
+ Hits         1326     1518     +192     
+ Misses         45       44       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

petercorke and others added 2 commits October 13, 2026 09:10
near() with SE2Metric and with a metric of your own, within() with no
coordinates, closest() skipping a vertex without one, the index refusing
a graph whose coordinates are all NaN, a query that is not finite or the
wrong length.  The pickle round trips are routed through one helper marked
for the scanners.  An unreachable guard in near() is an assertion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@petercorke
petercorke merged commit f9555d8 into main Oct 12, 2026
12 checks passed
@petercorke
petercorke deleted the feat/spatial-queries branch October 12, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant