Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ A non-exhaustive table of exported objects are:
| `coordinax.angles` | `AbstractAngle`, `Angle`, `wrap_to` |
| `coordinax.distances` | `AbstractDistance`, `Distance` |
| `coordinax.charts` | `CartesianProductChart`, </br> `cartesian_chart`, `guess_chart`, `cdict`, `pt_map`, `jac_pt_map`, </br> `cart0d`, </br> `cart1d`, `radial1d`, `time1d`, </br> `cart2d`, `polar2d`, </br> `cart3d`, `cyl3d`, `sph3d`, `lonlat_sph3d`, `loncoslat_sph3d`, `math_sph3d`, </br> `cartnd`, </br> `spacetimect` |
| `coordinax.representations` | `cconvert`, `change_basis`, </br> `Representation`, `point`, `coord_disp`, `coord_vel`, `coord_acc`, `phys_disp`, `phys_vel`, `phys_acc`, </br> `PointGeometry`, `point_geom`, `TangentGeometry`, `tangent_geom`, </br> `NoBasis`, `no_basis`, `CoordinateBasis`, `coord_basis`, `PhysicalBasis`, `phys_basis`, </br> `Location`, `loc`, `Displacement`, `dpl`, `Velocity`, `vel`, `Acceleration`, `acc`, </br> `guess_geometry_kind`, `guess_semantic_kind`, `guess_rep` |
| `coordinax.representations` | `cconvert`, `change_basis`, `tangent_map`, </br> `Representation`, `point`, `coord_disp`, `coord_vel`, `coord_acc`, `phys_disp`, `phys_vel`, `phys_acc`, </br> `PointGeometry`, `point_geom`, `TangentGeometry`, `tangent_geom`, </br> `NoBasis`, `no_basis`, `CoordinateBasis`, `coord_basis`, `PhysicalBasis`, `phys_basis`, </br> `Location`, `loc`, `Displacement`, `dpl`, `Velocity`, `vel`, `Acceleration`, `acc`, </br> `guess_geometry_kind`, `guess_semantic_kind`, `guess_rep` |
| `coordinax.vectors` | `Point`, `ToUnitsOptions` |
| `coordinax.manifolds` | `guess_manifold`, `scale_factors`, `angle_between`, </br> `EuclideanManifold`, `EuclideanMetric`, `euclidean3d`, </br> `EmbeddedManifold`, `EmbeddedChart` </br> `twosphere`, `embedded_twosphere`, </br> `CustomManifold`,`CustomAtlas`, |
| `coordinax.transforms` | `act`, `simplify`, `compose`, `materialize_transform`, </br> `AbstractTransform`, `Identity`, `Composed`, `Translate`, `Rotate`, `Reflect`, `Scale`, `Shear`, `identity`, </br> `AbstractTransformGroup`, `IdentityGroup`, `DiffeomorphismGroup`, `AffineGroup`, `EuclideanGroup`, `OrthogonalGroup`, `SpecialOrthogonalGroup`, `PoincareGroup`, `LorentzGroup`, `ProperOrthochronousLorentzGroup` |
Expand Down Expand Up @@ -1728,6 +1728,44 @@ A representation is therefore **not** the same thing as a chart: the chart deter

- Inherits all failure semantics from `guess_geometry_kind`.

!!! info `tangent_map`

Transform a tangent vector from one chart to another.

**Signature:**

```text
tangent_map(v, from_chart, from_geom, from_rep, to_chart, to_geom, to_rep, /, *, at) -> CDict
```

A 4-argument shorthand form is also supported:

```text
tangent_map(v, from_chart, from_rep, to_chart, /, *, at) -> CDict
```

**Arguments:**

- `v`: `CDict` — tangent vector components in `from_chart` with basis `from_rep.basis`.
- `from_chart`: source chart.
- `from_geom`: source geometry (e.g. `TangentGeometry`).
- `from_rep`: source `Representation` (must have `TangentGeometry`).
- `to_chart`: target chart.
- `to_geom`: target geometry.
- `to_rep`: target `Representation`.
- `at`: `CDict` — base point in `from_chart` coordinates at which the tangent space is attached. Required for non-Cartesian charts (since the Jacobian depends on the base point).

**Semantics by basis:**

- **`CoordinateBasis`**: delegates to `jac_pt_map(at, from_chart, to_chart)` to obtain the Jacobian $J^j{}_i = \partial\tilde{q}^j/\partial q^i$, then applies $\tilde{v}^j = J^j{}_i v^i$.
- **`PhysicalBasis`**: fetch the orthonormal frame matrices $B_{\rm from}$ (columns = physical basis vectors in Cartesian) and $B_{\rm to}$ via `frame_cart`, compute $R = B_{\rm to}^T B_{\rm from}$, apply $\hat{v}' = R \hat{v}$.

**Same-chart optimisation:** when `from_chart is to_chart`, returns `v` unchanged.

