📝 docs(spec): an angle carries no branch cut; the context does - #740
Merged
nstarman merged 1 commit intoAug 19, 2026
Merged
Conversation
The maths section said "to store an angle as a real number one must choose a
branch cut" and offered "the two standard choices". Both halves are wrong, and
the software section three thousand lines down already said so -- `Angle`
"do[es] not encode branch-cut convention in the type itself". The file
contradicted itself.
No cut is enforced. `Angle(7.0, "rad")` and `Angle(-3.0, "rad")` are both
well-formed and neither is reduced on construction, so a stored angle is an
unreduced *representative* of a class in R/2piZ rather than a value confined to
an interval. And the cut is not one of two: `wrap_to` honours whatever interval
of width 2pi the caller names, so the same direction has as many representatives
as there are cuts --
wrap_to(7 rad, [0, 2pi)) -> Angle(0.71681469, 'rad')
wrap_to(7 rad, [10, 10 + 2pi)) -> Angle(13.28318531, 'rad')
-- neither more correct than the other. The cut belongs to the context that
imposes it: a chart through its component domain, or a caller through an
explicit wrap. An angle that has met neither carries none.
Rewritten around "representative" instead of "stored value", with the cut
attributed to the context, and pinned by examples rather than asserted -- the
arbitrary-cut one especially, since it is what makes "contextual" concrete. The
`wrap_to` entry no longer implies its two examples are the menu.
343 passed; `prek run --all-files` clean; docs build succeeds.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR resolves an internal contradiction in docs/spec.md by clarifying that an Angle stores an unreduced real representative of a class in ( \mathbb{R}/2\pi\mathbb{Z} ), and that any branch-cut choice is imposed by context (e.g., chart domain or explicit wrapping) rather than being encoded in the Angle type.
Changes:
- Rewrites the math-spec definition of angular quantities around “representatives” (no canonicalization on construction) and moves branch-cut responsibility to context.
- Expands the software-spec “Angle” section with worked
pyconexamples showing unreduced stored values. - Expands the
wrap_toentry with examples demonstrating arbitrary caller-chosen cuts (any interval of width (2\pi)).
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #740 +/- ##
=======================================
Coverage 96.59% 96.59%
=======================================
Files 266 266
Lines 8934 8934
=======================================
Hits 8630 8630
Misses 304 304 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
docs/spec.mdcontradicted itself about what anAngleis.Maths section (line 40): "To store an angle as a real number one must choose a branch cut … The two standard choices are$[0, 2\pi)$ and $(-\pi, \pi]$ ."
Software section (line 1028): "Angles represent directions on$S^1$ and do not encode branch-cut convention in the type itself."
The second is right. Both halves of the first are wrong.
No cut is enforced
The stored value is an unreduced representative of a class in$\mathbb{R}/2\pi\mathbb{Z}$ , not a value confined to an interval:
Nothing canonicalises on construction.
And the cut is not one of two
wrap_tohonours whatever interval of widthNeither is more correct. The cut belongs to the context that imposes it — a chart through its component domain, or a caller through an explicit wrap — and an angle that has met neither carries none.
What changed
The maths section is rewritten around representative rather than stored value, attributes the cut to the context, and drops the implication that only two exist. The
Angleandwrap_toentries gain worked examples, so this is pinned rather than asserted — the arbitrary-cut one especially, since that is what makes "contextual" concrete instead of a claim about taste.$S^1 = \mathbb{R}/2\pi\mathbb{Z}$, compactness, and "the cut is an artifact of representation" were already right and are kept.Why it matters beyond the prose
This is the definition the rest of the library leans on. It is the reason
POLAR = Interval("rad", 0, pi)andAZIMUTH = Interval("rad", -pi, pi)are domains rather than distinct types — topology is a property of the coordinate's domain, dimension a property of its container. Getting the definition wrong invites conflating the two, which is live right now inpt_project, where the container of a projected angle tracks the ambient chart (AngleviaSpherical3D, plainQuantityviaCart3D) with nothing declaring it. That is a separate change; this one fixes the definition it would be argued from.Verification
docs/spec.md: 343 passed — including the four new examples, which CI now gates thanks to #733.prek run --all-filesclean;uv run --frozen nox -s docssucceeds with warnings as errors.🤖 Generated with Claude Code