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

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

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

  • 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: Any, **kwargs: Any)#

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(ndim=4)
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(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.Cart3D)
Rn(3)
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], /) 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[str, Any], /) 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)
Parameters:
Return type:

AbstractManifold

coordinax.manifolds.pt_embed(p_pos: dict[str, Any], embedded: object, /, *, usys: AbstractUnitSystem | None = 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)$:

$$ x &= R sintheta cosphi \ y &= R sintheta sinphi \ z &= R costheta $$

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[str, Any]) – 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[str, Any]

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

embed_tangent

Embedding for tangent vector components

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
>>> import wadler_lindig as wl

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)
>>> wl.pprint(p_ambient, short_arrays='compact', named_unit=False)
{'r': Quantity(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)
>>> r2 = jnp.linalg.norm(jnp.array(list(p_ambient_cart.values())))
>>> jnp.allclose(r2, u.Q(25.0, "km"), atol=u.Q(1e-10, "km"))
Array(False, dtype=bool)

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)
>>> wl.pprint(p_ambient_pole, short_arrays='compact', named_unit=False)
{'r': Quantity(5, 'km'), 'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}
coordinax.manifolds.pt_embed(p_intrinsic: dict[str, Any], M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

Embed intrinsic point coordinates into ambient coordinates (manifold).

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

dict[str, Any]

Embed intrinsic point coordinates into ambient coordinates (manifold).

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

dict[str, Any]

Embed intrinsic point coordinates into ambient coordinates.

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

dict[str, Any]

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': Q(45, 'deg'), 'phi': Q(30, 'deg')}
coordinax.manifolds.pt_project(*args: object, usys: AbstractUnitSystem | None = 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)$:

$$

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]

$$

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[str, Any]

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 $sintheta = 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

project_tangent

Project ambient velocity/acceleration onto tangent 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)
>>> wl.pprint(p_sphere, short_arrays='compact', named_unit=False)
{'theta': Quantity(0.78547367, 'rad'), 'phi': Quantity(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.Q(jnp.pi / 3, "rad"),
...             "phi": u.Q(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°
>>> wl.pprint(p_normalized, short_arrays='compact', named_unit=False)
{'theta': Quantity(0.95531662, 'rad'), 'phi': Quantity(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': Q(0., 'rad'), 'phi': Q(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': Q(3.14159265, 'rad'), 'phi': Q(0., 'rad')}
coordinax.manifolds.pt_project(p_ambient: dict[str, Any], M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

Project ambient coordinates onto intrinsic chart coordinates (manifold).

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

dict[str, Any]

Project ambient coordinates onto intrinsic chart coordinates (manifold).

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

dict[str, Any]

Project ambient coordinates onto intrinsic chart coordinates.

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

dict[str, Any]

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': Q(45, 'deg'), 'phi': Q(30, 'deg')}
coordinax.manifolds.pt_project(p_ambient: dict[str, Any], from_chart: AbstractChart, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(45, 'deg'), 'phi': Q(30, 'deg')}
coordinax.manifolds.pt_project(p_ambient: object, from_ambient_chart: AbstractChart, M: HyperSphericalManifold, /, *, usys: AbstractUnitSystem | None = None) object
Parameters:
Return type:

dict[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(0., 'rad')}
coordinax.manifolds.pt_map(*args: Any, **kwargs: Any)#

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 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 pt_map represents the coordinate expression

$$ psi_{mathrm{to}} circ F circ varphi_{mathrm{from}}^{-1}. $$

  • 3D Spherical → Cartesian:

    $$

    x &= r sintheta cosphi \ y &= r sintheta sinphi \ z &= r costheta

    $$

Raises:

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

Parameters:
Return type:

dict[str, Any]

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

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': Q(0.92729522, 'rad'), 'z': Q(5., 'm')}
coordinax.manifolds.pt_map(q: NoneType, /, *fixed_args: Any, **fixed_kw: Any) Callable[..., Any]
Parameters:
Return type:

dict[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(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)
QMatrix([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[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(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)
QMatrix([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[str, Any]

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[str, Any], from_M: AbstractManifold, from_chart: AbstractChart, to_M: AbstractManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: Radial1D, to_M: EuclideanManifold, to_chart: Cart1D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: Cart1D, to_M: EuclideanManifold, to_chart: Radial1D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: Polar2D, to_M: EuclideanManifold, to_chart: Cart2D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: Cart2D, to_M: EuclideanManifold, to_chart: Polar2D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: LonLatSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: LonCosLatSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: MathSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

ProlateSpheroidal3D -> Cart3D.

We calculate through cylindrical coordinates first:

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

Then convert to Cartesian:

$x=rhocosphi$, $y=rhosinphi$, $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[str, Any], from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

Cart3D -> Cylindrical3D.

>>> import coordinax.main 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': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Cart3D | Cylindrical3D, to_M: EuclideanManifold, to_chart: AbstractSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(0., 'rad'), 'lat': Q(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(1.57079633, dtype=float64),
 'distance': Array(1., dtype=float64, weak_type=True)}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(0., 'rad'), 'phi': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(0., 'rad'), 'phi': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(0, 'rad'), 'lat': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(45., 'deg'), 'lat': Q(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': 1.5707963267948966, 'distance': 1.0}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: MathSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(60, 'deg'), 'phi': Q(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[str, Any], from_M: EuclideanManifold, from_chart: MathSpherical3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(30, 'deg'), 'phi': Q(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[str, Any], from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

ProlateSpheroidal3D -> Cylindrical3D.

Uses the focal length $Delta$ stored on from_chart.

Validity constraints (enforced by the representation) are:

  • $Delta > 0$,

  • $mu ge Delta^2$,

  • $|nu| le Delta^2$.

The conversion proceeds via

$rho = sqrt{(mu-Delta^2)left(1-frac{|\nu|}{Delta^2}right)}$, $z = sqrt{mu,frac{|\nu|}{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': Q(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[str, Any], from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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), $|nu| = dfrac{2Delta^2}{S + D},z^2$, and $nu = |\nu|,mathrm{sign}(z)$, with a stability fix when $Delta^2 - |nu|$ 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': Q(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': Q(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[str, Any], from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

{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': Q(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': Q(0., 'rad')}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: EuclideanManifold, from_chart: CartND, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(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': Q(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[str, Any], from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: CartND, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any]

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) QMatrix
Parameters:
Return type:

dict[str, Any]

Transform a QMatrix between charts.

Converts the components of a QMatrix 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[str, Any]

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.

Return type:

dict[str, Any]

Returns:

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

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

  • .. py (function:: pt_map(p: dict[str, typing.Any], from_M: coordinax._src.product.manifold.CartesianProductManifold, from_chart: coordinax._src.product.chart.AbstractCartesianProductChart, to_M: coordinax._src.product.manifold.CartesianProductManifold, to_chart: coordinax._src.product.chart.AbstractCartesianProductChart, /, *, usys: unxt._src.unitsystems.base.AbstractUnitSystem | None = None) -> dict[str, typing.Any]) – :noindex:

  • 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,;pright) – = 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$.

Parameters:

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[str, Any], from_M: AbstractManifold, from_chart: AbstractChart, to_M: CartesianProductManifold, to_chart: AbstractCartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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[str, Any], from_M: CartesianProductManifold, from_chart: AbstractCartesianProductChart, to_M: AbstractManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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(p: dict[str, Any], from_chart: EmbeddedChart, to_chart: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(45, 'deg'), 'phi': Q(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[str, Any], from_chart: AbstractChart, to_chart: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(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[str, Any], from_chart: EmbeddedChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(1., 'rad'), 'phi': Q(0.5, 'rad')}
coordinax.manifolds.pt_map(p: dict[str, Any], M: EmbeddedManifold, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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')))
>>> 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': Q(0.66164791, 'rad'), 'lat': Q(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[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(0., 'rad')}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: HyperSphericalManifold, from_chart: AbstractChart, to_M: HyperSphericalManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

Identity conversion for matching charts.

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

dict[str, Any]

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': Q(0, 'rad'), 'lat': Q(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': Q(45, 'deg'), 'lat': Q(0, 'deg')}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: HyperSphericalManifold, from_chart: LonLatSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(90, 'deg'), 'phi': Q(45, 'deg')}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: LonCosLatSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(45., 'deg'), 'lat': Q(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[str, Any], from_M: HyperSphericalManifold, from_chart: LonCosLatSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(90., 'deg'), 'phi': Q(45., 'deg')}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: MathSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(60, 'deg'), 'phi': Q(30, 'deg')}
coordinax.manifolds.pt_map(p: dict[str, Any], from_M: HyperSphericalManifold, from_chart: MathSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict[str, Any]
Parameters:
Return type:

dict[str, Any]

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': Q(30, 'deg'), 'phi': Q(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[str, Any]

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': Q(1.57079633, 'rad'), 'phi': Q(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': Q(0, 'deg'), 'lat': Q(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[str, Any]

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[str, Any]

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: coordinax.vectors._src.point.Point, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) coordinax.vectors._src.point.Point
Parameters:
Return type:

dict[str, Any]

Convert a point from one chart to another.

>>> import unxt as u
>>> import coordinax.main 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: coordinax.vectors._src.point.Point, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) coordinax.vectors._src.point.Point
Parameters:
Return type:

dict[str, Any]

Convert a vector from one chart to another.

>>> import unxt as u
>>> import coordinax.main 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]>
Parameters:
Return type:

dict[str, Any]

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

Return the diagonal entries of the metric matrix.

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

coordinax.manifolds.scale_factors(metric: FlatMetric, chart: AbstractChart, /, *, at: dict[str, Any], usys: AbstractUnitSystem | None = None) QMatrix
Parameters:
Return type:

Any

Compute only the Euclidean metric diagonal instead of forming J.T @ J.

>>> 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(jnp.array(2.0), "km"),
...     "theta": u.Angle(jnp.pi / 2, "rad"),
...     "phi": u.Angle(jnp.array(0.0), "rad"),
... }
>>> cxm.scale_factors(metric, cxc.sph3d, at=at)
QMatrix([1., 4., 4.], '(, km2 / rad2, km2 / rad2)')
coordinax.manifolds.scale_factors(metric: MinkowskiMetric, chart: MinkowskiCT, /, *, at: dict[str, Any], usys: AbstractUnitSystem | None = None) QMatrix
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)
QMatrix([-1.,  1.,  1.,  1.], '(, , , )')
coordinax.manifolds.scale_factors(metric: RoundMetric, chart: AbstractChart, /, *, at: dict[str, Any], usys: AbstractUnitSystem | None = None) QMatrix
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^2theta_j$ as a 1-D vector, avoiding the O(n^2) cost of 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 QMatrix:

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

Quantity angles → dimensionless QMatrix:

>>> at = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> cxm.scale_factors(metric, cxc.sph2, at=at)
QMatrix([1., 1.], '(, )')
coordinax.manifolds.scale_factors(chart: AbstractChart, /, *, at: dict[str, Any], usys: AbstractUnitSystem | None = None) QMatrix
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)
QMatrix([1., 4., 4.], '(, km2 / rad2, km2 / rad2)')
coordinax.manifolds.scale_factors(metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict[str, Any], usys: AbstractUnitSystem | None = None) QMatrix
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.

>>> 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)
QMatrix([1., 1.], '(, )')
coordinax.manifolds.scale_factors(metric: PullbackMetric, chart: AbstractChart, /, *, at: dict[str, Any], usys: AbstractUnitSystem | None = None) QMatrix
Parameters:
Return type:

Any

Return scale factors for a pullback (induced) metric via Jacobian pullback.

Computes the Jacobian of the composed embedding intrinsic Cartesian ambient to obtain a unit-consistent Jacobian where every entry has the same unit (ambient_cart_unit / intrinsic_unit). The squared column norms then give the scale factors with correct units.

>>> 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)
QMatrix([4., 4.], '(m2 / rad2, m2 / rad2)')
Parameters:
Return type:

Any

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

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[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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')
Parameters:
Return type:

Any

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

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 coordinax.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 with a helpful message 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).

Parameters:
  • M (EmbeddedManifold) – The manifold carrying the metric field.

  • point (dict) – A component dictionary giving the coordinates in chart.

  • chart (AbstractChart) – The coordinate chart in which to express the metric.

  • M – An embedded submanifold; carries intrinsic, ambient, and embed_map fields.

  • point – A coordinate dictionary in the intrinsic chart coordinates.

  • chart – The intrinsic chart (passed through for API consistency).

Raises:
  • NotImplementedError – When no specific dispatch rule is registered for the given types.

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d1.Cart1D | coordinax._src.charts.d2.Cart2D | coordinax._src.charts.d3.Cart3D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in a Cartesian chartg = 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 coordinax.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)

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.dn.CartND, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in CartNDg = 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 coordinax.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)

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d1.Radial1D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in Radial1Dg = 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 coordinax.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

  • QMatrix([1.], '(,)')

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d2.Polar2D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in Polar2Dg = 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 coordinax.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

  • QMatrix([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

  • QMatrix([1., 9.], '(, m2 / rad2)')

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d3.Cylindrical3D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in Cylindrical3Dg = 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 coordinax.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

  • QMatrix([1., 9., 1.], '(, m2 / rad2, )')

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d3.Spherical3D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in Spherical3Dg = 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 coordinax.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

  • QMatrix([1., 4., 4.], '(, m2 / rad2, m2 / rad2)')

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d3.MathSpherical3D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • Euclidean metric in MathSpherical3Dg = 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 coordinax.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

  • QMatrix([1., 4., 4.], '(, m2 / rad2, m2 / rad2)')

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.charts.d3.LonLatSpherical3D, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • 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 coordinax.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

  • QMatrix([4., 4., 1.], '(m2 / rad2, m2 / rad2, )')

  • .. py:function: – metric_matrix(M: coordinax._src.euclidean.manifold.EuclideanManifold, point: dict, chart: coordinax._src.base.charts.AbstractChart, /) -> coordinax._src.metric.matrix.DenseMetric: :noindex:

  • 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 coordinax.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

  • .. py:function: – metric_matrix(M: coordinax._src.minkowski.manifold.MinkowskiManifold, point: dict, chart: coordinax._src.minkowski.charts.MinkowskiCT, /) -> coordinax._src.metric.matrix.DiagonalMetric: :noindex:

  • 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 coordinax.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)

  • .. py:function: – metric_matrix(M: coordinax._src.minkowski.manifold.MinkowskiManifold, point: dict, chart: coordinax._src.base.charts.AbstractChart, /) -> coordinax._src.metric.matrix.DenseMetric: :noindex:

  • 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 coordinax.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

  • .. py:function: – metric_matrix(M: coordinax._src.product.manifold.CartesianProductManifold, point: dict, chart: coordinax._src.product.chart.AbstractCartesianProductChart, /) -> coordinax._src.metric.matrix.DenseMetric: :noindex:

  • 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 coordinax.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

  • .. py:function: – metric_matrix(M: coordinax._src.embedded.manifold.EmbeddedManifold, point: dict, chart: coordinax._src.base.charts.AbstractChart, /) -> coordinax._src.metric.matrix.DenseMetric: :noindex:

  • Induced metric on an embedded submanifold via Jacobian pullback.

  • Computes $g_{ij} = sum_k J^k_i J^k_j$ where $J$ is the Jacobian of the

  • composition intrinsic → Cartesian ambient. Routing through Cartesian

  • ambient coordinates ensures all entries of $J$ share the same unit

  • (cart_unit / intrinsic_unit), so the matrix product $J^T J$ is

  • unit-compatible and the result carries physically correct units.

Returns:

Induced metric matrix at point, backed by a QMatrix with units cart_unit^2 / (intrinsic_unit_i * intrinsic_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 coordinax.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, weak_type=True)

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, weak_type=True)
>>> g2.matrix.unit[0, 0]
Unit("m2 / rad2")
coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict[str, Any], 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 coordinax.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
>>> g.diagonal
Array([1., 1.], dtype=float64)

$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_representation(M: Any, chart: Any, /)#

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 coordinax.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 coordinax.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 coordinax.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, /) 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 coordinax.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'>
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 coordinax.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 coordinax.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.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 coordinax.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 coordinax.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'>
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. {func}`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))
default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

Return type:

AbstractChart[Any, Any, Any]

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

$$ g(q) = begin{pmatrix}

g_{11}(q) & cdots & g_{1n}(q) \ vdots & ddots & vdots \ g_{n1}(q) & cdots & g_{nn}(q) end{pmatrix}.

$$

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.

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.

atlas#

The atlas that defines the smooth structure of this manifold. The atlas records the intrinsic dimension and determines which charts are compatible.

Type:

AbstractAtlas

ndim#

Intrinsic dimension $n$ of the manifold, forwarded from {attr}`atlas.ndim <AbstractAtlas.ndim>`.

Type:

int

default_chart#

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

Type:

AbstractChart

Return type:

AbstractChart[Any, Any, Any]

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

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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).

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: QMatrix | Array)#

Bases: AbstractMetricMatrix

Diagonal metric matrix stored as a 1-D array or QMatrix.

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 (QMatrix | 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: QMatrix | 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 QMatrix, 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 QMatrix() 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)

QMatrix diagonal — diagonal units are preserved and off-diagonal entries get the geometric-mean unit:

>>> from coordinax.internal import QMatrix
>>> d = DiagonalMetric(QMatrix(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 QMatrix.

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)

QMatrix diagonal — returns a Quantity:

>>> import unxt as u
>>> from coordinax.internal import QMatrix
>>> d = DiagonalMetric(QMatrix(jnp.array([2.0, 3.0]), unit=("m2", "s2")))
>>> d.determinant
Q(6., 'm2 s2')
final class coordinax.manifolds.DenseMetric(matrix: QMatrix | 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 (QMatrix | 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: QMatrix | 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 QMatrix-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)

QMatrix — inverse carries reciprocal units:

>>> import unxt as u
>>> from coordinax.internal import QMatrix, UnitsMatrix
>>> g = DenseMetric(
...     QMatrix(
...         jnp.array([[4.0, 0.0], [0.0, 1.0]]),
...         unit=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 QMatrix 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)

QMatrix — returns a Quantity:

>>> import unxt as u
>>> from coordinax.internal import QMatrix
>>> g = DenseMetric(QMatrix(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[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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
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.

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

default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

Return type:

AbstractChart[Any, Any, Any]

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

default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

Return type:

AbstractChart[Any, Any, Any]

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

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.

atlas#

The atlas of coordinate charts compatible with this manifold. Its {attr}`~EuclideanAtlas.ndim` equals ndim.

Type:

EuclideanAtlas

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[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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

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

default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

Return type:

AbstractChart[Any, Any, Any]

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

$$g = begin{pmatrix} 1 & 0 \ 0 & sin^2theta end{pmatrix}.$$

Parameters:

ndim (int) – Intrinsic dimension of the sphere (e.g. ndim=2 for $S^2$).

Examples

>>> import jax.numpy as jnp
>>> import coordinax.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)}
>>> cxmapi.metric_matrix(cxm.S2, at, cxc.sph2).diagonal
Array([1., 1.], dtype=float64)
ndim: int#

Intrinsic dimension of the sphere.

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

(1,) * ndim — the round sphere metric is Riemannian.

Type:

Metric signature

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[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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

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(ndim: int = 4)#

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.

Parameters:

ndim (int) – Intrinsic dimension of the manifold. Always 4 for Minkowski spacetime.

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=4))
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(ndim=4))
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

default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

Return type:

AbstractChart[Any, Any, Any]

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

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 coordinax.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, ...]#

(-1, 1, 1, 1) — Lorentzian pseudo-Riemannian.

Type:

Metric signature

property ndim: int#

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

final class coordinax.manifolds.MinkowskiManifold(ndim: int = 4, /)#

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.

Parameters:

ndim (int) – Intrinsic dimension. Always 4 for Minkowski spacetime.

Examples

>>> import coordinax.manifolds as cxm
>>> import coordinax.charts as cxc
>>> M = cxm.MinkowskiManifold()
>>> M
MinkowskiManifold(ndim=4)
>>> M.ndim
4
>>> M.atlas
MinkowskiAtlas(ndim=4)
>>> M.default_chart()
MinkowskiCT(M=MinkowskiManifold(ndim=4))
>>> M.has_chart(cxc.minkowskict)
True
>>> M.has_chart(cxc.cart3d)
False
ndim: int#

Intrinsic dimension of Minkowski spacetime (always 4).

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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

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
default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

Return type:

AbstractChart[Any, Any, Any]

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

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.

atlas#

The product atlas formed from the factor atlases.

Type:

CartesianProductAtlas

metric#

The canonical product metric formed from the factor metrics.

Type:

ProductMetric

ndim#

Total intrinsic dimension $sum_i n_i$.

Type:

int

default_chart#

Product of the default charts from each factor atlas.

Type:

CartesianProductChart

Return type:

AbstractChart[Any, Any, Any]

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=60)
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[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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
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.

intrinsic: IntrinsicT#
ambient: AmbientT#
abstractmethod embed(point: dict[str, Any], /, *, usys: AbstractUnitSystem | None = None)#

Embed intrinsic coordinates into ambient coordinates.

Parameters:
Return type:

dict[str, Any]

abstractmethod project(point: dict[str, Any], /, *, usys: AbstractUnitSystem | None = None)#

Project ambient coordinates to intrinsic coordinates.

Parameters:
Return type:

dict[str, Any]

final class coordinax.manifolds.CustomEmbeddingMap(intrinsic: IntrinsicT, ambient: AmbientT, embed_fn: EPCallable, project_fn: EPCallable)#

Bases: AbstractEmbeddingMap[IntrinsicT, AmbientT]

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[str, Any], /, *, usys: AbstractUnitSystem | None = None)#

Embed intrinsic coordinates into ambient coordinates.

Parameters:
Return type:

dict[str, Any]

project(point: dict[str, Any], /, *, usys: AbstractUnitSystem | None = None)#

Project ambient coordinates to intrinsic coordinates.

Parameters:
Return type:

dict[str, Any]

final class coordinax.manifolds.TwoSphereIn3D(radius: AbstractQuantity | float | int)#

Bases: AbstractEmbeddingMap[IntrinsicT, AmbientT]

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 {class}`~coordinax.charts.Spherical3D, then {meth}`embed` returns spherical coordinates (r, theta, phi) and {meth}`project` expects the same.

  • If ambient is {class}`~coordinax.charts.Cart3D, then {meth}`embed` performs SphericalTwoSphere -> Spherical3D -> Cart3D and returns Cartesian coordinates (x, y, z); {meth}`project` performs Cart3D -> Spherical3D -> SphericalTwoSphere.

Parameters:
  • radius (AbstractQuantity | float | int) – Sphere radius R.

  • ambient – Ambient chart. Defaults to {class}`~coordinax.charts.Spherical3D.

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)

Embed/project through an ambient {class}`~coordinax.charts.Cart3D chart (routing via {class}`~coordinax.charts.Spherical3D internally):

>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> xyz = cxm.pt_embed(p, chart)
>>> p3 = cxm.pt_project(xyz, chart)
>>> p3

{‘theta’: Angle(1.57079633, ‘rad’), ‘phi’: Angle(0., ‘rad’)}

>>> bool(jnp.allclose(u.ustrip("rad", p3["phi"]), u.ustrip("rad", p["phi"])))
True
radius: AbstractQuantity | float | int#
property intrinsic: AbstractChart[Any, Any, Any]#

The intrinsic chart is always coordinax.charts.SphericalTwoSphere.

property ambient: AbstractChart[Any, Any, Any]#

The ambient chart is always coordinax.charts.Spherical3D.

embed(q: dict[str, Any], /, *, usys: AbstractUnitSystem | None = None)#

Embed SphericalTwoSphere intrinsic coords into Spherical3D coords.

Parameters:
Return type:

dict[str, Any]

project(x_sph: dict[str, Any], /, *, usys: AbstractUnitSystem | None = None)#

Project Spherical3D onto SphericalTwoSphere intrinsic coords.

Parameters:
Return type:

dict[str, Any]

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

>>> manifold = cxm.embedded_twosphere(radius=u.Q(2.0, "km"))
>>> manifold
EmbeddedManifold(intrinsic=HyperSphericalManifold(…),

ambient=Rn(3), embed_map=TwoSphereIn3D(radius=Q(2., ‘km’)))

>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, manifold)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

With Cartesian ambient:

>>> manifold = cxm.embedded_twosphere(

… radius=u.Q(2.0, “km”), ambient=cxc.cart3d, … ) >>> xyz = cxm.pt_embed(p, manifold) >>> p3 = cxm.pt_project(xyz, manifold) >>> p3 {‘theta’: Angle(1.57079633, ‘rad’), ‘phi’: Angle(0., ‘rad’)}

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[str, Any], from_intrinsic_chart: AbstractChart[Any, Any, Any], to_ambient_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)#
Parameters:
Return type:

dict[str, Any]

project(ambient_point: dict[str, Any], from_ambient_chart: AbstractChart[Any, Any, Any], to_intrinsic_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)#
Parameters:
Return type:

dict[str, Any]

property metric: PullbackMetric#

Induced (pullback) Riemannian metric from the ambient manifold.

property atlas: AbstractAtlas#
angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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
final class coordinax.manifolds.EmbeddedChart(embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])#

Bases: AbstractChart[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'))
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')))
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[str, Any])) – 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[str, Any])

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:

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.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, weak_type=True)
>>> g.matrix.unit[0, 0]
Unit("km2 / rad2")
embed_map: AbstractEmbeddingMap#
ambient_metric: AbstractMetricField#
property signature: tuple[int, ...]#

(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).

Type:

Metric signature

property ndim: int#

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

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]

default_chart_for(M: AbstractManifold, /)#

Return a default chart from the atlas for the given manifold.

This is a thin convenience wrapper over self.default_chart() that checks that the manifold’s atlas matches this atlas.

>>> import coordinax.manifolds as cxm
>>> M = cxm.R2
>>> M.atlas.default_chart_for(M)
Cart2D(M=Rn(2))
>>> try: M.atlas.default_chart_for(cxm.R3)
... except ValueError as e: print(e)
Atlas EuclideanAtlas(ndim=2) does not match manifold atlas
EuclideanAtlas(ndim=3).
Parameters:

M (AbstractManifold)

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

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.

metric: AbstractMetricField#

Riemannian metric for this manifold, used for norm and distance computations.

angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict[str, Any], vvec: dict[str, Any], /, *, at: dict[str, Any], 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#

Intrinsic dimension of the manifold.