Skip to content

✨ feat(vectors): add the Point overload of chord_distance - #774

Merged
nstarman merged 3 commits into
GalacticDynamics:mainfrom
nstarman:claude/chord-distance-point
Aug 20, 2026
Merged

✨ feat(vectors): add the Point overload of chord_distance#774
nstarman merged 3 commits into
GalacticDynamics:mainfrom
nstarman:claude/chord-distance-point

Conversation

@nstarman

Copy link
Copy Markdown
Contributor

geodesic_distance has a Point overload and chord_distance did 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_distance

That overload brings both operands into a Cartesian chart. For a chord that is exactly wrong:

  • a Euclidean manifold is its own ambient, so chord_distance refuses it — converting to Cartesian would land every call on the refused case;
  • an intrinsic sphere chart has no global Cartesian representation, so there is nothing to convert to. geodesic_distance(p, q) on sph2 raises NoGlobalCartesianChartError today 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.

>>> p = cx.Point({"theta": u.Angle(jnp.pi/2, "rad"), "phi": u.Angle(0.0, "rad")}, chart=cxc.sph2)
>>> q = cx.Point({"theta": u.Angle(jnp.pi/2, "rad"), "phi": u.Angle(jnp.pi/2, "rad")}, chart=cxc.sph2)
>>> round(float(cx.chord_distance(p, q)), 6)
1.414214

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) against pi/2 — the point of having two verbs), flat space refusing with the error pointing at geodesic_distance, and the cross-frame refusal.

241 passed across tests/unit/vectors; nox -s precommit clean.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 20, 2026 04:25
@github-actions github-actions Bot added ✅ Add / update / pass tests Add, update, or pass tests. ✨ Introduce new features Introduce new features. labels Aug 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, Point dispatch for chord_distance that (1) rejects cross-frame and cross-manifold pairs and (2) converts b into a’s chart before delegating to manifold-level chord_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.

Comment thread tests/unit/vectors/test_chord_distance_point.py
@nstarman nstarman added this to the v0.24.0 milestone Aug 20, 2026
`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.
@nstarman
nstarman force-pushed the claude/chord-distance-point branch from aeeb292 to c72c496 Compare August 20, 2026 12:52
Mirrors the geodesic_distance coverage: an element-wise batch case and
a different-manifolds refusal, per review feedback on GalacticDynamics#774.
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.71%. Comparing base (015e547) to head (7ba33bb).
⚠️ Report is 2 commits behind head on main.

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.
📢 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.

@nstarman
nstarman requested a lite review from Copilot August 20, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/coordinax/vectors/_src/register_chord_distance.py
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.
@nstarman
nstarman merged commit 48c12bc into GalacticDynamics:main Aug 20, 2026
15 checks passed
@nstarman
nstarman deleted the claude/chord-distance-point branch August 20, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✅ Add / update / pass tests Add, update, or pass tests. ✨ Introduce new features Introduce new features.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants