✨ feat(vectors): add the Point overload of chord_distance - #774
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a Point-level overload for chord_distance so it can be called ergonomically at the same level as geodesic_distance, while preserving chord semantics (map the second operand into the first operand’s chart and delegate to the manifold-level rule), and enforcing frame/manifold strictness.
Changes:
- Register a
Point, Pointdispatch forchord_distancethat (1) rejects cross-frame and cross-manifold pairs and (2) convertsbintoa’s chart before delegating to manifold-levelchord_distance. - Add a focused unit test module covering analytic correctness on
S^2, symmetry, chart-invariance, Euclidean refusal, and cross-frame refusal. - Ensure the new registration module is imported during vectors initialization.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/unit/vectors/test_chord_distance_point.py | Adds unit tests for the new Point overload behavior and key refusal/invariance properties. |
| src/coordinax/vectors/_src/register_chord_distance.py | Implements the Point overload dispatch for chord_distance with frame/manifold guards and chart conversion. |
| src/coordinax/vectors/_src/init.py | Imports the registration module so the dispatch implementation is loaded. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
`geodesic_distance` has one and `chord_distance` did not, so the two verbs were usable at different levels -- GalacticDynamics#720 flagged the asymmetry and left it open. It could not be written the same way. `geodesic_distance`'s overload brings both operands into a Cartesian chart, which for a chord is exactly wrong: a Euclidean manifold is its own ambient, so every call would land on the case `chord_distance` refuses, and an intrinsic sphere chart has no global Cartesian representation to convert to at all -- `geodesic_distance(p, q)` on `sph2` raises `NoGlobalCartesianChartError` today for that reason. The second operand is mapped into the first's chart instead, and the measurement delegated to the manifold-level rule. The chord is a property of the embedding, and the intrinsic chart is what carries it. Frame-strict, matching `geodesic_distance`, and refusing a cross-manifold pair. Eleven tests: the analytic `2 sin(dphi / 2)` across five separations, symmetry, chart-invariance with operands in different charts, that it differs from the geodesic (sqrt(2) against pi/2, the point of two verbs), flat space refusing, and the cross-frame refusal. 241 passed across `tests/unit/vectors`; `nox -s precommit` clean.
aeeb292 to
c72c496
Compare
Mirrors the geodesic_distance coverage: an element-wise batch case and a different-manifolds refusal, per review feedback on GalacticDynamics#774.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #774 +/- ##
=======================================
Coverage 96.71% 96.71%
=======================================
Files 268 269 +1
Lines 9125 9140 +15
=======================================
+ Hits 8825 8840 +15
Misses 300 300 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Review noted the overload was not exported the way `geodesic_distance` is. The stated consequence does not hold -- `cx.chord_distance(p, q)` already resolved, because `plum` registers every rule on one function object and `cx.chord_distance is cxm.chord_distance` is `True`, exactly as it is for `geodesic_distance`. The tests and doctests pass on `main` as written. The asymmetry underneath is real though: `cx.vectors.geodesic_distance` existed and `cx.vectors.chord_distance` did not, so the two verbs were reachable through different sets of namespaces. Exported from `coordinax.vectors` and imported at the top level alongside `geodesic_distance`, so they now match. 255 passed across `tests/unit/vectors`; `nox -s precommit` clean.
geodesic_distancehas aPointoverload andchord_distancedid not, so the two verbs were usable at different levels. #720 flagged the asymmetry and left it open; this closes it.It could not mirror
geodesic_distanceThat overload brings both operands into a Cartesian chart. For a chord that is exactly wrong:
chord_distancerefuses it — converting to Cartesian would land every call on the refused case;geodesic_distance(p, q)onsph2raisesNoGlobalCartesianChartErrortoday for precisely that reason.So the second operand is mapped into the first's chart instead, and the measurement delegated to the manifold-level rule. The chord is a property of the embedding, and the intrinsic chart is what carries it.
Frame-strict, matching
geodesic_distance, and refusing a cross-manifold pair.Tests
Eleven: the analytic
2 sin(dphi/2)across five separations, symmetry, chart-invariance with the operands in different charts, that it differs from the geodesic (sqrt(2)againstpi/2— the point of having two verbs), flat space refusing with the error pointing atgeodesic_distance, and the cross-frame refusal.241 passed across
tests/unit/vectors;nox -s precommitclean.🤖 Generated with Claude Code