coordinax.manifolds

Contents

coordinax.manifolds#

The coordinax.manifolds module provides manifold and atlas objects, plus manifold-level point operations.

Overview#

In coordinax, a manifold is represented as a pair \((M, \mathcal{A})\):

  • \(M\): the geometric manifold

  • \(\mathcal{A}\): an atlas describing compatible charts

Manifold objects are responsible for compatibility checks (which charts belong on the manifold) and for manifold-level wrappers around chart operations.

For a step-by-step walkthrough, see Working With Manifolds.

Quick Start#

import coordinax.charts as cxc
import coordinax.manifolds as cxm
import unxt as u

# Euclidean manifold in 3 dimensions.
M = cxm.EuclideanManifold(3)

# Check chart compatibility.
assert M.has_chart(cxc.cart3d)
assert not M.has_chart(cxc.cart2d)

# Chart-level point transition map.
p = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
p_sph = cxc.pt_map(p, cxc.cart3d, cxc.sph3d)

# Guess manifold from data/chart.
M2 = cxm.guess_manifold(p)
M3 = cxm.guess_manifold(cxc.sph2)

# Metric angle between two tangent vectors.
at = {"x": u.Q(0, "km"), "y": u.Q(0, "km"), "z": u.Q(0, "km")}
uvec = {"x": u.Q(1, "km"), "y": u.Q(0, "km"), "z": u.Q(0, "km")}
vvec = {"x": u.Q(0, "km"), "y": u.Q(1, "km"), "z": u.Q(0, "km")}
ang = cxm.angle_between(cxc.cart3d, uvec, vvec, at=at)

Functional API#

  • guess_manifold: infer a manifold from manifold/chart/data inputs

  • scale_factors: return the metric diagonal in a chart at a base point

  • metric_matrix: the metric at a point, in a chart. Its container is split by chart family, deliberately. Flat charts (cart1d/2d/3d, cartnd, minkowskict) return a bare array: their metric is dimensionless, and they are the pure-JAX fast path, where boxing costs ~27% per call and 2.3x on tree_flatten. Curvilinear charts (polar2d, cyl3d, sph3d, โ€ฆ) return a QuantityMatrix carrying real units such as \(m^2/rad^2\), which an embedded sphere scales as \(R^2\) โ€“ information a bare array cannot hold. The intrinsic sphere charts (sph2, lonlat_sph2) return a dimensionless QuantityMatrix: angles over angles, but boxed like the curvilinear rules whose consumers they share. A generic consumer normalises with getattr(g, "value", g); the convention is pinned by tests/unit/manifolds/test_metric_container_convention.py

  • angle_between: return the metric angle between two tangent-vector CDicts

  • norm: compute the Riemannian norm \(\|v\|_g = \sqrt{g_p(v,v)}\) of a tangent vector in a chart. Requires a positive-definite metric; raises NotImplementedError for an indefinite one (e.g. Minkowski), where the square root would be nan

  • geodesic_distance: length of the shortest path between two points along the manifold. Symmetric and chart-invariant, computed from the manifoldโ€™s geometry: the straight line in flat space, the great circle on a sphere. A manifold with no closed-form geodesic raises NotImplementedError rather than approximating, as does Minkowski, whose indefinite metric admits no distance

  • chord_distance: length of the straight line between two points through the ambient space they are embedded in โ€“ the tunnel rather than the surface path. A different measurement from geodesic_distance, not an approximation to it: on a sphere of radius \(R\) separated by a central angle \(\theta\), the geodesic is \(R\theta\) and the chord is \(2R\sin(\theta/2)\). Needs an embedding, so a manifold that is its own ambient (anything Euclidean) raises NotImplementedError and points at geodesic_distance

  • interval: signed squared interval \(\Delta s^2 = \Delta x^\top G\,\Delta x\) of the coordinate difference, with the metric taken at the first point. Defined for every metric, including indefinite ones, and the causal invariant when Lorentzian. It is not the squared geodesic_distance except where the metric is constant along the path, i.e. on a flat manifold in Cartesian coordinates โ€“ flatness alone is not enough, since a curvilinear chart on flat space has a varying metric. The verbs that read its sign need a timelike direction and live in the coordinax.manifolds.lorentzian sub-namespace below

  • pt_embed: embed intrinsic coordinates into ambient coordinates

  • pt_project: project ambient coordinates back to intrinsic chart coordinates

  • pt_map: manifold-related re-export of point realization map

Available Objects#

Manifolds#

  • AbstractLorentzianMetricField: structural marker for metrics with a Lorentzian signature; gates the lorentzian sub-namespace below

  • AbstractManifold: base manifold interface

  • EuclideanManifold / R3: Euclidean manifold family and 3D convenience

  • HyperSphericalManifold: intrinsic two-sphere manifold

  • CartesianProductManifold: Cartesian product manifold

  • EmbeddedManifold: manifold with explicit embedding into an ambient manifold

  • CustomManifold: manifold backed by a caller-provided atlas

Atlases#

  • AbstractAtlas: base atlas interface

  • EuclideanAtlas: atlas for Euclidean charts of fixed dimension

  • HyperSphericalAtlas: atlas for intrinsic two-sphere charts

  • CartesianProductAtlas: atlas for product manifolds

  • CustomAtlas: explicit atlas with caller-controlled chart membership

Embeddings and Embedded Charts#

  • AbstractEmbeddingMap: base embedding map interface

  • CustomEmbeddingMap: user-defined embedding maps

  • TwoSphereIn3D / embedded_twosphere: standard two-sphere embedding in 3D

  • EmbeddedChart: convenience chart wrapper combining intrinsic chart and embedding

Notes#

  • Manifold methods delegate chart transitions to cxc.pt_map.

  • For intrinsic two-sphere workflows, use HyperSphericalManifold and intrinsic two-sphere charts (sph2, lonlat_sph2, etc.) rather than Euclidean 2D charts.

coordinax.manifolds module.

coordinax.manifolds.guess_manifold(*args, **kwargs)#

Guess the manifold from arguments.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(2)
>>> guess_manifold(M) is M
True
>>> cxm.guess_manifold({"x": 1, "y": 2, "z": 3})
Rn(3)
>>> cxm.guess_manifold(cxc.sph3d)
Rn(3)
>>> cxm.guess_manifold(cxc.sph2)
HyperSphericalManifold(ndim=2)
coordinax.manifolds.guess_manifold(obj: EuclideanAtlas, /) → EuclideanManifold
Parameters:
Return type:

AbstractManifold

Return the manifold of a Euclidean atlas.

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.EuclideanAtlas(3)
>>> cxm.guess_manifold(atlas)
Rn(3)
coordinax.manifolds.guess_manifold(_: type[MinkowskiCT], /) → MinkowskiManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a MinkowskiCT chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.MinkowskiCT)
MinkowskiManifold()
coordinax.manifolds.guess_manifold(obj: HyperSphericalAtlas, /) → HyperSphericalManifold
Parameters:
Return type:

AbstractManifold

Return the manifold of a HyperSphericalAtlas.

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.HyperSphericalAtlas()
>>> cxm.guess_manifold(atlas)
HyperSphericalManifold(ndim=2)
coordinax.manifolds.guess_manifold(obj: AbstractSphericalTwoSphere, /) → HyperSphericalManifold
Parameters:
Return type:

AbstractManifold

Return a HyperSphericalManifold manifold.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.SphericalTwoSphere())
HyperSphericalManifold(ndim=2)
coordinax.manifolds.guess_manifold(_: type[AbstractSphericalTwoSphere], /) → HyperSphericalManifold
Parameters:
Return type:

AbstractManifold

Return a HyperSphericalManifold manifold, from the chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.SphericalTwoSphere)
HyperSphericalManifold(ndim=2)
coordinax.manifolds.guess_manifold(_: type[CircularOneSphere], /) → HyperSphericalManifold
Parameters:
Return type:

AbstractManifold

Return a HyperSphericalManifold manifold, from the chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.CircularOneSphere)
HyperSphericalManifold(ndim=1)
coordinax.manifolds.guess_manifold(obj: AbstractManifold, /) → AbstractManifold
Parameters:
Return type:

AbstractManifold

Return the manifold of a manifold.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> cxm.guess_manifold(M) is M
True
coordinax.manifolds.guess_manifold(_: type[AbstractChart], /) → AbstractManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.PoincarePolar6D)
NoManifold()
coordinax.manifolds.guess_manifold(chart: AbstractChart, /) → AbstractManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.Cart3D)
Rn(3)
coordinax.manifolds.guess_manifold(_: type[Cart0D], /) → EuclideanManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.Cart0D)
Rn(0)
coordinax.manifolds.guess_manifold(_: type[Cart1D | Radial1D | Time1D], /) → EuclideanManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.Cart1D)
Rn(1)
coordinax.manifolds.guess_manifold(_: type[Cart2D | Polar2D], /) → EuclideanManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.Cart2D)
Rn(2)
coordinax.manifolds.guess_manifold(_: type[Cart3D | Cylindrical3D | AbstractSpherical3D | ProlateSpheroidal3D], /) → EuclideanManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.Cart3D)
Rn(3)
coordinax.manifolds.guess_manifold(obj: dict, /) → AbstractManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a mapping.

Redispatches based on the inferred chart.

>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold({"x": 1, "y": 2, "z": 3})
Rn(3)
coordinax.manifolds.guess_manifold(_: type[CartND], /) → EuclideanManifold
Parameters:
Return type:

AbstractManifold

Infer manifold from a chart class.

CartND holds its components in one array, so the dimension is per instance; the class default is RN, as CartND() and cxc.cartnd carry.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.CartND)
Rn(True)
Parameters:
Return type:

AbstractManifold

coordinax.manifolds.pt_embed(p_pos, embedded, /, *, usys=None)#

Embed intrinsic point coordinates into ambient coordinates.

This function maps point coordinates from an intrinsic chart chart on an embedded manifold to the corresponding ambient space coordinates. It is the fundamental operation for working with embedded manifolds such as spheres, cylinders, or other submanifolds of Euclidean space.

Mathematical Definition:

Given an embedding \(\iota: M \to \mathbb{R}^n\) of a manifold \(M\) into ambient space \(\mathbb{R}^n\), and intrinsic coordinates \(q = (q^1, \ldots, q^k)\) on a chart \(U \subset M\), this function computes the ambient coordinates:

\[x = \iota(q) = (x^1(q), \ldots, x^n(q))\]

For example, embedding the 2-sphere \(S^2\) into \(\mathbb{R}^3\) using spherical coordinates \((\theta, \phi)\):

\[\begin{split}x &= R \sin\theta \cos\phi \\ y &= R \sin\theta \sin\phi \\ z &= R \cos\theta\end{split}\]

where \(R\) is the radius parameter stored in the embedding object.

Parameters:
  • embedded (object) โ€“

    The embedded manifold chart, typically an coordinax.manifolds.EmbeddedChart instance. This encapsulates:

    • intrinsic: The intrinsic chart (e.g., SphericalTwoSphere)

    • embedding: An AbstractEmbedding that owns the ambient chart and any parameters (e.g., TwoSphereIn3D with radius)

  • p_pos (dict) โ€“ Dictionary of intrinsic position coordinates. Keys must match embedded.components (e.g., "theta" and "phi" for SphericalTwoSphere). Values must have appropriate dimensions (e.g., angles for angular coordinates).

  • usys (AbstractUnitSystem | None) โ€“ Unit system for the transformation. This is sometimes required for transformations that depend on physical constants (e.g., speed of light or Delta in {class}`~coordinax.charts.ProlateSpheroidal3D`) but p is raw values without units.

Returns:

Dictionary of ambient position coordinates. Keys match embedded.ambient.components (e.g., "x", "y", "z" for Cart3D). Values have dimensions appropriate for the ambient space (e.g., length for Cartesian coordinates).

Return type:

dict

Raises:
  • NotImplementedError โ€“ If no embedding rule is registered for the specific combination of intrinsic chart and embedding type.

  • ValueError โ€“ If required parameters are missing from the embedding or have incorrect dimensions.

Notes

  • This is a point-only transformation. It does not handle velocities or other time derivatives. Use embed_tangent for differential quantities.

  • Embedding parameters (like radius for TwoSphereIn3D) are stored on the embedding object and must have appropriate physical dimensions.

  • The embedding is purely geometric and does not encode physical components or metric information. Physical components require additional metric data.

  • Singularities of the intrinsic chart (e.g., poles on the sphere at \(\theta = 0, \pi\)) are inherited by the embedding but may not be problematic in the ambient space.

See also

pt_project

Inverse operation projecting ambient to intrinsic coordinates

coordinax.manifolds.EmbeddedChart

Container for embedded manifold charts

Examples

Embedding a point on the 2-sphere into 3D Cartesian coordinates:

>>> import quaxed.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u

Create an embedded manifold for a sphere of radius 5 km:

>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(5, "km")))

Embed a point at the equator:

>>> p_intrinsic = {
...     "theta": u.Angle(jnp.pi / 2, "rad"),  # equator
...     "phi": u.Angle(0.0, "rad"),           # along x-axis
... }
>>> p_ambient = cxm.pt_embed(p_intrinsic, chart)
>>> p_ambient
{'r': Q(5, 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

Verify the point lies on the sphere:

>>> p_ambient_cart = cxm.pt_map(p_ambient, chart.ambient, cxc.cart3d)
>>> r = jnp.linalg.norm(jnp.array(list(p_ambient_cart.values())))
>>> jnp.allclose(r, chart.embed_map.radius, atol=u.Q(1e-10, "km"))
Q(True, '')

Embedding a point at the north pole:

>>> p_pole = {
...     "theta": u.Angle(0.0, "rad"),  # north pole
...     "phi": u.Angle(0.0, "rad"),    # phi is arbitrary at poles
... }
>>> p_ambient_pole = cxm.pt_embed(p_pole, chart)
>>> p_ambient_pole
{'r': Q(5, 'km'), 'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_embed(p_intrinsic: dict, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Embed intrinsic point coordinates into ambient coordinates (manifold).

coordinax.manifolds.pt_embed(p_intrinsic: dict, from_intrinsic_chart: AbstractChart, to_ambient_chart: AbstractChart, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Embed intrinsic point coordinates into ambient coordinates (manifold).

coordinax.manifolds.pt_embed(p_intrinsic: dict, from_intrinsic_chart: AbstractChart, to_ambient_chart: AbstractChart, embed_map: AbstractEmbeddingMap, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Embed intrinsic point coordinates into ambient coordinates.

coordinax.manifolds.pt_embed(p_intrinsic: dict, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Embed intrinsic point coordinates into ambient coordinates.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p_intrinsic = {"theta": u.Q(45, "deg"), "phi": u.Q(30, "deg")}
>>> cxm.pt_embed(p_intrinsic, chart)
{'r': Q(2., 'km'), 'theta': Angle(45, 'deg'), 'phi': Angle(30, 'deg')}
coordinax.manifolds.pt_project(*args, usys=None)#

Project ambient space coordinates onto intrinsic chart coordinates.

This function performs the inverse operation of pt_embed, taking coordinates from the ambient (embedding) space and projecting them back onto the intrinsic manifold chart. Itโ€™s essential for converting between extrinsic and intrinsic charts of points on embedded submanifolds.

Mathematical Definition:

For an embedding \(\iota: M \to \mathbb{R}^n\) of a \(k\)-dimensional manifold \(M\) into \(n\)-dimensional Euclidean space, the projection \(\pi: \mathbb{R}^n \to M\) (or more precisely, onto a chart \(U \subset M\)) satisfies:

\[\pi \circ \iota = \mathrm{id}_M\]

meaning that projecting after embedding returns the original point (up to numerical precision and chart domain).

For a 2-sphere \(S^2 \subset \mathbb{R}^3\) with radius \(R\), given Cartesian coordinates \((x, y, z)\):

\[\begin{split}r &= \sqrt{x^2 + y^2 + z^2} \\ \theta &= \arccos\!\left(\frac{z}{r}\right) \in [0, \pi] \\ \phi &= \operatorname{atan2}(y, x) \in (-\pi, \pi]\end{split}\]

The projection normalizes the input by \(r\), so it maps any point in \(\mathbb{R}^3 \setminus \{0\}\) to the sphere.

Key properties:

  • Local inverse: On the manifold, \(\pi(\iota(q)) = q\) exactly

  • Normalization: Points near the manifold are projected onto it (e.g., points near the sphere are normalized to lie exactly on it)

  • Singularities: Projection may have singularities where the manifoldโ€™s chart has coordinate singularities (e.g., poles of a sphere)

  • Not globally defined: Projection is typically only defined for points in a neighborhood of the manifold, not all of \(\mathbb{R}^n\)

Parameters:
  • *args (object) โ€“

    Typically an EmbeddedChart chart and a dictionary of ambient coordinates, but the function supports multiple dispatch patterns. Common signature:

    • embeddedEmbeddedChart

      The embedded manifold chart specifying the chart and ambient space

    • p_ambientCDict

      Ambient coordinates keyed by embedded.ambient.components (e.g., {"x": ..., "y": ..., "z": ...} for Cartesian ambient space)

  • usys (AbstractUnitSystem | None) โ€“ Unit system for the transformation. This is sometimes required for transformations that depend on physical constants (e.g., speed of light or Delta in {class}`~coordinax.charts.ProlateSpheroidal3D`) but p is raw values without units.

Returns:

Intrinsic chart coordinates keyed by the manifold chartโ€™s components. For a sphere, these are typically {"theta": ..., "phi": ...}.

Return type:

dict

Raises:
  • NotImplementedError โ€“ If no projection is defined for the given manifold and ambient space.

  • ValueError โ€“ If the ambient point cannot be projected (e.g., origin for sphere).

Notes

  • Normalization: Implementations often normalize inputs to handle points that are close to but not exactly on the manifold. For a sphere, any non-zero point in \(\mathbb{R}^3\) is normalized to radius \(R\).

  • Coordinate singularities: At chart singularities (e.g., sphere poles where \(\sin\theta = 0\)), convention determines the value of singular coordinates (typically \(\phi = 0\) at poles).

  • Round-trip accuracy: For points on the manifold, pt_project(pt_embed(q, embedded), embedded) should return q up to floating-point precision.

  • Extension to nearby points: Projection extends the manifold chart to a neighborhood in the ambient space, useful for perturbed or approximate data.

  • Orthogonal projection: For Riemannian manifolds with the induced metric, this is often the orthogonal projection onto the manifold (shortest distance from the ambient point to the manifold).

See also

pt_embed

Embed intrinsic chart coordinates into ambient space

Examples

>>> import jax
>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> import wadler_lindig as wl

Project Cartesian coordinates onto a 2-sphere. Create an embedded 2-sphere of radius 1 km:

>>> sphere = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1, "km")))

Project a point in 3D Cartesian space onto the sphere. Start with a point slightly off the sphere:

>>> p_cart = {"x": u.Q(0.5, "km"), "y": u.Q(0.5, "km"), "z": u.Q(0.707, "km")}
>>> p_sphere = cxm.pt_project(p_cart, cxc.cart3d, sphere)
>>> p_sphere
{'theta': Angle(0.78547367, 'rad'), 'phi': Angle(0.78539816, 'rad')}

The projection normalizes the point to lie exactly on the sphere. Verify round-trip accuracy (project after embed returns original point):

>>> q_sphere = {"theta": u.Angle(jnp.pi / 3, "rad"),
...             "phi": u.Angle(jnp.pi / 4, "rad")}
>>> q_cart = cxm.pt_embed(q_sphere, sphere)
>>> q_recovered = cxm.pt_project(q_cart, sphere)
>>> all(jax.tree.map(jnp.isclose, q_sphere, q_recovered))
True

Project from an arbitrary point in space (not on sphere). The projection normalizes the radius:

>>> p_far = {"x": u.Q(2.0,"km"), "y": u.Q(2.0,"km"), "z": u.Q(2.0,"km")}
>>> p_normalized = cxm.pt_project(p_far, cxc.cart3d, sphere)
>>> # Direction is preserved: all coordinates equal โ†’ theta โ‰ˆ 54.7ยฐ, phi = 45ยฐ
>>> p_normalized
{'theta': Angle(0.95531662, 'rad'), 'phi': Angle(0.78539816, 'rad')}

Handle coordinate singularities at the poles. At the north pole (\(\theta = 0\)), \(\phi\) is conventionally set to 0:

>>> p_north = {"x": u.Q(0.0, "km"), "y": u.Q(0.0, "km"),
...            "z": u.Q(1.0, "km")}  # North pole
>>> cxm.pt_project(p_north, cxc.cart3d, sphere)
{'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}

At the south pole (\(\theta = \pi\)), \(\phi\) is also set to 0:

>>> p_south = { "x": u.Q(0, "km"), "y": u.Q(0, "km"), "z": u.Q(-1, "km")}
>>> cxm.pt_project(p_south, cxc.cart3d, sphere)
{'theta': Angle(3.14159265, 'rad'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_project(p_ambient: dict, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Project ambient coordinates onto intrinsic chart coordinates (manifold).

coordinax.manifolds.pt_project(p_ambient: dict, from_ambient_chart: AbstractChart, to_intrinsic_chart: AbstractChart, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Project ambient coordinates onto intrinsic chart coordinates (manifold).

coordinax.manifolds.pt_project(p_ambient: dict, from_ambient_chart: AbstractChart, to_intrinsic_chart: AbstractChart, embed_map: AbstractEmbeddingMap, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Project ambient coordinates onto intrinsic chart coordinates.

coordinax.manifolds.pt_project(p_ambient: dict, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Project ambient coordinates onto intrinsic chart coordinates.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p_amb = {"r": u.Q(2.0, "km"), "theta": u.Q(45, "deg"), "phi": u.Q(30, "deg")}
>>> cxm.pt_project(p_amb, chart)
{'theta': Angle(45, 'deg'), 'phi': Angle(30, 'deg')}
coordinax.manifolds.pt_project(p_ambient: dict, from_chart: AbstractChart, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Project ambient coordinates onto intrinsic chart coordinates.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p_amb = {"r": u.Q(2.0, "km"), "theta": u.Q(45, "deg"), "phi": u.Q(30, "deg")}
>>> cxm.pt_project(p_amb, cxc.sph3d, chart)
{'theta': Angle(45, 'deg'), 'phi': Angle(30, 'deg')}
coordinax.manifolds.pt_project(p_ambient: object, from_ambient_chart: AbstractChart, M: HyperSphericalManifold, /, *, usys: AbstractUnitSystem | None = None) → object
Parameters:
Return type:

dict

Project a point from the 3D chart to the two-sphere intrinsic chart.

This projection map is a special case for projecting from 3D charts to the two-sphere intrinsic chart, which is a common use case. The projection does not depend on the radius of the embedding, so this projection works in general.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> q = {"x": u.Q(1.0, "km"), "y": u.Q(0.0, "km"), "z": u.Q(0.0, "km")}
>>> cxm.pt_project(q, cxc.cart3d, cxm.S2)
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_project(p_ambient: Point, M: HyperSphericalManifold, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

dict

Project a point from an ambient space onto a manifold.

Examples

>>> import coordinax as cx
>>> import coordinax.manifolds as cxm
>>> import unxt as u

Project a spherical 3D point onto the 2-sphere:

>>> q = cx.Point.from_(
...     {"r": u.Q(1, "m"), "theta": u.Q(2, "rad"), "phi": u.Q(3, "rad")},
...     cx.sph3d)
>>> cxm.pt_project(q, cxm.S2)
Point(
  {'theta': Angle(2, 'rad'), 'phi': Angle(3, 'rad')},
  chart=SphericalTwoSphere(M=Sn(2))
)
coordinax.manifolds.pt_map(*args, **kwargs)#

Transform position coordinates from one chart to another.

This function implements the most general point-coordinate map between two compatible chart representations of the same geometric point. It is a point-wise map that preserves the physical location while changing the coordinate description.

For charts in the same atlas on the same manifold, this reduces to the ordinary chart transition map handled by coordinax.charts.pt_map. It is the intrinsic coordinate-change operation: the underlying point on the manifold is unchanged, and only its coordinate representation is changed.

However, this function is not restricted to two charts on the same manifold. It may also represent a realization-style map between charts attached to different manifolds when one is a realization of the other, such as an intrinsic chart on an embedded manifold and a chart on its ambient manifold. In that case, this function may change both the chart and the manifold in which the point is being represented.

Mathematical Definition:

Let \((U, \varphi_{\mathrm{from}})\) and \((V, \varphi_{\mathrm{to}})\) be charts on the same manifold \(M\), with overlapping domains. The transition map is

\[\varphi_{\mathrm{to}} \circ \varphi_{\mathrm{from}}^{-1} : \varphi_{\mathrm{from}}(U \cap V) \to \varphi_{\mathrm{to}}(U \cap V).\]

If a point \(p \in U \cap V\) has coordinates \(q = \varphi_{\mathrm{from}}(p)\), then this function returns \(p' = \varphi_{\mathrm{to}}(p)\) for the same manifold point.

More generally, if \(\varphi_{\mathrm{from}} : U \subset M \to \mathbb{R}^n\) and \(\psi_{\mathrm{to}} : W \subset N \to \mathbb{R}^m\) are chart maps on manifolds \(M\) and \(N\), and there is a point map \(F : M \supset U \to W \subset N\), then coordinax.charts.pt_map represents the coordinate expression

\[\psi_{\mathrm{to}} \circ F \circ \varphi_{\mathrm{from}}^{-1}.\]
  • 3D Spherical โ†’ Cartesian:

\[\begin{split}x &= r \sin\theta \cos\phi \\ y &= r \sin\theta \sin\phi \\ z &= r \cos\theta\end{split}\]
Raises:

NotImplementedError โ€“ If no transformation rule is registered for the specific pair of charts (to_chart, from_chart).

Parameters:
Return type:

dict

Notes

  • This is a position-only transformation.

  • This function may map between charts on the same manifold or across manifolds, provided a compatible point map is defined between them.

  • Transformations preserve physical dimensions. For example, converting from polar to Cartesian preserves that r has length dimension and produces x and y with length dimension.

  • Some transformations may introduce singularities (e.g., polar coordinates at the origin, spherical coordinates at poles).

  • Transformations are composable: transforming \(A \to B \to C\) yields the same result as a direct \(A \to C\) transformation (up to numerical precision).

  • Identity transformations (same from_chart and to_chart) return the input unchanged.

See also

coordinax.charts.pt_map

transform position coordinates between charts on the

same

Examples

>>> import quaxed.numpy as jnp
>>> import coordinax.charts as cxc
>>> import unxt as u

Transform from 2D polar to Cartesian:

>>> p_polar = {"r": u.Q(2.0, "m"), "theta": u.Angle(jnp.pi / 4, "rad")}
>>> cxc.pt_map(p_polar, cxc.polar2d, cxc.cart2d)
{'x': Q(1.41421356, 'm'), 'y': Q(1.41421356, 'm')}

Transform from 3D spherical to Cartesian:

>>> p_sph = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad"),
...          "r": u.Q(5.0, "km")}
>>> cxc.pt_map(p_sph, cxc.sph3d, cxc.cart3d)
{'x': Q(5., 'km'), 'y': Q(0., 'km'), 'z': Q(3.061617e-16, 'km')}

Transform from Cartesian to cylindrical:

>>> p_xyz = {"x": u.Q(3.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(5.0, "m")}
>>> cxc.pt_map(p_xyz, cxc.cart3d, cxc.cyl3d)
{'rho': Q(5., 'm'), 'phi': Angle(0.92729522, 'rad'), 'z': Q(5., 'm')}
coordinax.manifolds.pt_map(p: dict, from_M: CartesianProductManifold, from_chart: AbstractCartesianProductChart, to_M: CartesianProductManifold, to_chart: AbstractCartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

ABC CartesianProductChart -> CartesianProductChart (factorwise).

Transforms between product charts by applying coordinax.charts.pt_map to each factor independently. Requires compatible factor structure (same number of factors, pairwise compatible).

Mathematical definition:

\[\varphi \left(\prod_i S_i,\;\prod_i R_i,\;p\right) = \bigl(\varphi(S_i,\,R_i,\,p_i)\bigr)_i\]

where \(\varphi\) denotes {func}`~coordinax.charts.pt_map` and \(p_i\) are the factor dictionaries split from \(p\).

Examples

>>> import coordinax.charts as cxc
>>> import unxt as u

Transform a Cartesian product chart between spatial representations:

>>> prod_crt = cxc.CartesianProductChart((cxc.time1d, cxc.cart3d), ("t", "q"))
>>> prod_sph = cxc.CartesianProductChart((cxc.time1d, cxc.sph3d), ("t", "q"))
>>> p = {"t.t": u.Q(1.0, "s"), "q.x": u.Q(1.0, "m"), "q.y": u.Q(0.0, "m"),
...      "q.z": u.Q(0.0, "m")}
>>> result = cxc.pt_map(p, prod_crt, prod_sph)
>>> result["t.t"]
Q(1., 's')
>>> result["q.r"]
Q(1., 'm')
coordinax.manifolds.pt_map(p: dict, from_M: AbstractManifold, from_chart: AbstractChart, to_M: CartesianProductManifold, to_chart: AbstractCartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

AbstractChart -> Cartesian -> AbstractCartesianProductChart.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> chart = cxc.CartesianProductChart((cxc.sph2, cxc.cart1d), ("S2", "R1"))
>>> map = cxc.pt_map.invoke(dict[str, u.Q], cxc.Cart3D, cxc.CartesianProductChart)
>>> try: map({}, cxc.cart3d, chart)
... except NotImplementedError as e: print(e)
No general transform between Cart3D and CartesianProductChart.
Define explicit rules for non-product to product conversions.
coordinax.manifolds.pt_map(p: dict, from_M: CartesianProductManifold, from_chart: AbstractCartesianProductChart, to_M: AbstractManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

AbstractCartesianProductChart -> Cartesian -> AbstractChart.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> chart = cxc.CartesianProductChart((cxc.sph2, cxc.cart1d), ("S2", "R1"))
>>> map = cxc.pt_map.invoke(dict[str, u.Q], cxc.CartesianProductChart, cxc.Cart3D)
>>> try: map({}, chart, cxc.cart3d)
... except NotImplementedError as e: print(e)
No general transform between CartesianProductChart and Cart3D.
Define explicit rules for product to non-product conversions.
coordinax.manifolds.pt_map(q: NoneType, /, *fixed_args: Any, **fixed_kw: Any) → Callable[..., Any]
Parameters:
Return type:

dict

Return a partial function for point transformation.

>>> import coordinax.charts as cxc
>>> import unxt as u

Coordinates without units are the default.

>>> q = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d)
>>> map(q)
{'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

Coordinates without units are also accepted, interpreted having units of the unxt.AbstractUnitSystem, which must be passed.

>>> q = {"x": 1.0, "y": 0.0, "z": 0.0}
>>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> map(q)
{'r': Array(1., dtype=float64, ...),
 'theta': Array(1.57079633, dtype=float64, ...),
 'phi': Array(0., dtype=float64, ...)}

unxt.Quantity inputs are also accepted, and are interpreted as being in Cartesian coordinates.

>>> p = u.Q([1.0, 0.0, 0.0], "m")
>>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d)
>>> map(p)
QM([1.        , 1.57079633, 0.        ], '(m, rad, rad)')

Array-Like inputs are interpreted as Cartesian coordinates with units from the required unxt.AbstractUnitSystem.

>>> q = [1.0, 0.0, 0.0]
>>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> map(q)
Array([1.        , 1.57079633, 0.        ], dtype=float64)
coordinax.manifolds.pt_map(from_chart: AbstractChart, to_chart: AbstractChart, /, **fixed_kw: Any) → Callable[..., Any]
Parameters:
Return type:

dict

Return a partial function for point transformation.

>>> import coordinax.charts as cxc
>>> import unxt as u

Coordinates without units are the default.

>>> p = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d)
>>> map(p)
{'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

Coordinates without units are also accepted, interpreted having units of the unxt.AbstractUnitSystem, which must be passed.

>>> p = {"x": 1.0, "y": 0.0, "z": 0.0}
>>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> map(p)
{'r': Array(1., dtype=float64, ...),
 'theta': Array(1.57079633, dtype=float64, ...),
 'phi': Array(0., dtype=float64, ...)}

unxt.Quantity inputs are also accepted, and are interpreted as being in Cartesian coordinates.

>>> p = u.Q([1.0, 0.0, 0.0], "m")
>>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d)
>>> map(p)
QM([1.        , 1.57079633, 0.        ], '(m, rad, rad)')

Array-Like inputs are interpreted as Cartesian coordinates with units from the required unxt.AbstractUnitSystem.

>>> p = [1.0, 0.0, 0.0]
>>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> map(p)
Array([1.        , 1.57079633, 0.        ], dtype=float64)
coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

dict

Point transformation from chart to chart, using their manifolds.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {}
>>> cxc.pt_map(p, cxc.cart0d, cxc.cart0d)
{}
>>> p = {"r": u.Q(5.0, "m")}
>>> cxc.pt_map(p, cxc.radial1d, cxc.cart1d)
{'x': Q(5., 'm')}
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")}
>>> cxc.pt_map(p, cxc.polar2d, cxc.cart2d)
{'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg"), "phi": u.Q(0, "deg")}
>>> cxc.pt_map(p, cxc.sph3d, cxc.cart3d)
{'x': Q(5., 'm'), 'y': Q(0., 'm'), 'z': Q(3.061617e-16, 'm')}
coordinax.manifolds.pt_map(p: dict, from_M: AbstractManifold, from_chart: AbstractChart, to_M: AbstractManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

AbstractChart -> Cartesian -> AbstractChart.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")}
>>> map = cxc.pt_map.invoke(dict[str, u.Q], cxm.Rn, cxc.AbstractChart,
...                         cxm.Rn, cxc.AbstractChart)
>>> map(p, cxm.R2, cxc.polar2d, cxm.R2, cxc.cart2d)
{'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Identity conversion for matching charts.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import quaxed.numpy as jnp
>>> q = {}
>>> q2 = cxc.pt_map(q, cxm.R0, cxc.cart0d, cxm.R0, cxc.cart0d)
>>> q is q2
True
>>> q = {"r": u.Q(3.0, "m")}
>>> q2 = cxc.pt_map(q, cxm.R1, cxc.radial1d, cxm.R1, cxc.radial1d)
>>> q is q2
True
>>> q = {"x": u.Q(1.0, "m"), "y": u.Q(2.0, "m")}
>>> q2 = cxc.pt_map(q, cxm.R2, cxc.cart2d, cxm.R2, cxc.cart2d)
>>> q is q2
True
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Radial1D, to_M: EuclideanManifold, to_chart: Cart1D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Radial1D -> Cart1D.

The r coordinate is converted to the x coordinate of the 1D system.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> q = {"r": u.Q(5.0, "m")}
>>> cxc.pt_map(q, cxm.R1, cxc.radial1d, cxm.R1, cxc.cart1d)
{'x': Q(5., 'm')}
>>> q = {"r": 5.0}  # No units
>>> cxc.pt_map(q, cxm.R1, cxc.radial1d, cxm.R1, cxc.cart1d)
{'x': 5.0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart1D, to_M: EuclideanManifold, to_chart: Radial1D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart1D -> Radial1D.

The x coordinate is converted to the r coordinate of the 1D system.

Assumptions:

  • Cart1D and Radial1D are

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"x": u.Q(5.0, "m")}
>>> cxc.pt_map(p, cxm.R1, cxc.cart1d, cxm.R1, cxc.radial1d)
{'r': Q(5., 'm')}
>>> p = {"x": 5.0}  # No units
>>> cxc.pt_map(p, cxm.R1, cxc.cart1d, cxm.R1, cxc.radial1d)
{'r': 5.0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Polar2D, to_M: EuclideanManifold, to_chart: Cart2D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Polar2D -> Cart2D.

The r and theta coordinates are converted to the x and y coordinates of the 2D Cartesian system.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")}
>>> cxc.pt_map(p, cxm.R2, cxc.polar2d, cxm.R2, cxc.cart2d)
{'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
>>> p = {"r": 5, "theta": 90}  # No units
>>> usys = u.unitsystem("km", "deg")
>>> cxc.pt_map(p, cxm.R2, cxc.polar2d, cxm.R2, cxc.cart2d, usys=usys)
{'x': Array(3.061617e-16, dtype=float64, ...),
 'y': Array(5., dtype=float64, ...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart2D, to_M: EuclideanManifold, to_chart: Polar2D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart2D -> Polar2D.

The x and y coordinates are converted to the r and theta coordinates of the 2D polar system.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"x": u.Q(3, "m"), "y": u.Q(4, "m")}
>>> cxc.pt_map(p, cxm.R2, cxc.cart2d, cxm.R2, cxc.polar2d)
{'r': Q(5., 'm'), 'theta': Angle(0.92729522, 'rad')}
>>> p = {"x": 3, "y": 4}  # No units
>>> cxc.pt_map(p, cxm.R2, cxc.cart2d, cxm.R2, cxc.polar2d)
{'r': Array(5., dtype=float64, ...),
 'theta': Array(0.92729522, dtype=float64, ...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cylindrical3D -> Cart3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"rho": u.Q(1.0, "m"), "phi": u.Q(90, "deg"), "z": u.Q(2.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.cart3d)
{'x': Q(6.123234e-17, 'm'), 'y': Q(1., 'm'), 'z': Q(2., 'm')}
>>> p = {"rho": 1.0, "phi": 90, "z": 2.0}  # No units
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.cart3d, usys=usys)
{'x': Array(6.123234e-17, dtype=float64, ...), 'y': Array(1., dtype=float64, ...),
 'z': 2.0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Spherical3D -> Cart3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import quaxed.numpy as jnp

A point on the +z axis (theta=0):

>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "deg"), "phi": u.Q(0, "deg")}
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cart3d)
{'x': Q(0., 'm'), 'y': Q(0., 'm'), 'z': Q(1., 'm')}

A point on the equator (theta=90 deg, phi=0):

>>> p = {"r": 2.0, "theta": 90, "phi": 0}
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cart3d, usys=usys)
{'x': Array(2., dtype=float64, ...),
 'y': Array(0., dtype=float64, ...),
 'z': Array(1.2246468e-16, dtype=float64, ...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: LonLatSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

LonLatSpherical3D -> Cart3D.

>>> import coordinax.charts as cxc
>>> import unxt as u

A point at the north pole (lat=90 deg):

>>> p = {"lon": u.Q(0, "deg"), "lat": u.Q(90, "deg"), "distance": u.Q(1.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.lonlat_sph3d, cxm.R3, cxc.cart3d)
{'x': Q(6.123234e-17, 'm'), 'y': Q(0., 'm'), 'z': Q(1., 'm')}

A point on the equator at lon=0:

>>> p = {"lon": 0, "lat": 0, "distance": 2}
>>> cxc.pt_map(p, cxm.R3, cxc.lonlat_sph3d, cxm.R3, cxc.cart3d)
{'x': Array(2., dtype=float64, ...),
 'y': Array(0., dtype=float64, ...),
 'z': Array(0., dtype=float64, ...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: LonCosLatSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

LonCosLatSpherical3D -> Cart3D.

Components are (lon_coslat, lat, r), where lon_coslat := lon * cos(lat). Longitude is undefined at the poles (cos(lat) == 0); we set lon = 0 by convention there to avoid NaNs.

>>> import coordinax.charts as cxc
>>> import unxt as u

A point on the equator (lat=0, so lon_coslat = lon):

>>> p = {"lon_coslat": u.Q(0, "deg"), "lat": u.Q(0, "deg"),
...      "distance": u.Q(1.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.loncoslat_sph3d, cxm.R3, cxc.cart3d)
{'x': Q(1., 'm'), 'y': Q(0., 'm'), 'z': Q(0., 'm')}

At the north pole (lat=90), lon_coslat is effectively 0 regardless of lon:

>>> p = {"lon_coslat": u.Q(0, "deg"), "lat": u.Q(90, "deg"),
...      "distance": u.Q(2.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.loncoslat_sph3d, cxm.R3, cxc.cart3d)
{'x': Q(1.2246468e-16, 'm'), 'y': Q(0., 'm'), 'z': Q(2., 'm')}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: MathSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

MathSpherical3D -> Cart3D.

  • theta: azimuth in the x-y plane (longitude-like)

  • phi : polar angle from +z, with phi in [0, pi]

>>> import coordinax.charts as cxc
>>> import unxt as u

A point on the +z axis (phi=0):

>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "deg"), "phi": u.Q(0, "deg")}
>>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.cart3d)
{'x': Q(0., 'm'), 'y': Q(0., 'm'), 'z': Q(1., 'm')}

A point on the +x axis (theta=0, phi=90):

>>> p = {"r": u.Q(2.0, "m"), "theta": u.Q(0, "deg"), "phi": u.Q(90, "deg")}
>>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.cart3d)
{'x': Q(2., 'm'), 'y': Q(0., 'm'), 'z': Q(1.2246468e-16, 'm')}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

ProlateSpheroidal3D -> Cart3D.

We calculate through cylindrical coordinates first:

\(\rho = \sqrt{(\mu-\Delta^2)\left(1-\frac{\lvert\nu\rvert}{\Delta^2}\right)}\) \(z = \sqrt{\mu\,\frac{\lvert\nu\rvert}{\Delta^2}}\;\mathrm{sign}(\nu)\) \(\phi = \phi.\)

Then convert to Cartesian:

\(x=\rho\cos\phi\), \(y=\rho\sin\phi\), \(z=z\).

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import quaxed.numpy as jnp
>>> prolatesph3d = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")}
>>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cart3d)
{'x': Q(0.8660254, 'm'), 'y': Q(0., 'm'), 'z': Q(1.11803399, 'm')}
>>> p = {"mu": 5.0, "nu": 1.0, "phi": 0}  # No units
>>> usys = u.unitsystem("m", "rad")
>>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cart3d, usys=usys)
{'x': Array(0.8660254, dtype=float64),
 'y': Array(0., dtype=float64),
 'z': Array(1.11803399, dtype=float64)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart3D -> Cylindrical3D.

>>> import coordinax as cx
>>> import unxt as u
>>> p = {"x": u.Q(3.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(5.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.cyl3d)
{'rho': Q(5., 'm'), 'phi': Angle(0.92729522, 'rad'), 'z': Q(5., 'm')}
>>> p = {"x": 3.0, "y": 4.0, "z": 5.0}  # No units
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.cyl3d)
{'rho': Array(5., dtype=float64, ...),
 'phi': Array(0.92729522, dtype=float64, ...),
 'z': 5.0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D | Cylindrical3D, to_M: EuclideanManifold, to_chart: AbstractSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart3D -> Spherical3D -> AbstractSpherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(1.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.loncoslat_sph3d)
{'lon_coslat': Angle(0., 'rad'), 'lat': Angle(90., 'deg'), 'distance': Q(1., 'm')}
>>> p = {"rho": 0, "phi": 180, "z": 1}
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.loncoslat_sph3d, usys=usys)
{'lon_coslat': Array(1.10218212e-14, dtype=float64, ...),
 'lat': Array(90., dtype=float64, ...),
 'distance': Array(1., dtype=float64, weak_type=True)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart3D -> Spherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u

A point on the +z axis:

>>> p = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(1.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.sph3d)
{'r': Q(1., 'm'), 'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}

A point on the +x axis:

>>> p = {"x": 2.0, "y": 0.0, "z": 0.0}  # No units
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.sph3d)
{'r': Array(2., dtype=float64, ...),
 'theta': Array(1.57079633, dtype=float64, ...),
 'phi': Array(0., dtype=float64, ...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cylindrical3D -> Spherical3D.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

A point on the z-axis (rho=0):

>>> p = {"rho": u.Q(0.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(1.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.sph3d)
{'r': Q(1., 'm'), 'theta': Angle(0., 'rad'), 'phi': Angle(0, 'rad')}

A point in the xy-plane (z=0):

>>> p = {"rho": 3.0, "phi": 0, "z": 0.0}  # No units
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.sph3d)
{'r': Array(3., dtype=float64, ...), 'theta': Array(1.57079633, dtype=float64, ...),
 'phi': 0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Spherical3D -> Cylindrical3D.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u

A point on the +z axis (theta=0):

>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "rad"), "phi": u.Q(0, "rad")}
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cyl3d)
{'rho': Q(0., 'm'), 'phi': Angle(0, 'rad'), 'z': Q(1., 'm')}

A point on the equator (theta=90 deg):

>>> p = {"r": 2.0, "theta": 90, "phi": 0}  # No units
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cyl3d, usys=usys)
{'rho': Array(2., dtype=float64, ...), 'phi': 0,
 'z': Array(1.2246468e-16, dtype=float64, ...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Spherical3D -> LonLatSpherical3D.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

Spherical theta=0 corresponds to lat=90 (north pole):

>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "rad"), "phi": u.Q(0, "rad")}
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.lonlat_sph3d)
{'lon': Angle(0, 'rad'), 'lat': Angle(90., 'deg'), 'distance': Q(1., 'm')}

Spherical theta=90 deg corresponds to lat=0 (equator):

>>> p = {"r": 1.0, "theta": 0, "phi": 0}  # No units
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.lonlat_sph3d)
{'lon': 0, 'lat': 1.5707963267948966, 'distance': 1.0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Spherical3D -> LonCosLatSpherical3D.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

On the equator (theta=90 deg), lon_coslat equals lon:

>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(90, "deg"), "phi": u.Q(45, "deg")}
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.loncoslat_sph3d)
{'lon_coslat': Angle(45., 'deg'), 'lat': Angle(0, 'deg'), 'distance': Q(1., 'm')}

At the north pole (theta=0), lon_coslat = 0 regardless of phi:

>>> p = {"r": 1.0, "theta": 0, "phi": 45}  # No units
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.loncoslat_sph3d, usys=usys)
{'lon_coslat': Array(2.7554553e-15, dtype=float64, ...),
 'lat': 90.0, 'distance': 1.0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: MathSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Spherical3D -> MathSpherical3D.

Swaps theta and phi: Physics (theta=polar, phi=azimuth) to Math (theta=azimuth, phi=polar).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(30, "deg"), "phi": u.Q(60, "deg")}
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.math_sph3d)
{'r': Q(1., 'm'), 'theta': Angle(60, 'deg'), 'phi': Angle(30, 'deg')}
>>> p = {"r": 1.0, "theta": 30, "phi": 60}  # No units
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.math_sph3d, usys=usys)
{'r': 1.0, 'theta': 60, 'phi': 30}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: MathSpherical3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

MathSpherical3D -> Spherical3D.

Swaps theta and phi: Math (theta=azimuth, phi=polar) to Physics (theta=polar, phi=azimuth).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(60, "deg"), "phi": u.Q(30, "deg")}
>>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.sph3d)
{'r': Q(1., 'm'), 'theta': Angle(30, 'deg'), 'phi': Angle(60, 'deg')}
>>> p = {"r": 1.0, "theta": 60, "phi": 30}  # No units
>>> usys = u.unitsystem("m", "deg")
>>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.sph3d, usys=usys)
{'r': 1.0, 'theta': 30, 'phi': 60}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

ProlateSpheroidal3D -> Cylindrical3D.

Uses the focal length \(\Delta\) stored on from_chart.

Validity constraints (enforced by the representation) are:

  • \(\Delta > 0\),

  • \(\mu \ge \Delta^2\),

  • \(\lvert\nu\rvert \le \Delta^2\).

The conversion proceeds via

\(\rho = \sqrt{(\mu-\Delta^2)\left(1-\frac{\lvert\nu\rvert}{\Delta^2}\right)}\), \(z = \sqrt{\mu\,\frac{\lvert\nu\rvert}{\Delta^2}}\,\mathrm{sign}(\nu)\), \(\phi = \phi\).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> prolatesph3d = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")}
>>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cyl3d)
{'rho': Q(0.8660254, 'm'), 'phi': Angle(0, 'rad'), 'z': Q(1.11803399, 'm')}
>>> p = {"mu": 5.0, "nu": 1.0, "phi": 0}  # No units
>>> usys = u.unitsystem("m", "rad")
>>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cyl3d, usys=usys)
{'rho': Array(0.8660254, dtype=float64), 'phi': 0,
 'z': Array(1.11803399, dtype=float64)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cylindrical3D -> ProlateSpheroidal3D.

Uses the focal length \(\Delta\) stored on to_chart.

Let \(R^2 = \rho^2\) and \(z^2 = z^2\) and define

\(S = R^2 + z^2 + \Delta^2\), \(D_f = R^2 + z^2 - \Delta^2\), \(D = \sqrt{D_f^2 + 4 R^2 \Delta^2}\).

Then

\(\mu = \Delta^2 + \tfrac12(D + D_f)\) (with numerically-stable branches), \(\lvert\nu\rvert = \dfrac{2\Delta^2}{S + D}\,z^2\), and \(\nu = \lvert\nu\rvert\,\mathrm{sign}(z)\), with a stability fix when \(\Delta^2 - \lvert\nu\rvert\) is small.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> prolatesph3d = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))

A point on the z-axis (rho=0):

>>> p = {"rho": u.Q(0.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(3.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, prolatesph3d)
{'mu': Q(9., 'm2'), 'nu': Q(4., 'm2'), 'phi': Angle(0, 'rad')}

A point in the xy-plane (z=0):

>>> p = {"rho": u.Q(2.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(0.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, prolatesph3d)
{'mu': Q(8., 'm2'), 'nu': Q(0., 'm2'), 'phi': Angle(0, 'rad')}

Without units:

>>> p = {"rho": 2.0, "phi": 0, "z": 3.0}  # No units
>>> usys = u.unitsystem("m", "rad")
>>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, prolatesph3d, usys=usys)
{'mu': Array(14.52079729, dtype=float64),
 'nu': Array(2.47920271, dtype=float64), 'phi': 0}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart3D -> Cylindrical3D -> ProlateSpheroidal3D.

ProlateSpheroidal3D is only registered as a target from Cylindrical3D; without this route the generic A -> A.cartesian -> B fallback would send Cart3D -> Cart3D -> ProlateSpheroidal3D and recurse forever. Route through Cylindrical3D instead (mirrors the Cart3D -> AbstractSpherical3D rule).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> prolate = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> p = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(3.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, prolate)
{'mu': Q(9., 'm2'), 'nu': Q(4., 'm2'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

{class}`coordinax.charts.ProlateSpheroidal3D` -> itself.

If the focal length is unchanged (to_chart.Delta == from_chart.Delta), this is the identity map.

If the focal length changes, we convert via cylindrical coordinates:

Prolate(Delta_in) -> Cylindrical -> Prolate(Delta_out).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

Same focal length (identity transformation):

>>> prolate = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")}
>>> cxc.pt_map(p, cxm.R3, prolate, cxm.R3, prolate)
{'mu': Q(5., 'm2'), 'nu': Q(1., 'm2'), 'phi': Angle(0., 'rad')}

Different focal lengths (converts via cylindrical):

>>> prolate_in = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> prolate_out = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(3.0, "m"))
>>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")}
>>> cxc.pt_map(p, cxm.R3, prolate_in, cxm.R3, prolate_out)
{'mu': Q(9.85889894, 'm2'), 'nu': Q(1.14110106, 'm2'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: CartND, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

CartND -> AbstractChart.

Converts from N-dimensional Cartesian (with a single โ€˜qโ€™ array) to any other chart type by first extracting the appropriate fixed-dimensional Cartesian representation.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

Convert 3D CartND to Spherical:

>>> p = {"q": u.Q([1.0, 0.0, 0.0], "m")}
>>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R3, cxc.sph3d)
{'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

Convert 2D CartND to Polar:

>>> p = {"q": u.Q([3.0, 4.0], "m")}
>>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R2, cxc.polar2d)
{'r': Q(5., 'm'), 'theta': Angle(0.92729522, 'rad')}

Convert 1D CartND to Radial:

>>> p = {"q": u.Q([5.0], "m")}
>>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R1, cxc.radial1d)
{'r': Q(5., 'm')}

Convert CartND to Cart3D:

>>> p = {"q": u.Q([1.0, 2.0, 3.0], "m")}
>>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R3, cxc.cart3d)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: CartND, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

AbstractChart -> CartND.

Converts from any chart type to N-dimensional Cartesian (with a single โ€˜qโ€™ array) by first transforming to the appropriate fixed-dimensional Cartesian representation.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

Convert Cart3D to CartND:

>>> p = {"x": u.Q(1.0, "m"), "y": u.Q(2.0, "m"), "z": u.Q(3.0, "m")}
>>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.cartnd)
{'q': Q([1., 2., 3.], 'm')}

Convert Cart2D to CartND:

>>> p = {"x": u.Q(3.0, "m"), "y": u.Q(4.0, "m")}
>>> cxc.pt_map(p, cxm.R2, cxc.cart2d, cxm.R2, cxc.cartnd)
{'q': Q([3., 4.], 'm')}

Convert Radial to CartND:

>>> p = {"r": u.Q(3.0, "m")}
>>> cxc.pt_map(p, cxc.radial1d, cxc.cartnd)
{'q': Q([3.], 'm')}

Convert Cylindrical to CartND (z-axis point):

>>> p = {"rho": u.Q(0.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(5.0, "m")}
>>> cxc.pt_map(p, cxc.cyl3d, cxc.cartnd)
{'q': Q([0., 0., 5.], 'm')}
coordinax.manifolds.pt_map(q: AbstractQuantity, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → AbstractQuantity
Parameters:
Return type:

dict

Identity point transform for Quantity inputs on uniform-unit charts.

For charts where all components share the same unit (Cartesian charts, 0D/1D charts), a Quantity can be passed directly and is returned unchanged when the source and target charts are the same type.

This dispatch only handles identity transformations (same chart type). For transformations between different chart types with Quantity input, the Quantity must first be converted to a coordinate dictionary.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u

1D Cartesian (identity):

>>> q = u.Q([5.0], "m")
>>> cxc.pt_map(q, cxm.R1, cxc.cart1d, cxm.R1, cxc.cart1d, usys=None) is q
True

2D Cartesian (identity):

>>> q = u.Q([3.0, 4.0], "m")
>>> cxc.pt_map(q, cxm.R2, cxc.cart2d, cxm.R2, cxc.cart2d, usys=None) is q
True

3D Cartesian (identity):

>>> q = u.Q([1.0, 2.0, 3.0], "km")
>>> cxc.pt_map(q, cxm.R3, cxc.cart3d, cxm.R3, cxc.cart3d, usys=None) is q
True

N-D Cartesian (identity):

>>> q = u.Q([1.0, 2.0, 3.0, 4.0], "m")
>>> cxc.pt_map(q, cxm.RN, cxc.cartnd, cxm.RN, cxc.cartnd, usys=None) is q
True
coordinax.manifolds.pt_map(p: AbstractQuantity, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

dict

Transform a QuantityMatrix between charts.

Converts the components of a QuantityMatrix from one chart to another, preserving the matrix structure with potentially different units per component.

>>> import coordinax.charts as cxc
>>> import unxt as u

2D Cartesian to Polar:

>>> q = u.Q([3.0, 4.0], "m")
>>> result = cxc.pt_map(q, cxc.cart2d, cxc.polar2d)
>>> result.shape
(2,)
>>> result.unit
UnitsMatrix("(m, rad)")

3D Cartesian to Spherical:

>>> q = u.Q([1.0, 0.0, 0.0], "kpc")
>>> result = cxc.pt_map(q, cxc.cart3d, cxc.sph3d)
>>> result.shape
(3,)

Batched transformation:

>>> q_batch = u.Q([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], "m")
>>> result = cxc.pt_map(q_batch, cxc.cart3d, cxc.sph3d)
>>> result.shape
(2, 3)
coordinax.manifolds.pt_map(p: Array | list, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None) → Array
Parameters:
Return type:

dict

Point transform for array input.

Transforms a point represented as a raw array (without units) from one chart to another. The unit system usys provides the units for interpreting the array components.

Returns:

Array of shape (..., ndim) containing the transformed coordinates in to_chart.

Return type:

dict

Parameters:

Examples

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp

Cartesian to Spherical (3D):

>>> usys = u.unitsystem("m", "rad")
>>> p = jnp.array([1.0, 0.0, 0.0])  # Point on x-axis
>>> cxc.pt_map(p, cxc.cart3d, cxc.sph3d, usys=usys)
Array([1.        , 1.57079633, 0.        ], dtype=float64)

The result is [r, theta, phi] = [1, pi/2, 0] (on equator, x-axis).

Spherical to Cartesian (3D):

>>> p = jnp.array([2.0, jnp.pi/4, 0.0])  # r=2, theta=45ยฐ, phi=0
>>> cxc.pt_map(p, cxc.sph3d, cxc.cart3d, usys=usys)
Array([1.41421356, 0.        , 1.41421356], dtype=float64)

Cartesian to Cylindrical:

>>> p = jnp.array([3.0, 4.0, 5.0])
>>> cxc.pt_map(p, cxc.cart3d, cxc.cyl3d, usys=usys)
Array([5.        , 0.92729522, 5.        ], dtype=float64)

The result is [rho, phi, z] = [5, arctan(4/3), 5].

Batched transformation:

>>> p_batch = jnp.array([[1.0, 0.0, 0.0],
...                      [0.0, 1.0, 0.0],
...                      [0.0, 0.0, 1.0]])
>>> cxc.pt_map(p_batch, cxc.cart3d, cxc.sph3d, usys=usys)
Array([[1.        , 1.57079633, 0.        ],
       [1.        , 1.57079633, 1.57079633],
       [1.        , 0.        , 0.        ]], dtype=float64)

2D Cartesian to Polar:

>>> usys_2d = u.unitsystem("m", "rad")
>>> p = jnp.array([3.0, 4.0])
>>> cxc.pt_map(p, cxc.cart2d, cxc.polar2d, usys=usys_2d)
Array([5.        , 0.92729522], dtype=float64)
coordinax.manifolds.pt_map(p: dict, from_M: CartesianProductManifold, from_chart: CartesianProductChart, to_M: NoManifold, to_chart: PoincarePolar6D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cartesian phase space cart3d x cart3d -> PoincarePolar6D (gala forward).

The source is a two-factor Cartesian product chart: factor 0 is position (x, y, z), factor 1 its velocity (vx, vy, vz). Implements galaโ€™s cartesian_to_poincare_polar (Papaphilippou & Laskar 1996):

rho = hypot(x, y), phi = atan2(x, y) (galaโ€™s azimuth convention), dt_rho = (x*vx + y*vy) / rho, Lz = x*vy - y*vx, pp_phi = sqrt(2|Lz|) cos(phi), pp_phidot = sqrt(2|Lz|) sin(phi), dt_z = vz.

sqrt(|Lz|) discards sign(Lz), so there is no global inverse. A partial inverse (assuming Lz >= 0) is registered below.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> ps = cxc.CartesianProductChart((cxc.cart3d, cxc.cart3d), ("q", "p"))
>>> q = {"q.x": u.Q(3.0, "kpc"), "q.y": u.Q(4.0, "kpc"), "q.z": u.Q(5.0, "kpc"),
...      "p.x": u.Q(1.0, "kpc/Myr"), "p.y": u.Q(2.0, "kpc/Myr"),
...      "p.z": u.Q(0.5, "kpc/Myr")}
>>> out = cxc.pt_map(q, ps.M, ps, cxc.poincarepolar6d.M, cxc.poincarepolar6d)
>>> sorted(out)
['dt_rho', 'dt_z', 'pp_phi', 'pp_phidot', 'rho', 'z']

Lz = x*vy - y*vx = 2 kpc^2/Myr, so sqrt(2|Lz|) = 2; phi = atan2(3, 4):

>>> out["rho"], out["dt_rho"], out["dt_z"]
(Q(5., 'kpc'), Q(2.2, 'kpc / Myr'), Q(0.5, 'kpc / Myr'))
>>> out["pp_phi"].round(4), out["pp_phidot"].round(4)
(Q(1.6, 'kpc / Myr(1/2)'), Q(1.2, 'kpc / Myr(1/2)'))

A non-Cartesian or wrong-arity product source is rejected:

>>> bad = cxc.CartesianProductChart((cxc.cart3d, cxc.polar2d), ("q", "p"))
>>> try:
...     cxc.pt_map({}, bad.M, bad, cxc.poincarepolar6d.M, cxc.poincarepolar6d)
... except NotImplementedError:
...     print("rejected")
rejected
coordinax.manifolds.pt_map(p: dict, from_M: NoManifold, from_chart: PoincarePolar6D, to_M: CartesianProductManifold, to_chart: CartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

PoincarePolar6D -> Cartesian phase space (partial inverse of gala map).

Inverts the gala forward map. Because the forward uses sqrt(2|Lz|) the sign of the angular momentum is not recoverable, so this assumes Lz >= 0 (the standard convention) and is a partial inverse โ€” exact only when the original point had non-negative Lz:

s = hypot(pp_phi, pp_phidot), phi = atan2(pp_phidot, pp_phi), Lz = s**2 / 2, x = rho sin(phi), y = rho cos(phi), vx = sin(phi) dt_rho - cos(phi) Lz/rho, vy = cos(phi) dt_rho + sin(phi) Lz/rho, vz = dt_z.

(Singular on the axis rho = 0, inherent to the coordinates.)

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> ps = cxc.CartesianProductChart((cxc.cart3d, cxc.cart3d), ("q", "p"))

Round-trips a forward result whose Lz >= 0:

>>> q = {"q.x": u.Q(3.0, "kpc"), "q.y": u.Q(4.0, "kpc"), "q.z": u.Q(5.0, "kpc"),
...      "p.x": u.Q(1.0, "kpc/Myr"), "p.y": u.Q(2.0, "kpc/Myr"),
...      "p.z": u.Q(0.5, "kpc/Myr")}
>>> pp = cxc.pt_map(q, ps.M, ps, cxc.poincarepolar6d.M, cxc.poincarepolar6d)
>>> back = cxc.pt_map(pp, cxc.poincarepolar6d.M, cxc.poincarepolar6d, ps.M, ps)
>>> back["q.x"].round(6), back["q.y"].round(6), back["p.x"].round(6)
(Q(3., 'kpc'), Q(4., 'kpc'), Q(1., 'kpc / Myr'))
coordinax.manifolds.pt_map(p: dict, from_chart: EmbeddedChart, to_chart: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Convert between embedded manifolds with a shared ambient space.

This function transforms intrinsic coordinates from one embedded manifold to another by: 1. Embedding the point into the ambient space of the source manifold 2. Transforming in the ambient space (if the ambient charts differ) 3. Projecting back to the intrinsic coordinates of the target manifold

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> import quaxed.numpy as jnp

Example 1: Two spheres with different radii

Both spheres use the same intrinsic SphericalTwoSphere chart but have different radii:

>>> sphere1 = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")))
>>> sphere2 = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))

A point on sphere1 (theta=pi/4, phi=0):

>>> p = {"theta": u.Q(45, "deg"), "phi": u.Q(0, "deg")}
>>> p2 = cxc.pt_map(p, sphere1, sphere2)
>>> {k: v.uconvert("deg") for k, v in p2.items()}
{'theta': Angle(45, 'deg'), 'phi': Angle(0, 'deg')}

The angular coordinates are preserved (both spheres share the same angular parameterization via projection through the shared ambient space).

coordinax.manifolds.pt_map(p: dict, from_chart: AbstractChart, to_chart: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Project an ambient position into an embedded chart.

This transforms coordinates from an ambient chart (e.g., Cartesian or Spherical) into the intrinsic coordinates of an embedded manifold.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> import quaxed.numpy as jnp

From Cartesian ambient to SphericalTwoSphere intrinsic:

>>> sphere = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1.0, "m")))

A point on the unit sphere in Cartesian coords (on equator, x-axis):

>>> p_cart = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> cxc.pt_map(p_cart, cxc.cart3d, sphere)
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

From Spherical ambient to SphericalTwoSphere intrinsic:

The ambient spherical coords (r, theta, phi) project to intrinsic (theta, phi), discarding the radial component:

>>> p_sph = {"r": 5, "theta": 1, "phi": 0.5}  # No units
>>> usys = u.unitsystem("m", "rad")
>>> cxc.pt_map(p_sph, cxc.sph3d, sphere, usys=usys)
{'theta': 1, 'phi': 0.5}
coordinax.manifolds.pt_map(p: dict, from_chart: EmbeddedChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Embed intrinsic coordinates into an ambient representation.

This transforms intrinsic coordinates of an embedded manifold into coordinates of an ambient chart, which may differ from the embeddingโ€™s native ambient chart.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> import quaxed.numpy as jnp

From SphericalTwoSphere intrinsic to Cartesian ambient:

>>> sphere = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1.0, "m")))

A point on the unit sphere (on equator, x-axis):

>>> p_sph = {"theta": u.Q(1.0, "rad"), "phi": u.Q(0.0, "rad")}
>>> cxc.pt_map(p_sph, sphere, cxc.cart3d)
{'x': Q(0.84147098, 'm'), 'y': Q(0., 'm'), 'z': Q(0.54030231, 'm')}

From SphericalTwoSphere intrinsic to Spherical ambient:

>>> p_sph = {"theta": u.Q(1.0, "rad"), "phi": u.Q(0.5, "rad")}
>>> cxc.pt_map(p_sph, sphere, cxc.sph3d)
{'r': Q(1., 'm'), 'theta': Angle(1., 'rad'), 'phi': Angle(0.5, 'rad')}
coordinax.manifolds.pt_map(p: dict, M: EmbeddedManifold, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Convert between embedded manifolds with a shared ambient space.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.embedded_twosphere(radius=u.Q(1, "kpc"))
>>> M
EmbeddedManifold(intrinsic=HyperSphericalManifold(...),
                 ambient=Rn(3),
                 embed_map=TwoSphereIn3D(radius=Q(1, 'kpc'),
                                         ambient=Spherical3D(M=Rn(3))))
>>> x_cart = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> x_sph2 = cxc.pt_map(x_cart, M, cxc.cart3d, cxc.loncoslat_sph2)
>>> x_sph2
{'lon_coslat': Angle(0.66164791, 'rad'), 'lat': Angle(53.3007748, 'deg')}
>>> cxc.pt_map(x_sph2, M, cxc.loncoslat_sph2, cxc.cart3d)
{'x': Q(0.26726124, 'kpc'), 'y': Q(0.53452248, 'kpc'),
 'z': Q(0.80178373, 'kpc')}
coordinax.manifolds.pt_map(p: Any, from_chart: Abstract3D, to_chart: AbstractSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

dict

Project a point from the ambient chart to the two-sphere intrinsic chart.

This realization map is a special case for projecting from 3D charts to the two-sphere intrinsic chart, which is a common use case. The projection does not depend on the radius of the embedding, so this projection works in general.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> q = {"x": u.Q(1.0, "km"), "y": u.Q(0.0, "km"), "z": u.Q(0.0, "km")}
>>> cxc.pt_map(q, cxc.cart3d, cxc.sph2)
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: AbstractChart, to_M: HyperSphericalManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Identity conversion for matching charts.

Returns the input object itself when it is already canonical โ€“ angular components held as unxt.Angle, as the chart declares. A non-canonical input is canonicalised instead, so it necessarily comes back as a new dict: the alternative is pt_map(q, chart, chart) preserving a container that every other route to the same chart would have normalised.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> q = {"theta": u.Angle(30, "deg"), "phi": u.Angle(60, "deg")}
>>> cxc.pt_map(q, cxc.sph2, cxc.sph2) is q
True
>>> q = {"lon": u.Angle(45, "deg"), "lat": u.Angle(10, "deg")}
>>> cxc.pt_map(q, cxc.lonlat_sph2, cxc.lonlat_sph2) is q
True
>>> q = {"lon_coslat": u.Angle(30, "deg"), "lat": u.Angle(20, "deg")}
>>> cxc.pt_map(q, cxc.loncoslat_sph2, cxc.loncoslat_sph2) is q
True
>>> q = {"theta": u.Angle(60, "deg"), "phi": u.Angle(30, "deg")}
>>> cxc.pt_map(q, cxc.math_sph2, cxc.math_sph2) is q
True
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: AbstractSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: AbstractSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Route between two-sphere charts via SphericalTwoSphere.

Each chart registers only its direct sph2 <-> chart conversion, and the two-sphere has no Cartesian chart, so the generic router cannot bridge two non-canonical charts (it raises NoGlobalCartesianChartError). Go A -> SphericalTwoSphere -> B instead. Canonical pairs (either side is SphericalTwoSphere) and matching-type pairs are handled by the more specific direct/identity rules above, so this fallback fires only for distinct non-canonical charts โ€“ and covers any future AbstractSphericalTwoSphere subclass automatically.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> p = {"lon": u.Q(45, "deg"), "lat": u.Q(30, "deg")}
>>> out = cxc.pt_map(p, cxm.S2, cxc.lonlat_sph2, cxm.S2, cxc.math_sph2)
>>> sorted(out)
['phi', 'theta']
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: LonLatSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

SphericalTwoSphere -> LonLatSphericalTwoSphere.

lat = pi/2 - theta, lon = phi.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"theta": u.Q(0, "rad"), "phi": u.Q(0, "rad")}  # North pole
>>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.lonlat_sph2)
{'lon': Angle(0, 'rad'), 'lat': Angle(90., 'deg')}
>>> p = {"theta": u.Q(90, "deg"), "phi": u.Q(45, "deg")}  # Equator
>>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.lonlat_sph2)
{'lon': Angle(45, 'deg'), 'lat': Angle(0, 'deg')}
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: LonLatSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

LonLatSphericalTwoSphere -> SphericalTwoSphere.

theta = pi/2 - lat, phi = lon.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"lon": u.Q(45, "deg"), "lat": u.Q(0, "deg")}
>>> cxc.pt_map(p, cxm.S2, cxc.lonlat_sph2, cxm.S2, cxc.sph2)
{'theta': Angle(90, 'deg'), 'phi': Angle(45, 'deg')}
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: LonCosLatSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

SphericalTwoSphere -> LonCosLatSphericalTwoSphere.

lat = pi/2 - theta, lon_coslat = phi * cos(lat).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import quaxed.numpy as jnp
>>> p = {"theta": u.Q(90, "deg"), "phi": u.Q(45, "deg")}  # equator
>>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.loncoslat_sph2)
{'lon_coslat': Angle(45., 'deg'), 'lat': Angle(0, 'deg')}
>>> p = {"theta": u.Q(0, "deg"), "phi": u.Q(45, "deg")}  # north pole
>>> result = cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.loncoslat_sph2)
>>> bool(jnp.allclose(u.ustrip("deg", result["lat"]), 90.0))
True
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: LonCosLatSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

LonCosLatSphericalTwoSphere -> SphericalTwoSphere.

theta = pi/2 - lat, phi = lon_coslat / cos(lat).

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> p = {"lon_coslat": u.Q(45, "deg"), "lat": u.Q(0, "deg")}
>>> cxc.pt_map(p, cxm.S2, cxc.loncoslat_sph2, cxm.S2, cxc.sph2)
{'theta': Angle(90, 'deg'), 'phi': Angle(45., 'deg')}
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: MathSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

SphericalTwoSphere -> MathSphericalTwoSphere.

Swaps theta and phi (physics -> math convention).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"theta": u.Q(30, "deg"), "phi": u.Q(60, "deg")}
>>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.math_sph2)
{'theta': Angle(60, 'deg'), 'phi': Angle(30, 'deg')}
coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: MathSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

MathSphericalTwoSphere -> SphericalTwoSphere.

Swaps theta and phi (math -> physics convention).

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"theta": u.Q(60, "deg"), "phi": u.Q(30, "deg")}
>>> cxc.pt_map(p, cxm.S2, cxc.math_sph2, cxm.S2, cxc.sph2)
{'theta': Angle(30, 'deg'), 'phi': Angle(60, 'deg')}
coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, to_rep: Representation, /, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

dict

Convert point data between charts.

Convert a point from Cartesian coordinates to spherical coordinates:

>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc

Define a point in Cartesian coordinates:

>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}

Convert it to spherical coordinates:

>>> q = cxc.pt_map(p, cxc.cart3d, cxr.point, cxc.sph3d, cxr.point)
>>> q
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}

The output q represents the same geometric point but expressed in the target chart.

The representation remains unchanged; only the chart changes:

>>> cxc.pt_map(q, cxc.sph3d, cxr.point, cxc.cart3d, cxr.point)
{'x': Array(1., dtype=float64, ...), 'y': Array(2., dtype=float64, ...),
 'z': Array(3., dtype=float64, ...)}

Letโ€™s work through more examples.

Cartesian to Spherical (with units):

>>> import unxt as u
>>> p = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> cxc.pt_map(p, cxc.cart3d, cxr.point, cxc.sph3d, cxr.point)
{'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

Cylindrical to Cartesian (without units):

>>> p = {"rho": 3.0, "phi": 0, "z": 4.0}
>>> cxc.pt_map(p, cxc.cyl3d, cxr.point, cxc.cart3d, cxr.point)
{'x': Array(3., dtype=float64, ...), 'y': Array(0., dtype=float64, ...),
 'z': 4.0}

Polar to Cartesian (2D):

>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")}
>>> cxc.pt_map(p, cxc.polar2d, cxr.point, cxc.cart2d, cxr.point)
{'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}

Between Spherical variants (Spherical to LonLatSpherical):

>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(45, "deg"), "phi": u.Q(0, "deg")}
>>> cxc.pt_map(p, cxc.sph3d, cxr.point, cxc.lonlat_sph3d, cxr.point)
{'lon': Angle(0, 'deg'), 'lat': Angle(45, 'deg'), 'distance': Q(1., 'm')}

Identity conversion (same chart):

>>> p = {"x": u.Q(2.0, "m"), "y": u.Q(3.0, "m")}
>>> cxc.pt_map(p, cxc.cart2d, cxr.point, cxc.cart2d, cxr.point) is p
True
coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, /, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

dict

Convert point data between charts.

Convert a point from Cartesian coordinates to spherical coordinates:

>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc

Define a point in Cartesian coordinates:

>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}

Convert it to spherical coordinates:

>>> q = cxc.pt_map(p, cxc.cart3d, cxr.point, cxc.sph3d)
>>> q
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}

The output q represents the same geometric point but expressed in the target chart.

The representation remains unchanged; only the chart changes:

>>> cxc.pt_map(q, cxc.sph3d, cxr.point, cxc.cart3d)
{'x': Array(1., dtype=float64, ...), 'y': Array(2., dtype=float64, ...),
 'z': Array(3., dtype=float64, ...)}
coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, from_geom: PointGeometry, from_rep: Representation, to_chart: AbstractChart, to_geom: PointGeometry, to_rep: Representation, /, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

dict

Convert point data between charts.

Convert a point from Cartesian coordinates to spherical coordinates:

>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc

Define a point in Cartesian coordinates:

>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}

Convert it to spherical coordinates:

>>> cxc.pt_map(p, cxc.cart3d, cxr.point_geom, cxr.point,
...                             cxc.sph3d, cxr.point_geom, cxr.point)
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}
coordinax.manifolds.pt_map(from_vec: Point, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → Point
Parameters:
Return type:

dict

Convert a point from one chart to another.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> vec = cx.Point.from_([1, 1, 1], "m")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 1 1]>
>>> sph_vec = cxc.pt_map(vec, cx.sph3d)
>>> print(sph_vec)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [1.732 0.955 0.785]>
coordinax.manifolds.pt_map(from_vec: Point, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → Point
Parameters:
Return type:

dict

Convert a vector from one chart to another.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> vec = cx.Point.from_([1, 1, 1], "m")
>>> sph_vec = cxc.pt_map(vec, cxc.cart3d, cx.sph3d)
>>> print(sph_vec)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [1.732 0.955 0.785]>
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: TubularChart, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

TubularChart -> Cart3D: \(\gamma(\tau) + n_1U_1 + n_2U_2\).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinaxs.curveframes as cxfc
>>> def circle(tau):
...     t = tau.ustrip("s")
...     return u.Q(jnp.stack([jnp.cos(t), jnp.sin(t), jnp.zeros_like(t)]), "km")
>>> chart = cxfc.TubularChart(
...     cxfc.BishopBuilder(circle, "s", normal_0="auto"),
...     tau_bounds=(u.Q(0.0, "s"), u.Q(2 * jnp.pi, "s")),
... )
>>> p = {"tau": u.Q(0.0, "s"), "n1": u.Q(0.1, "km"), "n2": u.Q(0.0, "km")}
>>> cxc.pt_map(p, chart.M, chart, chart.M, cxc.cart3d)
{'x': Q(1.1, 'km'), 'y': Q(0., 'km'), 'z': Q(0., 'km')}

Raw coordinates take the same route, given a usys to say what their numbers mean, and come back raw:

>>> usys = u.unitsystem("km", "s", "kg", "rad")
>>> p = {"tau": 0.0, "n1": 0.1, "n2": 0.0}
>>> cxc.pt_map(p, chart.M, chart, chart.M, cxc.cart3d, usys=usys)
{'x': Array(1.1, dtype=float64...),
 'y': Array(0., dtype=float64...),
 'z': Array(0., dtype=float64...)}
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: TubularChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

Cart3D -> TubularChart, via the nearest-point projection.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinaxs.curveframes as cxfc
>>> def circle(tau):
...     t = tau.ustrip("s")
...     return u.Q(jnp.stack([jnp.cos(t), jnp.sin(t), jnp.zeros_like(t)]), "km")
>>> chart = cxfc.TubularChart(
...     cxfc.BishopBuilder(circle, "s", normal_0="auto"),
...     tau_bounds=(u.Q(0.0, "s"), u.Q(2 * jnp.pi, "s")),
... )

A point exactly on the curve has zero offsets. Bishop runs an ODE solve internally, so the recovered values are checked with a tolerance rather than pinned to exact digits:

>>> on_curve = chart.builder.location(u.Q(0.0, "s"))
>>> p = {k: on_curve[i] for i, k in enumerate(("x", "y", "z"))}
>>> back = cxc.pt_map(p, chart.M, cxc.cart3d, chart.M, chart)
>>> bool(jnp.allclose(back["tau"].ustrip("s"), 0.0, atol=1e-6))
True
>>> bool(jnp.allclose(back["n1"].ustrip("km"), 0.0, atol=1e-6))
True
>>> bool(jnp.allclose(back["n2"].ustrip("km"), 0.0, atol=1e-6))
True
coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: TubularChart, to_M: EuclideanManifold, to_chart: TubularChart, /, *, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

dict

TubularChart -> TubularChart. Identity only for the same chart object.

Parameterized charts compare conservatively (equal only when identical), so this declines to a Cartesian round trip whenever the two differ.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinaxs.curveframes as cxfc
>>> def circle(tau):
...     t = tau.ustrip("s")
...     return u.Q(jnp.stack([jnp.cos(t), jnp.sin(t), jnp.zeros_like(t)]), "km")
>>> chart = cxfc.TubularChart(
...     cxfc.BishopBuilder(circle, "s", normal_0="auto"),
...     tau_bounds=(u.Q(0.0, "s"), u.Q(2 * jnp.pi, "s")),
... )
>>> p = {"tau": u.Q(0.0, "s"), "n1": u.Q(0.1, "km"), "n2": u.Q(0.0, "km")}
>>> cxc.pt_map(p, chart.M, chart, chart.M, chart) is p
True
Parameters:
Return type:

dict

coordinax.manifolds.scale_factors(chart, /, *args, **kwargs)#

Return the diagonal entries of the metric matrix.

Dispatches on the first argument (metric or manifold) and the chart.

coordinax.manifolds.scale_factors(metric: RoundMetric, chart: LonLatSphericalTwoSphere | MathSphericalTwoSphere, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Scale factors for the orthogonal relabelled two-sphere charts.

LonLat and Math relabel/swap the canonical angles, so the nested sine-product does not apply โ€“ but they stay orthogonal, so the metric is diagonal and its diagonal is the answer:

  • LonLat (lon, lat): diag(cos^2(lat), 1)

  • Math (theta, phi): diag(sin^2(phi), 1)

Taken from metric_matrix rather than re-derived, so there is one source of truth for the pullback.

>>> import math
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {"lon": u.Q(0.6, "rad"), "lat": u.Q(math.radians(40), "rad")}
>>> h = cxm.scale_factors(cxm.RoundMetric(2), cxc.lonlat_sph2, at=at)
>>> [round(float(v), 4) for v in h.value]
[0.5868, 1.0]

Bare angle values are interpreted via usys["angle"] when a unit system is supplied:

>>> usys = u.unitsystem("m", "s", "kg", "deg")
>>> h = cxm.scale_factors(cxm.RoundMetric(2), cxc.lonlat_sph2,
...                       at={"lon": 30.0, "lat": 40.0}, usys=usys)
>>> [round(float(v), 4) for v in h.value]
[0.5868, 1.0]
coordinax.manifolds.scale_factors(metric: RoundMetric, chart: LonCosLatSphericalTwoSphere, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

LonCosLat is non-orthogonal, so it has no scale factors at all.

coordinax.manifolds.scale_factors(metric: RoundMetric, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Return round-metric diagonal directly without forming the nxn matrix.

Computes the cumulative-sine diagonal \(g_{kk} = \prod_{j<k} \sin^2\theta_j\) as a 1-D vector, avoiding the O(n^2) cost of RoundMetric.metric_matrix.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

Bare angles (no units) โ†’ dimensionless QuantityMatrix:

>>> metric = cxm.RoundMetric(2)
>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)}
>>> cxm.scale_factors(metric, cxc.sph2, at=at)
QM([1., 1.], '(, )')

Quantity angles โ†’ dimensionless QuantityMatrix:

>>> at = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> cxm.scale_factors(metric, cxc.sph2, at=at)
QM([1., 1.], '(, )')
coordinax.manifolds.scale_factors(chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Manifold-level dispatch: delegate to the attached metric.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {
...     "r": u.Q(jnp.array(2.0), "km"),
...     "theta": u.Angle(jnp.pi / 2, "rad"),
...     "phi": u.Angle(jnp.array(0.0), "rad"),
... }
>>> cxm.scale_factors(cxc.sph3d, at=at)
QM([1., 4., 4.], '(, km2 / rad2, km2 / rad2)')
coordinax.manifolds.scale_factors(metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Return the diagonal entries of the metric at at in chart.

Uses the metric_matrix dispatch API to compute the metric, then extracts the diagonal entries.

The chart must be orthogonal. Per-axis factors describe a metric only where the off-diagonal terms vanish, so a chart whose metric is dense is refused rather than answered with a diagonal that does not reproduce it.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.RoundMetric(2)
>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)}
>>> cxm.scale_factors(metric, cxc.sph2, at=at)
QM([1., 1.], '(, )')

LonCosLatSpherical3D is non-orthogonal โ€“ its g_01 = distance**2 * lon_coslat * tan(lat) โ€“ and so has no scale factors. (The unit two-sphere carries no distance, which is why SphericalTwoSphereโ€™s sibling message states the same cross term without that factor.)

>>> import unxt as u
>>> at = {"lon_coslat": u.Angle(0.3, "rad"), "lat": u.Angle(0.6, "rad"),
...       "distance": u.Q(2.0, "m")}
>>> try: cxm.scale_factors(cxc.loncoslat_sph3d, at=at)
... except NotImplementedError as e: print(e)
scale_factors is a diagonal (orthogonal-frame) concept and the metric of
LonCosLatSpherical3D... is not diagonal, so no set of per-axis factors
reproduces it. Use coordinax.manifolds.metric_matrix.
coordinax.manifolds.scale_factors(metric: PullbackMetric, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Return scale factors for a pullback (induced) metric.

The scale factors of an induced metric are the diagonal of that metric, so this delegates to the EmbeddedManifold metric_matrix rule rather than re-deriving the Jacobian. That keeps one implementation of the pullback, and inherits its handling of a non-intrinsic chart, of a non-Euclidean ambient metric (the ambient Gram carries the signature), and of batched points.

Points are interpreted in the passed chart, which need not be the embeddingโ€™s own intrinsic chart.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")),
... )
>>> at = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> cxm.scale_factors(M.metric, cxc.sph2, at=at)
QM([4., 4.], '(m2 / rad2, m2 / rad2)')

A chart other than the embeddingโ€™s intrinsic one:

>>> at = {"lon": u.Angle(0.0, "rad"), "lat": u.Angle(0.0, "rad")}
>>> cxm.scale_factors(M.metric, cxc.lonlat_sph2, at=at)
QM([4., 4.], '(m2 / rad2, m2 / rad2)')

The delegate reads the ambient Gram off the ambient manifold, so an ambient_metric that disagrees with it cannot be honoured. That is refused rather than answered with the diagonal of a different metric:

>>> pb = cxm.PullbackMetric(cxm.TwoSphereIn3D(radius=1.0), cxm.RoundMetric(3))
>>> try: cxm.scale_factors(pb, cxc.sph2, at=at)
... except NotImplementedError as e: print(e)
the pullback of RoundMetric(ndim=3) cannot be evaluated: the ambient
manifold Rn(3) of chart Spherical3D... carries FlatMetric(ndim=3)
coordinax.manifolds.scale_factors(metric: MinkowskiMetric, chart: MinkowskiCT, /, *, at: dict, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Return the Minkowski metric diagonal \(\eta = \operatorname{diag}(-1,1,1,1)\).

In the canonical coordinax.charts.MinkowskiCT chart the metric is constant, so at is ignored and the result does not depend on the base point.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.MinkowskiMetric()
>>> at = {"ct": jnp.array(0.0), "x": jnp.array(1.0),
...       "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> cxm.scale_factors(metric, cxc.minkowskict, at=at)
QM([-1.,  1.,  1.,  1.], '(, , , )')
Parameters:
Return type:

Any

coordinax.manifolds.angle_between(chart, uvec, vvec, /, *args, **kwargs)#

Return the metric angle between two nonzero tangent vectors.

The inputs uvec and vvec are component dictionaries representing tangent-vector components in the coordinate basis of chart. The metric is evaluated at a base point supplied via at=....

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m")}
>>> uvec = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m")}
>>> vvec = {"x": u.Q(0.0, "m"), "y": u.Q(1.0, "m")}
>>> cxm.angle_between(cxc.cart2d, uvec, vvec, at=at)
Angle(1.57079633, 'rad')
>>> at_sph = {
...     "r": u.Q(2.0, "m"),
...     "theta": u.Angle(jnp.pi / 2, "rad"),
...     "phi": u.Angle(0.0, "rad"),
... }
>>> u_tan = {
...     "r": u.Q(0.0, "m"), "theta": u.Angle(1.0, "rad"),
...     "phi": u.Angle(0.0, "rad"),
... }
>>> v_tan = {
...     "r": u.Q(0.0, "m"), "theta": u.Angle(0.0, "rad"),
...     "phi": u.Angle(1.0, "rad"),
... }
>>> cxm.angle_between(cxc.sph3d, u_tan, v_tan, at=at_sph)
Angle(1.57079633, 'rad')
coordinax.manifolds.angle_between(chart: AbstractChart, uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None) → AbstractAngle
Parameters:
Return type:

Any

Manifold-level dispatch: delegate to the attached metric.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m")}
>>> uvec = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m")}
>>> vvec = {"x": u.Q(0.0, "m"), "y": u.Q(1.0, "m")}
>>> cxm.angle_between(cxc.cart2d, uvec, vvec, at=at)
Angle(1.57079633, 'rad')
coordinax.manifolds.angle_between(metric: AbstractMetricField, chart: AbstractChart, uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None) → AbstractAngle
Parameters:
Return type:

Any

Return the metric angle between two tangent vectors.

The input component dictionaries are interpreted as tangent-vector components in the coordinate basis of chart. The metric is evaluated at the base point at.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {
...     "r": u.Q(2.0, "m"),
...     "theta": u.Angle(jnp.pi / 2, "rad"),
...     "phi": u.Angle(0.0, "rad"),
... }
>>> uvec = {"r": u.Q(0.0, "m"), "theta": u.Angle(1.0, "rad"),
...         "phi": u.Angle(0.0, "rad")}
>>> vvec = {"r": u.Q(0.0, "m"), "theta": u.Angle(0.0, "rad"),
...         "phi": u.Angle(1.0, "rad")}
>>> cxm.angle_between(metric, cxc.sph3d, uvec, vvec, at=at)
Angle(1.57079633, 'rad')

An indefinite metric is not rejected outright. Two spacelike directions span a plane on which the Minkowski metric is positive-definite, so the angle between them is an ordinary one:

>>> at4 = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> xhat = {"ct": u.Q(0.0, "m"), "x": u.Q(1.0, "m"),
...         "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> yhat = {"ct": u.Q(0.0, "m"), "x": u.Q(0.0, "m"),
...         "y": u.Q(1.0, "m"), "z": u.Q(0.0, "m")}
>>> cxm.angle_between(cxc.minkowskict, xhat, yhat, at=at4)
Angle(1.57079633, 'rad')

Two timelike directions have no circular angle between them โ€“ the invariant is a hyperbolic one, the relative rapidity โ€“ so this raises rather than clipping arccos to a meaningless value:

>>> obs = {"ct": u.Q(1.0, "m"), "x": u.Q(0.0, "m"),
...        "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> moving = {"ct": u.Q(1.25, "m"), "x": u.Q(0.75, "m"),
...           "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> try:
...     cxm.angle_between(cxc.minkowskict, obs, moving, at=at4)
... except ValueError as e:
...     print(str(e).split(":")[0])
angle_between is undefined for two timelike tangent vectors
Parameters:
Return type:

Any

coordinax.manifolds.metric_matrix(M, point, chart, /)#

Compute the coordinate metric matrix at point in chart.

Dispatches on the triple (type(M), type(point), type(chart)). Concrete implementations for each (M, chart) pair are registered in the corresponding register_metric.py modules via plum.dispatch().

Parameters:
  • M (Any) โ€“ The manifold carrying the metric field.

  • point (Any) โ€“ A component dictionary giving the coordinates in chart.

  • chart (Any) โ€“ The coordinate chart in which to express the metric.

Returns:

The metric matrix at point. The concrete type (~coordinax._src.metric.matrix.DiagonalMetric or ~coordinax._src.metric.matrix.DenseMetric) depends on the (M, chart) pair and is declared by metric_representation().

Return type:

Any

Raises:

NotImplementedError โ€“ When no specific dispatch rule is registered for the given types.

Examples

>>> import jax.numpy as jnp
>>> import coordinaxs.api.manifolds as cxmapi
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {"x": jnp.array(1.0), "y": jnp.array(2.0), "z": jnp.array(3.0)}
>>> cxmapi.metric_matrix(cxm.R3, at, cxc.cart3d)
DiagonalMetric(diagonal=f64[3])
coordinax.manifolds.metric_matrix(M: AbstractManifold, point: dict, chart: AbstractChart, /) → AbstractMetricMatrix
Parameters:
Return type:

Any

Fallback โ€” raise NotImplementedError for unregistered pairs.

Concrete (manifold, chart) pairs register their own dispatch rules in the relevant register_metric.py modules (loaded as part of Phase 2). This fallback raises NotImplementedError when no specific dispatch rule is registered for the given manifold M, point point and chart chart types. See the abstract metric_matrix() for the full parameter documentation.

coordinax.manifolds.metric_matrix(M: NoManifold, point: dict, chart: AbstractChart, /) → AbstractMetricMatrix
Parameters:
Return type:

Any

Refuse, naming the missing manifold as the reason.

The generic fallback advises registering a dispatch rule โ€“ wrong for both populations that reach here: a chart that left M unset, and one that declares no_manifold by design.

coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Cart1D | Cart2D | Cart3D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in a Cartesian chart: g = I_n.

The metric matrix is the identity in any Cartesian chart, represented compactly as a coordinax._src.metric.matrix.DiagonalMetric with all-one diagonal.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric

Cart1D:

>>> at = {"x": jnp.array(3.0)}
>>> g = metric_matrix(cxm.R1, at, cxc.cart1d)
>>> isinstance(g, DiagonalMetric)
True
>>> g.diagonal
Array([1.], dtype=float64)

Cart2D:

>>> at = {"x": jnp.array(1.0), "y": jnp.array(2.0)}
>>> metric_matrix(cxm.R2, at, cxc.cart2d).diagonal
Array([1., 1.], dtype=float64)

Cart3D:

>>> at = {"x": jnp.array(1.0), "y": jnp.array(2.0), "z": jnp.array(3.0)}
>>> metric_matrix(cxm.R3, at, cxc.cart3d).diagonal
Array([1., 1., 1.], dtype=float64)
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: CartND, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in CartND: g = I_N where N is inferred from the point.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> at = {"q": jnp.array([1.0, 2.0, 3.0])}
>>> metric_matrix(cxm.R3, at, cxc.cartnd).diagonal
Array([1., 1., 1.], dtype=float64)
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Radial1D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in Radial1D: g = diag(1).

The only component is g_rr = 1 (the radial direction is an isometry of Euclidean distance in 1-D).

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric

Dimensionless:

>>> at = {"r": jnp.array(2.0)}
>>> g = metric_matrix(cxm.R1, at, cxc.radial1d)
>>> isinstance(g, DiagonalMetric)
True

With length units:

>>> at = {"r": u.Q(2.0, "m")}
>>> g = metric_matrix(cxm.R1, at, cxc.radial1d)
>>> g.diagonal
QM([1.], '(,)')
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Polar2D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in Polar2D: g = diag(1, rยฒ).

point must contain keys "r" (length) and "theta" (angle).

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric

Dimensionless r:

>>> at = {"r": jnp.array(3.0), "theta": jnp.array(0.5)}
>>> g = metric_matrix(cxm.R2, at, cxc.polar2d)
>>> g.diagonal
QM([1., 9.], '(, )')

Length-valued r and angle-valued theta:

>>> at = {"r": u.Q(3.0, "m"), "theta": u.Angle(0.5, "rad")}
>>> g = metric_matrix(cxm.R2, at, cxc.polar2d)
>>> g.diagonal
QM([1., 9.], '(, m2 / rad2)')
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Cylindrical3D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in Cylindrical3D: g = diag(1, ฯยฒ, 1).

point must contain keys "rho" (length), "phi" (angle), and "z" (length).

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = {"rho": u.Q(3.0, "m"), "phi": u.Angle(0.0, "rad"), "z": u.Q(1.0, "m")}
>>> g = metric_matrix(cxm.R3, at, cxc.cyl3d)
>>> g.diagonal
QM([1., 9., 1.], '(, m2 / rad2, )')
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Spherical3D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in Spherical3D: g = diag(1, rยฒ, rยฒsinยฒฮธ).

Physics convention: ฮธ is the polar (colatitude) angle measured from the +z axis, ฯ† is the azimuthal angle. point must contain keys "r" (length), "theta" (polar angle), and "phi" (azimuthal angle).

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = {
...     "r": u.Q(2.0, "m"),
...     "theta": u.Angle(jnp.pi / 2, "rad"),
...     "phi": u.Angle(0.0, "rad"),
... }
>>> g = metric_matrix(cxm.R3, at, cxc.sph3d)
>>> isinstance(g, DiagonalMetric)
True
>>> g.diagonal
QM([1., 4., 4.], '(, m2 / rad2, m2 / rad2)')
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: MathSpherical3D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in MathSpherical3D: g = diag(1, rยฒsinยฒฯ†, rยฒ).

Math convention: ฯ† is the polar angle from the +z axis (colatitude), ฮธ is the azimuthal angle. point must contain keys "r" (length), "theta" (azimuthal angle), and "phi" (polar / colatitude angle).

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = {
...     "r": u.Q(2.0, "m"),
...     "theta": u.Angle(0.0, "rad"),
...     "phi": u.Angle(jnp.pi / 2, "rad"),
... }
>>> g = metric_matrix(cxm.R3, at, cxc.math_sph3d)
>>> isinstance(g, DiagonalMetric)
True
>>> g.diagonal
QM([1., 4., 4.], '(, m2 / rad2, m2 / rad2)')
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: LonLatSpherical3D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in LonLatSpherical3D.

The metric is g = diag(distanceยฒcosยฒlat, distanceยฒ, 1) (components ordered as (lon, lat, distance)). point must contain keys "lon", "lat", and "distance" (length).

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = {
...     "lon": u.Angle(0.0, "rad"),
...     "lat": u.Angle(0.0, "rad"),
...     "distance": u.Q(2.0, "m"),
... }
>>> g = metric_matrix(cxm.R3, at, cxc.lonlat_sph3d)
>>> isinstance(g, DiagonalMetric)
True
>>> g.diagonal
QM([4., 4., 1.], '(m2 / rad2, m2 / rad2, )')
coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: ProlateSpheroidal3D, /) → DiagonalMetric
Parameters:
Return type:

Any

Euclidean metric in ProlateSpheroidal3D, as a diagonal.

Confocal prolate spheroidal coordinates are orthogonal, and stay so under this chartโ€™s per-coordinate reparameterisation to \((\mu, \nu, \phi)\): each of \(\mu\) and \(\nu\) is a function of one confocal coordinate alone, so no cross terms appear. The off-diagonal entries of \(J^\top J\) are therefore zero up to round-off โ€“ measured at \(8\times 10^{-17}\) relative, over a grid of \((\Delta, \mu, \nu, \phi)\).

Given in closed form, by differentiating this chartโ€™s own pt_map โ€“ \(\rho = \sqrt{\mu - \Delta^2}\sqrt{1 - t}\) and \(z = \mathrm{sign}(\nu)\sqrt{\mu}\sqrt{t}\) with \(t = |\nu|/\Delta^2\) โ€“ rather than by evaluating the Jacobian pullback and keeping its diagonal. The pullback is NaN across the whole \(\nu = 0\) plane, which the domain admits and pt_map round-trips exactly: forward-mode AD evaluates \(\mathrm{d}\sqrt{t}\) as \(\tfrac{1}{2\sqrt{t}}\,\dot t\), so at \(t = 0\) every column picks up \(\infty \times 0\), and the whole \(\mathrm{d}z\) row came back NaN โ€“ including \(\partial z/\partial\phi\), which is identically zero. Only \(g_{\nu\nu}\) is genuinely singular there; \(g_{\mu\mu}\) and \(g_{\phi\phi}\) have finite limits and were being lost with it.

The result is declared diagonal, which is what tells coordinax.manifolds.scale_factors this chart is orthogonal.

coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: AbstractChart, /) → DenseMetric
Parameters:
Return type:

Any

Euclidean metric in a general chart via Jacobian pullback g = J^T J.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DenseMetric
>>> from coordinax._src.charts.d3 import LonCosLatSpherical3D

Non-orthogonal chart (fallback, returns DenseMetric):

>>> M = cxm.R3
>>> chart = LonCosLatSpherical3D()
>>> at = {
...     "lon_coslat": u.Angle(0.0, "rad"),
...     "lat": u.Angle(0.0, "rad"),
...     "distance": u.Q(2.0, "m"),
... }
>>> g = metric_matrix(M, at, chart)
>>> isinstance(g, DenseMetric)
True
coordinax.manifolds.metric_matrix(M: CartesianProductManifold, point: dict, chart: AbstractCartesianProductChart, /) → DenseMetric
Parameters:
Return type:

Any

Product metric (block-diagonal) in a product chart.

Assembles the block-diagonal matrix from factor metrics by recursively calling the standalone metric_matrix dispatch API.

>>> import jax.numpy as jnp
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DenseMetric

Two-factor Euclidean product (Rยฒ x Rยน):

>>> M = cxm.CartesianProductManifold(
...     factors=(cxm.R2, cxm.R1), factor_names=("xy", "z")
... )
>>> chart = M.default_chart()
>>> at = {k: jnp.array(0.0) for k in chart.components}
>>> g = metric_matrix(M, at, chart)
>>> isinstance(g, DenseMetric)
True
>>> g.ndim
3
coordinax.manifolds.metric_matrix(M: EmbeddedManifold, point: dict, chart: AbstractChart, /) → DenseMetric
Parameters:
Return type:

Any

Induced metric on an embedded submanifold via Jacobian pullback.

Computes \(g_{ij} = \sum_{kl} J^k_i G_{kl} J^l_j\) where \(J\) is the Jacobian of the composition chart โ†’ intrinsic โ†’ Cartesian ambient (the chart โ†’ intrinsic leg is the identity when chart is the intrinsic chart) and \(G\) is the ambient metric at the embedded point. \(G\) is the identity for a Euclidean ambient, so the familiar \(J^T J\) is the special case; for a Lorentzian ambient \(G = \eta\) and the induced metric of a timelike direction is correctly negative.

Routing through Cartesian ambient coordinates makes every ambient output share the single unit cart_unit (so column i of \(J\) has unit cart_unit / chart_unit_i) and makes \(G\) dimensionless; each g_{ij} term then has a consistent unit cart_unit^2 / (chart_unit_i * chart_unit_j) and the result carries physically correct units.

Parameters:
  • M (EmbeddedManifold) โ€“ An embedded submanifold; carries intrinsic, ambient, and embed_map fields.

  • point (dict) โ€“ A coordinate dictionary in the passed chartโ€™s coordinates.

  • chart (AbstractChart) โ€“ The chart in which point is expressed and in which the metric is returned; mapped into the embeddingโ€™s intrinsic chart when the two differ.

Returns:

Induced metric matrix at point, backed by a QuantityMatrix with units cart_unit^2 / (chart_unit_i * chart_unit_j).

Return type:

DenseMetric

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DenseMetric

Unit sphere โ€” values should be the identity:

>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=1.0),
... )
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> g = metric_matrix(M, p, cxc.sph2)
>>> isinstance(g, DenseMetric)
True
>>> g.matrix.value
Array([[1., 0.],
       [0., 1.]], dtype=float64)

Radius-2 sphere โ€” metric scaled by Rยฒ:

>>> M2 = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")),
... )
>>> g2 = metric_matrix(M2, p, cxc.sph2)
>>> g2.matrix.value
Array([[4., 0.],
       [0., 4.]], dtype=float64)
>>> g2.matrix.unit[0, 0]
Unit("m2 / rad2")

The metric is returned in the coordinates of the passed chart:

>>> p_ll = {"lon": u.Angle(0.0, "rad"), "lat": u.Angle(jnp.pi / 3, "rad")}
>>> metric_matrix(M, p_ll, cxc.lonlat_sph2).matrix.value
Array([[0.25, 0.  ],
       [0.  , 1.  ]], dtype=float64)
coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict, chart: AbstractSphericalHyperSphere, /) → DiagonalMetric
Parameters:
Return type:

Any

Round metric on the unit \(n\)-sphere in a standard angular chart.

Computes diagonal entries directly via _sine_product_diagonal:

\[g_{kk} = \prod_{j=0}^{k-1} \sin^2(\theta_j)\]
>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric

\(S^2\) at the equator \(\theta = \pi/2\):

>>> M = cxm.HyperSphericalManifold(2)
>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)}
>>> g = metric_matrix(M, at, cxc.sph2)
>>> isinstance(g, DiagonalMetric)
True
>>> bool(jnp.allclose(g.diagonal.value, jnp.array([1.0, 1.0])))
True

\(S^2\) at \(\theta = \pi/6\):

>>> at = {"theta": jnp.array(jnp.pi / 6), "phi": jnp.array(0.0)}
>>> g = metric_matrix(M, at, cxc.sph2)
>>> round(float(g.diagonal[1]), 10)  # sin\u00b2(\u03c0/6) \u2248 0.25
0.25
coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict, chart: LonCosLatSphericalTwoSphere, /) → DenseMetric
Parameters:
Return type:

Any

Round metric on the non-orthogonal LonCosLat chart (pullback).

The nested sine-product used by the canonical dispatch assumes the components are polar angles in nested order; that is false for these charts (LonLat, Math swap/relabel the polar and azimuthal angles; LonCosLat is non-orthogonal). The metric is instead pulled back from the canonical chart: g = Jc^T diag(1, sin^2 theta) Jc, where Jc is the Jacobian of the coordinate map chart -> SphericalTwoSphere.

>>> import math
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

g = [[1, a], [a, 1 + a^2]] with a = lon_coslat * tan(lat); note the nonzero off-diagonal, and that det g == 1 (the area element is dL dlat):

>>> at = {"lon_coslat": u.Q(0.4, "rad"), "lat": u.Q(0.7, "rad")}
>>> g = cxm.metric_matrix(cxm.S2, at, cxc.loncoslat_sph2)
>>> [round(float(v), 4) for v in g.matrix.value.ravel()]
[1.0, 0.3369, 0.3369, 1.1135]
>>> round(float(math.prod([0.4, math.tan(0.7)])), 4)
0.3369

Leading axes are batch; the two component axes trail:

>>> import jax.numpy as jnp
>>> at = {"lon_coslat": u.Q(jnp.zeros((5,)), "rad"),
...       "lat": u.Q(jnp.zeros((5,)), "rad")}
>>> cxm.metric_matrix(cxm.S2, at, cxc.loncoslat_sph2).matrix.value.shape
(5, 2, 2)

Warning

LonCosLat is not a chart at the poles: lon_coslat = lon * cos(lat) collapses every longitude onto 0 at lat = +-pi/2, so the map is not injective there. Away from the poles g_LL = cos^2(lat) * sec^2(lat) is exactly 1 and det g is exactly 1; evaluated at a pole that product becomes 0 * inf and the returned matrix is degenerate (det g == 0) rather than the limiting identity. Precision degrades within roughly 1e-10 rad of the pole, where the chartโ€™s condition number ~ lon_coslat^2 tan^2(lat) exceeds what float64 can carry. LonLat and Math are genuinely singular at their poles too, but there the degenerate metric is the correct limit.

coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict, chart: LonLatSphericalTwoSphere | MathSphericalTwoSphere, /) → DiagonalMetric
Parameters:
Return type:

Any

Round metric on an orthogonal relabelled two-sphere chart.

LonLat and Math relabel/swap the canonical angles, so the nested sine-product does not apply, but they stay orthogonal: the pullback is exactly diagonal, so only the diagonal is kept.

  • LonLat (lon, lat): diag(cos^2(lat), 1)

  • Math (theta, phi): diag(sin^2(phi), 1)

>>> import math
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {"lon": u.Q(0.6, "rad"), "lat": u.Q(math.radians(40), "rad")}
>>> g = cxm.metric_matrix(cxm.S2, at, cxc.lonlat_sph2)
>>> [round(float(v), 4) for v in g.diagonal.value]
[0.5868, 1.0]

Leading axes are batch; the component axis trails:

>>> import jax.numpy as jnp
>>> at = {"lon": u.Q(jnp.zeros((5,)), "rad"), "lat": u.Q(jnp.zeros((5,)), "rad")}
>>> cxm.metric_matrix(cxm.S2, at, cxc.lonlat_sph2).diagonal.value.shape
(5, 2)
coordinax.manifolds.metric_matrix(M: MinkowskiManifold, point: dict, chart: MinkowskiCT, /) → DiagonalMetric
Parameters:
Return type:

Any

Minkowski metric \(\eta = \operatorname{diag}(-1, 1, 1, 1)\) in CT chart.

All four components (ct, x, y, z) carry dimension "length", so the entries are dimensionless.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> M = cxm.MinkowskiManifold()
>>> at = {"ct": jnp.array(0.0), "x": jnp.array(1.0),
...       "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> g = metric_matrix(M, at, cxc.minkowskict)
>>> isinstance(g, DiagonalMetric)
True
>>> g.diagonal
Array([-1.,  1.,  1.,  1.], dtype=float64)
coordinax.manifolds.metric_matrix(M: MinkowskiManifold, point: dict, chart: AbstractChart, /) → DenseMetric
Parameters:
Return type:

Any

Minkowski metric in a general chart via Jacobian pullback \(g = J^T \eta J\).

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix
>>> from coordinax._src.metric.matrix import DiagonalMetric

Canonical chart uses the specific dispatch above, not this fallback:

>>> M = cxm.MinkowskiManifold()
>>> at = {"ct": jnp.array(0.0), "x": jnp.array(1.0),
...       "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> g = metric_matrix(M, at, cxc.minkowskict)
>>> isinstance(g, DiagonalMetric)
True
coordinax.manifolds.metric_representation(M, chart, /)#

Return the AbstractMetricMatrix subtype for (manifold, chart).

A lightweight, allocation-free query that reports which concrete ~coordinax._src.metric.matrix.AbstractMetricMatrix subclass metric_matrix() will return for a given (manifold, chart) pair, without actually computing the metric values.

Dispatches on (type(manifold), type(chart)). Concrete rules are registered in the relevant register_metric.py modules. The default fallback returns ~coordinax._src.metric.matrix.DenseMetric.

Parameters:
  • M (Any) โ€“ The manifold.

  • chart (Any) โ€“ The coordinate chart.

Returns:

The metric matrix type guaranteed for this (manifold, chart) pair.

Return type:

Any

Examples

>>> import coordinaxs.api.manifolds as cxmapi
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxmapi.metric_representation(cxm.R3, cxc.cart3d) is cxm.DiagonalMetric
True
coordinax.manifolds.metric_representation(M: AbstractManifold, chart: AbstractChart, /) → type[AbstractMetricMatrix]
Parameters:
Return type:

Any

Return DenseMetric as the default fallback.

More specific rules (e.g. for Cartesian charts) override this and return DiagonalMetric.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> from coordinaxs.api.manifolds import metric_representation
>>> from coordinax._src.metric.matrix import DenseMetric
>>> from coordinax._src.charts.d3 import LonCosLatSpherical3D

Non-orthogonal chart falls back to DenseMetric:

>>> metric_representation(cxm.R3, LonCosLatSpherical3D()) is DenseMetric
True
coordinax.manifolds.metric_representation(M: EuclideanManifold, chart: AbstractChart, /) → type[DenseMetric]
Parameters:
Return type:

Any

Euclidean manifold in a general (non-Cartesian) chart โ†’ DenseMetric.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_representation
>>> from coordinax._src.metric.matrix import DenseMetric
>>> from coordinax._src.charts.d3 import LonCosLatSpherical3D
>>> chart = LonCosLatSpherical3D()
>>> metric_representation(cxm.R3, chart)
<class 'coordinax._src.metric.matrix.DenseMetric'>
coordinax.manifolds.metric_representation(M: EuclideanManifold, chart: Cart1D | Cart2D | Cart3D | CartND | Radial1D | Polar2D | Cylindrical3D | Spherical3D | MathSpherical3D | LonLatSpherical3D | ProlateSpheroidal3D, /) → type[DiagonalMetric]
Parameters:
Return type:

Any

Euclidean manifold in a Cartesian or orthogonal curvilinear chart.

Returns DiagonalMetric.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_representation
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> metric_representation(cxm.R3, cxc.cart3d)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>
>>> metric_representation(cxm.R2, cxc.polar2d)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>
>>> metric_representation(cxm.R3, cxc.sph3d)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>

Prolate spheroidal coordinates are orthogonal too โ€“ reparameterising each of the confocal coordinates separately does not couple them:

>>> import unxt as u
>>> chart = cxc.ProlateSpheroidal3D(Delta=u.Q(1.0, "m"))
>>> metric_representation(cxm.R3, chart)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>
coordinax.manifolds.metric_representation(M: CartesianProductManifold, chart: AbstractCartesianProductChart, /) → type[DenseMetric]
Parameters:
Return type:

Any

Product manifold in a product chart โ†’ DenseMetric.

The product metric is block-diagonal in general (not necessarily diagonal even if each factor metric is diagonal), so DenseMetric is the conservative declaration.

>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_representation
>>> from coordinax._src.metric.matrix import DenseMetric
>>> M = cxm.CartesianProductManifold(
...     factors=(cxm.R2, cxm.R1), factor_names=("xy", "z")
... )
>>> chart = M.default_chart()
>>> metric_representation(M, chart)
<class 'coordinax._src.metric.matrix.DenseMetric'>
coordinax.manifolds.metric_representation(M: EmbeddedManifold, chart: AbstractChart, /) → type[DenseMetric]
Parameters:
Return type:

Any

Embedded manifold in any intrinsic chart โ†’ DenseMetric.

>>> import unxt as u
>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> from coordinaxs.api.manifolds import metric_representation
>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")),
... )
>>> metric_representation(M, cxc.sph2)
<class 'coordinax._src.metric.matrix.DenseMetric'>
coordinax.manifolds.metric_representation(M: HyperSphericalManifold, chart: AbstractSphericalHyperSphere, /) → type[DiagonalMetric]
Parameters:
Return type:

Any

Return DiagonalMetric for a unit \(n\)-sphere in a standard angular chart.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.metric_representation(cxm.S2, cxc.sph2)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>
coordinax.manifolds.metric_representation(M: HyperSphericalManifold, chart: LonCosLatSphericalTwoSphere, /) → type[DenseMetric]
Parameters:
Return type:

Any

LonCosLat is non-orthogonal, so its round metric is genuinely dense.

Its off-diagonal is g_01 = lon_coslat * tan(lat), nonzero away from the equator.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.metric_representation(cxm.S2, cxc.loncoslat_sph2)
<class 'coordinax._src.metric.matrix.DenseMetric'>
coordinax.manifolds.metric_representation(M: HyperSphericalManifold, chart: LonLatSphericalTwoSphere | MathSphericalTwoSphere, /) → type[DiagonalMetric]
Parameters:
Return type:

Any

LonLat and Math relabel the canonical angles but stay orthogonal.

Their round metrics are exactly diagonal โ€“ diag(cos^2(lat), 1) and diag(sin^2(phi), 1) โ€“ so DiagonalMetric is the accurate classification and gives callers the O(n) diagonal path.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxm.metric_representation(cxm.S2, cxc.lonlat_sph2)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>
coordinax.manifolds.metric_representation(M: MinkowskiManifold, chart: AbstractChart, /) → type[DenseMetric]
Parameters:
Return type:

Any

Minkowski manifold in a general chart โ†’ DenseMetric.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_representation
>>> from coordinax._src.metric.matrix import DenseMetric
>>> M = cxm.MinkowskiManifold()
>>> metric_representation(M, cxc.minkowskict)
<class 'coordinax._src.metric.matrix.DenseMetric'>
coordinax.manifolds.metric_representation(M: MinkowskiManifold, chart: MinkowskiCT, /) → type[DiagonalMetric]
Parameters:
Return type:

Any

Minkowski manifold in the canonical CT chart โ†’ DiagonalMetric.

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_representation
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> M = cxm.MinkowskiManifold()
>>> metric_representation(M, cxc.minkowskict)
<class 'coordinax._src.metric.matrix.DiagonalMetric'>
coordinax.manifolds.norm(v, metric, /, *args, **kwargs)#

Compute the Riemannian norm of a vector.

Dispatches on the type of the second argument (metric-related) and the type of v.

coordinax.manifolds.norm(G: Array, v: Array, /) → Array
Parameters:
Return type:

Any

Compute the norm of a vector using a general (possibly curved) metric.

This assumes G is evaluated at the correct chart and position, and v components match the chartโ€™s ordering.

Examples

>>> import jax.numpy as jnp
>>> from coordinax.manifolds import norm

Identity metric (Euclidean), 3-4-0 vector:

>>> G = jnp.eye(3)
>>> v = jnp.array([3, 4, 0])
>>> norm(G, v)
Array(5., dtype=float64)
coordinax.manifolds.norm(G: Array, v: AbstractQuantity, /) → AbstractQuantity
Parameters:
Return type:

Any

Compute the norm of a vector using a general (possibly curved) metric.

This assumes G is evaluated at the correct chart and position, and v components match the chartโ€™s ordering.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> from coordinax.manifolds import norm

Identity metric (Euclidean), quantity vector:

>>> G = jnp.eye(3)
>>> v = u.Q(jnp.array([3.0, 4.0, 0.0]), "m/s")
>>> norm(G, v)
Q(5., 'm / s')
coordinax.manifolds.norm(G: unxts.linalg._src._quantity_matrix.QuantityMatrix, v: AbstractQuantity, /) → AbstractQuantity
Parameters:
Return type:

Any

Compute the norm of a vector using a general (possibly curved) metric.

This assumes G is evaluated at the correct chart and position, and v components match the chartโ€™s ordering.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import unxts.linalg as ul
>>> from coordinax.manifolds import norm

Dimensionless identity metric, quantity vector:

>>> dmls = ("", "", "")
>>> G = ul.QuantityMatrix(jnp.eye(3), unit=(dmls, dmls, dmls))
>>> v = u.Q(jnp.array([3.0, 4.0, 0.0]), "m/s")
>>> norm(G, v)
Q(5., 'm / s')
coordinax.manifolds.norm(G: Array, v: dict, /) → Array | AbstractQuantity
Parameters:
Return type:

Any

Compute the norm of a vector using a general (possibly curved) metric.

This assumes G is evaluated at the correct chart and position, and v components match the chartโ€™s ordering.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> from coordinax.manifolds import norm

Identity metric (Euclidean), CDict of quantities:

>>> G = jnp.eye(3)
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> norm(G, v)
Q(5., 'm / s')

Identity metric (Euclidean), CDict of bare arrays โ€” returns a bare Array:

>>> v = {"x": jnp.array(3.0), "y": jnp.array(4.0), "z": jnp.array(0.0)}
>>> norm(G, v)
Array(5., dtype=float64)
coordinax.manifolds.norm(v: dict, metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Norm of a vector w.r.t. the metric and chart.

  • Quantity values (any unit combination): usys is optional. The result is an AbstractQuantity whose unit reflects the physical dimensions of the norm (e.g. m / s when components mix m/s and 1/s). Mixed-unit components are handled correctly via {class}`~unxts.linalg.QuantityMatrix` arithmetic.

  • Bare jax.Array values: usys is required (raises TypeError if omitted); a plain jax.Array is returned.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

CDict of same-unit quantities โ€” returns Quantity:

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> cxm.norm(v, metric, cxc.cart3d, at=at)
Q(5., 'm / s')

CDict of mixed-unit quantities on a spherical chart โ€” also returns Quantity. At r = 5 m, ฮธ = ฯ€/2 the metric is G = diag(1, 25 mยฒ, 25 mยฒ), so radial velocity (1 m/s, 0, 0) has norm 1 m/s:

>>> at_sph = {"r": u.Q(5.0, "m"), "theta": u.Q(jnp.pi / 2, "rad"),
...           "phi": u.Q(0.0, "rad")}
>>> v_mixed = {"r": u.Q(1.0, "m/s"), "theta": u.Q(0.0, "rad/s"),
...            "phi": u.Q(0.0, "rad/s")}
>>> cxm.norm(v_mixed, metric, cxc.sph3d, at=at_sph)
Q(1., 'm / s')

CDict of bare arrays (usys required) โ€” returns bare Array:

>>> v_bare = {"x": jnp.array(3.0), "y": jnp.array(4.0), "z": jnp.array(0.0)}
>>> cxm.norm(v_bare, metric, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)

This was using the Euclidean metric, but it also works on curved metrics.

>>> metric = cxm.RoundMetric(2)
>>> at = {"theta": u.Q(jnp.pi / 4, "rad"), "phi": u.Q(1.0, "rad")}
>>> v = {"theta": u.Q(0.5, "rad/s"), "phi": u.Q(0.5, "rad/s")}
>>> cxm.norm(v, metric, cxc.sph2, at=at)
Q(0.61237244, 'rad / s')

An indefinite metric has no real-valued norm and is rejected rather than silently returning nan:

>>> v4 = {"ct": u.Q(5.0, "m"), "x": u.Q(1.0, "m"),
...       "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> at4 = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> try:
...     cxm.norm(v4, cxm.MinkowskiMetric(), cxc.minkowskict, at=at4)
... except NotImplementedError as e:
...     print(str(e)[:52])
norm() supports only positive-definite metrics, but
coordinax.manifolds.norm(v: AbstractQuantity, metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → AbstractQuantity
Parameters:
Return type:

Any

Dispatch for a AbstractQuantity.

The quantity is wrapped in a one-element CDict using the chartโ€™s first (and only) component name, then the CDict overload is invoked.

This overload is primarily useful for 1-D manifolds (e.g. EuclideanManifold(1) with a Cart1D chart).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

Norm of a 1-D displacement on the real line:

>>> M = cxm.FlatMetric(1)
>>> at = {"x": jnp.array(0.0)}
>>> cxm.norm(u.Q(jnp.array([5.0]), "m/s"), M, cxc.cart1d, at=at)
Q(5., 'm / s')
coordinax.manifolds.norm(v: Array, metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Dispatch for a packed 1-D jax.Array (v first).

v is a stacked array whose entries correspond to the chartโ€™s component ordering (chart.components). Because the array carries no unit information, usys is required; passing usys=None (the default) raises TypeError.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

3-4-0 triple in Cartesian 3-D:

>>> M = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> cxm.norm(jnp.array([3.0, 4.0, 0.0]), M, cxc.cart3d,
...          at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)

Missing usys raises TypeError:

>>> try:
...     cxm.norm(jnp.array([3.0, 4.0, 0.0]), M, cxc.sph3d, at=at)
... except TypeError as e:
...     print(e)
norm(): `usys` is required when `v` is a bare jax.Array ...

Unit-sphere Sยฒ: v = (1, 0) at the equator has norm 1:

>>> M = cxm.RoundMetric(2)
>>> at_eq = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)}
>>> cxm.norm(jnp.array([1.0, 0.0]), M, cxc.sph2,
...          at=at_eq, usys=u.unitsystems.si)
Array(1., dtype=float64)
coordinax.manifolds.norm(v: dict, chart: AbstractChart, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Norm of a CDict vector w.r.t. the chart.

Dispatches to the metric-level overload via the chartโ€™s attached metric. For flat (Euclidean) charts at is optional; for curved metrics it is required and passed through to metric.metric_matrix.

coordinax.manifolds.norm(v: Array, chart: AbstractChart, /, *, at: Any = None, usys: Any = None) → Array
Parameters:
Return type:

Any

Norm of a packed jax.Array w.r.t. the chart.

Delegates to the metric-level norm(v, metric, chart, ...) overload via the chartโ€™s attached metric. For Euclidean Cartesian charts the fast-path jnp.linalg.norm overload is picked automatically. For curved charts at and usys are required; missing usys raises TypeError.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

Euclidean fast-path (no at needed):

>>> cxm.norm(jnp.array([3.0, 4.0, 0.0]), cxc.cart3d)
Array(5., dtype=float64)

Curved chart (at and usys required):

>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)}
>>> cxm.norm(jnp.array([1.0, 0.0]), cxc.sph2, at=at, usys=u.unitsystems.si)
Array(1., dtype=float64)
coordinax.manifolds.norm(v: AbstractQuantity, chart: AbstractChart, /, *, at: Any = None, usys: Any = None) → AbstractQuantity
Parameters:
Return type:

Any

Norm of a single AbstractQuantity w.r.t. the chart.

Wraps the quantity in a one-element CDict keyed by the chartโ€™s first component name, then delegates to the metric-level overload. Primarily useful for 1-D charts (Cart1D).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> at = {"x": jnp.array(0.0)}
>>> cxm.norm(u.Q(5.0, "m/s"), cxc.cart1d, at=at)
Q(5., 'm / s')
>>> cxm.norm(u.Q(-3.0, "m"), cxc.cart1d, at=at)
Q(3., 'm')
coordinax.manifolds.norm(v: Array, metric: FlatMetric, chart: Cart0D | Cart1D | Cart2D | Cart3D | CartND, /, *, at: Any = None, usys: Any = None) → Array
Parameters:
Return type:

Any

Short-circuit: Euclidean Cartesian chart -> jnp.linalg.norm.

When both the metric is flat (FlatMetric) and the chart is Cartesian (Cart3D), the metric matrix is the 3x3 identity, so โ€–vโ€– = โˆš(vแต€ I v) = โ€–vโ€–โ‚‚. This overload skips the general matrix computation and calls jnp.linalg.norm directly. at and usys are ignored.

Examples

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

3-4-0 triple gives norm 5:

>>> cxm.norm(jnp.array([3.0, 4.0, 0.0]), M, cxc.cart3d, at=at)
Array(5., dtype=float64)

at and usys are unused and technically skippable in this fast path:

>>> cxm.norm(jnp.array([1.0, 0.0, 0.0]), M, cxc.cart3d)
Array(1., dtype=float64)
coordinax.manifolds.norm(data: dict, metric: FlatMetric, chart: Cart0D | Cart1D | Cart2D | Cart3D, /, *, at: dict | None = None, usys: Any = None) → AbstractQuantity | Array
Parameters:
Return type:

Any

Compute the Euclidean norm of a vector.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> M = cxm.FlatMetric(3)
>>> v = {"x": u.Q(3, "m"), "y": u.Q(4, "m"), "z": u.Q(0, "m")}
>>> cxm.norm(v, M, cxc.cart3d)
Q(5., 'm')

Works in any dimension:

>>> M = cxm.FlatMetric(2)
>>> v2 = {"x": u.Q(3, "km"), "y": u.Q(4, "km")}
>>> cxm.norm(v2, M, cxc.cart2d)
Q(5., 'km')
Parameters:
Return type:

Any

coordinax.manifolds.chord_distance(*args, **kwargs)#

Straight-line distance between two points through their ambient space.

The chord, as opposed to geodesic_distanceโ€™s path along the manifold. Defined wherever the manifold carries an embedding; a manifold that is its own ambient space has no distinct chord and is refused.

coordinax.manifolds.chord_distance(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Return the ambient straight-line distance, on the chartโ€™s manifold.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

A quarter turn along the equator of the unit sphere: the arc is pi / 2, the chord through the interior is sqrt(2).

>>> a = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> b = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(jnp.pi / 2, "rad")}
>>> round(float(cxm.chord_distance(cxc.sph2, a, b)), 6)
1.414214
>>> round(float(cxm.geodesic_distance(cxc.sph2, a, b).ustrip("rad")), 6)
1.570796

Antipodes are one diameter apart through the middle, half the great-circle distance around the outside:

>>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")}
>>> s = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")}
>>> round(float(cxm.chord_distance(cxc.sph2, n, s)), 6)
2.0
coordinax.manifolds.chord_distance(M: HyperSphericalManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Return the chord of the unit hypersphere, through its canonical embedding.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

Any chart on the sphere gives the same answer:

>>> a = {"lon": u.Angle(0.0, "rad"), "lat": u.Angle(0.0, "rad")}
>>> b = {"lon": u.Angle(jnp.pi / 2, "rad"), "lat": u.Angle(0.0, "rad")}
>>> round(float(cxm.chord_distance(cxc.lonlat_sph2, a, b)), 6)
1.414214
coordinax.manifolds.chord_distance(M: EmbeddedManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Return the chord through the manifoldโ€™s own ambient space.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

A sphere of radius 2 m: antipodes are one diameter apart.

>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")),
... )
>>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")}
>>> s = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")}
>>> cxm.chord_distance(M, cxc.sph2, n, s).round(6)
Distance(4., 'm')
coordinax.manifolds.chord_distance(M: EuclideanManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Refuse: flat space is its own ambient, so the chord is the geodesic.

Returning the same number under a second name invites the reader to think two things were measured.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")}
>>> try: cxm.chord_distance(cxc.cart3d, a, b)
... except NotImplementedError as e: print(e)
chord_distance is a measurement through an ambient space, and Rn(3) is its
own ambient -- its chord is the straight line, which is what
`geodesic_distance` already returns.
coordinax.manifolds.chord_distance(M: AbstractManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Refuse: without an embedding there is no ambient space to cut through.

coordinax.manifolds.chord_distance(a: Point, b: Point, /) → Any
Parameters:
Return type:

Any

Straight-line distance between two points, through their ambient space.

The counterpart to ~coordinax.geodesic_distanceโ€™s Point overload: that one measures along the manifold, this one through the space it is embedded in. The result is invariant to the chart each operand happens to use.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc

A quarter turn along the equator: the arc is pi / 2, the chord through the interior is sqrt(2).

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

The operands need not share a chart:

>>> r = cx.Point({"lon": u.Angle(jnp.pi / 2, "rad"),
...               "lat": u.Angle(0.0, "rad")}, chart=cxc.lonlat_sph2)
>>> round(float(cx.chord_distance(p, r)), 6)
1.414214

Flat space is its own ambient, so it has no chord distinct from its geodesic:

>>> a = cx.Point.from_([3.0, 0.0, 0.0], "m")
>>> b = cx.Point.from_([0.0, 4.0, 0.0], "m")
>>> try: cx.chord_distance(a, b)
... except NotImplementedError as e: print(str(e)[:48])
chord_distance is a measurement through an ambie
Parameters:
Return type:

Any

coordinax.manifolds.geodesic_distance(*args, **kwargs)#

Distance between two points on a manifold.

The length of the shortest path along the manifold: the straight line in flat space, the great circle on a sphere. It is computed from the manifoldโ€™s geometry, not from the coordinate difference, whose norm is asymmetric on a curved manifold and so is not a distance at all. A manifold with no closed-form geodesic raises rather than approximating.

Dispatches on the inputs: two ~coordinax.vectors.Point objects (chart/frame extracted automatically), or a chart / metric together with the two points as component dictionaries, packed quantities, or bare arrays.

coordinax.manifolds.geodesic_distance(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Geodesic distance between two points, on the chartโ€™s manifold.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

A 3-4-5 triangle in flat space:

>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")}
>>> cxm.geodesic_distance(cxc.cart3d, a, b).round(2)
Distance(5., 'm')

The same two points in a curvilinear chart give the same answer, which the norm of their coordinate difference would not:

>>> a_sph = cxc.pt_map(a, cxc.cart3d, cxc.sph3d)
>>> b_sph = cxc.pt_map(b, cxc.cart3d, cxc.sph3d)
>>> cxm.geodesic_distance(cxc.sph3d, a_sph, b_sph).round(2)
Distance(5., 'm')

On the unit sphere it is the great-circle distance, and symmetric:

>>> import jax.numpy as jnp
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> q = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(1.0, "rad")}
>>> round(float(cxm.geodesic_distance(cxc.sph2, p, q).ustrip("rad")), 6)
1.0
>>> round(float(cxm.geodesic_distance(cxc.sph2, q, p).ustrip("rad")), 6)
1.0
coordinax.manifolds.geodesic_distance(M: EuclideanManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Return the straight-line distance, measured in the Cartesian chart.

Mapping both points into the manifoldโ€™s Cartesian chart first is what makes this chart-invariant and symmetric. Measuring ||b - a|| in the chart the caller happened to use is neither, unless that chart is already Cartesian.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = {"r": u.Q(2.0, "m"), "theta": u.Angle(1.0, "rad"),
...      "phi": u.Angle(0.4, "rad")}
>>> b = {"r": u.Q(3.0, "m"), "theta": u.Angle(1.3, "rad"),
...      "phi": u.Angle(0.9, "rad")}
>>> round(float(cxm.geodesic_distance(cxc.sph3d, a, b).ustrip("m")), 6)
1.651376
coordinax.manifolds.geodesic_distance(chart: AbstractChart, a: AbstractQuantity, b: AbstractQuantity, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Distance between two points given as packed unxt.Quantity vectors.

Each quantityโ€™s trailing axis holds the components in chart.components order; it is unpacked into a component dictionary and delegated to the CDict overload.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = u.Q([3.0, 0.0, 0.0], "m")
>>> b = u.Q([0.0, 4.0, 0.0], "m")
>>> cxm.geodesic_distance(cxc.cart3d, a, b).round(2)
Distance(5., 'm')
coordinax.manifolds.geodesic_distance(chart: AbstractChart, a: Array, b: Array, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Distance between two points given as packed (unitless) arrays.

The trailing axis holds the components in chart.components order.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = jnp.array([3.0, 0.0, 0.0])
>>> b = jnp.array([0.0, 4.0, 0.0])
>>> float(cxm.geodesic_distance(cxc.cart3d, a, b))
5.0
coordinax.manifolds.geodesic_distance(M: HyperSphericalManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Return the great-circle distance on the unit hypersphere.

The points are embedded into the ambient Cartesian space as unit vectors and the central angle between them is taken, which for a unit sphere is the arc length. Any chart on the sphere works: the points are routed through the embeddingโ€™s own intrinsic chart first, so the answer does not depend on which one the caller used.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

A quarter turn along the equator is pi / 2:

>>> a = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> b = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(jnp.pi / 2, "rad")}
>>> round(float(cxm.geodesic_distance(cxc.sph2, a, b).ustrip("rad")), 6)
1.570796

Antipodes are pi apart โ€“ the coordinate-difference norm cannot reach this, since it has no way to know the sphere closes up:

>>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sth = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")}
>>> round(float(cxm.geodesic_distance(cxc.sph2, n, sth).ustrip("rad")), 6)
3.141593
coordinax.manifolds.geodesic_distance(M: EmbeddedManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Embedded sphere: the arc length, i.e. the radius times the central angle.

Delegates the angle to the intrinsic manifoldโ€™s own rule rather than re-deriving it, so there is one implementation of the great circle. Only the radius is applied here, which is what the embedding adds.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")),
... )
>>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")}
>>> s = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")}
>>> cxm.geodesic_distance(M, cxc.sph2, n, s).round(4)
Distance(6.2832, 'm')

Reached through an EmbeddedChart too, whose manifold is this one:

>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")))
>>> cxm.geodesic_distance(chart, n, s).round(4)
Distance(6.2832, 'm')
coordinax.manifolds.geodesic_distance(M: AbstractManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Refuse: no closed-form geodesic is known for this manifold.

Refused rather than approximated: the norm of the coordinate difference, which this used to return, is asymmetric on a curved manifold and so is not a distance at all.

coordinax.manifolds.geodesic_distance(M: MinkowskiManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Refuse: a pseudo-Riemannian manifold has no Riemannian distance.

The Minkowski metric is indefinite, so g(v, v) is negative for a timelike pair and its square root is not a length. Returning nan there while returning a plausible number for a spacelike pair โ€“ which is what taking the root unguarded does โ€“ hides the failure in exactly the half of spacetime a reader is least likely to probe.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> origin = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> event = {"ct": u.Q(5.0, "m"), "x": u.Q(1.0, "m"),
...          "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> try: cxm.geodesic_distance(cxc.minkowskict, origin, event)
... except NotImplementedError as e: print(e)
geodesic_distance() requires a Riemannian (positive-definite) metric;
MinkowskiManifold() is pseudo-Riemannian, whose indefinite metric
admits no distance. Use `interval` for the signed square, `proper_time`
for a timelike pair, or `proper_distance` for a spacelike one.
coordinax.manifolds.geodesic_distance(metric: AbstractMetricField, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Geodesic distance with the metric stated explicitly.

The geodesic is a property of the manifold, so this checks that metric is the one chart carries and then defers to the manifold rule. It exists so a caller can be explicit, and so a mismatched metric is refused rather than silently replaced by the chartโ€™s.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")}
>>> cxm.geodesic_distance(cxm.FlatMetric(3), cxc.cart3d, a, b).round(2)
Distance(5., 'm')
coordinax.manifolds.geodesic_distance(chart: Cart0D | Cart1D | Cart2D | Cart3D | CartND, a: Any, b: Any, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Packed operands in a Cartesian chart: measure without unpacking.

Registered as two signatures rather than one with a Quantity | Array union: each generic overload it overrides is typed for one of them, and a union would be narrower in the chart but wider in the operands, so plum would rank neither more specific and refuse the call as ambiguous.

The generic packed overloads split the operands into component dicts so a curvilinear chart can be mapped to Cartesian. Here there is nothing to map, and the components are already on the trailing axis in the right order, so the norm is taken directly.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = u.Q(jnp.asarray([3.0, 0.0, 0.0]), "m")
>>> b = u.Q(jnp.asarray([0.0, 4.0, 0.0]), "m")
>>> cxm.geodesic_distance(cxc.cart3d, a, b).round(2)
Distance(5., 'm')
>>> float(cxm.geodesic_distance(cxc.cart3d, jnp.asarray([3.0, 0.0, 0.0]),
...                             jnp.asarray([0.0, 4.0, 0.0])))
5.0
coordinax.manifolds.geodesic_distance(a: Point, b: Point, /) → Any
Parameters:
Return type:

Any

Distance between two points, via the manifold norm.

The two points are brought into a common Cartesian chart (so the result is invariant to the chart and component units each operand happens to use), and the distance is the manifold ~coordinax.manifolds.norm of their coordinate difference โ€“ the Euclidean distance for a flat manifold. A length result is returned as a Distance; a unitless (dimensionless) result is returned as a bare array.

Dimensionality follows the pointsโ€™ manifold: 2-D points give a 2-D distance, 3-D points a 3-D distance. There is no separation_3d โ€“ to measure in a particular N-D space, map the points into it first, then call geodesic_distance.

Examples

>>> import coordinax as cx
>>> import coordinax.charts as cxc

A 3-4-5 right triangle:

>>> p = cx.Point.from_([3.0, 0.0, 0.0], "m")
>>> q = cx.Point.from_([0.0, 4.0, 0.0], "m")
>>> cx.geodesic_distance(p, q).round(2)
Distance(5., 'm')

Chart- and unit-invariant โ€“ the same points expressed differently give the same distance:

>>> cx.geodesic_distance(p, q.cconvert(cxc.sph3d)).round(2)
Distance(5., 'm')
>>> q_km = cx.Point.from_([0.0, 0.004, 0.0], "km")
>>> cx.geodesic_distance(p, q_km).uconvert("m").round(2)
Distance(5., 'm')

The distance lives on the pointsโ€™ manifold, so 2-D points give a 2-D distance:

>>> p2 = cx.Point.from_([3.0, 0.0], "m")
>>> q2 = cx.Point.from_([0.0, 4.0], "m")
>>> cx.geodesic_distance(p2, q2).round(2)
Distance(5., 'm')
Parameters:
Return type:

Any

coordinax.manifolds.interval(*args, **kwargs)#

Signed squared interval between two points.

interval is the metric quadratic form of the coordinate difference, without the square root that norm takes. It is therefore defined for every metric, including indefinite ones, where norm has no real value and geodesic_distance refuses outright; for a Lorentzian metric its sign is the pairโ€™s causal character.

It is a first-order quantity, not a squared distance: it equals geodesic_distance**2 only where the coordinate difference is itself the geodesic, i.e. on a flat manifold in Cartesian coordinates. On a curved manifold the two differ โ€“ on the unit sphere at a separation of ~0.92 rad, by 13%.

coordinax.manifolds.interval(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Signed squared interval between two points, in the chartโ€™s metric.

Unlike geodesic_distance, this is defined for every metric, because it never takes a square root.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

For a Riemannian metric it is the squared geodesic_distance:

>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")}
>>> cxm.interval(cxc.cart3d, a, b).round(2)
Q(25., 'm2')

For Minkowski it is negative for a timelike pair โ€“ the case that used to make geodesic_distance return nan:

>>> o = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> ev = {"ct": u.Q(5.0, "m"), "x": u.Q(1.0, "m"),
...       "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> cxm.interval(cxc.minkowskict, o, ev).round(2)
Q(-24., 'm2')
coordinax.manifolds.interval(metric: AbstractMetricField, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Signed squared interval with respect to an explicit metric.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> o = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> ev = {"ct": u.Q(1.0, "m"), "x": u.Q(5.0, "m"),
...       "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> cxm.interval(cxm.MinkowskiMetric(), cxc.minkowskict, o, ev).round(2)
Q(24., 'm2')
>>> try:
...     cxm.interval(cxm.FlatMetric(4), cxc.minkowskict, o, ev)
... except ValueError as e:
...     print(str(e)[:56])
interval(): metric-level dispatch needs the chart's own
coordinax.manifolds.interval(chart: AbstractChart, a: AbstractQuantity, b: AbstractQuantity, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Signed squared interval for packed unxt.Quantity vectors.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> a = u.Q([0.0, 0.0, 0.0, 0.0], "m")
>>> b = u.Q([5.0, 1.0, 0.0, 0.0], "m")
>>> cxm.interval(cxc.minkowskict, a, b).round(2)
Q(-24., 'm2')
Parameters:
Return type:

Any

class coordinax.manifolds.AbstractAtlas#

Bases: object

Atlas protocol for manifolds.

An atlas defines the set of charts that may be used to represent coordinates on a manifold. In differential geometry, a smooth manifold is defined by a pair \((M, \\mathcal{A})\) where \(M\) is a topological space and \(\\mathcal{A}\) is a maximal smooth atlas โ€” a collection of compatible charts whose domains cover the \(M\).

Responsibilities of an atlas include:

  • declaring the dimension of the manifold it covers,

  • determining whether a chart is compatible with the manifold,

  • providing a default chart used when one is not explicitly specified.

The atlas does not perform coordinate transformations itself. Those are implemented by chart-level transition maps and higher-level transformation machinery (e.g. coordinax.charts.pt_map()).

Notes

  • Atlas objects are structural descriptors, not numerical objects.

  • Multiple manifolds may share the same atlas type if their smooth structures coincide.

  • Charts belonging to the same atlas are assumed to have compatible transition maps.

Some atlas implementations allow charts to register themselves as compatible coordinate systems. For example, Euclidean charts register with coordinax.manifolds.EuclideanAtlas so they can be recognized automatically.

Examples

Constructing a Euclidean atlas

In the Euclidean case the atlas consists of common coordinate systems on \(\mathbb{R}^n\).

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.EuclideanAtlas(3)

The atlas records the dimension of the manifold:

>>> atlas.ndim
3

It can provide a canonical chart:

>>> atlas.default_chart()
Cart3D(M=Rn(3))

The atlas determines whether a chart belongs to the manifold.

>>> import coordinax.charts as cxc
>>> cxc.cart3d in atlas
True
>>> cxc.cyl3d in atlas
True

Charts with the wrong dimensionality are rejected:

>>> cxc.cart2d in atlas
False

Atlas-manifold interaction

A manifold object typically owns an atlas describing its smooth structure.

>>> from coordinax.manifolds import EuclideanManifold
>>> M = EuclideanManifold(3)
>>> M.atlas.ndim
3

The manifold uses the atlas to verify chart compatibility:

>>> M.has_chart(cxc.cart3d)
True
>>> M.has_chart(cxc.cart2d)
False
ndim: int#

Dimension of the manifold that this atlas covers.

abstractmethod default_chart()#

Return a default chart from the atlas.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.EuclideanAtlas(2)
>>> atlas.default_chart()
Cart2D(M=Rn(2))
abstractmethod has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether the atlas supports the given chart.

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.EuclideanAtlas(2)
>>> atlas.has_chart(cxc.cart2d)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

class coordinax.manifolds.AbstractMetricField#

Bases: object

Abstract base class for metrics of manifolds.

The metric defines a bilinear form on the tangent space of a chart.

\[g_p: T_p M \times T_p M \to \mathbb{R}\]

The metric can be represented as a matrix in the coordinate basis of a chart. Let \((U,\varphi)\) be a chart with coordinates \(q = (q^1, \dots, q^n)\).

The coordinate basis of the tangent space is

\(\left\{ \frac{\partial}{\partial q^i} \right\}_{i=1}^n\).

The metric matrix is defined by evaluating the metric on these basis vectors:

\[g_{ij}(q) = g_p \left( \frac{\partial}{\partial q^i}, \frac{\partial}{\partial q^j} \right).\]

This gives an n times n matrix

\[\begin{split}g(q) = \begin{pmatrix} g_{11}(q) & \cdots & g_{1n}(q) \\ \vdots & \ddots & \vdots \\ g_{n1}(q) & \cdots & g_{nn}(q) \end{pmatrix}.\end{split}\]

The metric matrix is computed via the standalone dispatch function coordinax.manifolds.metric_matrix().

Examples

>>> import coordinax.manifolds as cxm
>>> cxm.FlatMetric(3).ndim
3
property ndim: int#

Return the dimension of the metric (inferred from the chart).

abstract property signature: tuple[int, ...]#

Return the signature of the metric as a tuple of integers.

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

class coordinax.manifolds.AbstractManifold#

Bases: object

Abstract interface for smooth manifolds.

A smooth manifold of dimension \(n\) is a topological space \(M\) equipped with a maximal smooth atlas \(\mathcal{A}\) โ€” a collection of compatible charts \((U_\alpha, \varphi_\alpha)\) whose domains cover \(M\). Each chart \(\varphi_\alpha : U_\alpha \subset M \to \mathbb{R}^n\) assigns local coordinates to points in an open neighbourhood \(U_\alpha\).

AbstractManifold is the base class for all manifold objects in coordinax. It couples a manifold to its atlas and exposes a small set of coordinate-level operations:

  • chart introspection โ€” querying which charts belong to the manifold,

  • point transition maps โ€” converting point coordinates between two charts in the same atlas, and

  • Cartesian realization โ€” converting point coordinates into (or out of) a canonical ambient Cartesian chart when the manifold admits one.

All geometric and numerical work is delegated to the charts and the atlas; the manifold itself is a lightweight descriptor.

Variables:
  • atlas (AbstractAtlas) โ€“ The atlas that defines the smooth structure of this manifold. The atlas records the intrinsic dimension and determines which charts are compatible.

  • ndim (int) โ€“ Intrinsic dimension \(n\) of the manifold, forwarded from {attr}`atlas.ndim <AbstractAtlas.ndim>`.

  • default_chart (AbstractChart) โ€“ A canonical chart chosen by the atlas, forwarded from {meth}`atlas.default_chart() <AbstractAtlas.default_chart>`.

Notes

  • Manifold objects are structural descriptors, not numerical arrays. They carry no point data.

  • Subclasses are typically frozen dataclasses registered with JAX as static pytree nodes so that they can appear as static metadata inside JIT-compiled functions.

  • The two-sphere \(S^2\) is not a product manifold: its atlas requires at least two charts with non-trivial overlaps, and its transition maps do not factor. Contrast with the Euclidean case where a single global chart covers all of \(\mathbb{R}^n\).

Examples

AbstractManifold cannot be instantiated directly; use a concrete subclass such as {class}`~coordinax.manifolds.EuclideanManifold` or {class}`~coordinax.manifolds.HyperSphericalManifold`.

Basic construction and introspection

The Euclidean 3-manifold \(\mathbb{R}^3\) with its standard atlas:

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M
Rn(3)

The intrinsic dimension is read from the atlas:

>>> M.ndim
3

The atlas itself is accessible and carries the same dimension:

>>> M.atlas
EuclideanAtlas(ndim=3)

The default chart is the canonical Cartesian chart for that dimension:

>>> M.default_chart()
Cart3D(M=Rn(3))

Chart membership

{meth}`has_chart` returns True when a chart instance belongs to the manifoldโ€™s atlas:

>>> import coordinax.charts as cxc
>>> M.has_chart(cxc.cart3d)
True
>>> M.has_chart(cxc.sph3d)
True

Charts with the wrong dimensionality are rejected:

>>> M.has_chart(cxc.cart2d)
False

{meth}`check_chart` raises {exc}`ValueError` for unsupported charts, which makes it convenient as an assertion inside other methods:

>>> try:
...     M.check_chart(cxc.cart2d)
... except ValueError as e:
...     print(e)
Chart Cart2D(M=Rn(2)) is not supported by this manifold atlas.

Non-Euclidean manifolds

The two-sphere \(S^2\) is a 2-dimensional manifold that is not a subspace of any Euclidean atlas. Its atlas admits only angular charts:

>>> S2 = cxm.HyperSphericalManifold(2)
>>> S2.ndim
2
>>> S2.has_chart(cxc.sph2)
True
>>> S2.has_chart(cxc.cart2d)
False
>>> S2.default_chart()
SphericalTwoSphere(M=Sn(2))
atlas: AbstractAtlas#

Charts compatible with this manifold. This defines the smooth structure.

metric: AbstractMetricField#

The manifoldโ€™s metric. This defines the geometric structure.

property ndim: int#

Return the dimension of the manifold.

This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

class coordinax.manifolds.AbstractDiagonalMetricField#

Bases: AbstractMetricField

Abstract base class for metrics whose matrix is diagonal.

A metric is diagonal (equivalently, the coordinate chart is an orthogonal coordinate system) when all off-diagonal entries of the metric matrix vanish at every base point in every chart in the metricโ€™s diagonal domain:

\[g_{ij}(p) = 0 \quad \text{for } i \neq j.\]

The coordinate basis vectors \(\partial/\partial q^i\) are then mutually orthogonal. The diagonal entries \(g_{ii}(p)\) give the squared scale factors

\[h_i(p)^2 = g_{ii}(p),\]

and the infinitesimal line element simplifies to

\[ds^2 = \sum_i g_{ii}(q)\,(dq^i)^2.\]

Role: structural marker, not behavioral interface.

This class adds no new abstract methods beyond those of AbstractMetricField. Its purpose is to declare that the metric_matrix dispatch must return a DiagonalMetric at every valid base point for charts where this metric is used as diagonal (typically orthogonal charts).

In particular, manifold/atlas chart membership (for example, has_chart) is a broader structural notion and does not, by itself, imply orthogonality or diagonality.

See also

FlatMetric

flat Riemannian metric on \(\mathbb{R}^n\); in Cartesian charts \(g = I_n\); in orthogonal curvilinear charts computed by Jacobian pullback \(g = J^\top J\).

RoundMetric

round metric on \(S^{n-1}\) in the intrinsic hyperspherical chart; diagonal entries follow the cumulative-sine rule \(g_{kk} = \prod_{j < k} \sin^2\!\theta_j\).

MinkowskiMetric

Lorentzian pseudo-Riemannian metric \(\eta = \operatorname{diag}(-1, 1, 1, 1)\) on Minkowski spacetime; diagonal in the canonical Cartesian spacetime chart.

Examples

>>> import coordinax.manifolds as cxm

FlatMetric is an AbstractDiagonalMetricField:

>>> isinstance(cxm.FlatMetric(3), AbstractDiagonalMetricField)
True

MinkowskiMetric is also an AbstractDiagonalMetricField:

>>> isinstance(cxm.MinkowskiMetric(), AbstractDiagonalMetricField)
True

General (non-diagonal) metrics such as PullbackMetric are not:

>>> import unxt as u
>>> isinstance(
...     cxm.PullbackMetric(
...         cxm.TwoSphereIn3D(radius=u.Q(1.0, "m")),
...         cxm.FlatMetric(3),
...     ),
...     AbstractDiagonalMetricField,
... )
False
property ndim: int#

Return the dimension of the metric (inferred from the chart).

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

abstract property signature: tuple[int, ...]#

Return the signature of the metric as a tuple of integers.

class coordinax.manifolds.AbstractLorentzianMetricField#

Bases: AbstractMetricField

Abstract base class for metrics with a Lorentzian signature.

Subclassing signals exactly one timelike direction โ€“ signature \((-,+,\ldots,+)\) โ€“ so a tangent vector, or a pair of points, has a causal character: timelike, null, or spacelike.

A marker carrying no implementation. It lets the verbs that need such a signature โ€“ ~coordinax.manifolds.causal_character, ~coordinax.manifolds.proper_time, ~coordinax.manifolds.proper_distance โ€“ state that precondition as a type, and lets a curved spacetime metric join them by inheriting it.

Orthogonal to AbstractDiagonalMetricField, which describes the matrixโ€™s shape rather than its signature. MinkowskiMetric is both.

Examples

>>> import coordinax.manifolds as cxm
>>> isinstance(cxm.MinkowskiMetric(), cxm.AbstractLorentzianMetricField)
True
>>> isinstance(cxm.FlatMetric(3), cxm.AbstractLorentzianMetricField)
False
property ndim: int#

Return the dimension of the metric (inferred from the chart).

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

abstract property signature: tuple[int, ...]#

Return the signature of the metric as a tuple of integers.

class coordinax.manifolds.AbstractMetricMatrix#

Bases: Module

Abstract base class for typed metric matrix representations.

Concrete subclasses encode the sparsity structure of a metric matrix (diagonal vs. dense) and provide matrix-level operations consistent with that structure.

abstract property ndim: int#

Dimension of the metric.

abstractmethod to_dense()#

Return an equivalent DenseMetric.

For diagonal metrics, off-diagonal entries are zero. For dense metrics, returns self.

Return type:

DenseMetric

final class coordinax.manifolds.DiagonalMetric(diagonal: QuantityMatrix | Array)#

Bases: AbstractMetricMatrix

Diagonal metric matrix stored as a 1-D array or QuantityMatrix.

Encodes a metric whose coordinate matrix is diagonal โ€” i.e. orthogonal coordinate charts. Storing only the diagonal avoids materialising the full \(n \times n\) matrix and makes operations like matrix-vector products and inversion run in \(O(n)\).

Parameters:

diagonal (QuantityMatrix | Array) โ€“ The diagonal entries \(g_{11}, g_{22}, \\ldots, g_{nn}\).

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> d = DiagonalMetric(jnp.array([1.0, 4.0, 9.0]))
>>> d.ndim
3
>>> d.determinant
Array(36., dtype=float64)
>>> d.inverse.diagonal
Array([1.        , 0.25      , 0.11111111], dtype=float64)
diagonal: QuantityMatrix | Array#
property ndim: int#

Dimension of the metric.

to_dense()#

Convert to a full \(n \times n\) matrix with zeros off the diagonal.

When the diagonal is a QuantityMatrix, the off-diagonal entry (i, j) is assigned the geometric-mean unit sqrt(diag_unit[i] * diag_unit[j]). This choice ensures that g[i, j] * v[j] is unit-compatible with g[i, i] * v[i] during matrix-vector contraction, which is required for the QuantityMatrix() dot-product to succeed even when the coordinate components have different physical dimensions (e.g. metres and radians in spherical coordinates).

Return type:

DenseMetric

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DiagonalMetric

Plain array โ€” off-diagonal entries are zero:

>>> d = DiagonalMetric(jnp.array([1.0, 4.0]))
>>> d.to_dense().matrix
Array([[1., 0.],
       [0., 4.]], dtype=float64)

QuantityMatrix diagonal โ€” diagonal units are preserved and off-diagonal entries get the geometric-mean unit:

>>> import unxts.linalg as ul
>>> d = DiagonalMetric(
...     ul.QuantityMatrix(jnp.array([1.0, 4.0]), unit=("m2", "s2"))
... )
>>> d.to_dense().matrix.unit[0, 0]
Unit("m2")
>>> d.to_dense().matrix.unit[1, 1]
Unit("s2")
>>> d.to_dense().matrix.unit[0, 1]  # geometric mean: sqrt(m2 * s2)
Unit("m s")
property inverse: DiagonalMetric#

Inverse diagonal metric โ€” reciprocal of each diagonal entry.

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DiagonalMetric
>>> d = DiagonalMetric(jnp.array([2.0, 4.0]))
>>> d.inverse.diagonal
Array([0.5 , 0.25], dtype=float64)
property determinant: Array | AbstractQuantity#

Product of the diagonal entries.

Returns a AbstractQuantity when the diagonal is a QuantityMatrix.

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DiagonalMetric

Bare array โ€” returns a plain Array:

>>> DiagonalMetric(jnp.array([2.0, 3.0])).determinant
Array(6., dtype=float64)

QuantityMatrix diagonal โ€” returns a Quantity:

>>> import unxt as u
>>> import unxts.linalg as ul
>>> d = DiagonalMetric(
...     ul.QuantityMatrix(jnp.array([2.0, 3.0]), unit=("m2", "s2"))
... )
>>> d.determinant
Q(6., 'm2 s2')
final class coordinax.manifolds.DenseMetric(matrix: QuantityMatrix | Array)#

Bases: AbstractMetricMatrix

Dense symmetric metric matrix.

Stores the full \(n \\times n\) metric matrix. Used for non-orthogonal charts or metrics that cannot be expressed diagonally.

Parameters:

matrix (QuantityMatrix | Array) โ€“ The full metric matrix \(g_{ij}\).

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DenseMetric
>>> g = DenseMetric(jnp.eye(3))
>>> g.ndim
3
>>> g.determinant
Array(1., dtype=float64)
matrix: QuantityMatrix | Array#
property ndim: int#

Dimension of the metric.

to_dense()#

Return self โ€” already in dense form.

Return type:

DenseMetric

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DenseMetric
>>> g = DenseMetric(jnp.eye(2))
>>> g.to_dense() is g
True
property inverse: DenseMetric#

Inverse via jax.numpy.linalg.inv() (positive-definite assumption).

Returns a QuantityMatrix-backed DenseMetric with units 1 / ref_unit when the matrix carries units. Assumes all entries share the same unit (physically well-formed metrics from the Cartesian-Jacobian pullback always satisfy this).

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DenseMetric

Bare array:

>>> g = DenseMetric(jnp.array([[2.0, 0.0], [0.0, 4.0]]))
>>> g.inverse.matrix
Array([[0.5 , 0.  ],
       [0.  , 0.25]], dtype=float64)

QuantityMatrix โ€” inverse carries reciprocal units:

>>> import unxt as u
>>> import unxts.linalg as ul
>>> g = DenseMetric(
...     ul.QuantityMatrix(
...         jnp.array([[4.0, 0.0], [0.0, 1.0]]),
...         unit=ul.UnitsMatrix((
...             ("m2 / rad2", "m2 / rad2"),
...             ("m2 / rad2", "m2 / rad2"),
...         )),
...     )
... )
>>> g.inverse.matrix.unit[0, 0]
Unit("rad2 / m2")
>>> g.inverse.matrix.value
Array([[0.25, 0.  ],
       [0.  , 1.  ]], dtype=float64)
property determinant: Array | AbstractQuantity#

Determinant via the custom det_p JAX primitive.

Routes through Quax, so a QuantityMatrix matrix returns a AbstractQuantity while a plain array returns a bare Array. The unit is the product of the main-diagonal units โ€” valid for diagonal and uniform-unit matrices.

Examples

>>> import jax.numpy as jnp
>>> from coordinax._src.metric.matrix import DenseMetric

Bare array โ€” returns a plain Array:

>>> DenseMetric(jnp.eye(3)).determinant
Array(1., dtype=float64)

QuantityMatrix โ€” returns a Quantity:

>>> import unxt as u
>>> import unxts.linalg as ul
>>> g = DenseMetric(
...     ul.QuantityMatrix(jnp.eye(2), unit=(("m2", ""), ("", "s2")))
... )
>>> g.determinant
Q(1., 'm2 s2')
class coordinax.manifolds.NoManifold#

Bases: AbstractManifold

A degenerate placeholder manifold with no charts and no geometry.

NoManifold is a sentinel value used when a manifold object is required by the API but none has been specified by the user.

  • ndim == False signals โ€œno manifold specifiedโ€.

  • has_chart(chart) always returns False.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> M = cxm.NoManifold()
>>> M.ndim
0
>>> M.has_chart(cxc.cart2d)
False
property atlas: NoAtlas#

Return the degenerate atlas on this manifold.

property metric: NoMetric#

Return the degenerate metric on this manifold.

has_chart(chart: Any, /)#

Return whether chart belongs to this manifold atlas.

Parameters:

chart (Any)

Return type:

bool

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
property ndim: int#

Return the dimension of the manifold.

This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

final class coordinax.manifolds.NoMetric#

Bases: AbstractMetricField

A degenerate placeholder metric with no geometry.

NoMetric is a sentinel value used when a metric object is required by the API but none has been specified by the user.

  • ndim == False signals โ€œno metric specifiedโ€.

property ndim: int#

Stand-in dimension of the degenerate metric.

property signature: tuple[int, ...]#

Signature of the degenerate metric.

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

class coordinax.manifolds.NoAtlas#

Bases: AbstractAtlas

Trivial atlas that supports no charts.

ndim: int = 0#

Dimension of the manifold that this atlas covers.

default_chart()#

Return a default chart from the atlas.

Return type:

NoReturn

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.EuclideanAtlas(2)
>>> atlas.default_chart()
Cart2D(M=Rn(2))
has_chart(_: Any, /)#

Return whether the atlas supports the given chart.

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.EuclideanAtlas(2)
>>> atlas.has_chart(cxc.cart2d)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:

_ (Any)

Return type:

bool

final class coordinax.manifolds.EuclideanAtlas(ndim: int)#

Bases: AbstractAtlas

Atlas of coordinate charts for the Euclidean manifold \(\mathbb{R}^n\).

An atlas \(\mathcal{A}\) is the collection of compatible charts that together cover a smooth manifold. For the Euclidean manifold \(\mathbb{R}^n\), the atlas \(\mathcal{A}_{\mathbb{R}^n}\) admits a chart \(C = (U, \varphi)\) if and only if both of the following hold:

  1. The chart dimensionality matches \(n\) (i.e. \(\varphi\) maps into \(\mathbb{R}^n\)).

  2. The chart class is explicitly registered with {class}`~coordinax.manifolds.EuclideanAtlas` via {meth}`register`, or the chart has a compatible transition map to the default Cartesian chart (detected via chart.cartesian).

Built-in charts. The following chart classes are registered automatically:

  • 0-D: Cart0D

  • 1-D: Cart1D, Radial1D

  • 2-D: Cart2D, Polar2D

  • 3-D: Cart3D, Cylindrical3D, Spherical3D, LonLatSpherical3D, LonCosLatSpherical3D, MathSpherical3D, ProlateSpheroidal3D

  • N-D: CartND

Default chart. The atlas provides a canonical Cartesian chart for each dimension: Cart0D through Cart3D for \(n \leq 3\), and CartND for \(n > 3\).

Parameters:

ndim (int) โ€“ Dimension \(n \geq 0\) of the Euclidean manifold that this atlas covers.

Examples

Construction

>>> import coordinax.manifolds as cxmd
>>> atlas = cxmd.EuclideanAtlas(3)
>>> atlas
EuclideanAtlas(ndim=3)
>>> atlas.ndim
3

Default chart

The atlas provides a canonical Cartesian chart for each dimension:

>>> atlas.default_chart()
Cart3D(M=Rn(3))
>>> cxmd.EuclideanAtlas(2).default_chart()
Cart2D(M=Rn(2))
>>> cxmd.EuclideanAtlas(1).default_chart()
Cart1D(M=Rn(1))

For \(n > 3\) the fallback is CartND:

>>> cxmd.EuclideanAtlas(10).default_chart()
CartND(M=Rn(True))

Chart membership

Use the in operator (via {meth}`~AbstractAtlas.__contains__`) to test whether a chart instance belongs to this atlas:

>>> import coordinax.charts as cxc
>>> cxc.cart3d in atlas
True
>>> cxc.sph3d in atlas
True

Charts with the wrong dimensionality are rejected:

>>> cxc.cart2d in atlas
False
ndim: int#

Dimension of the Euclidean manifold.

default_chart()#

Return the default chart for this atlas.

Return type:

AbstractChart[Any, Any, Any]

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxmd
>>> cxmd.EuclideanAtlas(0).default_chart()
Cart0D(M=Rn(0))
>>> cxmd.EuclideanAtlas(1).default_chart()
Cart1D(M=Rn(1))
>>> cxmd.EuclideanAtlas(2).default_chart()
Cart2D(M=Rn(2))
>>> cxmd.EuclideanAtlas(3).default_chart()
Cart3D(M=Rn(3))

For higher dimensions, the default is CartND:

>>> cxmd.EuclideanAtlas(100).default_chart()
CartND(M=Rn(True))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether the atlas supports the given chart.

This checks if the chart has the same dimension as the atlas and is either explicitly registered or has a common point transition map to the default Cartesian chart.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxmd
>>> atlas = cxmd.EuclideanAtlas(2)
>>> atlas.has_chart(cxc.cart2d)
True
>>> atlas.has_chart(cxc.polar2d)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

classmethod register(registrant: CT, /)#

Register a class for Euclidean Atlas eligibility.

This does not guarantee that a particular atlas will support the chart, since support also depends on dimensionality and transition maps. It simply makes the chart class eligible for support if those other conditions are met.

Parameters:

registrant (TypeVar(CT, bound= type[AbstractChart[Any, Any, Any]]))

Return type:

TypeVar(CT, bound= type[AbstractChart[Any, Any, Any]])

final class coordinax.manifolds.FlatMetric(ndim: int = <property object>)#

Bases: AbstractDiagonalMetricField

Euclidean (flat) Riemannian metric on \(\mathbb{R}^n\).

In Cartesian coordinates the metric is the identity matrix \(g = I_n\). In any other chart, the metric matrix is computed via the pullback

\[g_{ij} = \sum_k \frac{\partial x^k}{\partial q^i} \frac{\partial x^k}{\partial q^j} = (J^T J)_{ij},\]

where \(J = \partial x / \partial q\) is the Jacobian of the chart-to-Cartesian transition map.

This pullback is diagonal precisely for orthogonal coordinate charts. FlatMetric is treated as AbstractDiagonalMetricField on that orthogonal chart domain; atlas chart compatibility alone does not imply orthogonality.

Parameters:

ndim (int) โ€“ Dimension of the Euclidean space.

Examples

>>> import jax.numpy as jnp
>>> import coordinaxs.api.manifolds as cxmapi
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> m = cxm.FlatMetric(3)
>>> m.signature
(1, 1, 1)
>>> m.ndim
3

The metric matrix is obtained via the dispatch API on the associated manifold:

>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> cxmapi.metric_matrix(cxm.R3, at, cxc.cart3d).diagonal
Array([1., 1., 1.], dtype=float64)
ndim: int#

Dimension of the Euclidean space.

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

property signature: tuple[int, ...]#

Signature of the metric as a tuple of 1โ€™s.

final class coordinax.manifolds.EuclideanManifold(ndim: int, /)#

Bases: AbstractManifold

The \(n\)-dimensional Euclidean manifold \(\mathbb{R}^n\).

The Euclidean manifold of dimension \(n\) is the smooth manifold \((\mathbb{R}^n, \mathcal{A}_{\mathbb{R}^n})\).

Charts and atlas. The smooth structure is described by an {class}`coordinax.manifolds.EuclideanAtlas` whose charts are local diffeomorphisms

\[\varphi : U \subset \mathbb{R}^n \to \mathbb{R}^n.\]

A chart \(C = (U, \varphi)\) is admitted by the atlas when its dimensionality matches \(n\) and either (1) it is explicitly registered with {class}`coordinax.manifolds.EuclideanAtlas`, or (2) it possesses a compatible transition map to the default Cartesian chart. For \(n = 3\) the built-in charts include Cartesian \((x, y, z)\), spherical \((r, \theta, \phi)\), cylindrical \((\rho, \phi, z)\), and several angular variants; see {class}`coordinax.manifolds.EuclideanAtlas` for the full list.

Transition maps. For any two charts \(C_\alpha = (U_\alpha, \varphi_\alpha)\) and \(C_\beta = (U_\beta, \varphi_\beta)\) in the atlas, the transition map is

\[\tau_{\alpha \to \beta} = \varphi_\beta \circ \varphi_\alpha^{-1} : \varphi_\alpha(U_\alpha \cap U_\beta) \to \varphi_\beta(U_\alpha \cap U_\beta).\]

Because \(\mathbb{R}^n\) is flat and simply connected, every transition map is a smooth diffeomorphism on a connected open domain. For example, the Cartesian-to-spherical transition on \(\mathbb{R}^3\) is

\[\tau_{C \to S}(x, y, z) = \Bigl(\sqrt{x^2 + y^2 + z^2},\; \arccos\!\tfrac{z}{r},\; \operatorname{atan2}(y, x)\Bigr).\]

Pre-built instance. The module exports {obj}`coordinax.manifolds.R3` as a pre-built instance for the common case \(\mathbb{R}^3\).

Parameters:

ndim (int) โ€“ Intrinsic dimension \(n \geq 0\) of the manifold โ€” the number of independent coordinates required to label a point.

Variables:

atlas (EuclideanAtlas) โ€“ The atlas of coordinate charts compatible with this manifold. Its {attr}`~EuclideanAtlas.ndim` equals ndim.

Examples

Construction

Construct a Euclidean manifold of arbitrary dimension:

>>> import coordinax.manifolds as cxmd
>>> M = cxmd.EuclideanManifold(3)
>>> M
Rn(3)

The intrinsic dimension is accessible via {attr}`~EuclideanManifold.ndim`:

>>> M.ndim
3

The atlas is a {class}`EuclideanAtlas` with matching dimensionality:

>>> M.atlas
EuclideanAtlas(ndim=3)

Default chart

The default chart is the standard Cartesian chart for the given dimension:

>>> M.default_chart()
Cart3D(M=Rn(3))
>>> cxmd.EuclideanManifold(2).default_chart()
Cart2D(M=Rn(2))
>>> cxmd.EuclideanManifold(1).default_chart()
Cart1D(M=Rn(1))

Chart membership

Check whether a chart belongs to this manifoldโ€™s atlas:

>>> import coordinax.charts as cxc
>>> M.has_chart(cxc.cart3d)
True
>>> M.has_chart(cxc.sph3d)
True

Charts with the wrong dimensionality are rejected:

>>> M.has_chart(cxc.cart2d)
False

{meth}`check_chart` raises if the chart is not supported:

>>> try:
...     M.check_chart(cxc.cart2d)
... except ValueError as e:
...     print(e)
Chart Cart2D(M=Rn(2)) is not supported by this manifold atlas.

Pre-built instances

For the most common case โ€” three-dimensional Euclidean space \(\mathbb{R}^3\) โ€” the module provides a pre-built instance:

>>> cxmd.R3
Rn(3)
ndim: int#

Intrinsic dimension of the manifold.

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

atlas: AbstractAtlas#

Charts compatible with this manifold. This defines the smooth structure.

metric: AbstractMetricField#

The manifoldโ€™s metric. This defines the geometric structure.

coordinax.manifolds.Rn#

alias of EuclideanManifold

final class coordinax.manifolds.HyperSphericalAtlas(ndim: int = 2)#

Bases: AbstractAtlas

Atlas for spherical manifolds (e.g. the circle or 2-sphere).

E.g. the 2-sphere contains charts:

  • ~coordinax.charts.SphericalTwoSphere,

  • ~coordinax.charts.LonLatSphericalTwoSphere,

  • ~coordinax.charts.LonCosLatSphericalTwoSphere, and

  • ~coordinax.charts.MathSphericalTwoSphere.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> atlas = cxm.HyperSphericalAtlas()
>>> atlas.ndim
2
>>> cxc.sph2 in atlas
True
>>> cxc.lonlat_sph2 in atlas
True
>>> cxc.cart2d in atlas
False
>>> atlas.default_chart()
SphericalTwoSphere(M=Sn(2))
Parameters:

ndim (int)

ndim: int#

Dimension of the two-sphere.

default_chart()#

Return the default chart (SphericalTwoSphere) for this atlas.

Return type:

AbstractChart[Any, Any, Any]

has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether the atlas supports the given chart.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> atlas = cxm.HyperSphericalAtlas()
>>> atlas.has_chart(cxc.sph2)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

classmethod register(registrant: CT, /)#

Register a chart class for HyperSphericalAtlas eligibility.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> cxc.SphericalTwoSphere in SPHERICAL_ATLAS_ELIGIBLE_CHARTS
True
Parameters:

registrant (TypeVar(CT, bound= type[AbstractChart[Any, Any, Any]]))

Return type:

TypeVar(CT, bound= type[AbstractChart[Any, Any, Any]])

final class coordinax.manifolds.RoundMetric(ndim: int = <property object>)#

Bases: AbstractDiagonalMetricField

Round metric on the unit \(n\)-sphere \(S^{n-1}\) in standard spherical coordinates.

The round metric on \(S^2\) in the \((\theta, \phi)\) spherical chart is

\[\begin{split}g = \begin{pmatrix} 1 & 0 \\ 0 & \sin^2\theta \end{pmatrix}.\end{split}\]
Parameters:

ndim (int) โ€“ Intrinsic dimension of the sphere (e.g. ndim=2 for \(S^2\)).

Examples

>>> import jax.numpy as jnp
>>> import coordinaxs.api.manifolds as cxmapi
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> m = cxm.RoundMetric(2)
>>> m.signature
(1, 1)
>>> m.ndim
2

The metric matrix is obtained via the dispatch API on the associated manifold:

>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)}
>>> d = cxmapi.metric_matrix(cxm.S2, at, cxc.sph2).diagonal
>>> bool(jnp.allclose(d.value, jnp.array([1.0, 1.0])))
True
ndim: int#

Intrinsic dimension of the sphere.

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

property signature: tuple[int, ...]#

Metric signature (1,) * ndim, the round sphere metric is Riemannian.

final class coordinax.manifolds.HyperSphericalManifold(ndim: int = 2, /)#

Bases: AbstractManifold

The unit two-sphere \(S^2\) as a smooth manifold.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> S2 = cxm.HyperSphericalManifold(2)
>>> S2.ndim
2
>>> S2.has_chart(cxc.sph2)
True
>>> S2.default_chart()
SphericalTwoSphere(M=Sn(2))
Parameters:

ndim (int)

ndim: int#

Intrinsic dimension of the manifold.

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

atlas: AbstractAtlas#

Charts compatible with this manifold. This defines the smooth structure.

metric: AbstractMetricField#

The manifoldโ€™s metric. This defines the geometric structure.

coordinax.manifolds.Sn#

alias of HyperSphericalManifold

final class coordinax.manifolds.MinkowskiAtlas#

Bases: AbstractAtlas

Atlas of coordinate charts for Minkowski spacetime \(\mathbb{R}^{1,3}\).

An atlas \(\mathcal{A}\) is the collection of compatible charts that together cover a smooth manifold. For Minkowski spacetime \(\mathbb{R}^{1,3}\), the atlas \(\mathcal{A}\) admits a chart \(C\) if and only if:

  1. The chart dimensionality is 4.

  2. The chart class is {class}`~coordinax.charts.MinkowskiCT` (or a subclass explicitly registered via {meth}`register`).

Built-in charts:

  • {class}`~coordinax.charts.MinkowskiCT` โ€” canonical \((ct, x, y, z)\) chart.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> atlas = cxm.MinkowskiAtlas()
>>> atlas.ndim
4
>>> cxc.minkowskict in atlas
True
>>> cxc.cart3d in atlas
False
>>> atlas.default_chart()
MinkowskiCT(M=MinkowskiManifold())
ndim: int#

Dimension of Minkowski spacetime (always 4).

default_chart()#

Return the default chart (canonical MinkowskiCT).

Return type:

AbstractChart[Any, Any, Any]

Examples

>>> import coordinax.manifolds as cxm
>>> cxm.MinkowskiAtlas().default_chart()
MinkowskiCT(M=MinkowskiManifold())
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether the chart belongs to this atlas.

A chart belongs when its dimensionality is 4 and its class is {class}`~coordinax.charts.MinkowskiCT` (or another class registered via {meth}`register`).

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> atlas = cxm.MinkowskiAtlas()
>>> atlas.has_chart(cxc.minkowskict)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

classmethod register(registrant: CT, /)#

Register a chart class for {class}`MinkowskiAtlas` eligibility.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> cxc.MinkowskiCT in cxm.MinkowskiAtlas._ELIGIBLE_CHARTS
True
Parameters:

registrant (TypeVar(CT, bound= type[AbstractChart[Any, Any, Any]]))

Return type:

TypeVar(CT, bound= type[AbstractChart[Any, Any, Any]])

final class coordinax.manifolds.MinkowskiMetric#

Bases: AbstractDiagonalMetricField, AbstractLorentzianMetricField

Pseudo-Riemannian (Lorentzian) metric on Minkowski spacetime.

In the canonical {class}`~coordinax.charts.MinkowskiCT` chart \((ct, x, y, z)\), the Minkowski metric is

\[\eta = \operatorname{diag}(-1, 1, 1, 1),\]

where the time coordinate \(ct = c\,t\) absorbs the speed of light so that all four components carry the same unit (length). The line element is

\[ds^2 = -(d(ct))^2 + dx^2 + dy^2 + dz^2.\]

Signature. The metric is pseudo-Riemannian with Lorentzian signature \((-1, 1, 1, 1)\) meaning one negative and three positive eigenvalues (convention: โ€œmostly plusโ€).

Examples

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> from coordinaxs.api.manifolds import metric_matrix

Canonical Cartesian spacetime chart:

>>> m = cxm.MinkowskiMetric()
>>> M = cxm.MinkowskiManifold()
>>> at = {"ct": jnp.array(0.0), "x": jnp.array(0.0),
...       "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> metric_matrix(M, at, cxc.minkowskict).diagonal
Array([-1.,  1.,  1.,  1.], dtype=float64)

The signature is Lorentzian (pseudo-Riemannian):

>>> m.signature
(-1, 1, 1, 1)
>>> m.ndim
4
property signature: tuple[int, ...]#

Metric signature (-1, 1, 1, 1), Lorentzian pseudo-Riemannian.

property ndim: int#

Return the dimension of the metric (inferred from the chart).

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

final class coordinax.manifolds.MinkowskiManifold#

Bases: AbstractManifold

Minkowski spacetime \(\mathbb{R}^{1,3}\).

Minkowski spacetime is the 4-dimensional pseudo-Riemannian manifold \((\mathbb{R}^{1,3}, \eta)\) equipped with the metric \(\eta = \operatorname{diag}(-1, 1, 1, 1)\) in the canonical \((ct, x, y, z)\) chart. It is the geometric arena of special relativity.

Charts. The manifold admits all charts registered with coordinax.manifolds.MinkowskiAtlas. The built-in chart is MinkowskiCT โ€” the canonical \((ct, x, y, z)\) Cartesian spacetime chart.

Metric. The manifold carries the flat Minkowski metric \(\eta = \operatorname{diag}(-1, 1, 1, 1)\).

Pre-built instance. The module exports minkowski4d as a ready-to-use instance.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> M = cxm.MinkowskiManifold()
>>> M
MinkowskiManifold()
>>> M.ndim
4
>>> M.atlas
MinkowskiAtlas()
>>> M.default_chart()
MinkowskiCT(M=MinkowskiManifold())
>>> M.has_chart(cxc.minkowskict)
True
>>> M.has_chart(cxc.cart3d)
False
angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

property ndim: int#

Return the dimension of the manifold.

This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

atlas: AbstractAtlas#

Charts compatible with this manifold. This defines the smooth structure.

metric: AbstractMetricField#

The manifoldโ€™s metric. This defines the geometric structure.

final class coordinax.manifolds.CartesianProductAtlas(factors: tuple[AbstractAtlas, ...], factor_names: tuple[str, ...])#

Bases: AbstractAtlas

Atlas for a product manifold.

The atlas consists of Cartesian product charts formed from the atlases of the factor manifolds.

Consider the product manifold \(S^2 \times \\mathbb{R}\), where

  • \(S^2\) is the 2-sphere with spherical coordinates \((\theta, \\phi)\) and atlas of charts including SphericalTwoSphere.

  • \(\mathbb{R}\) is the real line with Cartesian coordinate \(x\) and atlas of charts including Cartesian1D.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> atlas = cxm.CartesianProductAtlas(
...     factors=(cxm.HyperSphericalAtlas(), cxm.EuclideanAtlas(1)),
...     factor_names=("S2", "R1"))
>>> atlas
CartesianProductAtlas(factors=(HyperSphericalAtlas(ndim=2), EuclideanAtlas(ndim=1)),
    factor_names=('S2', 'R1'))
>>> atlas.ndim
3
>>> chart = cxc.CartesianProductChart(
...     factors=(cxc.sph2, cxc.cart1d), factor_names=("S2", "R1"))
>>> chart in atlas
True
>>> cxc.sph2 in atlas
False
>>> atlas["R1"]  # Access factor atlas by name
EuclideanAtlas(ndim=1)
Parameters:
factors: tuple[AbstractAtlas, ...]#

Factor atlases that define the product atlas.

factor_names: tuple[str, ...]#

Names of the factor atlases, used for indexing and validation.

default_chart()#

Return a default chart for the product atlas.

Return type:

CartesianProductChart

Examples

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.CartesianProductAtlas(
...     factors=(cxm.HyperSphericalAtlas(), cxm.EuclideanAtlas(1)),
...     factor_names=("S2", "R1"))
>>> chart = atlas.default_chart()
>>> chart
CartesianProductChart(
    factors=(SphericalTwoSphere(M=Sn(2)), Cart1D(M=Rn(1))),
    factor_names=('S2', 'R1')
)
>>> chart["S2"] == cxm.HyperSphericalAtlas().default_chart()
True
>>> chart["R1"] == cxm.EuclideanAtlas(ndim=1).default_chart()
True
has_chart(chart: AbstractChart)#

Check if the atlas supports the given chart.

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> atlas = cxm.CartesianProductAtlas(
...     factors=(cxm.HyperSphericalAtlas(), cxm.EuclideanAtlas(1)),
...     factor_names=("S2", "R1"))
>>> chart = cxc.CartesianProductChart(
...     factors=(cxc.sph2, cxc.cart1d), factor_names=("S2", "R1"))
>>> atlas.has_chart(chart)
True
Parameters:

chart (AbstractChart)

Return type:

bool

property ndim: int#

The sum of the factor atlasโ€™ dimensions.

>>> import coordinax.manifolds as cxm
>>> atlas = cxm.CartesianProductAtlas(
...     factors=(cxm.HyperSphericalAtlas(), cxm.EuclideanAtlas(1)),
...     factor_names=("S2", "R1"))
>>> atlas.ndim
3
final class coordinax.manifolds.ProductMetric(factors: tuple[AbstractMetricField, ...])#

Bases: AbstractMetricField

Canonical product metric on a Cartesian product manifold.

For factor manifolds \((M_i, g_i)\), the product metric on \(M = M_1 \times \cdots \times M_k\) is

\[g_{(p_1,\ldots,p_k)}\big((v_1,\ldots,v_k),(w_1,\ldots,w_k)\big) = \sum_{i=1}^k g_i(v_i, w_i),\]

which is block diagonal in a product chart.

Parameters:

factors (tuple[AbstractMetricField, ...])

factors: tuple[AbstractMetricField, ...]#

Metrics for each factor manifold, in product order.

property ndim: int#

Return the dimension of the metric (inferred from the chart).

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

property signature: tuple[int, ...]#

Concatenated factor signatures in product order.

final class coordinax.manifolds.CartesianProductManifold(factors: tuple[AbstractManifold, ...], factor_names: tuple[str, ...])#

Bases: AbstractManifold

Manifold defined as a Cartesian product of other manifolds.

Given smooth manifolds \(M_1, M_2, \ldots, M_k\) of intrinsic dimensions \(n_1, n_2, \ldots, n_k\), the Cartesian product manifold is

\[M = M_1 \times M_2 \times \cdots \times M_k,\]

whose points are \(k\)-tuples \((p_1, p_2, \ldots, p_k)\) with \(p_i \in M_i\). The product is itself a smooth manifold of dimension

\[\dim(M) = n_1 + n_2 + \cdots + n_k.\]

Smooth structure. The atlas \(\mathcal{A}_M\) of the product manifold consists precisely of Cartesian product charts

\[C_1 \times C_2 \times \cdots \times C_k, \quad C_i \in \mathcal{A}_{M_i},\]

encoded as {class}`~coordinax.charts.CartesianProductChart` instances. Transition maps on \(M\) factor component-wise: if \(\tau_i : C_i^\alpha \to C_i^\beta\) is the transition map on \(M_i\), then the product transition map is

\[\tau : (p_1, \ldots, p_k) \mapsto \bigl(\tau_1(p_1), \ldots, \tau_k(p_k)\bigr).\]

Naming and indexing. Each factor is assigned a string name via factor_names. Names must be unique and are used to retrieve individual factor manifolds or factor atlases from the product atlas via string indexing (e.g. manifold.atlas["S2"]).

Parameters:
  • factors (tuple[AbstractManifold, ...]) โ€“ The constituent manifolds \(M_1, \ldots, M_k\) that form the product.

  • factor_names (tuple[str, ...]) โ€“ Unique string names for each factor, in the same order as factors. Used as keys when indexing into the product atlas.

Variables:
  • atlas (CartesianProductAtlas) โ€“ The product atlas formed from the factor atlases.

  • metric (ProductMetric) โ€“ The canonical product metric formed from the factor metrics.

  • ndim (int) โ€“ Total intrinsic dimension \(\sum_i n_i\).

  • default_chart (CartesianProductChart) โ€“ Product of the default charts from each factor atlas.

Examples

Basic construction

The most common example is the phase-space-like manifold \(S^2 \times \mathbb{R}\), which pairs the 2-sphere (2 angular degrees of freedom) with the real line (1 radial degree of freedom):

>>> import coordinax.manifolds as cxm
>>> import wadler_lindig as wl
>>> M = cxm.CartesianProductManifold(
...     factors=(cxm.S2, cxm.R1), factor_names=("S2", "R1")
... )
>>> wl.pprint(M, width=76)
CartesianProductManifold(factors=(Sn(2), Rn(1)), factor_names=('S2', 'R1'))

Dimension

The dimension equals the sum of the factor dimensions, \(\dim(S^2) + \dim(\mathbb{R}) = 2 + 1 = 3\):

>>> M.ndim
3

Atlas and default chart

The atlas is a {class}`CartesianProductAtlas` whose default chart is the Cartesian product of the default charts of each factor:

>>> M.atlas
CartesianProductAtlas(factors=(HyperSphericalAtlas(ndim=2), EuclideanAtlas(ndim=1)),
    factor_names=('S2', 'R1'))
>>> M.default_chart()
CartesianProductChart(
    factors=(SphericalTwoSphere(M=Sn(2)), Cart1D(M=Rn(1))),
    factor_names=('S2', 'R1')
)

Factor atlases can be retrieved by name:

>>> M.atlas["S2"]
HyperSphericalAtlas(ndim=2)
>>> M.atlas["R1"]
EuclideanAtlas(ndim=1)

Chart membership

A {class}`~coordinax.charts.CartesianProductChart` belongs to the product atlas when its factor charts belong to the corresponding factor atlases:

>>> import coordinax.charts as cxc
>>> product_chart = cxc.CartesianProductChart(
...     factors=(cxc.sph2, cxc.cart1d), factor_names=("S2", "R1")
... )
>>> M.has_chart(product_chart)
True

Non-product charts and wrong-factor charts are rejected:

>>> M.has_chart(cxc.sph2)
False

Higher-dimensional products

Any number of factors may be combined. The 4-dimensional manifold \(S^2 \times \mathbb{R}^2\) has \(\dim = 2 + 2 = 4\):

>>> M4 = cxm.CartesianProductManifold(
...     factors=(cxm.S2, cxm.R2), factor_names=("S2", "R2")
... )
>>> M4.ndim
4

Euclidean-Euclidean products

Factor manifolds need not be non-Euclidean. The product \(\mathbb{R}^2 \times \mathbb{R}\) reproduces 3-dimensional Euclidean space (though as a product structure rather than a single EuclideanManifold):

>>> Mprod = cxm.CartesianProductManifold(
...     factors=(cxm.R2, cxm.R1), factor_names=("xy", "z")
... )
>>> Mprod.ndim
3
>>> Mprod.default_chart()
CartesianProductChart(
    factors=(Cart2D(M=Rn(2)), Cart1D(M=Rn(1))), factor_names=('xy', 'z')
)
factors: tuple[AbstractManifold, ...]#
factor_names: tuple[str, ...]#
property atlas: CartesianProductAtlas#

Return the product atlas for the manifold.

>>> import coordinax.manifolds as cxm
>>> import wadler_lindig as wl
>>> M = cxm.CartesianProductManifold(
...     factors=(cxm.S2, cxm.R1), factor_names=("S2", "R1"))
>>> wl.pprint(M.atlas, width=60)
CartesianProductAtlas(
    factors=(HyperSphericalAtlas(), EuclideanAtlas(ndim=1)),
    factor_names=('S2', 'R1')
)
property metric: ProductMetric#

Return the canonical product metric from the factor metrics.

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

property ndim: int#

Return the dimension of the manifold.

This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

class coordinax.manifolds.AbstractEmbeddingMap#

Bases: Generic[IntrinsicT, AmbientT]

Abstract base class representing a smooth embedding.

An embedding represents a smooth injective map \(\iota : M \hookrightarrow N\) of an intrinsic manifold (with charts in coordinax.charts) into an ambient manifold.

Conceptually, an embedding provides:

  • A smooth map from intrinsic coordinates q^i to ambient coordinates x^a = x^a(q) via embed.

  • A (possibly local) inverse or projection map from ambient coordinates back to intrinsic coordinates via project.

Examples

A concrete example is the embedding of SphericalTwoSphere into Spherical3D: the intrinsic coordinates may be (ฮธ, ฯ†) on the unit 2-sphere, while the ambient coordinates are (r, ฮธ, ฯ†) with fixed radius r = R. A concrete subclass can therefore:

  • Map (ฮธ, ฯ†) โ†ฆ (R, ฮธ, ฯ†) in Spherical3D via embed.

  • Drop the radial component via project.

  • Realize to Cartesian coordinates by first embedding into Spherical3D and then delegating to its Cartesian realization.

Subclasses are responsible for implementing the coordinate-level maps; higher-level metric machinery (e.g. induced metrics) can be built on top of this interface.

This class is deliberately not an equinox.Module itself: the two attributes below are an interface for concrete maps to satisfy with either a field (CustomEmbeddingMap.ambient) or a property (TwoSphereIn3D.intrinsic), and a Module base would turn them into required constructor arguments.

intrinsic: IntrinsicT#
ambient: AmbientT#
abstractmethod embed(point: dict, /, *, usys: AbstractUnitSystem | None = None)#

Embed intrinsic coordinates into ambient coordinates.

Parameters:
  • point (dict) โ€“ A point in intrinsic coordinates.

  • usys (AbstractUnitSystem | None) โ€“ Optional unit system for the input and output coordinates.

Return type:

dict

abstractmethod project(point: dict, /, *, usys: AbstractUnitSystem | None = None)#

Project ambient coordinates to intrinsic coordinates.

Parameters:
  • point (dict) โ€“ A point in ambient coordinates.

  • usys (AbstractUnitSystem | None) โ€“ Optional unit system for the input and output coordinates.

Return type:

dict

final class coordinax.manifolds.CustomEmbeddingMap(intrinsic: IntrinsicT, ambient: AmbientT, embed_fn: EPCallable, project_fn: EPCallable)#

Bases: AbstractEmbeddingMap[IntrinsicT, AmbientT], Module

A concrete embedding map defined by user-provided functions.

This class allows users to define an embedding by providing custom embed and project functions, without needing to create a new subclass.

Parameters:
  • intrinsic (TypeVar(IntrinsicT, bound= AbstractChart[Any, Any, Any])) โ€“ The intrinsic chart.

  • ambient (TypeVar(AmbientT, bound= AbstractChart[Any, Any, Any])) โ€“ The ambient chart.

  • embed_fn (EPCallable) โ€“ A function that takes a point in intrinsic coordinates and returns the corresponding point in ambient coordinates.

  • project_fn (EPCallable) โ€“ A function that takes a point in ambient coordinates and returns the corresponding point in intrinsic coordinates.

intrinsic: IntrinsicT#
ambient: AmbientT#
embed_fn: EPCallable#
project_fn: EPCallable#
embed(point: dict, /, *, usys: AbstractUnitSystem | None = None)#

Embed intrinsic coordinates into ambient coordinates.

Parameters:
  • point (dict) โ€“ A point in intrinsic coordinates.

  • usys (AbstractUnitSystem | None) โ€“ Optional unit system for the input and output coordinates.

Return type:

dict

project(point: dict, /, *, usys: AbstractUnitSystem | None = None)#

Project ambient coordinates to intrinsic coordinates.

Parameters:
  • point (dict) โ€“ A point in ambient coordinates.

  • usys (AbstractUnitSystem | None) โ€“ Optional unit system for the input and output coordinates.

Return type:

dict

final class coordinax.manifolds.TwoSphereIn3D(radius: AbstractQuantity | float | int, ambient: AbstractChart[Any, Any, Any] = Spherical3D(M=Rn(3)))#

Bases: AbstractEmbeddingMap[IntrinsicT, AmbientT], Module

Embedding of cxc.SphericalTwoSphere as a 2-sphere in a 3D ambient chart.

This embedding models a 2-sphere of fixed radius \(R\) as the hypersurface \(r = R\) in 3D spherical coordinates \((r, \theta, \phi)\). The intrinsic chart is therefore expected to have components \((\theta, \phi)\).

The key design choice is that all coordinate-level embedding and projection operations are defined via an intermediate 3D spherical chart ({class}`~coordinax.charts.Spherical3D`), regardless of which ambient chart is selected. In particular:

  • If ambient is Spherical3D, then {meth}`embed` returns spherical coordinates (r, theta, phi) and {meth}`project` expects the same.

  • If ambient is Cart3D, then {meth}`embed` performs SphericalTwoSphere -> Spherical3D -> Cart3D and returns Cartesian coordinates (x, y, z); {meth}`project` performs Cart3D -> Spherical3D -> SphericalTwoSphere.

Parameters:

Examples

Embed/project SphericalTwoSphere through an ambient Spherical3D chart:

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, chart)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, chart)
>>> p2
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> jnp.allclose(p2["theta"].value, p["theta"].value)
Array(True, dtype=bool)

Embed/project through an ambient Cart3D chart (routing via Spherical3D internally):

>>> emb = cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"), ambient=cxc.cart3d)
>>> chart = cxm.EmbeddedChart(emb)
>>> xyz = cxm.pt_embed(p, chart)
>>> sorted(xyz)
['x', 'y', 'z']
>>> bool(jnp.allclose(u.ustrip("km", xyz["x"]), 2.0, atol=1e-6))
True
>>> p3 = cxm.pt_project(xyz, chart)
>>> bool(jnp.allclose(u.ustrip("rad", p3["phi"]), u.ustrip("rad", p["phi"])))
True
radius: AbstractQuantity | float | int#
ambient: AbstractChart[Any, Any, Any] = Spherical3D(M=Rn(3))#
property intrinsic: AbstractChart[Any, Any, Any]#

The intrinsic chart is always coordinax.charts.SphericalTwoSphere.

embed(q: dict, /, *, usys: AbstractUnitSystem | None = None)#

Embed SphericalTwoSphere intrinsic coords into the ambient chart.

Parameters:
Return type:

dict

project(x: dict, /, *, usys: AbstractUnitSystem | None = None)#

Project ambient coords onto SphericalTwoSphere intrinsic coords.

Parameters:
Return type:

dict

coordinax.manifolds.embedded_twosphere(radius: float | AbstractQuantity, ambient: AbstractChart[Any, Any, Any] = Spherical3D(M=Rn(3)))#

Create an coordinax.manifolds.EmbeddedManifold for the two-sphere.

This is a convenience helper that constructs an coordinax.manifolds.EmbeddedManifold with intrinsic=HyperSphericalManifold() and embedding=TwoSphereIn3D(radius, ambient).

Parameters:
Return type:

EmbeddedManifold

Examples

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u

Default ambient (Spherical3D):

>>> M = cxm.embedded_twosphere(radius=u.Q(2.0, "km"))
>>> M
EmbeddedManifold(intrinsic=HyperSphericalManifold(...),
                 ambient=Rn(3),
                 embed_map=TwoSphereIn3D(radius=Q(2., 'km'),
                                         ambient=Spherical3D(M=Rn(3))))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, M)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

With Cartesian ambient the embedding returns (x, y, z):

>>> M = cxm.embedded_twosphere(radius=u.Q(2.0, "km"), ambient=cxc.cart3d)
>>> xyz = cxm.pt_embed(p, M)
>>> sorted(xyz)
['x', 'y', 'z']
>>> bool(jnp.allclose(u.ustrip("km", xyz["x"]), 2.0, atol=1e-6))
True
final class coordinax.manifolds.EmbeddedManifold(intrinsic: AbstractManifold, ambient: AbstractManifold, embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])#

Bases: AbstractManifold, Generic[IntrinsicT, AmbientT]

Embedded manifold.

Examples

Embed/project {class}`~coordinax.charts.SphericalTwoSphere through an ambient {class}`~coordinax.charts.Spherical3D chart:

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2,
...     ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, M)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, M)
>>> p2
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> jnp.allclose(p2["theta"].value, p["theta"].value)
Array(True, dtype=bool)
Parameters:
intrinsic: AbstractManifold#
ambient: AbstractManifold#
embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT]#
embed(intrinsic_point: dict, from_intrinsic_chart: AbstractChart[Any, Any, Any], to_ambient_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)#
Parameters:
Return type:

dict

project(ambient_point: dict, from_ambient_chart: AbstractChart[Any, Any, Any], to_intrinsic_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)#
Parameters:
Return type:

dict

property metric: PullbackMetric#

Induced (pullback) Riemannian metric from the ambient manifold.

property atlas: AbstractAtlas#
angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

property ndim: int#

Return the dimension of the manifold.

This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

final class coordinax.manifolds.EmbeddedChart(embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])#

Bases: AbstractParameterizedChart[EmbeddedManifold, Ks, Ds], Generic[IntrinsicT, AmbientT, Ks, Ds]

Chart for intrinsic coordinates on an embedding manifold.

This is a convenience wrapper that combines an intrinsic chart with an embedding to an ambient Cartesian chart. It provides the same component and dimension information as the intrinsic chart, but also provides a realization map to Cartesian coordinates via the embedding.

The more correct way to represent an embedding manifold is with {class}`~coordinax.manifolds.EmbeddedManifold`.

Examples

Embed/project {class}`~coordinax.charts.SphericalTwoSphere through an ambient {class}`~coordinax.charts.Spherical3D chart:

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, chart)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, chart)
>>> p2
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> jnp.allclose(p2["theta"].value, p["theta"].value)
Array(True, dtype=bool)
Parameters:

embed_map (AbstractEmbeddingMap[TypeVar(IntrinsicT, bound= AbstractChart[Any, Any, Any]), TypeVar(AmbientT, bound= AbstractChart[Any, Any, Any])])

embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT]#

The embedding that defines the map to the ambient chart.

This is the core data of the EmbeddedChart, as it defines the ambient chart and the embedding parameters (e.g., radius for a sphere). The intrinsic chart is determined by the embeddingโ€™s intrinsic property, and the ambient chart is determined by the embeddingโ€™s ambient property.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.embed_map
TwoSphereIn3D(radius=Q(2., 'km'), ambient=Spherical3D(M=Rn(3)))
property M: EmbeddedManifold#

The manifold associated with this chart.

This is an EmbeddedManifold that combines the intrinsic and ambient manifolds defined by the embedding map.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.M
EmbeddedManifold(intrinsic=HyperSphericalManifold(ndim=2),
                 ambient=Rn(3),
                 embed_map=TwoSphereIn3D(radius=Q(2., 'km'),
                                         ambient=Spherical3D(M=Rn(3))))
property intrinsic: IntrinsicT#

The intrinsic chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.intrinsic
SphericalTwoSphere(M=Sn(2))
property ambient: AmbientT#

The ambient chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.ambient
Spherical3D(M=Rn(3))
property components: Ks#

Return the components of the intrinsic chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.components
('theta', 'phi')
property coord_dimensions: Ds#

Return the coordinate dimensions of the intrinsic chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.coord_dimensions
('angle', 'angle')
property cartesian: AbstractChart#

The ambient Cartesian chart for the embedding.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.cartesian
Cart3D(M=Rn(3))
check_data(data: CDictT, /, *, keys: bool = True, values: bool = False)#

Check that the data is compatible with the chart.

Parameters:
  • data (TypeVar(CDictT, bound= dict)) โ€“ The data to check.

  • keys (bool) โ€“ Whether to check that the keys of data match chart.components. If False, this check is skipped. Default is True.

  • values (bool) โ€“ Whether to check that the dimensions of the values in data match chart.coord_dimensions. If False, this check is skipped. Default is False.

Return type:

TypeVar(CDictT, bound= dict)

property ndim: int#

Number of coordinate components (chart dimension).

final class coordinax.manifolds.PullbackMetric(embed_map: AbstractEmbeddingMap, ambient_metric: AbstractMetricField)#

Bases: AbstractMetricField

Pullback metric induced by an embedding map.

Given an embedding \(\iota : N \hookrightarrow M\), the metric \(g_N\) on the submanifold is the pullback of the ambient metric \(g_M\):

\[g_N = \iota^* g_M, \quad \text{or component-wise}\quad (g_N)_{ij} = (J^T G J)_{ij},\]

where \(J = \partial \iota / \partial q\) is the Jacobian of the embedding map and \(G = g_M\) is the ambient metric evaluated at \(\iota(p)\).

Parameters:
  • embed_map (AbstractEmbeddingMap) โ€“ The embedding map from the submanifold into the ambient space.

  • ambient_metric (AbstractMetricField) โ€“ The Riemannian metric on the ambient manifold.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinaxs.api.manifolds as cxmapi
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> embed_map = cxm.TwoSphereIn3D(radius=u.Q(1.0, "km"))
>>> ambient_metric = cxm.FlatMetric(3)
>>> M = cxm.PullbackMetric(embed_map, ambient_metric)
>>> M.signature
(1, 1)
>>> M.ndim
2

The metric matrix is obtained via the dispatch API on an EmbeddedManifold:

>>> M_emb = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")),
... )
>>> at = {"theta": u.Q(jnp.pi / 2, "rad"), "phi": u.Q(0.0, "rad")}
>>> g = cxmapi.metric_matrix(M_emb, at, cxc.sph2)
>>> g.matrix.value
Array([[1., 0.],
       [0., 1.]], dtype=float64)
>>> g.matrix.unit[0, 0]
Unit("km2 / rad2")
embed_map: AbstractEmbeddingMap#
ambient_metric: AbstractMetricField#
property ndim: int#

Dimension of the submanifold.

property signature: tuple[int, ...]#

Metric signature (1,) * m where m is the intrinsic dimension.

Embedding into a Riemannian ambient manifold always produces a Riemannian induced metric (J^T g_M J is positive-definite when J has full column rank). An indefinite ambient carries no such guarantee โ€” a curve in Minkowski space is timelike or spacelike depending on where it goes, not on its dimension โ€” so there is no answer to give from the embedding alone.

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

final class coordinax.manifolds.CustomAtlas(charts: tuple[type[AbstractChart[Any, Any, Any]], ...], chart_default: AbstractChart[Any, Any, Any])#

Bases: AbstractAtlas

Atlas of explicitly registered charts for a custom manifold.

CustomAtlas is an explicit atlas: chart membership is determined only by the set of chart classes provided at construction time.

A chart belongs to the atlas iff:

  1. Its class is in charts.

  2. Its dimensionality matches the atlas ndim.

The default chart must be one of the registered classes and defines the atlas dimension.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> atlas = cxm.CustomAtlas(
...     charts=(cxc.Cart2D, cxc.Polar2D),
...     chart_default=cxc.cart2d,
... )
>>> atlas.ndim
2
>>> atlas.default_chart()
Cart2D(M=Rn(2))
>>> atlas.has_chart(cxc.polar2d)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:
charts: tuple[type[AbstractChart[Any, Any, Any]], ...]#

Explicitly registered chart classes for this atlas.

chart_default: AbstractChart[Any, Any, Any]#

Stored default chart instance provided at construction.

default_chart()#

Return the default chart for this atlas.

Return type:

AbstractChart[Any, Any, Any]

property ndim: int#

Intrinsic dimension of the manifold.

has_chart(chart: AbstractChart[Any, Any, Any])#

Return whether the atlas supports the given chart.

Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

final class coordinax.manifolds.CustomMetric(metric_matrix: ~collections.abc.Callable[[...], ~typing.Any], signature: tuple[int, ...] = <property object>)#

Bases: AbstractMetricField

Metric for a {class}`CustomManifold`, defined by user-provided callables.

CustomMetric allows users to supply their own metric without subclassing AbstractMetricField. Both the metric-matrix callable and the signature must be provided at construction time.

Parameters:
  • metric_matrix (Callable[..., Any]) โ€“ Callable (chart, /, *, at) -> Array returning the \((n \times n)\) metric matrix at the given base point.

  • signature (tuple[int, ...]) โ€“ Metric signature as a length-\(n\) tuple of +1 (positive eigenvalue) and -1 (negative eigenvalue) entries. Use (1,) * n for a Riemannian metric of dimension \(n\).

Examples

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> def flat_3d(chart, /, *, at):
...     return jnp.eye(3)
>>> atlas = cxm.CustomAtlas(
...     charts=(cxc.Cart3D,),
...     chart_default=cxc.cart3d,
... )
>>> metric = cxm.CustomMetric(metric_matrix=flat_3d, signature=(1, 1, 1))
>>> metric.signature
(1, 1, 1)
>>> metric.ndim
3
metric_matrix: Callable[[...], Any]#

Callable (chart, /, *, at) -> Array returning the metric matrix.

property ndim: int#

Return the dimension of the metric (inferred from the chart).

norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> metric.norm(v, cxc.cart3d, at=at)
Q(5., 'm / s')

With a stacked jax.Array (usys required):

>>> v = jnp.array([3.0, 4.0, 0.0])
>>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si)
Array(5., dtype=float64)
Parameters:
Return type:

Any

signature: tuple[int, ...]#

Metric signature as a length-n tuple of +1/-1 per eigenvalue.

final class coordinax.manifolds.CustomManifold(atlas: AbstractAtlas, metric: AbstractMetricField)#

Bases: AbstractManifold

Smooth manifold with a caller-defined explicit atlas.

CustomManifold is a thin wrapper around {class}`CustomAtlas` and inherits all chart validation and transition wrappers from {class}`~coordinax.manifolds.AbstractManifold`.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> atlas = cxm.CustomAtlas(
...     charts=(cxc.Cart2D, cxc.Polar2D),
...     chart_default=cxc.cart2d,
... )
>>> M = cxm.CustomManifold(atlas=atlas, metric=cxm.FlatMetric(2))
>>> M.ndim
2
>>> M.default_chart()
Cart2D(M=Rn(2))
>>> M.has_chart(cxc.polar2d)
True
Parameters:
atlas: AbstractAtlas#

Atlas defining chart compatibility for this manifold.

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#

Return the metric angle between two tangent vectors at at.

This is a thin convenience wrapper over cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).

Parameters:
Return type:

AbstractAngle

check_chart(chart: AbstractChart[Any, Any, Any], /)#

Check that chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.check_chart(cxc.cart2d)  # does not raise
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()#

Return a default chart from the atlas.

This is a convenience property that proxies to the atlas default chart.

Return type:

AbstractChart[Any, Any, Any]

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.default_chart()
Cart2D(M=Rn(2))
has_chart(chart: AbstractChart[Any, Any, Any], /)#

Return whether chart belongs to this manifold atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(2)
>>> M.has_chart(cxc.cart2d)
True
>>> M.has_chart(cxc.cart3d)
False
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

property ndim: int#

Return the dimension of the manifold.

This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#

Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).

Convenience wrapper that calls cxmapi.norm(v, self.metric, chart, at=at, usys=usys) directly. The chart must be passed as the second positional argument (after v).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3)
>>> chart = cxc.Cart3D(M=M)
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}

With a stacked jax.Array (usys required):

>>> usys = u.unitsystems.si
>>> v = jnp.array([3.0, 4.0, 0.0])
>>> M.norm(v, chart, at=at, usys=usys)
Array(5., dtype=float64)

With a CDict of quantities (usys optional):

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> M.norm(v, chart, at=at)
Q(5., 'm / s')
Parameters:
Return type:

Any

metric: AbstractMetricField#

Riemannian metric for this manifold, used for norm and distance computations.

coordinax.manifolds.lorentzian#

A sub-namespace for measurements that need a timelike direction โ€” gated on the metric type AbstractLorentzianMetricField (signature \((-,+,\ldots,+)\)), not on a chart. A metric without one has no method here, rather than a method that accepts and then refuses.

  • causal_character: classify a pair of events as "timelike", "null", or "spacelike". Returns a str, so not jit-able; branch on the sign of interval inside a trace

  • proper_time: elapsed proper time between two timelike-separated events

  • proper_distance: proper distance between two spacelike-separated events

  • rapidity_between: relative rapidity between two timelike tangent vectors โ€” the hyperbolic counterpart of angle_between, which refuses that pair

  • interval: re-exported for convenience โ€” canonical in coordinax.manifolds, since the signed quadratic form is defined for every metric. It appears here because causal_character is its sign and proper_time its root

Named for the signature rather than for โ€œspacetimeโ€ deliberately: charts.galileanct is a 4-D Galilean spacetime and is not Lorentzian, so a spacetime namespace would promise membership these verbs refuse. Named lorentzian rather than minkowski because the gate is the signature โ€” a curved spacetime metric (Schwarzschild, FLRW) inherits the marker and acquires all of them.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm

>>> birth = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> death = {
...     "ct": u.Q(5.0, "m"),
...     "x": u.Q(1.0, "m"),
...     "y": u.Q(0.0, "m"),
...     "z": u.Q(0.0, "m"),
... }

>>> cxm.lorentzian.causal_character(cxc.minkowskict, birth, death)
'timelike'

>>> cxm.lorentzian.proper_time(cxc.minkowskict, birth, death).uconvert("ns").round(2)
Q(16.34, 'ns')