**`cconvert` integration:** `cconvert` dispatches to `tangent_map` when the source representation has `TangentGeometry`, passing `at` through the `at` keyword argument.

**Same-chart basis conversion:** `change_basis(v, chart, from_basis, to_basis, /, *, at)` changes tangent component conventions without changing charts. In v1 it is defined only for Cartesian charts and `CoordinateBasis` $
Comment thread
nstarman marked this conversation as resolved.

</br>

### Geometric Kind
Expand Down
17 changes: 17 additions & 0 deletions packages/coordinax.api/src/coordinax/api/representations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ def change_basis(*args: Any, **kwargs: Any) -> Any:
raise NotImplementedError # pragma: no cover


@plum.dispatch.abstract
def tangent_map(*args: Any, **kwargs: Any) -> Any:
"""Compute the tangent map (Jacobian) of a chart transition.

This is an abstract API definition. See the main coordinax package for
concrete implementations.

Examples
--------
>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr

"""
raise NotImplementedError # pragma: no cover


@plum.dispatch.abstract
def cconvert(*args: Any, **kwargs: Any) -> Any:
"""Transform the current vector to the target chart.
Expand Down
2 changes: 2 additions & 0 deletions src/coordinax/main/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"phys_disp",
"phys_vel",
"phys_acc",
"tangent_map",
"change_basis",
# vectors
"Point",
Expand Down Expand Up @@ -151,6 +152,7 @@
point_geom,
subtract,
tangent_geom,
tangent_map,
vel,
)
from coordinax.transforms import (
Expand Down
2 changes: 2 additions & 0 deletions src/coordinax/representations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"guess_rep",
"guess_semantic_kind",
"subtract",
"tangent_map",
"change_basis",
# Representations
"Representation",
Expand Down Expand Up @@ -171,6 +172,7 @@
point,
point_geom,
tangent_geom,
tangent_map,
vel,
)
from coordinax.api.representations import (
Expand Down
1 change: 1 addition & 0 deletions src/coordinax/representations/_src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .register_cx import *
from .rep import *
from .semantics import *
from .tangent_map import *
4 changes: 3 additions & 1 deletion src/coordinax/representations/_src/basis_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
__all__ = ("change_basis",)

from jaxtyping import ArrayLike
from typing import Any
from typing import Any, TypeVar

import jax
import jax.scipy.linalg
Expand All @@ -28,6 +28,8 @@
from coordinax.internal import QuantityMatrix, UnitsMatrix
from coordinax.internal.custom_types import CDict, OptUSys

T = TypeVar("T", bound=u.Quantity)

_RAD = u.unit("rad")


Expand Down
36 changes: 9 additions & 27 deletions src/coordinax/representations/_src/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,19 +330,14 @@ def cconvert(
at: CDict | None = None,
usys: OptUSys = None,
) -> Any:
r"""Convert tangent data between basis conventions in the same chart.

Tangent conversions are basis changes when source and target charts are
identical. In this case, `cconvert` redispatches to `change_basis`.
r"""Convert tangent data between charts via Jacobian pushforward.

Examples
--------
Convert tangent data between coordinate and physical basis in the same
chart:

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr

>>> v = {"r": jnp.array(5.0), "theta": jnp.array(1.0), "phi": jnp.array(2.0)}
>>> at = {"r": jnp.array(3.0), "theta": jnp.array(0.5), "phi": jnp.array(0.0)}
>>> cxr.cconvert(v, cxc.sph3d, cxr.tangent_geom, cxr.coord_disp,
Expand All @@ -351,28 +346,15 @@ def cconvert(
'theta': Array(3., dtype=float64, ...),
'phi': Array(..., dtype=float64, ...)}

Tangent conversion across different charts is not implemented by this
dispatch:

>>> cxr.cconvert(v, cxc.sph3d, cxr.tangent_geom, cxr.coord_disp,
... cxc.cart3d, cxr.tangent_geom, cxr.coord_disp, at=at)
Traceback (most recent call last):
...
NotImplementedError: Tangent cconvert between different charts is not implemented;
use the same chart for basis changes.
>>> v = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> at = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> cxr.cconvert(v, cxc.cart2d, cxr.coord_disp, cxc.polar2d, cxr.coord_disp, at=at)
{'r': Array(1., ...), 'theta': Array(0., ...)}

"""
del from_geom, to_geom # represented by dispatch signature

if from_chart != to_chart:
msg = (
"Tangent cconvert between different charts is not implemented; "
"use the same chart for basis changes."
)
raise NotImplementedError(msg)

return cxrapi.change_basis(
x, from_chart, from_rep.basis, to_rep.basis, at=at, usys=usys
del from_geom, to_geom
return cxrapi.tangent_map(
x, from_chart, from_rep, to_chart, to_rep, at=at, usys=usys
)


Expand Down
Loading