Skip to content

Commit 45d6e11

Browse files
committed
WIP
Signed-off-by: nstarman <nstarman@users.noreply.github.com>
1 parent 94f5f44 commit 45d6e11

119 files changed

Lines changed: 4861 additions & 2476 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ authoritative specification file:
5151
When working inside a workspace package:
5252

5353
- You MUST read and follow that package’s own `docs/spec.md`.
54+
- Before editing any code, read the relevant `docs/spec.md` for the package you
55+
are changing.
56+
- If changing behavior, update docstrings, docs, and tests in the same PR.
5457
- If behavior in a package conflicts with its local spec, the code/tests are
5558
wrong and must be updated to match the spec.
5659
- Cross-package changes (e.g. coordinax → coordinax-hypothesis) MUST keep all
@@ -149,6 +152,11 @@ Instructions:
149152
- cross-reference them in docstrings and comments where appropriate,
150153
- update them whenever public semantics change.
151154

155+
- Any new transform or role must include:
156+
- spec-compliance checklist items (in PR description or doc),
157+
- concise doctest-like examples where appropriate,
158+
- property-based tests (prefer `coordinax-hypothesis`).
159+
152160
- Never "patch around" a failing spec-driven test. Fix the implementation or
153161
revise the spec explicitly.
154162

.pre-commit-config.yaml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,6 @@ repos:
7474
types_or: [yaml, markdown, html, css, scss, javascript, json]
7575
args: [--prose-wrap=always]
7676

77-
- repo: https://github.com/pre-commit/mirrors-mypy
78-
rev: "v1.19.1"
79-
hooks:
80-
- id: mypy
81-
files: src
82-
additional_dependencies:
83-
- optional_dependencies
84-
- plum-dispatch
85-
- quaxed
86-
- quax-blocks
87-
8877
- repo: https://github.com/codespell-project/codespell
8978
rev: "v2.4.1"
9079
hooks:

README.md

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ pip install coordinax
4444
dimensions). A rep does not store numerical values.
4545
- Vector: data + rep + role. Data are the coordinate values or physical
4646
components.
47-
- Role: semantic interpretation of vector data (Pos, Vel, PhysAcc, etc.). A role is
48-
not a rep.
47+
- Role: semantic interpretation of vector data (Pos, Vel, PhysAcc, etc.). A role
48+
is not a rep.
4949
- Metric: a bilinear form g on the tangent space defining inner products and
5050
norms (Euclidean, sphere intrinsic, Minkowski).
5151
- Physical components: components of a geometric vector expressed in an
@@ -84,7 +84,7 @@ We can also transform physical vector components between reps:
8484

8585
```
8686
v = {"x": u.Q(4.0, "km/s"), "y": u.Q(5.0, "km/s"), "z": u.Q(6.0, "km/s")}
87-
v_sph = cxt.tangent_transform(cxc.sph3d, cxc.cart3d, v, at=q)
87+
v_sph = cxt.physical_tangent_transform(cxc.sph3d, cxc.cart3d, v, at=q)
8888
```
8989

9090
## Metrics
@@ -148,12 +148,12 @@ bool(
148148

149149
Different vector roles transform via different mechanisms:
150150

151-
| Role | Transformation | Requires Base Point? |
152-
| -------------- | ----------------------------------- | -------------------- |
153-
| `Point` | Position transform (coordinate map) | No |
154-
| `PhysDisp` | Tangent transform (physical vector) | Sometimes[^1] |
155-
| `PhysVel` | Tangent transform (physical vector) | Sometimes[^1] |
156-
| `PhysAcc` | Tangent transform (physical vector) | Sometimes[^1] |
151+
| Role | Transformation | Requires Base Point? |
152+
| ---------- | ----------------------------------- | -------------------- |
153+
| `Point` | Position transform (coordinate map) | No |
154+
| `PhysDisp` | Tangent transform (physical vector) | Sometimes[^1] |
155+
| `PhysVel` | Tangent transform (physical vector) | Sometimes[^1] |
156+
| `PhysAcc` | Tangent transform (physical vector) | Sometimes[^1] |
157157

158158
[^1]:
159159
Required when converting between representations (e.g., Cartesian ↔
@@ -162,9 +162,9 @@ Different vector roles transform via different mechanisms:
162162

163163
## PointedVector: Ergonomic Tangent Vector Conversions
164164

165-
`PointedVector` provides a container for vectors anchored at a common base point,
166-
automatically managing the base point dependency required for tangent vector
167-
transformations:
165+
`PointedVector` provides a container for vectors anchored at a common base
166+
point, automatically managing the base point dependency required for tangent
167+
vector transformations:
168168

169169
```
170170
import coordinax as cx
@@ -236,25 +236,24 @@ d_sum = d1.add(d2) # role is Displacement
236236
disp_from_origin = cx.as_pos(new_pos)
237237
```
238238

239-
| Operation | Result | Allowed? |
240-
| ----------------------------- | -------------- | -------- |
241-
| `PhysDisp + PhysDisp` | `PhysDisp` ||
242-
| `Point + PhysDisp` | `Point` ||
243-
| `PhysDisp + Point` | ||
244-
| `Point + Point` | ||
239+
| Operation | Result | Allowed? |
240+
| --------------------- | ---------- | -------- |
241+
| `PhysDisp + PhysDisp` | `PhysDisp` ||
242+
| `Point + PhysDisp` | `Point` ||
243+
| `PhysDisp + Point` |||
244+
| `Point + Point` |||
245245

246246
> **⚠️ Critical: Physical Components with Uniform Units**
247247
>
248-
> `PhysDisp` stores **physical vector components in an orthonormal frame**,
249-
> not coordinate increments. All components must have uniform dimension
250-
> `[length]`.
248+
> `PhysDisp` stores **physical vector components in an orthonormal frame**, not
249+
> coordinate increments. All components must have uniform dimension `[length]`.
251250
>
252251
> For example, in cylindrical coordinates:
253252
>
254253
> -**Correct**: `PhysDisp(rho=1m, phi=2m, z=3m)` — physical components,
255254
> where `phi=2m` means "2 meters in the tangential direction"
256-
> -**Wrong**: `PhysDisp(rho=1m, phi=0.5rad, z=3m)` — coordinate
257-
> increments with mixed units
255+
> -**Wrong**: `PhysDisp(rho=1m, phi=0.5rad, z=3m)` — coordinate increments
256+
> with mixed units
258257
>
259258
> This applies to all tangent vectors (`PhysDisp`, `PhysVel`, `PhysAcc`), which
260259
> transform via orthonormal frame transformations, not coordinate chart

docs/api/embeddings.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ The `EmbeddedManifold` class represents a manifold embedded in an ambient space:
4545

4646
```python
4747
embed = cxc.EmbeddedManifold(
48-
intrinsic_chart=intrinsic, # Chart on the manifold
49-
ambient_chart=ambient, # Chart in the ambient space
50-
params={"R": radius}, # Parameters (e.g., radius)
48+
intrinsic_chart=cxc.twosphere, # Chart on the manifold
49+
ambient_chart=cxc.cart3d, # Chart in the ambient space
50+
params={"R": u.Q(1.0, "km")}, # Parameters (e.g., radius)
5151
)
5252
```
5353

docs/api/metrics.md

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ metric = cxm.metric_of(cxc.cart3d)
2525
# Compute the metric matrix at a point
2626
p = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
2727
g = metric.metric_matrix(cxc.cart3d, p)
28+
29+
# Compute the norm of a vector
30+
v = {"x": u.Q(3, "m"), "y": u.Q(4, "m"), "z": u.Q(0, "m")}
31+
magnitude = cxm.norm(metric, cxc.cart3d, v) # Returns 5 m
2832
```
2933

3034
## Built-in Metrics
@@ -48,21 +52,31 @@ metric = cxm.metric_of(cxc.cart3d)
4852
sphere_metric = cxm.metric_of(cxc.twosphere)
4953
```
5054

51-
### Index Raising and Lowering
55+
### Computing Norms
56+
57+
The `norm` function computes the magnitude of a vector using the metric tensor:
5258

5359
```python
54-
# Raise an index (covariant to contravariant)
55-
v_up = cxm.raise_index(metric, chart, v_down, at=p)
60+
import coordinax.charts as cxc
61+
import coordinax.metrics as cxm
62+
import unxt as u
5663

57-
# Lower an index (contravariant to covariant)
58-
v_down = cxm.lower_index(metric, chart, v_up, at=p)
64+
# Euclidean norm (position-independent)
65+
metric = cxm.metric_of(cxc.cart3d)
66+
v = {"x": u.Q(3, "m"), "y": u.Q(4, "m"), "z": u.Q(0, "m")}
67+
magnitude = cxm.norm(metric, cxc.cart3d, v) # 5 m
5968
```
6069

61-
### Computing Norms
70+
For curved metrics like the sphere, the norm depends on position:
6271

6372
```python
64-
# Compute the norm of a vector using the metric
65-
norm = cxm.norm(metric, chart, v, at=p)
73+
import jax.numpy as jnp
74+
75+
# On a sphere, the metric varies with latitude
76+
sphere_metric = cxm.metric_of(cxc.twosphere)
77+
p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0, "rad")}
78+
v = {"theta": u.Q(1, "rad/s"), "phi": u.Q(1, "rad/s")}
79+
magnitude = cxm.norm(sphere_metric, cxc.twosphere, v, at=p)
6680
```
6781

6882
```{eval-rst}

docs/api/objs.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import unxt as u
2121
q = cx.Vector(
2222
data={"x": u.Q(1.0, "kpc"), "y": u.Q(2.0, "kpc"), "z": u.Q(3.0, "kpc")},
2323
chart=cx.charts.cart3d,
24-
role=cx.roles.phys_disp,
24+
role=cx.roles.point,
2525
)
2626

2727
# Create a velocity vector
@@ -41,7 +41,7 @@ space = cx.PointedVector(base=q, speed=v)
4141
# Attach to a frame
4242
import coordinax.frames as cxf
4343

44-
coord = cxf.Coordinate({"length": q, "speed": v}, frame=cxf.ICRS())
44+
coord = cx.Coordinate({"base": q, "speed": v}, frame=cxf.ICRS())
4545
```
4646

4747
## Vector
@@ -53,7 +53,7 @@ The `Vector` class is the primary object for representing geometric vectors:
5353
q = cx.Vector(
5454
data={"x": u.Q(1.0, "kpc"), "y": u.Q(2.0, "kpc"), "z": u.Q(3.0, "kpc")},
5555
chart=cx.charts.cart3d,
56-
role=cx.roles.phys_disp,
56+
role=cx.roles.point,
5757
)
5858

5959
# From array (auto-detect chart and role)
@@ -62,6 +62,8 @@ q = cx.Vector.from_([1, 2, 3], "kpc")
6262
# From array with explicit chart and role
6363
v = cx.Vector.from_([10, 20, 30], "km/s", cx.charts.cart3d, cx.roles.phys_vel)
6464

65+
a = cx.Vector.from_([0.1, 0.2, 0.3], "m/s^2", cx.charts.cart3d, cx.roles.phys_acc)
66+
6567
# Access components
6668
print(q["x"]) # Quantity
6769
print(q.data) # Full data dict
@@ -76,15 +78,15 @@ print(q.role) # Role instance
7678

7779
## PointedVector
7880

79-
The `PointedVector` class groups related vectors (position, velocity, acceleration)
80-
at a common point:
81+
The `PointedVector` class groups related vectors (position, velocity,
82+
acceleration) at a common point:
8183

8284
```python
8385
space = cx.PointedVector(base=q, speed=v, acceleration=a)
8486

8587
# Access vectors
8688
space.base # position
87-
space.speed # velocity
89+
space["speed"] # velocity
8890
```
8991

9092
## Coordinate
@@ -94,7 +96,7 @@ The `Coordinate` class attaches vectors to a reference frame:
9496
```python
9597
import coordinax.frames as cxf
9698

97-
coord = cxf.Coordinate({"length": q, "speed": v}, frame=cxf.ICRS())
99+
coord = cx.Coordinate({"base": q, "speed": v}, frame=cxf.ICRS())
98100

99101
# Transform to another frame
100102
coord_gc = coord.to_frame(cxf.Galactocentric())
@@ -121,7 +123,7 @@ Convert a Point role to a PhysDisp role (interpret location as displacement from
121123
origin):
122124

123125
```python
124-
pos = cx.as_pos(point_vector)
126+
pos = cx.as_pos(q) # q is a Point vector defined above
125127
```
126128

127129
```{eval-rst}

docs/api/roles.md

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ meaning of vectors.
88
A **role** defines what a vector represents mathematically and physically:
99

1010
- **Point**: A location in space (affine, not a vector space element)
11-
- **Pos** (Position): A displacement vector from an origin
12-
- **Vel** (Velocity): A tangent vector representing rate of change of position
13-
- **PhysAcc** (Acceleration): A tangent vector representing rate of change of
14-
velocity
11+
- **PhysDisp**: A physical displacement (tangent) vector in an orthonormal frame
12+
- **PhysVel**: A physical velocity (tangent) vector in an orthonormal frame
13+
- **PhysAcc**: A physical acceleration (tangent) vector in an orthonormal frame
14+
- **CoordDisp / CoordVel / CoordAcc**: Coordinate-basis tangent components in a
15+
chart basis
1516

1617
Roles determine transformation laws: positions transform as points, while
17-
velocities transform using the Jacobian of the coordinate transformation.
18+
tangent vectors transform using the Jacobian (and, for physical roles, the local
19+
orthonormal frame), evaluated at a base point `at=`.
1820

1921
## Quick Start
2022

@@ -24,9 +26,10 @@ import coordinax.roles as cxr
2426

2527
# Use predefined role instances
2628
point = cxr.point # Point role
27-
pos = cxr.phys_disp # Position role
29+
pos = cxr.phys_disp # Physical displacement role
2830
vel = cxr.phys_vel # Velocity role
2931
acc = cxr.phys_acc # Acceleration role
32+
coord_vel = cxr.coord_vel # Coordinate-basis velocity role
3033

3134
# Or instantiate classes directly
3235
point_role = cxr.Point()
@@ -38,21 +41,26 @@ pos_role = cxr.PhysDisp()
3841
```
3942
AbstractRole
4043
├── Point # Affine location (transforms via point_transform)
41-
└── AbstractPhysicalRole
42-
├── PhysDisp # Position/displacement (transforms via point_transform)
43-
├── PhysVel # Velocity (transforms via physical_tangent_transform)
44-
└── PhysAcc # Acceleration (transforms via physical_tangent_transform)
44+
├── AbstractPhysRole
45+
│ ├── PhysDisp # Physical displacement (physical_tangent_transform; needs at=)
46+
│ ├── PhysVel # Physical velocity (physical_tangent_transform; needs at=)
47+
│ └── PhysAcc # Physical acceleration (physical_tangent_transform; needs at=)
48+
└── AbstractCoordRole
49+
├── CoordDisp # Coordinate-basis displacement (coord_transform; needs at=)
50+
├── CoordVel # Coordinate-basis velocity (coord_transform; needs at=)
51+
└── CoordAcc # Coordinate-basis acceleration (coord_transform; needs at=)
4552
```
4653

4754
## Role Semantics
4855

49-
### Point vs Pos
56+
### Point vs PhysDisp
5057

5158
- **Point**: An absolute location; cannot be added to other points
52-
- **Pos**: A displacement from an origin; forms a vector space
59+
- **PhysDisp**: A physical displacement vector in a tangent space; can translate
60+
a point
5361

54-
Use `as_pos(point)` to convert a Point to a PhysDisp (interpreting the point as a
55-
displacement from the origin).
62+
Use `as_pos(point)` to convert a Point to a PhysDisp (interpreting the point as
63+
a displacement from the origin).
5664

5765
### Physical Roles (Vel, PhysAcc)
5866

@@ -63,22 +71,36 @@ Jacobian of the coordinate transformation, not by simple coordinate conversion.
6371
import coordinax as cx
6472
import unxt as u
6573

66-
q = cx.Vector.from_(
74+
p = cx.Vector.from_(
6775
{"x": u.Q(1, "kpc"), "y": u.Q(2, "kpc"), "z": u.Q(3, "kpc")},
6876
cx.charts.cart3d,
69-
cx.roles.phys_disp,
77+
cx.roles.point,
7078
)
7179
v = cx.Vector.from_(
7280
{"x": u.Q(10, "km/s"), "y": u.Q(20, "km/s"), "z": u.Q(30, "km/s")},
7381
cx.charts.cart3d,
7482
cx.roles.phys_vel,
7583
)
7684

77-
# Position converts directly
78-
q_sph = q.vconvert(cx.charts.sph3d)
79-
8085
# Velocity requires the base point for correct transformation
81-
v_sph = v.vconvert(cx.charts.sph3d, q)
86+
v_sph = v.vconvert(cx.charts.sph3d, p)
87+
```
88+
89+
### Coordinate-Basis Roles
90+
91+
Coordinate-basis tangent roles (`CoordDisp`, `CoordVel`, `CoordAcc`) transform
92+
by the Jacobian pushforward and also require a base point `at=`:
93+
94+
```python
95+
import coordinax as cx
96+
import coordinax.charts as cxc
97+
import coordinax.roles as cxr
98+
import unxt as u
99+
100+
at = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m")}
101+
v = {"x": u.Q(2.0, "m/s"), "y": u.Q(0.0, "m/s")}
102+
103+
v_pol = cx.vconvert(cxr.coord_vel, cxc.polar2d, cxc.cart2d, v, at=at)
82104
```
83105

84106
```{eval-rst}

0 commit comments

Comments
 (0)