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 inputsscale_factors: return the metric diagonal in a chart at a base pointmetric_matrix: the metric at a point, in a chart. Its container is split by chart family, deliberately. Flat charts (cart1d/2d/3d,cartnd,minkowskict) return a bare array: their metric is dimensionless, and they are the pure-JAX fast path, where boxing costs ~27% per call and 2.3x ontree_flatten. Curvilinear charts (polar2d,cyl3d,sph3d, โฆ) return aQuantityMatrixcarrying real units such as \(m^2/rad^2\), which an embedded sphere scales as \(R^2\) โ information a bare array cannot hold. The intrinsic sphere charts (sph2,lonlat_sph2) return a dimensionlessQuantityMatrix: angles over angles, but boxed like the curvilinear rules whose consumers they share. A generic consumer normalises withgetattr(g, "value", g); the convention is pinned bytests/unit/manifolds/test_metric_container_convention.pyangle_between: return the metric angle between two tangent-vector CDictsnorm: compute the Riemannian norm \(\|v\|_g = \sqrt{g_p(v,v)}\) of a tangent vector in a chart. Requires a positive-definite metric; raisesNotImplementedErrorfor an indefinite one (e.g. Minkowski), where the square root would benangeodesic_distance: length of the shortest path between two points along the manifold. Symmetric and chart-invariant, computed from the manifoldโs geometry: the straight line in flat space, the great circle on a sphere. A manifold with no closed-form geodesic raisesNotImplementedErrorrather than approximating, as does Minkowski, whose indefinite metric admits no distancechord_distance: length of the straight line between two points through the ambient space they are embedded in โ the tunnel rather than the surface path. A different measurement fromgeodesic_distance, not an approximation to it: on a sphere of radius \(R\) separated by a central angle \(\theta\), the geodesic is \(R\theta\) and the chord is \(2R\sin(\theta/2)\). Needs an embedding, so a manifold that is its own ambient (anything Euclidean) raisesNotImplementedErrorand points atgeodesic_distanceinterval: signed squared interval \(\Delta s^2 = \Delta x^\top G\,\Delta x\) of the coordinate difference, with the metric taken at the first point. Defined for every metric, including indefinite ones, and the causal invariant when Lorentzian. It is not the squaredgeodesic_distanceexcept where the metric is constant along the path, i.e. on a flat manifold in Cartesian coordinates โ flatness alone is not enough, since a curvilinear chart on flat space has a varying metric. The verbs that read its sign need a timelike direction and live in thecoordinax.manifolds.lorentziansub-namespace belowpt_embed: embed intrinsic coordinates into ambient coordinatespt_project: project ambient coordinates back to intrinsic chart coordinatespt_map: manifold-related re-export of point realization map
Available Objects#
Manifolds#
AbstractLorentzianMetricField: structural marker for metrics with a Lorentzian signature; gates thelorentziansub-namespace belowAbstractManifold: base manifold interfaceEuclideanManifold/R3: Euclidean manifold family and 3D convenienceHyperSphericalManifold: intrinsic two-sphere manifoldCartesianProductManifold: Cartesian product manifoldEmbeddedManifold: manifold with explicit embedding into an ambient manifoldCustomManifold: manifold backed by a caller-provided atlas
Atlases#
AbstractAtlas: base atlas interfaceEuclideanAtlas: atlas for Euclidean charts of fixed dimensionHyperSphericalAtlas: atlas for intrinsic two-sphere chartsCartesianProductAtlas: atlas for product manifoldsCustomAtlas: explicit atlas with caller-controlled chart membership
Embeddings and Embedded Charts#
AbstractEmbeddingMap: base embedding map interfaceCustomEmbeddingMap: user-defined embedding mapsTwoSphereIn3D/embedded_twosphere: standard two-sphere embedding in 3DEmbeddedChart: convenience chart wrapper combining intrinsic chart and embedding
Notes#
Manifold methods delegate chart transitions to
cxc.pt_map.For intrinsic two-sphere workflows, use
HyperSphericalManifoldand intrinsic two-sphere charts (sph2,lonlat_sph2, etc.) rather than Euclidean 2D charts.
coordinax.manifolds module.
- coordinax.manifolds.guess_manifold(*args, **kwargs)#
Guess the manifold from arguments.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(2) >>> guess_manifold(M) is M True
>>> cxm.guess_manifold({"x": 1, "y": 2, "z": 3}) Rn(3)
>>> cxm.guess_manifold(cxc.sph3d) Rn(3)
>>> cxm.guess_manifold(cxc.sph2) HyperSphericalManifold(ndim=2)
- coordinax.manifolds.guess_manifold(obj: EuclideanAtlas, /) EuclideanManifold
- Parameters:
- Return type:
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:
Infer manifold from a MinkowskiCT chart class.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.guess_manifold(cxc.MinkowskiCT) MinkowskiManifold()
- coordinax.manifolds.guess_manifold(obj: HyperSphericalAtlas, /) HyperSphericalManifold
- Parameters:
- Return type:
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:
Return a HyperSphericalManifold manifold.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.SphericalTwoSphere()) HyperSphericalManifold(ndim=2)
- coordinax.manifolds.guess_manifold(_: type[AbstractSphericalTwoSphere], /) HyperSphericalManifold
- Parameters:
- Return type:
Return a HyperSphericalManifold manifold, from the chart class.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.SphericalTwoSphere) HyperSphericalManifold(ndim=2)
- coordinax.manifolds.guess_manifold(_: type[CircularOneSphere], /) HyperSphericalManifold
- Parameters:
- Return type:
Return a HyperSphericalManifold manifold, from the chart class.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> cxm.guess_manifold(cxc.CircularOneSphere) HyperSphericalManifold(ndim=1)
- coordinax.manifolds.guess_manifold(obj: AbstractManifold, /) AbstractManifold
- Parameters:
- Return type:
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:
Infer manifold from a chart class.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.guess_manifold(cxc.PoincarePolar6D) NoManifold()
- coordinax.manifolds.guess_manifold(chart: AbstractChart, /) AbstractManifold
- Parameters:
- Return type:
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:
Infer manifold from a chart class.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.guess_manifold(cxc.Cart0D) Rn(0)
- coordinax.manifolds.guess_manifold(_: type[Cart1D | Radial1D | Time1D], /) EuclideanManifold
- Parameters:
- Return type:
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:
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:
Infer manifold from a chart class.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.guess_manifold(cxc.Cart3D) Rn(3)
- coordinax.manifolds.guess_manifold(obj: dict, /) AbstractManifold
- Parameters:
- Return type:
Infer manifold from a mapping.
Redispatches based on the inferred chart.
>>> import coordinax.manifolds as cxm >>> cxm.guess_manifold({"x": 1, "y": 2, "z": 3}) Rn(3)
- coordinax.manifolds.guess_manifold(_: type[CartND], /) EuclideanManifold
- Parameters:
- Return type:
Infer manifold from a chart class.
CartND holds its components in one array, so the dimension is per instance; the class default is RN, as CartND() and cxc.cartnd carry.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.guess_manifold(cxc.CartND) Rn(True)
- Parameters:
- Return type:
- coordinax.manifolds.pt_embed(p_pos, embedded, /, *, usys=None)#
Embed intrinsic point coordinates into ambient coordinates.
This function maps point coordinates from an intrinsic chart chart on an embedded manifold to the corresponding ambient space coordinates. It is the fundamental operation for working with embedded manifolds such as spheres, cylinders, or other submanifolds of Euclidean space.
Mathematical Definition:
Given an embedding \(\iota: M \to \mathbb{R}^n\) of a manifold \(M\) into ambient space \(\mathbb{R}^n\), and intrinsic coordinates \(q = (q^1, \ldots, q^k)\) on a chart \(U \subset M\), this function computes the ambient coordinates:
\[x = \iota(q) = (x^1(q), \ldots, x^n(q))\]For example, embedding the 2-sphere \(S^2\) into \(\mathbb{R}^3\) using spherical coordinates \((\theta, \phi)\):
\[\begin{split}x &= R \sin\theta \cos\phi \\ y &= R \sin\theta \sin\phi \\ z &= R \cos\theta\end{split}\]where \(R\) is the radius parameter stored in the embedding object.
- Parameters:
embedded (
object) โThe embedded manifold chart, typically an
coordinax.manifolds.EmbeddedChartinstance. This encapsulates:intrinsic: The intrinsic chart (e.g.,SphericalTwoSphere)embedding: AnAbstractEmbeddingthat owns the ambient chart and any parameters (e.g.,TwoSphereIn3Dwithradius)
p_pos (
dict) โ Dictionary of intrinsic position coordinates. Keys must matchembedded.components(e.g.,"theta"and"phi"forSphericalTwoSphere). 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 orDeltain {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"forCart3D). Values have dimensions appropriate for the ambient space (e.g., length for Cartesian coordinates).- Return type:
- 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_tangentfor differential quantities.Embedding parameters (like
radiusforTwoSphereIn3D) 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_projectInverse operation projecting ambient to intrinsic coordinates
coordinax.manifolds.EmbeddedChartContainer for embedded manifold charts
Examples
Embedding a point on the 2-sphere into 3D Cartesian coordinates:
>>> import quaxed.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
Create an embedded manifold for a sphere of radius 5 km:
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(5, "km")))
Embed a point at the equator:
>>> p_intrinsic = { ... "theta": u.Angle(jnp.pi / 2, "rad"), # equator ... "phi": u.Angle(0.0, "rad"), # along x-axis ... } >>> p_ambient = cxm.pt_embed(p_intrinsic, chart) >>> p_ambient {'r': Q(5, 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
Verify the point lies on the sphere:
>>> p_ambient_cart = cxm.pt_map(p_ambient, chart.ambient, cxc.cart3d) >>> r = jnp.linalg.norm(jnp.array(list(p_ambient_cart.values()))) >>> jnp.allclose(r, chart.embed_map.radius, atol=u.Q(1e-10, "km")) Q(True, '')
Embedding a point at the north pole:
>>> p_pole = { ... "theta": u.Angle(0.0, "rad"), # north pole ... "phi": u.Angle(0.0, "rad"), # phi is arbitrary at poles ... } >>> p_ambient_pole = cxm.pt_embed(p_pole, chart) >>> p_ambient_pole {'r': Q(5, 'km'), 'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}
- coordinax.manifolds.pt_embed(p_intrinsic: dict, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
p_pos (dict)
embedded (object)
usys (AbstractUnitSystem | None)
- Return type:
Embed intrinsic point coordinates into ambient coordinates (manifold).
- coordinax.manifolds.pt_embed(p_intrinsic: dict, from_intrinsic_chart: AbstractChart, to_ambient_chart: AbstractChart, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
p_pos (dict)
embedded (object)
usys (AbstractUnitSystem | None)
- Return type:
Embed intrinsic point coordinates into ambient coordinates (manifold).
- coordinax.manifolds.pt_embed(p_intrinsic: dict, from_intrinsic_chart: AbstractChart, to_ambient_chart: AbstractChart, embed_map: AbstractEmbeddingMap, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
p_pos (dict)
embedded (object)
usys (AbstractUnitSystem | None)
- Return type:
Embed intrinsic point coordinates into ambient coordinates.
- coordinax.manifolds.pt_embed(p_intrinsic: dict, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
p_pos (dict)
embedded (object)
usys (AbstractUnitSystem | None)
- Return type:
Embed intrinsic point coordinates into ambient coordinates.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> p_intrinsic = {"theta": u.Q(45, "deg"), "phi": u.Q(30, "deg")} >>> cxm.pt_embed(p_intrinsic, chart) {'r': Q(2., 'km'), 'theta': Angle(45, 'deg'), 'phi': Angle(30, 'deg')}
- coordinax.manifolds.pt_project(*args, usys=None)#
Project ambient space coordinates onto intrinsic chart coordinates.
This function performs the inverse operation of
pt_embed, taking coordinates from the ambient (embedding) space and projecting them back onto the intrinsic manifold chart. Itโs essential for converting between extrinsic and intrinsic charts of points on embedded submanifolds.Mathematical Definition:
For an embedding \(\iota: M \to \mathbb{R}^n\) of a \(k\)-dimensional manifold \(M\) into \(n\)-dimensional Euclidean space, the projection \(\pi: \mathbb{R}^n \to M\) (or more precisely, onto a chart \(U \subset M\)) satisfies:
\[\pi \circ \iota = \mathrm{id}_M\]meaning that projecting after embedding returns the original point (up to numerical precision and chart domain).
For a 2-sphere \(S^2 \subset \mathbb{R}^3\) with radius \(R\), given Cartesian coordinates \((x, y, z)\):
\[\begin{split}r &= \sqrt{x^2 + y^2 + z^2} \\ \theta &= \arccos\!\left(\frac{z}{r}\right) \in [0, \pi] \\ \phi &= \operatorname{atan2}(y, x) \in (-\pi, \pi]\end{split}\]The projection normalizes the input by \(r\), so it maps any point in \(\mathbb{R}^3 \setminus \{0\}\) to the sphere.
Key properties:
Local inverse: On the manifold, \(\pi(\iota(q)) = q\) exactly
Normalization: Points near the manifold are projected onto it (e.g., points near the sphere are normalized to lie exactly on it)
Singularities: Projection may have singularities where the manifoldโs chart has coordinate singularities (e.g., poles of a sphere)
Not globally defined: Projection is typically only defined for points in a neighborhood of the manifold, not all of \(\mathbb{R}^n\)
- Parameters:
*args (
object) โTypically an
EmbeddedChartchart and a dictionary of ambient coordinates, but the function supports multiple dispatch patterns. Common signature:embeddedEmbeddedChartThe embedded manifold chart specifying the chart and ambient space
p_ambientCDictAmbient 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 orDeltain {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:
- Raises:
NotImplementedError โ If no projection is defined for the given manifold and ambient space.
ValueError โ If the ambient point cannot be projected (e.g., origin for sphere).
Notes
Normalization: Implementations often normalize inputs to handle points that are close to but not exactly on the manifold. For a sphere, any non-zero point in \(\mathbb{R}^3\) is normalized to radius \(R\).
Coordinate singularities: At chart singularities (e.g., sphere poles where \(\sin\theta = 0\)), convention determines the value of singular coordinates (typically \(\phi = 0\) at poles).
Round-trip accuracy: For points on the manifold,
pt_project(pt_embed(q, embedded), embedded)should returnqup 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_embedEmbed intrinsic chart coordinates into ambient space
Examples
>>> import jax >>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u >>> import wadler_lindig as wl
Project Cartesian coordinates onto a 2-sphere. Create an embedded 2-sphere of radius 1 km:
>>> sphere = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1, "km")))
Project a point in 3D Cartesian space onto the sphere. Start with a point slightly off the sphere:
>>> p_cart = {"x": u.Q(0.5, "km"), "y": u.Q(0.5, "km"), "z": u.Q(0.707, "km")} >>> p_sphere = cxm.pt_project(p_cart, cxc.cart3d, sphere) >>> p_sphere {'theta': Angle(0.78547367, 'rad'), 'phi': Angle(0.78539816, 'rad')}
The projection normalizes the point to lie exactly on the sphere. Verify round-trip accuracy (project after embed returns original point):
>>> q_sphere = {"theta": u.Angle(jnp.pi / 3, "rad"), ... "phi": u.Angle(jnp.pi / 4, "rad")} >>> q_cart = cxm.pt_embed(q_sphere, sphere) >>> q_recovered = cxm.pt_project(q_cart, sphere) >>> all(jax.tree.map(jnp.isclose, q_sphere, q_recovered)) True
Project from an arbitrary point in space (not on sphere). The projection normalizes the radius:
>>> p_far = {"x": u.Q(2.0,"km"), "y": u.Q(2.0,"km"), "z": u.Q(2.0,"km")} >>> p_normalized = cxm.pt_project(p_far, cxc.cart3d, sphere) >>> # Direction is preserved: all coordinates equal โ theta โ 54.7ยฐ, phi = 45ยฐ >>> p_normalized {'theta': Angle(0.95531662, 'rad'), 'phi': Angle(0.78539816, 'rad')}
Handle coordinate singularities at the poles. At the north pole (\(\theta = 0\)), \(\phi\) is conventionally set to 0:
>>> p_north = {"x": u.Q(0.0, "km"), "y": u.Q(0.0, "km"), ... "z": u.Q(1.0, "km")} # North pole >>> cxm.pt_project(p_north, cxc.cart3d, sphere) {'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}
At the south pole (\(\theta = \pi\)), \(\phi\) is also set to 0:
>>> p_south = { "x": u.Q(0, "km"), "y": u.Q(0, "km"), "z": u.Q(-1, "km")} >>> cxm.pt_project(p_south, cxc.cart3d, sphere) {'theta': Angle(3.14159265, 'rad'), 'phi': Angle(0., 'rad')}
- coordinax.manifolds.pt_project(p_ambient: dict, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project ambient coordinates onto intrinsic chart coordinates (manifold).
- coordinax.manifolds.pt_project(p_ambient: dict, from_ambient_chart: AbstractChart, to_intrinsic_chart: AbstractChart, M: EmbeddedManifold, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project ambient coordinates onto intrinsic chart coordinates (manifold).
- coordinax.manifolds.pt_project(p_ambient: dict, from_ambient_chart: AbstractChart, to_intrinsic_chart: AbstractChart, embed_map: AbstractEmbeddingMap, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project ambient coordinates onto intrinsic chart coordinates.
- coordinax.manifolds.pt_project(p_ambient: dict, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project ambient coordinates onto intrinsic chart coordinates.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> p_amb = {"r": u.Q(2.0, "km"), "theta": u.Q(45, "deg"), "phi": u.Q(30, "deg")} >>> cxm.pt_project(p_amb, chart) {'theta': Angle(45, 'deg'), 'phi': Angle(30, 'deg')}
- coordinax.manifolds.pt_project(p_ambient: dict, from_chart: AbstractChart, embedding: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project ambient coordinates onto intrinsic chart coordinates.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> p_amb = {"r": u.Q(2.0, "km"), "theta": u.Q(45, "deg"), "phi": u.Q(30, "deg")} >>> cxm.pt_project(p_amb, cxc.sph3d, chart) {'theta': Angle(45, 'deg'), 'phi': Angle(30, 'deg')}
- coordinax.manifolds.pt_project(p_ambient: object, from_ambient_chart: AbstractChart, M: HyperSphericalManifold, /, *, usys: AbstractUnitSystem | None = None) object
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project a point from the 3D chart to the two-sphere intrinsic chart.
This projection map is a special case for projecting from 3D charts to the two-sphere intrinsic chart, which is a common use case. The projection does not depend on the radius of the embedding, so this projection works in general.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> q = {"x": u.Q(1.0, "km"), "y": u.Q(0.0, "km"), "z": u.Q(0.0, "km")} >>> cxm.pt_project(q, cxc.cart3d, cxm.S2) {'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
- coordinax.manifolds.pt_project(p_ambient: Point, M: HyperSphericalManifold, /, *, usys: AbstractUnitSystem | None = None) Any
- Parameters:
args (object)
usys (AbstractUnitSystem | None)
- Return type:
Project a point from an ambient space onto a manifold.
Examples
>>> import coordinax as cx >>> import coordinax.manifolds as cxm >>> import unxt as u
Project a spherical 3D point onto the 2-sphere:
>>> q = cx.Point.from_( ... {"r": u.Q(1, "m"), "theta": u.Q(2, "rad"), "phi": u.Q(3, "rad")}, ... cx.sph3d) >>> cxm.pt_project(q, cxm.S2) Point( {'theta': Angle(2, 'rad'), 'phi': Angle(3, 'rad')}, chart=SphericalTwoSphere(M=Sn(2)) )
- coordinax.manifolds.pt_map(*args, **kwargs)#
Transform position coordinates from one chart to another.
This function implements the most general point-coordinate map between two compatible chart representations of the same geometric point. It is a point-wise map that preserves the physical location while changing the coordinate description.
For charts in the same atlas on the same manifold, this reduces to the ordinary chart transition map handled by coordinax.charts.pt_map. It is the intrinsic coordinate-change operation: the underlying point on the manifold is unchanged, and only its coordinate representation is changed.
However, this function is not restricted to two charts on the same manifold. It may also represent a realization-style map between charts attached to different manifolds when one is a realization of the other, such as an intrinsic chart on an embedded manifold and a chart on its ambient manifold. In that case, this function may change both the chart and the manifold in which the point is being represented.
Mathematical Definition:
Let \((U, \varphi_{\mathrm{from}})\) and \((V, \varphi_{\mathrm{to}})\) be charts on the same manifold \(M\), with overlapping domains. The transition map is
\[\varphi_{\mathrm{to}} \circ \varphi_{\mathrm{from}}^{-1} : \varphi_{\mathrm{from}}(U \cap V) \to \varphi_{\mathrm{to}}(U \cap V).\]If a point \(p \in U \cap V\) has coordinates \(q = \varphi_{\mathrm{from}}(p)\), then this function returns \(p' = \varphi_{\mathrm{to}}(p)\) for the same manifold point.
More generally, if \(\varphi_{\mathrm{from}} : U \subset M \to \mathbb{R}^n\) and \(\psi_{\mathrm{to}} : W \subset N \to \mathbb{R}^m\) are chart maps on manifolds \(M\) and \(N\), and there is a point map \(F : M \supset U \to W \subset N\), then coordinax.charts.pt_map represents the coordinate expression
\[\psi_{\mathrm{to}} \circ F \circ \varphi_{\mathrm{from}}^{-1}.\]3D Spherical โ Cartesian:
\[\begin{split}x &= r \sin\theta \cos\phi \\ y &= r \sin\theta \sin\phi \\ z &= r \cos\theta\end{split}\]- Raises:
NotImplementedError โ If no transformation rule is registered for the specific pair of charts
(to_chart, from_chart).- Parameters:
- Return type:
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
rhas length dimension and producesxandywith 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_chartandto_chart) return the input unchanged.
Examples
>>> import quaxed.numpy as jnp >>> import coordinax.charts as cxc >>> import unxt as u
Transform from 2D polar to Cartesian:
>>> p_polar = {"r": u.Q(2.0, "m"), "theta": u.Angle(jnp.pi / 4, "rad")} >>> cxc.pt_map(p_polar, cxc.polar2d, cxc.cart2d) {'x': Q(1.41421356, 'm'), 'y': Q(1.41421356, 'm')}
Transform from 3D spherical to Cartesian:
>>> p_sph = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad"), ... "r": u.Q(5.0, "km")} >>> cxc.pt_map(p_sph, cxc.sph3d, cxc.cart3d) {'x': Q(5., 'km'), 'y': Q(0., 'km'), 'z': Q(3.061617e-16, 'km')}
Transform from Cartesian to cylindrical:
>>> p_xyz = {"x": u.Q(3.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(5.0, "m")} >>> cxc.pt_map(p_xyz, cxc.cart3d, cxc.cyl3d) {'rho': Q(5., 'm'), 'phi': Angle(0.92729522, 'rad'), 'z': Q(5., 'm')}
- coordinax.manifolds.pt_map(p: dict, from_M: CartesianProductManifold, from_chart: AbstractCartesianProductChart, to_M: CartesianProductManifold, to_chart: AbstractCartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) dict
ABC CartesianProductChart -> CartesianProductChart (factorwise).
Transforms between product charts by applying coordinax.charts.pt_map to each factor independently. Requires compatible factor structure (same number of factors, pairwise compatible).
Mathematical definition:
\[\varphi \left(\prod_i S_i,\;\prod_i R_i,\;p\right) = \bigl(\varphi(S_i,\,R_i,\,p_i)\bigr)_i\]where \(\varphi\) denotes {func}`~coordinax.charts.pt_map` and \(p_i\) are the factor dictionaries split from \(p\).
Examples
>>> import coordinax.charts as cxc >>> import unxt as u
Transform a Cartesian product chart between spatial representations:
>>> prod_crt = cxc.CartesianProductChart((cxc.time1d, cxc.cart3d), ("t", "q")) >>> prod_sph = cxc.CartesianProductChart((cxc.time1d, cxc.sph3d), ("t", "q")) >>> p = {"t.t": u.Q(1.0, "s"), "q.x": u.Q(1.0, "m"), "q.y": u.Q(0.0, "m"), ... "q.z": u.Q(0.0, "m")} >>> result = cxc.pt_map(p, prod_crt, prod_sph) >>> result["t.t"] Q(1., 's') >>> result["q.r"] Q(1., 'm')
- coordinax.manifolds.pt_map(p: dict, from_M: AbstractManifold, from_chart: AbstractChart, to_M: CartesianProductManifold, to_chart: AbstractCartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) dict
AbstractChart -> Cartesian -> AbstractCartesianProductChart.
>>> import coordinax.charts as cxc >>> import unxt as u
>>> chart = cxc.CartesianProductChart((cxc.sph2, cxc.cart1d), ("S2", "R1")) >>> map = cxc.pt_map.invoke(dict[str, u.Q], cxc.Cart3D, cxc.CartesianProductChart) >>> try: map({}, cxc.cart3d, chart) ... except NotImplementedError as e: print(e) No general transform between Cart3D and CartesianProductChart. Define explicit rules for non-product to product conversions.
- coordinax.manifolds.pt_map(p: dict, from_M: CartesianProductManifold, from_chart: AbstractCartesianProductChart, to_M: AbstractManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
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.
Return a partial function for point transformation.
>>> import coordinax.charts as cxc >>> import unxt as u
Coordinates without units are the default.
>>> q = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d) >>> map(q) {'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
Coordinates without units are also accepted, interpreted having units of the unxt.AbstractUnitSystem, which must be passed.
>>> q = {"x": 1.0, "y": 0.0, "z": 0.0} >>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si) >>> map(q) {'r': Array(1., dtype=float64, ...), 'theta': Array(1.57079633, dtype=float64, ...), 'phi': Array(0., dtype=float64, ...)}
unxt.Quantity inputs are also accepted, and are interpreted as being in Cartesian coordinates.
>>> p = u.Q([1.0, 0.0, 0.0], "m") >>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d) >>> map(p) QM([1. , 1.57079633, 0. ], '(m, rad, rad)')
Array-Like inputs are interpreted as Cartesian coordinates with units from the required unxt.AbstractUnitSystem.
>>> q = [1.0, 0.0, 0.0] >>> map = cxc.pt_map(None, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si) >>> map(q) Array([1. , 1.57079633, 0. ], dtype=float64)
- coordinax.manifolds.pt_map(from_chart: AbstractChart, to_chart: AbstractChart, /, **fixed_kw: Any) Callable[..., Any]
Return a partial function for point transformation.
>>> import coordinax.charts as cxc >>> import unxt as u
Coordinates without units are the default.
>>> p = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d) >>> map(p) {'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
Coordinates without units are also accepted, interpreted having units of the unxt.AbstractUnitSystem, which must be passed.
>>> p = {"x": 1.0, "y": 0.0, "z": 0.0} >>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si) >>> map(p) {'r': Array(1., dtype=float64, ...), 'theta': Array(1.57079633, dtype=float64, ...), 'phi': Array(0., dtype=float64, ...)}
unxt.Quantity inputs are also accepted, and are interpreted as being in Cartesian coordinates.
>>> p = u.Q([1.0, 0.0, 0.0], "m") >>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d) >>> map(p) QM([1. , 1.57079633, 0. ], '(m, rad, rad)')
Array-Like inputs are interpreted as Cartesian coordinates with units from the required unxt.AbstractUnitSystem.
>>> p = [1.0, 0.0, 0.0] >>> map = cxc.pt_map(cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si) >>> map(p) Array([1. , 1.57079633, 0. ], dtype=float64)
- coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) Any
Point transformation from chart to chart, using their manifolds.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {} >>> cxc.pt_map(p, cxc.cart0d, cxc.cart0d) {}
>>> p = {"r": u.Q(5.0, "m")} >>> cxc.pt_map(p, cxc.radial1d, cxc.cart1d) {'x': Q(5., 'm')}
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")} >>> cxc.pt_map(p, cxc.polar2d, cxc.cart2d) {'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg"), "phi": u.Q(0, "deg")} >>> cxc.pt_map(p, cxc.sph3d, cxc.cart3d) {'x': Q(5., 'm'), 'y': Q(0., 'm'), 'z': Q(3.061617e-16, 'm')}
- coordinax.manifolds.pt_map(p: dict, from_M: AbstractManifold, from_chart: AbstractChart, to_M: AbstractManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
AbstractChart -> Cartesian -> AbstractChart.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")} >>> map = cxc.pt_map.invoke(dict[str, u.Q], cxm.Rn, cxc.AbstractChart, ... cxm.Rn, cxc.AbstractChart) >>> map(p, cxm.R2, cxc.polar2d, cxm.R2, cxc.cart2d) {'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
Identity conversion for matching charts.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u >>> import quaxed.numpy as jnp
>>> q = {} >>> q2 = cxc.pt_map(q, cxm.R0, cxc.cart0d, cxm.R0, cxc.cart0d) >>> q is q2 True
>>> q = {"r": u.Q(3.0, "m")} >>> q2 = cxc.pt_map(q, cxm.R1, cxc.radial1d, cxm.R1, cxc.radial1d) >>> q is q2 True
>>> q = {"x": u.Q(1.0, "m"), "y": u.Q(2.0, "m")} >>> q2 = cxc.pt_map(q, cxm.R2, cxc.cart2d, cxm.R2, cxc.cart2d) >>> q is q2 True
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Radial1D, to_M: EuclideanManifold, to_chart: Cart1D, /, *, usys: AbstractUnitSystem | None = None) dict
Radial1D -> Cart1D.
The r coordinate is converted to the x coordinate of the 1D system.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> q = {"r": u.Q(5.0, "m")} >>> cxc.pt_map(q, cxm.R1, cxc.radial1d, cxm.R1, cxc.cart1d) {'x': Q(5., 'm')}
>>> q = {"r": 5.0} # No units >>> cxc.pt_map(q, cxm.R1, cxc.radial1d, cxm.R1, cxc.cart1d) {'x': 5.0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart1D, to_M: EuclideanManifold, to_chart: Radial1D, /, *, usys: AbstractUnitSystem | None = None) dict
Cart1D -> Radial1D.
The x coordinate is converted to the r coordinate of the 1D system.
Assumptions:
Cart1D and Radial1D are
>>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"x": u.Q(5.0, "m")} >>> cxc.pt_map(p, cxm.R1, cxc.cart1d, cxm.R1, cxc.radial1d) {'r': Q(5., 'm')}
>>> p = {"x": 5.0} # No units >>> cxc.pt_map(p, cxm.R1, cxc.cart1d, cxm.R1, cxc.radial1d) {'r': 5.0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Polar2D, to_M: EuclideanManifold, to_chart: Cart2D, /, *, usys: AbstractUnitSystem | None = None) dict
Polar2D -> Cart2D.
The r and theta coordinates are converted to the x and y coordinates of the 2D Cartesian system.
>>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")} >>> cxc.pt_map(p, cxm.R2, cxc.polar2d, cxm.R2, cxc.cart2d) {'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
>>> p = {"r": 5, "theta": 90} # No units >>> usys = u.unitsystem("km", "deg") >>> cxc.pt_map(p, cxm.R2, cxc.polar2d, cxm.R2, cxc.cart2d, usys=usys) {'x': Array(3.061617e-16, dtype=float64, ...), 'y': Array(5., dtype=float64, ...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart2D, to_M: EuclideanManifold, to_chart: Polar2D, /, *, usys: AbstractUnitSystem | None = None) dict
Cart2D -> Polar2D.
The x and y coordinates are converted to the r and theta coordinates of the 2D polar system.
>>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"x": u.Q(3, "m"), "y": u.Q(4, "m")} >>> cxc.pt_map(p, cxm.R2, cxc.cart2d, cxm.R2, cxc.polar2d) {'r': Q(5., 'm'), 'theta': Angle(0.92729522, 'rad')}
>>> p = {"x": 3, "y": 4} # No units >>> cxc.pt_map(p, cxm.R2, cxc.cart2d, cxm.R2, cxc.polar2d) {'r': Array(5., dtype=float64, ...), 'theta': Array(0.92729522, dtype=float64, ...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cylindrical3D -> Cart3D.
>>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"rho": u.Q(1.0, "m"), "phi": u.Q(90, "deg"), "z": u.Q(2.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.cart3d) {'x': Q(6.123234e-17, 'm'), 'y': Q(1., 'm'), 'z': Q(2., 'm')}
>>> p = {"rho": 1.0, "phi": 90, "z": 2.0} # No units >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.cart3d, usys=usys) {'x': Array(6.123234e-17, dtype=float64, ...), 'y': Array(1., dtype=float64, ...), 'z': 2.0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
Spherical3D -> Cart3D.
>>> import coordinax.charts as cxc >>> import unxt as u >>> import quaxed.numpy as jnp
A point on the +z axis (theta=0):
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "deg"), "phi": u.Q(0, "deg")} >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cart3d) {'x': Q(0., 'm'), 'y': Q(0., 'm'), 'z': Q(1., 'm')}
A point on the equator (theta=90 deg, phi=0):
>>> p = {"r": 2.0, "theta": 90, "phi": 0} >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cart3d, usys=usys) {'x': Array(2., dtype=float64, ...), 'y': Array(0., dtype=float64, ...), 'z': Array(1.2246468e-16, dtype=float64, ...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: LonLatSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
LonLatSpherical3D -> Cart3D.
>>> import coordinax.charts as cxc >>> import unxt as u
A point at the north pole (lat=90 deg):
>>> p = {"lon": u.Q(0, "deg"), "lat": u.Q(90, "deg"), "distance": u.Q(1.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.lonlat_sph3d, cxm.R3, cxc.cart3d) {'x': Q(6.123234e-17, 'm'), 'y': Q(0., 'm'), 'z': Q(1., 'm')}
A point on the equator at lon=0:
>>> p = {"lon": 0, "lat": 0, "distance": 2} >>> cxc.pt_map(p, cxm.R3, cxc.lonlat_sph3d, cxm.R3, cxc.cart3d) {'x': Array(2., dtype=float64, ...), 'y': Array(0., dtype=float64, ...), 'z': Array(0., dtype=float64, ...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: LonCosLatSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
LonCosLatSpherical3D -> Cart3D.
Components are (lon_coslat, lat, r), where lon_coslat := lon * cos(lat). Longitude is undefined at the poles (cos(lat) == 0); we set lon = 0 by convention there to avoid NaNs.
>>> import coordinax.charts as cxc >>> import unxt as u
A point on the equator (lat=0, so lon_coslat = lon):
>>> p = {"lon_coslat": u.Q(0, "deg"), "lat": u.Q(0, "deg"), ... "distance": u.Q(1.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.loncoslat_sph3d, cxm.R3, cxc.cart3d) {'x': Q(1., 'm'), 'y': Q(0., 'm'), 'z': Q(0., 'm')}
At the north pole (lat=90), lon_coslat is effectively 0 regardless of lon:
>>> p = {"lon_coslat": u.Q(0, "deg"), "lat": u.Q(90, "deg"), ... "distance": u.Q(2.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.loncoslat_sph3d, cxm.R3, cxc.cart3d) {'x': Q(1.2246468e-16, 'm'), 'y': Q(0., 'm'), 'z': Q(2., 'm')}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: MathSpherical3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
MathSpherical3D -> Cart3D.
theta: azimuth in the x-y plane (longitude-like)
phi : polar angle from +z, with phi in [0, pi]
>>> import coordinax.charts as cxc >>> import unxt as u
A point on the +z axis (phi=0):
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "deg"), "phi": u.Q(0, "deg")} >>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.cart3d) {'x': Q(0., 'm'), 'y': Q(0., 'm'), 'z': Q(1., 'm')}
A point on the +x axis (theta=0, phi=90):
>>> p = {"r": u.Q(2.0, "m"), "theta": u.Q(0, "deg"), "phi": u.Q(90, "deg")} >>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.cart3d) {'x': Q(2., 'm'), 'y': Q(0., 'm'), 'z': Q(1.2246468e-16, 'm')}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
ProlateSpheroidal3D -> Cart3D.
We calculate through cylindrical coordinates first:
\(\rho = \sqrt{(\mu-\Delta^2)\left(1-\frac{\lvert\nu\rvert}{\Delta^2}\right)}\) \(z = \sqrt{\mu\,\frac{\lvert\nu\rvert}{\Delta^2}}\;\mathrm{sign}(\nu)\) \(\phi = \phi.\)
Then convert to Cartesian:
\(x=\rho\cos\phi\), \(y=\rho\sin\phi\), \(z=z\).
>>> import coordinax.charts as cxc >>> import unxt as u >>> import quaxed.numpy as jnp
>>> prolatesph3d = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")} >>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cart3d) {'x': Q(0.8660254, 'm'), 'y': Q(0., 'm'), 'z': Q(1.11803399, 'm')}
>>> p = {"mu": 5.0, "nu": 1.0, "phi": 0} # No units >>> usys = u.unitsystem("m", "rad") >>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cart3d, usys=usys) {'x': Array(0.8660254, dtype=float64), 'y': Array(0., dtype=float64), 'z': Array(1.11803399, dtype=float64)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cart3D -> Cylindrical3D.
>>> import coordinax as cx >>> import unxt as u
>>> p = {"x": u.Q(3.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(5.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.cyl3d) {'rho': Q(5., 'm'), 'phi': Angle(0.92729522, 'rad'), 'z': Q(5., 'm')}
>>> p = {"x": 3.0, "y": 4.0, "z": 5.0} # No units >>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.cyl3d) {'rho': Array(5., dtype=float64, ...), 'phi': Array(0.92729522, dtype=float64, ...), 'z': 5.0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D | Cylindrical3D, to_M: EuclideanManifold, to_chart: AbstractSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cart3D -> Spherical3D -> AbstractSpherical3D.
>>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(1.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.loncoslat_sph3d) {'lon_coslat': Angle(0., 'rad'), 'lat': Angle(90., 'deg'), 'distance': Q(1., 'm')}
>>> p = {"rho": 0, "phi": 180, "z": 1} >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.loncoslat_sph3d, usys=usys) {'lon_coslat': Array(1.10218212e-14, dtype=float64, ...), 'lat': Array(90., dtype=float64, ...), 'distance': Array(1., dtype=float64, weak_type=True)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cart3D -> Spherical3D.
>>> import coordinax.charts as cxc >>> import unxt as u
A point on the +z axis:
>>> p = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(1.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.sph3d) {'r': Q(1., 'm'), 'theta': Angle(0., 'rad'), 'phi': Angle(0., 'rad')}
A point on the +x axis:
>>> p = {"x": 2.0, "y": 0.0, "z": 0.0} # No units >>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, cxc.sph3d) {'r': Array(2., dtype=float64, ...), 'theta': Array(1.57079633, dtype=float64, ...), 'phi': Array(0., dtype=float64, ...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cylindrical3D -> Spherical3D.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
A point on the z-axis (rho=0):
>>> p = {"rho": u.Q(0.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(1.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.sph3d) {'r': Q(1., 'm'), 'theta': Angle(0., 'rad'), 'phi': Angle(0, 'rad')}
A point in the xy-plane (z=0):
>>> p = {"rho": 3.0, "phi": 0, "z": 0.0} # No units >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, cxc.sph3d) {'r': Array(3., dtype=float64, ...), 'theta': Array(1.57079633, dtype=float64, ...), 'phi': 0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Spherical3D -> Cylindrical3D.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
A point on the +z axis (theta=0):
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "rad"), "phi": u.Q(0, "rad")} >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cyl3d) {'rho': Q(0., 'm'), 'phi': Angle(0, 'rad'), 'z': Q(1., 'm')}
A point on the equator (theta=90 deg):
>>> p = {"r": 2.0, "theta": 90, "phi": 0} # No units >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.cyl3d, usys=usys) {'rho': Array(2., dtype=float64, ...), 'phi': 0, 'z': Array(1.2246468e-16, dtype=float64, ...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Spherical3D -> LonLatSpherical3D.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
Spherical theta=0 corresponds to lat=90 (north pole):
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(0, "rad"), "phi": u.Q(0, "rad")} >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.lonlat_sph3d) {'lon': Angle(0, 'rad'), 'lat': Angle(90., 'deg'), 'distance': Q(1., 'm')}
Spherical theta=90 deg corresponds to lat=0 (equator):
>>> p = {"r": 1.0, "theta": 0, "phi": 0} # No units >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.lonlat_sph3d) {'lon': 0, 'lat': 1.5707963267948966, 'distance': 1.0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Spherical3D -> LonCosLatSpherical3D.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
On the equator (theta=90 deg), lon_coslat equals lon:
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(90, "deg"), "phi": u.Q(45, "deg")} >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.loncoslat_sph3d) {'lon_coslat': Angle(45., 'deg'), 'lat': Angle(0, 'deg'), 'distance': Q(1., 'm')}
At the north pole (theta=0), lon_coslat = 0 regardless of phi:
>>> p = {"r": 1.0, "theta": 0, "phi": 45} # No units >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.loncoslat_sph3d, usys=usys) {'lon_coslat': Array(2.7554553e-15, dtype=float64, ...), 'lat': 90.0, 'distance': 1.0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Spherical3D, to_M: EuclideanManifold, to_chart: MathSpherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
Spherical3D -> MathSpherical3D.
Swaps theta and phi: Physics (theta=polar, phi=azimuth) to Math (theta=azimuth, phi=polar).
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(30, "deg"), "phi": u.Q(60, "deg")} >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.math_sph3d) {'r': Q(1., 'm'), 'theta': Angle(60, 'deg'), 'phi': Angle(30, 'deg')}
>>> p = {"r": 1.0, "theta": 30, "phi": 60} # No units >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.sph3d, cxm.R3, cxc.math_sph3d, usys=usys) {'r': 1.0, 'theta': 60, 'phi': 30}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: MathSpherical3D, to_M: EuclideanManifold, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) dict
MathSpherical3D -> Spherical3D.
Swaps theta and phi: Math (theta=azimuth, phi=polar) to Physics (theta=polar, phi=azimuth).
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(60, "deg"), "phi": u.Q(30, "deg")} >>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.sph3d) {'r': Q(1., 'm'), 'theta': Angle(30, 'deg'), 'phi': Angle(60, 'deg')}
>>> p = {"r": 1.0, "theta": 60, "phi": 30} # No units >>> usys = u.unitsystem("m", "deg") >>> cxc.pt_map(p, cxm.R3, cxc.math_sph3d, cxm.R3, cxc.sph3d, usys=usys) {'r': 1.0, 'theta': 30, 'phi': 60}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) dict
ProlateSpheroidal3D -> Cylindrical3D.
Uses the focal length \(\Delta\) stored on
from_chart.Validity constraints (enforced by the representation) are:
\(\Delta > 0\),
\(\mu \ge \Delta^2\),
\(\lvert\nu\rvert \le \Delta^2\).
The conversion proceeds via
\(\rho = \sqrt{(\mu-\Delta^2)\left(1-\frac{\lvert\nu\rvert}{\Delta^2}\right)}\), \(z = \sqrt{\mu\,\frac{\lvert\nu\rvert}{\Delta^2}}\,\mathrm{sign}(\nu)\), \(\phi = \phi\).
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> prolatesph3d = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
>>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")} >>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cyl3d) {'rho': Q(0.8660254, 'm'), 'phi': Angle(0, 'rad'), 'z': Q(1.11803399, 'm')}
>>> p = {"mu": 5.0, "nu": 1.0, "phi": 0} # No units >>> usys = u.unitsystem("m", "rad") >>> cxc.pt_map(p, cxm.R3, prolatesph3d, cxm.R3, cxc.cyl3d, usys=usys) {'rho': Array(0.8660254, dtype=float64), 'phi': 0, 'z': Array(1.11803399, dtype=float64)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cylindrical3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cylindrical3D -> ProlateSpheroidal3D.
Uses the focal length \(\Delta\) stored on
to_chart.Let \(R^2 = \rho^2\) and \(z^2 = z^2\) and define
\(S = R^2 + z^2 + \Delta^2\), \(D_f = R^2 + z^2 - \Delta^2\), \(D = \sqrt{D_f^2 + 4 R^2 \Delta^2}\).
Then
\(\mu = \Delta^2 + \tfrac12(D + D_f)\) (with numerically-stable branches), \(\lvert\nu\rvert = \dfrac{2\Delta^2}{S + D}\,z^2\), and \(\nu = \lvert\nu\rvert\,\mathrm{sign}(z)\), with a stability fix when \(\Delta^2 - \lvert\nu\rvert\) is small.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> prolatesph3d = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m"))
A point on the z-axis (rho=0):
>>> p = {"rho": u.Q(0.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(3.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, prolatesph3d) {'mu': Q(9., 'm2'), 'nu': Q(4., 'm2'), 'phi': Angle(0, 'rad')}
A point in the xy-plane (z=0):
>>> p = {"rho": u.Q(2.0, "m"), "phi": u.Q(0, "rad"), "z": u.Q(0.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, prolatesph3d) {'mu': Q(8., 'm2'), 'nu': Q(0., 'm2'), 'phi': Angle(0, 'rad')}
Without units:
>>> p = {"rho": 2.0, "phi": 0, "z": 3.0} # No units >>> usys = u.unitsystem("m", "rad") >>> cxc.pt_map(p, cxm.R3, cxc.cyl3d, cxm.R3, prolatesph3d, usys=usys) {'mu': Array(14.52079729, dtype=float64), 'nu': Array(2.47920271, dtype=float64), 'phi': 0}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) dict
Cart3D -> Cylindrical3D -> ProlateSpheroidal3D.
ProlateSpheroidal3Dis only registered as a target fromCylindrical3D; without this route the genericA -> A.cartesian -> Bfallback would sendCart3D -> Cart3D -> ProlateSpheroidal3Dand recurse forever. Route throughCylindrical3Dinstead (mirrors theCart3D -> AbstractSpherical3Drule).>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> prolate = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m")) >>> p = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(3.0, "m")} >>> cxc.pt_map(p, cxm.R3, cxc.cart3d, cxm.R3, prolate) {'mu': Q(9., 'm2'), 'nu': Q(4., 'm2'), 'phi': Angle(0., 'rad')}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: ProlateSpheroidal3D, to_M: EuclideanManifold, to_chart: ProlateSpheroidal3D, /, *, usys: AbstractUnitSystem | None = None) dict
{class}`coordinax.charts.ProlateSpheroidal3D` -> itself.
If the focal length is unchanged (
to_chart.Delta == from_chart.Delta), this is the identity map.If the focal length changes, we convert via cylindrical coordinates:
Prolate(Delta_in) -> Cylindrical -> Prolate(Delta_out).>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
Same focal length (identity transformation):
>>> prolate = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m")) >>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")} >>> cxc.pt_map(p, cxm.R3, prolate, cxm.R3, prolate) {'mu': Q(5., 'm2'), 'nu': Q(1., 'm2'), 'phi': Angle(0., 'rad')}
Different focal lengths (converts via cylindrical):
>>> prolate_in = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(2.0, "m")) >>> prolate_out = cxc.ProlateSpheroidal3D(Delta=u.StaticQuantity(3.0, "m")) >>> p = {"mu": u.Q(5.0, "m2"), "nu": u.Q(1.0, "m2"), "phi": u.Q(0, "rad")} >>> cxc.pt_map(p, cxm.R3, prolate_in, cxm.R3, prolate_out) {'mu': Q(9.85889894, 'm2'), 'nu': Q(1.14110106, 'm2'), 'phi': Angle(0., 'rad')}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: CartND, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
CartND -> AbstractChart.
Converts from N-dimensional Cartesian (with a single โqโ array) to any other chart type by first extracting the appropriate fixed-dimensional Cartesian representation.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
Convert 3D CartND to Spherical:
>>> p = {"q": u.Q([1.0, 0.0, 0.0], "m")} >>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R3, cxc.sph3d) {'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
Convert 2D CartND to Polar:
>>> p = {"q": u.Q([3.0, 4.0], "m")} >>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R2, cxc.polar2d) {'r': Q(5., 'm'), 'theta': Angle(0.92729522, 'rad')}
Convert 1D CartND to Radial:
>>> p = {"q": u.Q([5.0], "m")} >>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R1, cxc.radial1d) {'r': Q(5., 'm')}
Convert CartND to Cart3D:
>>> p = {"q": u.Q([1.0, 2.0, 3.0], "m")} >>> cxc.pt_map(p, cxm.RN, cxc.cartnd, cxm.R3, cxc.cart3d) {'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: CartND, /, *, usys: AbstractUnitSystem | None = None) dict
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
Identity point transform for Quantity inputs on uniform-unit charts.
For charts where all components share the same unit (Cartesian charts, 0D/1D charts), a Quantity can be passed directly and is returned unchanged when the source and target charts are the same type.
This dispatch only handles identity transformations (same chart type). For transformations between different chart types with Quantity input, the Quantity must first be converted to a coordinate dictionary.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
1D Cartesian (identity):
>>> q = u.Q([5.0], "m") >>> cxc.pt_map(q, cxm.R1, cxc.cart1d, cxm.R1, cxc.cart1d, usys=None) is q True
2D Cartesian (identity):
>>> q = u.Q([3.0, 4.0], "m") >>> cxc.pt_map(q, cxm.R2, cxc.cart2d, cxm.R2, cxc.cart2d, usys=None) is q True
3D Cartesian (identity):
>>> q = u.Q([1.0, 2.0, 3.0], "km") >>> cxc.pt_map(q, cxm.R3, cxc.cart3d, cxm.R3, cxc.cart3d, usys=None) is q True
N-D Cartesian (identity):
>>> q = u.Q([1.0, 2.0, 3.0, 4.0], "m") >>> cxc.pt_map(q, cxm.RN, cxc.cartnd, cxm.RN, cxc.cartnd, usys=None) is q True
- coordinax.manifolds.pt_map(p: AbstractQuantity, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Transform a QuantityMatrix between charts.
Converts the components of a QuantityMatrix from one chart to another, preserving the matrix structure with potentially different units per component.
>>> import coordinax.charts as cxc >>> import unxt as u
2D Cartesian to Polar:
>>> q = u.Q([3.0, 4.0], "m") >>> result = cxc.pt_map(q, cxc.cart2d, cxc.polar2d) >>> result.shape (2,) >>> result.unit UnitsMatrix("(m, rad)")
3D Cartesian to Spherical:
>>> q = u.Q([1.0, 0.0, 0.0], "kpc") >>> result = cxc.pt_map(q, cxc.cart3d, cxc.sph3d) >>> result.shape (3,)
Batched transformation:
>>> q_batch = u.Q([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], "m") >>> result = cxc.pt_map(q_batch, cxc.cart3d, cxc.sph3d) >>> result.shape (2, 3)
- coordinax.manifolds.pt_map(p: Array | list, from_M: EuclideanManifold, from_chart: AbstractChart, to_M: EuclideanManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None) Array
Point transform for array input.
Transforms a point represented as a raw array (without units) from one chart to another. The unit system
usysprovides the units for interpreting the array components.- Returns:
Array of shape
(..., ndim)containing the transformed coordinates into_chart.- Return type:
- Parameters:
Examples
>>> import coordinax.charts as cxc >>> import unxt as u >>> import jax.numpy as jnp
Cartesian to Spherical (3D):
>>> usys = u.unitsystem("m", "rad") >>> p = jnp.array([1.0, 0.0, 0.0]) # Point on x-axis >>> cxc.pt_map(p, cxc.cart3d, cxc.sph3d, usys=usys) Array([1. , 1.57079633, 0. ], dtype=float64)
The result is [r, theta, phi] = [1, pi/2, 0] (on equator, x-axis).
Spherical to Cartesian (3D):
>>> p = jnp.array([2.0, jnp.pi/4, 0.0]) # r=2, theta=45ยฐ, phi=0 >>> cxc.pt_map(p, cxc.sph3d, cxc.cart3d, usys=usys) Array([1.41421356, 0. , 1.41421356], dtype=float64)
Cartesian to Cylindrical:
>>> p = jnp.array([3.0, 4.0, 5.0]) >>> cxc.pt_map(p, cxc.cart3d, cxc.cyl3d, usys=usys) Array([5. , 0.92729522, 5. ], dtype=float64)
The result is [rho, phi, z] = [5, arctan(4/3), 5].
Batched transformation:
>>> p_batch = jnp.array([[1.0, 0.0, 0.0], ... [0.0, 1.0, 0.0], ... [0.0, 0.0, 1.0]]) >>> cxc.pt_map(p_batch, cxc.cart3d, cxc.sph3d, usys=usys) Array([[1. , 1.57079633, 0. ], [1. , 1.57079633, 1.57079633], [1. , 0. , 0. ]], dtype=float64)
2D Cartesian to Polar:
>>> usys_2d = u.unitsystem("m", "rad") >>> p = jnp.array([3.0, 4.0]) >>> cxc.pt_map(p, cxc.cart2d, cxc.polar2d, usys=usys_2d) Array([5. , 0.92729522], dtype=float64)
- coordinax.manifolds.pt_map(p: dict, from_M: CartesianProductManifold, from_chart: CartesianProductChart, to_M: NoManifold, to_chart: PoincarePolar6D, /, *, usys: AbstractUnitSystem | None = None) dict
Cartesian phase space
cart3d x cart3d->PoincarePolar6D(gala forward).The source is a two-factor Cartesian product chart: factor 0 is position
(x, y, z), factor 1 its velocity(vx, vy, vz). Implements galaโscartesian_to_poincare_polar(Papaphilippou & Laskar 1996):rho = hypot(x, y),phi = atan2(x, y)(galaโs azimuth convention),dt_rho = (x*vx + y*vy) / rho,Lz = x*vy - y*vx,pp_phi = sqrt(2|Lz|) cos(phi),pp_phidot = sqrt(2|Lz|) sin(phi),dt_z = vz.sqrt(|Lz|)discardssign(Lz), so there is no global inverse. A partial inverse (assumingLz >= 0) is registered below.>>> import coordinax.charts as cxc >>> import unxt as u >>> ps = cxc.CartesianProductChart((cxc.cart3d, cxc.cart3d), ("q", "p")) >>> q = {"q.x": u.Q(3.0, "kpc"), "q.y": u.Q(4.0, "kpc"), "q.z": u.Q(5.0, "kpc"), ... "p.x": u.Q(1.0, "kpc/Myr"), "p.y": u.Q(2.0, "kpc/Myr"), ... "p.z": u.Q(0.5, "kpc/Myr")} >>> out = cxc.pt_map(q, ps.M, ps, cxc.poincarepolar6d.M, cxc.poincarepolar6d) >>> sorted(out) ['dt_rho', 'dt_z', 'pp_phi', 'pp_phidot', 'rho', 'z']
Lz = x*vy - y*vx = 2 kpc^2/Myr, so sqrt(2|Lz|) = 2; phi = atan2(3, 4):
>>> out["rho"], out["dt_rho"], out["dt_z"] (Q(5., 'kpc'), Q(2.2, 'kpc / Myr'), Q(0.5, 'kpc / Myr')) >>> out["pp_phi"].round(4), out["pp_phidot"].round(4) (Q(1.6, 'kpc / Myr(1/2)'), Q(1.2, 'kpc / Myr(1/2)'))
A non-Cartesian or wrong-arity product source is rejected:
>>> bad = cxc.CartesianProductChart((cxc.cart3d, cxc.polar2d), ("q", "p")) >>> try: ... cxc.pt_map({}, bad.M, bad, cxc.poincarepolar6d.M, cxc.poincarepolar6d) ... except NotImplementedError: ... print("rejected") rejected
- coordinax.manifolds.pt_map(p: dict, from_M: NoManifold, from_chart: PoincarePolar6D, to_M: CartesianProductManifold, to_chart: CartesianProductChart, /, *, usys: AbstractUnitSystem | None = None) dict
PoincarePolar6D-> Cartesian phase space (partial inverse of gala map).Inverts the gala forward map. Because the forward uses
sqrt(2|Lz|)the sign of the angular momentum is not recoverable, so this assumesLz >= 0(the standard convention) and is a partial inverse โ exact only when the original point had non-negativeLz:s = hypot(pp_phi, pp_phidot),phi = atan2(pp_phidot, pp_phi),Lz = s**2 / 2,x = rho sin(phi),y = rho cos(phi),vx = sin(phi) dt_rho - cos(phi) Lz/rho,vy = cos(phi) dt_rho + sin(phi) Lz/rho,vz = dt_z.(Singular on the axis
rho = 0, inherent to the coordinates.)>>> import coordinax.charts as cxc >>> import unxt as u >>> ps = cxc.CartesianProductChart((cxc.cart3d, cxc.cart3d), ("q", "p"))
Round-trips a forward result whose Lz >= 0:
>>> q = {"q.x": u.Q(3.0, "kpc"), "q.y": u.Q(4.0, "kpc"), "q.z": u.Q(5.0, "kpc"), ... "p.x": u.Q(1.0, "kpc/Myr"), "p.y": u.Q(2.0, "kpc/Myr"), ... "p.z": u.Q(0.5, "kpc/Myr")} >>> pp = cxc.pt_map(q, ps.M, ps, cxc.poincarepolar6d.M, cxc.poincarepolar6d) >>> back = cxc.pt_map(pp, cxc.poincarepolar6d.M, cxc.poincarepolar6d, ps.M, ps) >>> back["q.x"].round(6), back["q.y"].round(6), back["p.x"].round(6) (Q(3., 'kpc'), Q(4., 'kpc'), Q(1., 'kpc / Myr'))
- coordinax.manifolds.pt_map(p: dict, from_chart: EmbeddedChart, to_chart: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict
Convert between embedded manifolds with a shared ambient space.
This function transforms intrinsic coordinates from one embedded manifold to another by: 1. Embedding the point into the ambient space of the source manifold 2. Transforming in the ambient space (if the ambient charts differ) 3. Projecting back to the intrinsic coordinates of the target manifold
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u >>> import quaxed.numpy as jnp
Example 1: Two spheres with different radii
Both spheres use the same intrinsic SphericalTwoSphere chart but have different radii:
>>> sphere1 = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1.0, "km"))) >>> sphere2 = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
A point on sphere1 (theta=pi/4, phi=0):
>>> p = {"theta": u.Q(45, "deg"), "phi": u.Q(0, "deg")} >>> p2 = cxc.pt_map(p, sphere1, sphere2) >>> {k: v.uconvert("deg") for k, v in p2.items()} {'theta': Angle(45, 'deg'), 'phi': Angle(0, 'deg')}
The angular coordinates are preserved (both spheres share the same angular parameterization via projection through the shared ambient space).
- coordinax.manifolds.pt_map(p: dict, from_chart: AbstractChart, to_chart: EmbeddedChart, /, *, usys: AbstractUnitSystem | None = None) dict
Project an ambient position into an embedded chart.
This transforms coordinates from an ambient chart (e.g., Cartesian or Spherical) into the intrinsic coordinates of an embedded manifold.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u >>> import quaxed.numpy as jnp
From Cartesian ambient to SphericalTwoSphere intrinsic:
>>> sphere = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1.0, "m")))
A point on the unit sphere in Cartesian coords (on equator, x-axis):
>>> p_cart = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> cxc.pt_map(p_cart, cxc.cart3d, sphere) {'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
From Spherical ambient to SphericalTwoSphere intrinsic:
The ambient spherical coords (r, theta, phi) project to intrinsic (theta, phi), discarding the radial component:
>>> p_sph = {"r": 5, "theta": 1, "phi": 0.5} # No units >>> usys = u.unitsystem("m", "rad") >>> cxc.pt_map(p_sph, cxc.sph3d, sphere, usys=usys) {'theta': 1, 'phi': 0.5}
- coordinax.manifolds.pt_map(p: dict, from_chart: EmbeddedChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
Embed intrinsic coordinates into an ambient representation.
This transforms intrinsic coordinates of an embedded manifold into coordinates of an ambient chart, which may differ from the embeddingโs native ambient chart.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u >>> import quaxed.numpy as jnp
From SphericalTwoSphere intrinsic to Cartesian ambient:
>>> sphere = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(1.0, "m")))
A point on the unit sphere (on equator, x-axis):
>>> p_sph = {"theta": u.Q(1.0, "rad"), "phi": u.Q(0.0, "rad")} >>> cxc.pt_map(p_sph, sphere, cxc.cart3d) {'x': Q(0.84147098, 'm'), 'y': Q(0., 'm'), 'z': Q(0.54030231, 'm')}
From SphericalTwoSphere intrinsic to Spherical ambient:
>>> p_sph = {"theta": u.Q(1.0, "rad"), "phi": u.Q(0.5, "rad")} >>> cxc.pt_map(p_sph, sphere, cxc.sph3d) {'r': Q(1., 'm'), 'theta': Angle(1., 'rad'), 'phi': Angle(0.5, 'rad')}
- coordinax.manifolds.pt_map(p: dict, M: EmbeddedManifold, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
Convert between embedded manifolds with a shared ambient space.
>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.embedded_twosphere(radius=u.Q(1, "kpc")) >>> M EmbeddedManifold(intrinsic=HyperSphericalManifold(...), ambient=Rn(3), embed_map=TwoSphereIn3D(radius=Q(1, 'kpc'), ambient=Spherical3D(M=Rn(3)))) >>> x_cart = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> x_sph2 = cxc.pt_map(x_cart, M, cxc.cart3d, cxc.loncoslat_sph2) >>> x_sph2 {'lon_coslat': Angle(0.66164791, 'rad'), 'lat': Angle(53.3007748, 'deg')}
>>> cxc.pt_map(x_sph2, M, cxc.loncoslat_sph2, cxc.cart3d) {'x': Q(0.26726124, 'kpc'), 'y': Q(0.53452248, 'kpc'), 'z': Q(0.80178373, 'kpc')}
- coordinax.manifolds.pt_map(p: Any, from_chart: Abstract3D, to_chart: AbstractSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) Any
Project a point from the ambient chart to the two-sphere intrinsic chart.
This realization map is a special case for projecting from 3D charts to the two-sphere intrinsic chart, which is a common use case. The projection does not depend on the radius of the embedding, so this projection works in general.
>>> import unxt as u >>> import coordinax.charts as cxc
>>> q = {"x": u.Q(1.0, "km"), "y": u.Q(0.0, "km"), "z": u.Q(0.0, "km")} >>> cxc.pt_map(q, cxc.cart3d, cxc.sph2) {'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: AbstractChart, to_M: HyperSphericalManifold, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) dict
Identity conversion for matching charts.
Returns the input object itself when it is already canonical โ angular components held as unxt.Angle, as the chart declares. A non-canonical input is canonicalised instead, so it necessarily comes back as a new dict: the alternative is pt_map(q, chart, chart) preserving a container that every other route to the same chart would have normalised.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> q = {"theta": u.Angle(30, "deg"), "phi": u.Angle(60, "deg")} >>> cxc.pt_map(q, cxc.sph2, cxc.sph2) is q True
>>> q = {"lon": u.Angle(45, "deg"), "lat": u.Angle(10, "deg")} >>> cxc.pt_map(q, cxc.lonlat_sph2, cxc.lonlat_sph2) is q True
>>> q = {"lon_coslat": u.Angle(30, "deg"), "lat": u.Angle(20, "deg")} >>> cxc.pt_map(q, cxc.loncoslat_sph2, cxc.loncoslat_sph2) is q True
>>> q = {"theta": u.Angle(60, "deg"), "phi": u.Angle(30, "deg")} >>> cxc.pt_map(q, cxc.math_sph2, cxc.math_sph2) is q True
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: AbstractSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: AbstractSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
Route between two-sphere charts via SphericalTwoSphere.
Each chart registers only its direct
sph2 <-> chartconversion, and the two-sphere has no Cartesian chart, so the generic router cannot bridge two non-canonical charts (it raisesNoGlobalCartesianChartError). GoA -> SphericalTwoSphere -> Binstead. Canonical pairs (either side is SphericalTwoSphere) and matching-type pairs are handled by the more specific direct/identity rules above, so this fallback fires only for distinct non-canonical charts โ and covers any future AbstractSphericalTwoSphere subclass automatically.>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
>>> p = {"lon": u.Q(45, "deg"), "lat": u.Q(30, "deg")} >>> out = cxc.pt_map(p, cxm.S2, cxc.lonlat_sph2, cxm.S2, cxc.math_sph2) >>> sorted(out) ['phi', 'theta']
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: LonLatSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
SphericalTwoSphere -> LonLatSphericalTwoSphere.
lat = pi/2 - theta, lon = phi.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"theta": u.Q(0, "rad"), "phi": u.Q(0, "rad")} # North pole >>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.lonlat_sph2) {'lon': Angle(0, 'rad'), 'lat': Angle(90., 'deg')}
>>> p = {"theta": u.Q(90, "deg"), "phi": u.Q(45, "deg")} # Equator >>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.lonlat_sph2) {'lon': Angle(45, 'deg'), 'lat': Angle(0, 'deg')}
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: LonLatSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
LonLatSphericalTwoSphere -> SphericalTwoSphere.
theta = pi/2 - lat, phi = lon.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"lon": u.Q(45, "deg"), "lat": u.Q(0, "deg")} >>> cxc.pt_map(p, cxm.S2, cxc.lonlat_sph2, cxm.S2, cxc.sph2) {'theta': Angle(90, 'deg'), 'phi': Angle(45, 'deg')}
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: LonCosLatSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
SphericalTwoSphere -> LonCosLatSphericalTwoSphere.
lat = pi/2 - theta, lon_coslat = phi * cos(lat).
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u >>> import quaxed.numpy as jnp
>>> p = {"theta": u.Q(90, "deg"), "phi": u.Q(45, "deg")} # equator >>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.loncoslat_sph2) {'lon_coslat': Angle(45., 'deg'), 'lat': Angle(0, 'deg')}
>>> p = {"theta": u.Q(0, "deg"), "phi": u.Q(45, "deg")} # north pole >>> result = cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.loncoslat_sph2) >>> bool(jnp.allclose(u.ustrip("deg", result["lat"]), 90.0)) True
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: LonCosLatSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
LonCosLatSphericalTwoSphere -> SphericalTwoSphere.
theta = pi/2 - lat, phi = lon_coslat / cos(lat).
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
>>> p = {"lon_coslat": u.Q(45, "deg"), "lat": u.Q(0, "deg")} >>> cxc.pt_map(p, cxm.S2, cxc.loncoslat_sph2, cxm.S2, cxc.sph2) {'theta': Angle(90, 'deg'), 'phi': Angle(45., 'deg')}
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: SphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: MathSphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
SphericalTwoSphere -> MathSphericalTwoSphere.
Swaps theta and phi (physics -> math convention).
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"theta": u.Q(30, "deg"), "phi": u.Q(60, "deg")} >>> cxc.pt_map(p, cxm.S2, cxc.sph2, cxm.S2, cxc.math_sph2) {'theta': Angle(60, 'deg'), 'phi': Angle(30, 'deg')}
- coordinax.manifolds.pt_map(p: dict, from_M: HyperSphericalManifold, from_chart: MathSphericalTwoSphere, to_M: HyperSphericalManifold, to_chart: SphericalTwoSphere, /, *, usys: AbstractUnitSystem | None = None) dict
MathSphericalTwoSphere -> SphericalTwoSphere.
Swaps theta and phi (math -> physics convention).
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> import unxt as u
>>> p = {"theta": u.Q(60, "deg"), "phi": u.Q(30, "deg")} >>> cxc.pt_map(p, cxm.S2, cxc.math_sph2, cxm.S2, cxc.sph2) {'theta': Angle(30, 'deg'), 'phi': Angle(60, 'deg')}
- coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, to_rep: Representation, /, usys: AbstractUnitSystem | None = None) Any
Convert point data between charts.
Convert a point from Cartesian coordinates to spherical coordinates:
>>> import coordinax.representations as cxr >>> import coordinax.charts as cxc
Define a point in Cartesian coordinates:
>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}
Convert it to spherical coordinates:
>>> q = cxc.pt_map(p, cxc.cart3d, cxr.point, cxc.sph3d, cxr.point) >>> q {'r': Array(3.74165739, dtype=float64, ...), 'theta': Array(0.64052231, dtype=float64, ...), 'phi': Array(1.10714872, dtype=float64, ...)}
The output q represents the same geometric point but expressed in the target chart.
The representation remains unchanged; only the chart changes:
>>> cxc.pt_map(q, cxc.sph3d, cxr.point, cxc.cart3d, cxr.point) {'x': Array(1., dtype=float64, ...), 'y': Array(2., dtype=float64, ...), 'z': Array(3., dtype=float64, ...)}
Letโs work through more examples.
Cartesian to Spherical (with units):
>>> import unxt as u >>> p = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> cxc.pt_map(p, cxc.cart3d, cxr.point, cxc.sph3d, cxr.point) {'r': Q(1., 'm'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
Cylindrical to Cartesian (without units):
>>> p = {"rho": 3.0, "phi": 0, "z": 4.0} >>> cxc.pt_map(p, cxc.cyl3d, cxr.point, cxc.cart3d, cxr.point) {'x': Array(3., dtype=float64, ...), 'y': Array(0., dtype=float64, ...), 'z': 4.0}
Polar to Cartesian (2D):
>>> p = {"r": u.Q(5.0, "m"), "theta": u.Q(90, "deg")} >>> cxc.pt_map(p, cxc.polar2d, cxr.point, cxc.cart2d, cxr.point) {'x': Q(3.061617e-16, 'm'), 'y': Q(5., 'm')}
Between Spherical variants (Spherical to LonLatSpherical):
>>> p = {"r": u.Q(1.0, "m"), "theta": u.Q(45, "deg"), "phi": u.Q(0, "deg")} >>> cxc.pt_map(p, cxc.sph3d, cxr.point, cxc.lonlat_sph3d, cxr.point) {'lon': Angle(0, 'deg'), 'lat': Angle(45, 'deg'), 'distance': Q(1., 'm')}
Identity conversion (same chart):
>>> p = {"x": u.Q(2.0, "m"), "y": u.Q(3.0, "m")} >>> cxc.pt_map(p, cxc.cart2d, cxr.point, cxc.cart2d, cxr.point) is p True
- coordinax.manifolds.pt_map(x: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, /, usys: AbstractUnitSystem | None = None) Any
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
Convert point data between charts.
Convert a point from Cartesian coordinates to spherical coordinates:
>>> import coordinax.representations as cxr >>> import coordinax.charts as cxc
Define a point in Cartesian coordinates:
>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}
Convert it to spherical coordinates:
>>> cxc.pt_map(p, cxc.cart3d, cxr.point_geom, cxr.point, ... cxc.sph3d, cxr.point_geom, cxr.point) {'r': Array(3.74165739, dtype=float64, ...), 'theta': Array(0.64052231, dtype=float64, ...), 'phi': Array(1.10714872, dtype=float64, ...)}
- coordinax.manifolds.pt_map(from_vec: Point, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) Point
Convert a point from one chart to another.
>>> import unxt as u >>> import coordinax as cx >>> import coordinax.charts as cxc
>>> vec = cx.Point.from_([1, 1, 1], "m") >>> print(vec) <Point: chart=Cart3D (x, y, z) [m] [1 1 1]>
>>> sph_vec = cxc.pt_map(vec, cx.sph3d) >>> print(sph_vec) <Point: chart=Spherical3D (r[m], theta[rad], phi[rad]) [1.732 0.955 0.785]>
- coordinax.manifolds.pt_map(from_vec: Point, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) Point
Convert a vector from one chart to another.
>>> import unxt as u >>> import coordinax as cx >>> import coordinax.charts as cxc
>>> vec = cx.Point.from_([1, 1, 1], "m") >>> sph_vec = cxc.pt_map(vec, cxc.cart3d, cx.sph3d) >>> print(sph_vec) <Point: chart=Spherical3D (r[m], theta[rad], phi[rad]) [1.732 0.955 0.785]>
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: TubularChart, to_M: EuclideanManifold, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) dict
TubularChart -> Cart3D: \(\gamma(\tau) + n_1U_1 + n_2U_2\).
Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinaxs.curveframes as cxfc
>>> def circle(tau): ... t = tau.ustrip("s") ... return u.Q(jnp.stack([jnp.cos(t), jnp.sin(t), jnp.zeros_like(t)]), "km")
>>> chart = cxfc.TubularChart( ... cxfc.BishopBuilder(circle, "s", normal_0="auto"), ... tau_bounds=(u.Q(0.0, "s"), u.Q(2 * jnp.pi, "s")), ... ) >>> p = {"tau": u.Q(0.0, "s"), "n1": u.Q(0.1, "km"), "n2": u.Q(0.0, "km")} >>> cxc.pt_map(p, chart.M, chart, chart.M, cxc.cart3d) {'x': Q(1.1, 'km'), 'y': Q(0., 'km'), 'z': Q(0., 'km')}
Raw coordinates take the same route, given a
usysto say what their numbers mean, and come back raw:>>> usys = u.unitsystem("km", "s", "kg", "rad") >>> p = {"tau": 0.0, "n1": 0.1, "n2": 0.0} >>> cxc.pt_map(p, chart.M, chart, chart.M, cxc.cart3d, usys=usys) {'x': Array(1.1, dtype=float64...), 'y': Array(0., dtype=float64...), 'z': Array(0., dtype=float64...)}
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: Cart3D, to_M: EuclideanManifold, to_chart: TubularChart, /, *, usys: AbstractUnitSystem | None = None) dict
Cart3D -> TubularChart, via the nearest-point projection.
Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinaxs.curveframes as cxfc
>>> def circle(tau): ... t = tau.ustrip("s") ... return u.Q(jnp.stack([jnp.cos(t), jnp.sin(t), jnp.zeros_like(t)]), "km")
>>> chart = cxfc.TubularChart( ... cxfc.BishopBuilder(circle, "s", normal_0="auto"), ... tau_bounds=(u.Q(0.0, "s"), u.Q(2 * jnp.pi, "s")), ... )
A point exactly on the curve has zero offsets. Bishop runs an ODE solve internally, so the recovered values are checked with a tolerance rather than pinned to exact digits:
>>> on_curve = chart.builder.location(u.Q(0.0, "s")) >>> p = {k: on_curve[i] for i, k in enumerate(("x", "y", "z"))} >>> back = cxc.pt_map(p, chart.M, cxc.cart3d, chart.M, chart) >>> bool(jnp.allclose(back["tau"].ustrip("s"), 0.0, atol=1e-6)) True >>> bool(jnp.allclose(back["n1"].ustrip("km"), 0.0, atol=1e-6)) True >>> bool(jnp.allclose(back["n2"].ustrip("km"), 0.0, atol=1e-6)) True
- coordinax.manifolds.pt_map(p: dict, from_M: EuclideanManifold, from_chart: TubularChart, to_M: EuclideanManifold, to_chart: TubularChart, /, *, usys: AbstractUnitSystem | None = None) dict
TubularChart -> TubularChart. Identity only for the same chart object.
Parameterized charts compare conservatively (equal only when identical), so this declines to a Cartesian round trip whenever the two differ.
Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinaxs.curveframes as cxfc
>>> def circle(tau): ... t = tau.ustrip("s") ... return u.Q(jnp.stack([jnp.cos(t), jnp.sin(t), jnp.zeros_like(t)]), "km")
>>> chart = cxfc.TubularChart( ... cxfc.BishopBuilder(circle, "s", normal_0="auto"), ... tau_bounds=(u.Q(0.0, "s"), u.Q(2 * jnp.pi, "s")), ... ) >>> p = {"tau": u.Q(0.0, "s"), "n1": u.Q(0.1, "km"), "n2": u.Q(0.0, "km")} >>> cxc.pt_map(p, chart.M, chart, chart.M, chart) is p True
- coordinax.manifolds.scale_factors(chart, /, *args, **kwargs)#
Return the diagonal entries of the metric matrix.
Dispatches on the first argument (metric or manifold) and the chart.
- coordinax.manifolds.scale_factors(metric: RoundMetric, chart: LonLatSphericalTwoSphere | MathSphericalTwoSphere, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Scale factors for the orthogonal relabelled two-sphere charts.
LonLatandMathrelabel/swap the canonical angles, so the nested sine-product does not apply โ but they stay orthogonal, so the metric is diagonal and its diagonal is the answer:LonLat(lon, lat):diag(cos^2(lat), 1)Math(theta, phi):diag(sin^2(phi), 1)
Taken from metric_matrix rather than re-derived, so there is one source of truth for the pullback.
>>> import math >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = {"lon": u.Q(0.6, "rad"), "lat": u.Q(math.radians(40), "rad")} >>> h = cxm.scale_factors(cxm.RoundMetric(2), cxc.lonlat_sph2, at=at) >>> [round(float(v), 4) for v in h.value] [0.5868, 1.0]
Bare angle values are interpreted via
usys["angle"]when a unit system is supplied:>>> usys = u.unitsystem("m", "s", "kg", "deg") >>> h = cxm.scale_factors(cxm.RoundMetric(2), cxc.lonlat_sph2, ... at={"lon": 30.0, "lat": 40.0}, usys=usys) >>> [round(float(v), 4) for v in h.value] [0.5868, 1.0]
- coordinax.manifolds.scale_factors(metric: RoundMetric, chart: LonCosLatSphericalTwoSphere, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
LonCosLat is non-orthogonal, so it has no scale factors at all.
- coordinax.manifolds.scale_factors(metric: RoundMetric, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Return round-metric diagonal directly without forming the nxn matrix.
Computes the cumulative-sine diagonal \(g_{kk} = \prod_{j<k} \sin^2\theta_j\) as a 1-D vector, avoiding the O(n^2) cost of
RoundMetric.metric_matrix.>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
Bare angles (no units) โ dimensionless QuantityMatrix:
>>> metric = cxm.RoundMetric(2) >>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)} >>> cxm.scale_factors(metric, cxc.sph2, at=at) QM([1., 1.], '(, )')
Quantity angles โ dimensionless QuantityMatrix:
>>> at = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> cxm.scale_factors(metric, cxc.sph2, at=at) QM([1., 1.], '(, )')
- coordinax.manifolds.scale_factors(chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Manifold-level dispatch: delegate to the attached metric.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = { ... "r": u.Q(jnp.array(2.0), "km"), ... "theta": u.Angle(jnp.pi / 2, "rad"), ... "phi": u.Angle(jnp.array(0.0), "rad"), ... } >>> cxm.scale_factors(cxc.sph3d, at=at) QM([1., 4., 4.], '(, km2 / rad2, km2 / rad2)')
- coordinax.manifolds.scale_factors(metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Return the diagonal entries of the metric at
atinchart.Uses the
metric_matrixdispatch API to compute the metric, then extracts the diagonal entries.The chart must be orthogonal. Per-axis factors describe a metric only where the off-diagonal terms vanish, so a chart whose metric is dense is refused rather than answered with a diagonal that does not reproduce it.
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.RoundMetric(2) >>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)} >>> cxm.scale_factors(metric, cxc.sph2, at=at) QM([1., 1.], '(, )')
LonCosLatSpherical3D is non-orthogonal โ its
g_01 = distance**2 * lon_coslat * tan(lat)โ and so has no scale factors. (The unit two-sphere carries nodistance, which is why SphericalTwoSphereโs sibling message states the same cross term without that factor.)>>> import unxt as u >>> at = {"lon_coslat": u.Angle(0.3, "rad"), "lat": u.Angle(0.6, "rad"), ... "distance": u.Q(2.0, "m")} >>> try: cxm.scale_factors(cxc.loncoslat_sph3d, at=at) ... except NotImplementedError as e: print(e) scale_factors is a diagonal (orthogonal-frame) concept and the metric of LonCosLatSpherical3D... is not diagonal, so no set of per-axis factors reproduces it. Use coordinax.manifolds.metric_matrix.
- coordinax.manifolds.scale_factors(metric: PullbackMetric, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Return scale factors for a pullback (induced) metric.
The scale factors of an induced metric are the diagonal of that metric, so this delegates to the
EmbeddedManifoldmetric_matrixrule rather than re-deriving the Jacobian. That keeps one implementation of the pullback, and inherits its handling of a non-intrinsicchart, of a non-Euclidean ambient metric (the ambient Gram carries the signature), and of batched points.Points are interpreted in the passed chart, which need not be the embeddingโs own intrinsic chart.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")), ... ) >>> at = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> cxm.scale_factors(M.metric, cxc.sph2, at=at) QM([4., 4.], '(m2 / rad2, m2 / rad2)')
A chart other than the embeddingโs intrinsic one:
>>> at = {"lon": u.Angle(0.0, "rad"), "lat": u.Angle(0.0, "rad")} >>> cxm.scale_factors(M.metric, cxc.lonlat_sph2, at=at) QM([4., 4.], '(m2 / rad2, m2 / rad2)')
The delegate reads the ambient Gram off the ambient manifold, so an
ambient_metricthat disagrees with it cannot be honoured. That is refused rather than answered with the diagonal of a different metric:>>> pb = cxm.PullbackMetric(cxm.TwoSphereIn3D(radius=1.0), cxm.RoundMetric(3)) >>> try: cxm.scale_factors(pb, cxc.sph2, at=at) ... except NotImplementedError as e: print(e) the pullback of RoundMetric(ndim=3) cannot be evaluated: the ambient manifold Rn(3) of chart Spherical3D... carries FlatMetric(ndim=3)
- coordinax.manifolds.scale_factors(metric: MinkowskiMetric, chart: MinkowskiCT, /, *, at: dict, usys: AbstractUnitSystem | None = None) unxts.linalg._src._quantity_matrix.QuantityMatrix
Return the Minkowski metric diagonal \(\eta = \operatorname{diag}(-1,1,1,1)\).
In the canonical coordinax.charts.MinkowskiCT chart the metric is constant, so
atis ignored and the result does not depend on the base point.>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.MinkowskiMetric() >>> at = {"ct": jnp.array(0.0), "x": jnp.array(1.0), ... "y": jnp.array(0.0), "z": jnp.array(0.0)} >>> cxm.scale_factors(metric, cxc.minkowskict, at=at) QM([-1., 1., 1., 1.], '(, , , )')
- coordinax.manifolds.angle_between(chart, uvec, vvec, /, *args, **kwargs)#
Return the metric angle between two nonzero tangent vectors.
The inputs
uvecandvvecare component dictionaries representing tangent-vector components in the coordinate basis ofchart. The metric is evaluated at a base point supplied viaat=....Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m")} >>> uvec = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m")} >>> vvec = {"x": u.Q(0.0, "m"), "y": u.Q(1.0, "m")} >>> cxm.angle_between(cxc.cart2d, uvec, vvec, at=at) Angle(1.57079633, 'rad')
>>> at_sph = { ... "r": u.Q(2.0, "m"), ... "theta": u.Angle(jnp.pi / 2, "rad"), ... "phi": u.Angle(0.0, "rad"), ... } >>> u_tan = { ... "r": u.Q(0.0, "m"), "theta": u.Angle(1.0, "rad"), ... "phi": u.Angle(0.0, "rad"), ... } >>> v_tan = { ... "r": u.Q(0.0, "m"), "theta": u.Angle(0.0, "rad"), ... "phi": u.Angle(1.0, "rad"), ... } >>> cxm.angle_between(cxc.sph3d, u_tan, v_tan, at=at_sph) Angle(1.57079633, 'rad')
- coordinax.manifolds.angle_between(chart: AbstractChart, uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None) AbstractAngle
Manifold-level dispatch: delegate to the attached metric.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = {"x": u.Q(0.0, "m"), "y": u.Q(0.0, "m")} >>> uvec = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m")} >>> vvec = {"x": u.Q(0.0, "m"), "y": u.Q(1.0, "m")} >>> cxm.angle_between(cxc.cart2d, uvec, vvec, at=at) Angle(1.57079633, 'rad')
- coordinax.manifolds.angle_between(metric: AbstractMetricField, chart: AbstractChart, uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None) AbstractAngle
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 pointat.>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = { ... "r": u.Q(2.0, "m"), ... "theta": u.Angle(jnp.pi / 2, "rad"), ... "phi": u.Angle(0.0, "rad"), ... } >>> uvec = {"r": u.Q(0.0, "m"), "theta": u.Angle(1.0, "rad"), ... "phi": u.Angle(0.0, "rad")} >>> vvec = {"r": u.Q(0.0, "m"), "theta": u.Angle(0.0, "rad"), ... "phi": u.Angle(1.0, "rad")} >>> cxm.angle_between(metric, cxc.sph3d, uvec, vvec, at=at) Angle(1.57079633, 'rad')
An indefinite metric is not rejected outright. Two spacelike directions span a plane on which the Minkowski metric is positive-definite, so the angle between them is an ordinary one:
>>> at4 = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")} >>> xhat = {"ct": u.Q(0.0, "m"), "x": u.Q(1.0, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> yhat = {"ct": u.Q(0.0, "m"), "x": u.Q(0.0, "m"), ... "y": u.Q(1.0, "m"), "z": u.Q(0.0, "m")} >>> cxm.angle_between(cxc.minkowskict, xhat, yhat, at=at4) Angle(1.57079633, 'rad')
Two timelike directions have no circular angle between them โ the invariant is a hyperbolic one, the relative rapidity โ so this raises rather than clipping
arccosto a meaningless value:>>> obs = {"ct": u.Q(1.0, "m"), "x": u.Q(0.0, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> moving = {"ct": u.Q(1.25, "m"), "x": u.Q(0.75, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> try: ... cxm.angle_between(cxc.minkowskict, obs, moving, at=at4) ... except ValueError as e: ... print(str(e).split(":")[0]) angle_between is undefined for two timelike tangent vectors
- coordinax.manifolds.metric_matrix(M, point, chart, /)#
Compute the coordinate metric matrix at
pointinchart.Dispatches on the triple
(type(M), type(point), type(chart)). Concrete implementations for each(M, chart)pair are registered in the correspondingregister_metric.pymodules viaplum.dispatch().- Parameters:
- 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 bymetric_representation().- Return type:
- Raises:
NotImplementedError โ When no specific dispatch rule is registered for the given types.
Examples
>>> import jax.numpy as jnp >>> import coordinaxs.api.manifolds as cxmapi >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = {"x": jnp.array(1.0), "y": jnp.array(2.0), "z": jnp.array(3.0)} >>> cxmapi.metric_matrix(cxm.R3, at, cxc.cart3d) DiagonalMetric(diagonal=f64[3])
- coordinax.manifolds.metric_matrix(M: AbstractManifold, point: dict, chart: AbstractChart, /) AbstractMetricMatrix
Fallback โ raise NotImplementedError for unregistered pairs.
Concrete
(manifold, chart)pairs register their own dispatch rules in the relevantregister_metric.pymodules (loaded as part of Phase 2). This fallback raises NotImplementedError when no specific dispatch rule is registered for the given manifoldM, pointpointand chartcharttypes. See the abstractmetric_matrix()for the full parameter documentation.- coordinax.manifolds.metric_matrix(M: NoManifold, point: dict, chart: AbstractChart, /) AbstractMetricMatrix
Refuse, naming the missing manifold as the reason.
The generic fallback advises registering a dispatch rule โ wrong for both populations that reach here: a chart that left M unset, and one that declares no_manifold by design.
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Cart1D | Cart2D | Cart3D, /) DiagonalMetric
Euclidean metric in a Cartesian chart:
g = I_n.The metric matrix is the identity in any Cartesian chart, represented compactly as a coordinax._src.metric.matrix.DiagonalMetric with all-one diagonal.
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
Cart1D:
>>> at = {"x": jnp.array(3.0)} >>> g = metric_matrix(cxm.R1, at, cxc.cart1d) >>> isinstance(g, DiagonalMetric) True >>> g.diagonal Array([1.], dtype=float64)
Cart2D:
>>> at = {"x": jnp.array(1.0), "y": jnp.array(2.0)} >>> metric_matrix(cxm.R2, at, cxc.cart2d).diagonal Array([1., 1.], dtype=float64)
Cart3D:
>>> at = {"x": jnp.array(1.0), "y": jnp.array(2.0), "z": jnp.array(3.0)} >>> metric_matrix(cxm.R3, at, cxc.cart3d).diagonal Array([1., 1., 1.], dtype=float64)
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: CartND, /) DiagonalMetric
Euclidean metric in CartND:
g = I_Nwhere N is inferred from the point.>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix
>>> at = {"q": jnp.array([1.0, 2.0, 3.0])} >>> metric_matrix(cxm.R3, at, cxc.cartnd).diagonal Array([1., 1., 1.], dtype=float64)
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Radial1D, /) DiagonalMetric
Euclidean metric in
Radial1D:g = diag(1).The only component is
g_rr = 1(the radial direction is an isometry of Euclidean distance in 1-D).>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
Dimensionless:
>>> at = {"r": jnp.array(2.0)} >>> g = metric_matrix(cxm.R1, at, cxc.radial1d) >>> isinstance(g, DiagonalMetric) True
With length units:
>>> at = {"r": u.Q(2.0, "m")} >>> g = metric_matrix(cxm.R1, at, cxc.radial1d) >>> g.diagonal QM([1.], '(,)')
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Polar2D, /) DiagonalMetric
Euclidean metric in
Polar2D:g = diag(1, rยฒ).point must contain keys
"r"(length) and"theta"(angle).>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
Dimensionless
r:>>> at = {"r": jnp.array(3.0), "theta": jnp.array(0.5)} >>> g = metric_matrix(cxm.R2, at, cxc.polar2d) >>> g.diagonal QM([1., 9.], '(, )')
Length-valued
rand angle-valuedtheta:>>> at = {"r": u.Q(3.0, "m"), "theta": u.Angle(0.5, "rad")} >>> g = metric_matrix(cxm.R2, at, cxc.polar2d) >>> g.diagonal QM([1., 9.], '(, m2 / rad2)')
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Cylindrical3D, /) DiagonalMetric
Euclidean metric in
Cylindrical3D:g = diag(1, ฯยฒ, 1).pointmust contain keys"rho"(length),"phi"(angle), and"z"(length).>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = {"rho": u.Q(3.0, "m"), "phi": u.Angle(0.0, "rad"), "z": u.Q(1.0, "m")} >>> g = metric_matrix(cxm.R3, at, cxc.cyl3d) >>> g.diagonal QM([1., 9., 1.], '(, m2 / rad2, )')
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: Spherical3D, /) DiagonalMetric
Euclidean metric in
Spherical3D:g = diag(1, rยฒ, rยฒsinยฒฮธ).Physics convention:
ฮธis the polar (colatitude) angle measured from the+zaxis,ฯis the azimuthal angle.pointmust contain keys"r"(length),"theta"(polar angle), and"phi"(azimuthal angle).>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = { ... "r": u.Q(2.0, "m"), ... "theta": u.Angle(jnp.pi / 2, "rad"), ... "phi": u.Angle(0.0, "rad"), ... } >>> g = metric_matrix(cxm.R3, at, cxc.sph3d) >>> isinstance(g, DiagonalMetric) True >>> g.diagonal QM([1., 4., 4.], '(, m2 / rad2, m2 / rad2)')
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: MathSpherical3D, /) DiagonalMetric
Euclidean metric in
MathSpherical3D:g = diag(1, rยฒsinยฒฯ, rยฒ).Math convention:
ฯis the polar angle from the+zaxis (colatitude),ฮธis the azimuthal angle.pointmust contain keys"r"(length),"theta"(azimuthal angle), and"phi"(polar / colatitude angle).>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = { ... "r": u.Q(2.0, "m"), ... "theta": u.Angle(0.0, "rad"), ... "phi": u.Angle(jnp.pi / 2, "rad"), ... } >>> g = metric_matrix(cxm.R3, at, cxc.math_sph3d) >>> isinstance(g, DiagonalMetric) True >>> g.diagonal QM([1., 4., 4.], '(, m2 / rad2, m2 / rad2)')
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: LonLatSpherical3D, /) DiagonalMetric
Euclidean metric in
LonLatSpherical3D.The metric is
g = diag(distanceยฒcosยฒlat, distanceยฒ, 1)(components ordered as(lon, lat, distance)).pointmust contain keys"lon","lat", and"distance"(length).>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> at = { ... "lon": u.Angle(0.0, "rad"), ... "lat": u.Angle(0.0, "rad"), ... "distance": u.Q(2.0, "m"), ... } >>> g = metric_matrix(cxm.R3, at, cxc.lonlat_sph3d) >>> isinstance(g, DiagonalMetric) True >>> g.diagonal QM([4., 4., 1.], '(m2 / rad2, m2 / rad2, )')
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: ProlateSpheroidal3D, /) DiagonalMetric
Euclidean metric in
ProlateSpheroidal3D, as a diagonal.Confocal prolate spheroidal coordinates are orthogonal, and stay so under this chartโs per-coordinate reparameterisation to \((\mu, \nu, \phi)\): each of \(\mu\) and \(\nu\) is a function of one confocal coordinate alone, so no cross terms appear. The off-diagonal entries of \(J^\top J\) are therefore zero up to round-off โ measured at \(8\times 10^{-17}\) relative, over a grid of \((\Delta, \mu, \nu, \phi)\).
Given in closed form, by differentiating this chartโs own pt_map โ \(\rho = \sqrt{\mu - \Delta^2}\sqrt{1 - t}\) and \(z = \mathrm{sign}(\nu)\sqrt{\mu}\sqrt{t}\) with \(t = |\nu|/\Delta^2\) โ rather than by evaluating the Jacobian pullback and keeping its diagonal. The pullback is NaN across the whole \(\nu = 0\) plane, which the domain admits and pt_map round-trips exactly: forward-mode AD evaluates \(\mathrm{d}\sqrt{t}\) as \(\tfrac{1}{2\sqrt{t}}\,\dot t\), so at \(t = 0\) every column picks up \(\infty \times 0\), and the whole \(\mathrm{d}z\) row came back NaN โ including \(\partial z/\partial\phi\), which is identically zero. Only \(g_{\nu\nu}\) is genuinely singular there; \(g_{\mu\mu}\) and \(g_{\phi\phi}\) have finite limits and were being lost with it.
The result is declared diagonal, which is what tells coordinax.manifolds.scale_factors this chart is orthogonal.
- coordinax.manifolds.metric_matrix(M: EuclideanManifold, point: dict, chart: AbstractChart, /) DenseMetric
Euclidean metric in a general chart via Jacobian pullback
g = J^T J.>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DenseMetric >>> from coordinax._src.charts.d3 import LonCosLatSpherical3D
Non-orthogonal chart (fallback, returns
DenseMetric):>>> M = cxm.R3 >>> chart = LonCosLatSpherical3D() >>> at = { ... "lon_coslat": u.Angle(0.0, "rad"), ... "lat": u.Angle(0.0, "rad"), ... "distance": u.Q(2.0, "m"), ... } >>> g = metric_matrix(M, at, chart) >>> isinstance(g, DenseMetric) True
- coordinax.manifolds.metric_matrix(M: CartesianProductManifold, point: dict, chart: AbstractCartesianProductChart, /) DenseMetric
Product metric (block-diagonal) in a product chart.
Assembles the block-diagonal matrix from factor metrics by recursively calling the standalone
metric_matrixdispatch API.>>> import jax.numpy as jnp >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DenseMetric
Two-factor Euclidean product (Rยฒ x Rยน):
>>> M = cxm.CartesianProductManifold( ... factors=(cxm.R2, cxm.R1), factor_names=("xy", "z") ... ) >>> chart = M.default_chart() >>> at = {k: jnp.array(0.0) for k in chart.components} >>> g = metric_matrix(M, at, chart) >>> isinstance(g, DenseMetric) True >>> g.ndim 3
- coordinax.manifolds.metric_matrix(M: EmbeddedManifold, point: dict, chart: AbstractChart, /) DenseMetric
Induced metric on an embedded submanifold via Jacobian pullback.
Computes \(g_{ij} = \sum_{kl} J^k_i G_{kl} J^l_j\) where \(J\) is the Jacobian of the composition
chart โ intrinsic โ Cartesian ambient(thechart โ intrinsicleg is the identity whenchartis the intrinsic chart) and \(G\) is the ambient metric at the embedded point. \(G\) is the identity for a Euclidean ambient, so the familiar \(J^T J\) is the special case; for a Lorentzian ambient \(G = \eta\) and the induced metric of a timelike direction is correctly negative.Routing through Cartesian ambient coordinates makes every ambient output share the single unit
cart_unit(so column i of \(J\) has unitcart_unit / chart_unit_i) and makes \(G\) dimensionless; eachg_{ij}term then has a consistent unitcart_unit^2 / (chart_unit_i * chart_unit_j)and the result carries physically correct units.- Parameters:
M (EmbeddedManifold) โ An embedded submanifold; carries
intrinsic,ambient, andembed_mapfields.point (dict) โ A coordinate dictionary in the passed
chartโs coordinates.chart (AbstractChart) โ The chart in which
pointis expressed and in which the metric is returned; mapped into the embeddingโs intrinsic chart when the two differ.
- Returns:
Induced metric matrix at
point, backed by aQuantityMatrixwith unitscart_unit^2 / (chart_unit_i * chart_unit_j).- Return type:
Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DenseMetric
Unit sphere โ values should be the identity:
>>> M = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=1.0), ... ) >>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> g = metric_matrix(M, p, cxc.sph2) >>> isinstance(g, DenseMetric) True >>> g.matrix.value Array([[1., 0.], [0., 1.]], dtype=float64)
Radius-2 sphere โ metric scaled by Rยฒ:
>>> M2 = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")), ... ) >>> g2 = metric_matrix(M2, p, cxc.sph2) >>> g2.matrix.value Array([[4., 0.], [0., 4.]], dtype=float64) >>> g2.matrix.unit[0, 0] Unit("m2 / rad2")
The metric is returned in the coordinates of the passed chart:
>>> p_ll = {"lon": u.Angle(0.0, "rad"), "lat": u.Angle(jnp.pi / 3, "rad")} >>> metric_matrix(M, p_ll, cxc.lonlat_sph2).matrix.value Array([[0.25, 0. ], [0. , 1. ]], dtype=float64)
- coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict, chart: AbstractSphericalHyperSphere, /) DiagonalMetric
Round metric on the unit \(n\)-sphere in a standard angular chart.
Computes diagonal entries directly via
_sine_product_diagonal:\[g_{kk} = \prod_{j=0}^{k-1} \sin^2(\theta_j)\]>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
\(S^2\) at the equator \(\theta = \pi/2\):
>>> M = cxm.HyperSphericalManifold(2) >>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)} >>> g = metric_matrix(M, at, cxc.sph2) >>> isinstance(g, DiagonalMetric) True >>> bool(jnp.allclose(g.diagonal.value, jnp.array([1.0, 1.0]))) True
\(S^2\) at \(\theta = \pi/6\):
>>> at = {"theta": jnp.array(jnp.pi / 6), "phi": jnp.array(0.0)} >>> g = metric_matrix(M, at, cxc.sph2) >>> round(float(g.diagonal[1]), 10) # sin\u00b2(\u03c0/6) \u2248 0.25 0.25
- coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict, chart: LonCosLatSphericalTwoSphere, /) DenseMetric
Round metric on the non-orthogonal LonCosLat chart (pullback).
The nested sine-product used by the canonical dispatch assumes the components are polar angles in nested order; that is false for these charts (LonLat, Math swap/relabel the polar and azimuthal angles; LonCosLat is non-orthogonal). The metric is instead pulled back from the canonical chart:
g = Jc^T diag(1, sin^2 theta) Jc, whereJcis the Jacobian of the coordinate mapchart -> SphericalTwoSphere.>>> import math >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
g = [[1, a], [a, 1 + a^2]]witha = lon_coslat * tan(lat); note the nonzero off-diagonal, and thatdet g == 1(the area element isdL dlat):>>> at = {"lon_coslat": u.Q(0.4, "rad"), "lat": u.Q(0.7, "rad")} >>> g = cxm.metric_matrix(cxm.S2, at, cxc.loncoslat_sph2) >>> [round(float(v), 4) for v in g.matrix.value.ravel()] [1.0, 0.3369, 0.3369, 1.1135] >>> round(float(math.prod([0.4, math.tan(0.7)])), 4) 0.3369
Leading axes are batch; the two component axes trail:
>>> import jax.numpy as jnp >>> at = {"lon_coslat": u.Q(jnp.zeros((5,)), "rad"), ... "lat": u.Q(jnp.zeros((5,)), "rad")} >>> cxm.metric_matrix(cxm.S2, at, cxc.loncoslat_sph2).matrix.value.shape (5, 2, 2)
Warning
LonCosLatis not a chart at the poles:lon_coslat = lon * cos(lat)collapses every longitude onto0atlat = +-pi/2, so the map is not injective there. Away from the polesg_LL = cos^2(lat) * sec^2(lat)is exactly1anddet gis exactly1; evaluated at a pole that product becomes0 * infand the returned matrix is degenerate (det g == 0) rather than the limiting identity. Precision degrades within roughly1e-10rad of the pole, where the chartโs condition number~ lon_coslat^2 tan^2(lat)exceeds what float64 can carry.LonLatandMathare genuinely singular at their poles too, but there the degenerate metric is the correct limit.- coordinax.manifolds.metric_matrix(M: HyperSphericalManifold, point: dict, chart: LonLatSphericalTwoSphere | MathSphericalTwoSphere, /) DiagonalMetric
Round metric on an orthogonal relabelled two-sphere chart.
LonLatandMathrelabel/swap the canonical angles, so the nested sine-product does not apply, but they stay orthogonal: the pullback is exactly diagonal, so only the diagonal is kept.LonLat(lon, lat):diag(cos^2(lat), 1)Math(theta, phi):diag(sin^2(phi), 1)
>>> import math >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = {"lon": u.Q(0.6, "rad"), "lat": u.Q(math.radians(40), "rad")} >>> g = cxm.metric_matrix(cxm.S2, at, cxc.lonlat_sph2) >>> [round(float(v), 4) for v in g.diagonal.value] [0.5868, 1.0]
Leading axes are batch; the component axis trails:
>>> import jax.numpy as jnp >>> at = {"lon": u.Q(jnp.zeros((5,)), "rad"), "lat": u.Q(jnp.zeros((5,)), "rad")} >>> cxm.metric_matrix(cxm.S2, at, cxc.lonlat_sph2).diagonal.value.shape (5, 2)
- coordinax.manifolds.metric_matrix(M: MinkowskiManifold, point: dict, chart: MinkowskiCT, /) DiagonalMetric
Minkowski metric \(\eta = \operatorname{diag}(-1, 1, 1, 1)\) in CT chart.
All four components
(ct, x, y, z)carry dimension"length", so the entries are dimensionless.>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> M = cxm.MinkowskiManifold() >>> at = {"ct": jnp.array(0.0), "x": jnp.array(1.0), ... "y": jnp.array(0.0), "z": jnp.array(0.0)} >>> g = metric_matrix(M, at, cxc.minkowskict) >>> isinstance(g, DiagonalMetric) True >>> g.diagonal Array([-1., 1., 1., 1.], dtype=float64)
- coordinax.manifolds.metric_matrix(M: MinkowskiManifold, point: dict, chart: AbstractChart, /) DenseMetric
Minkowski metric in a general chart via Jacobian pullback \(g = J^T \eta J\).
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix >>> from coordinax._src.metric.matrix import DiagonalMetric
Canonical chart uses the specific dispatch above, not this fallback:
>>> M = cxm.MinkowskiManifold() >>> at = {"ct": jnp.array(0.0), "x": jnp.array(1.0), ... "y": jnp.array(0.0), "z": jnp.array(0.0)} >>> g = metric_matrix(M, at, cxc.minkowskict) >>> isinstance(g, DiagonalMetric) True
- coordinax.manifolds.metric_representation(M, chart, /)#
Return the AbstractMetricMatrix subtype for
(manifold, chart).A lightweight, allocation-free query that reports which concrete ~coordinax._src.metric.matrix.AbstractMetricMatrix subclass
metric_matrix()will return for a given(manifold, chart)pair, without actually computing the metric values.Dispatches on
(type(manifold), type(chart)). Concrete rules are registered in the relevantregister_metric.pymodules. The default fallback returns ~coordinax._src.metric.matrix.DenseMetric.- Parameters:
- Returns:
The metric matrix type guaranteed for this
(manifold, chart)pair.- Return type:
Examples
>>> import coordinaxs.api.manifolds as cxmapi >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> cxmapi.metric_representation(cxm.R3, cxc.cart3d) is cxm.DiagonalMetric True
- coordinax.manifolds.metric_representation(M: AbstractManifold, chart: AbstractChart, /) type[AbstractMetricMatrix]
Return DenseMetric as the default fallback.
More specific rules (e.g. for Cartesian charts) override this and return DiagonalMetric.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> from coordinaxs.api.manifolds import metric_representation >>> from coordinax._src.metric.matrix import DenseMetric >>> from coordinax._src.charts.d3 import LonCosLatSpherical3D
Non-orthogonal chart falls back to
DenseMetric:>>> metric_representation(cxm.R3, LonCosLatSpherical3D()) is DenseMetric True
- coordinax.manifolds.metric_representation(M: EuclideanManifold, chart: AbstractChart, /) type[DenseMetric]
Euclidean manifold in a general (non-Cartesian) chart โ DenseMetric.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_representation >>> from coordinax._src.metric.matrix import DenseMetric
>>> from coordinax._src.charts.d3 import LonCosLatSpherical3D >>> chart = LonCosLatSpherical3D() >>> metric_representation(cxm.R3, chart) <class 'coordinax._src.metric.matrix.DenseMetric'>
- coordinax.manifolds.metric_representation(M: EuclideanManifold, chart: Cart1D | Cart2D | Cart3D | CartND | Radial1D | Polar2D | Cylindrical3D | Spherical3D | MathSpherical3D | LonLatSpherical3D | ProlateSpheroidal3D, /) type[DiagonalMetric]
Euclidean manifold in a Cartesian or orthogonal curvilinear chart.
Returns
DiagonalMetric.>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_representation >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> metric_representation(cxm.R3, cxc.cart3d) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
>>> metric_representation(cxm.R2, cxc.polar2d) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
>>> metric_representation(cxm.R3, cxc.sph3d) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
Prolate spheroidal coordinates are orthogonal too โ reparameterising each of the confocal coordinates separately does not couple them:
>>> import unxt as u >>> chart = cxc.ProlateSpheroidal3D(Delta=u.Q(1.0, "m")) >>> metric_representation(cxm.R3, chart) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
- coordinax.manifolds.metric_representation(M: CartesianProductManifold, chart: AbstractCartesianProductChart, /) type[DenseMetric]
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
DenseMetricis the conservative declaration.>>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_representation >>> from coordinax._src.metric.matrix import DenseMetric
>>> M = cxm.CartesianProductManifold( ... factors=(cxm.R2, cxm.R1), factor_names=("xy", "z") ... ) >>> chart = M.default_chart() >>> metric_representation(M, chart) <class 'coordinax._src.metric.matrix.DenseMetric'>
- coordinax.manifolds.metric_representation(M: EmbeddedManifold, chart: AbstractChart, /) type[DenseMetric]
Embedded manifold in any intrinsic chart โ DenseMetric.
>>> import unxt as u >>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> from coordinaxs.api.manifolds import metric_representation
>>> M = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")), ... ) >>> metric_representation(M, cxc.sph2) <class 'coordinax._src.metric.matrix.DenseMetric'>
- coordinax.manifolds.metric_representation(M: HyperSphericalManifold, chart: AbstractSphericalHyperSphere, /) type[DiagonalMetric]
Return DiagonalMetric for a unit \(n\)-sphere in a standard angular chart.
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> cxm.metric_representation(cxm.S2, cxc.sph2) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
- coordinax.manifolds.metric_representation(M: HyperSphericalManifold, chart: LonCosLatSphericalTwoSphere, /) type[DenseMetric]
LonCosLat is non-orthogonal, so its round metric is genuinely dense.
Its off-diagonal is
g_01 = lon_coslat * tan(lat), nonzero away from the equator.>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.metric_representation(cxm.S2, cxc.loncoslat_sph2) <class 'coordinax._src.metric.matrix.DenseMetric'>
- coordinax.manifolds.metric_representation(M: HyperSphericalManifold, chart: LonLatSphericalTwoSphere | MathSphericalTwoSphere, /) type[DiagonalMetric]
LonLat and Math relabel the canonical angles but stay orthogonal.
Their round metrics are exactly diagonal โ
diag(cos^2(lat), 1)anddiag(sin^2(phi), 1)โ so DiagonalMetric is the accurate classification and gives callers the O(n) diagonal path.>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> cxm.metric_representation(cxm.S2, cxc.lonlat_sph2) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
- coordinax.manifolds.metric_representation(M: MinkowskiManifold, chart: AbstractChart, /) type[DenseMetric]
Minkowski manifold in a general chart โ
DenseMetric.>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_representation >>> from coordinax._src.metric.matrix import DenseMetric
>>> M = cxm.MinkowskiManifold() >>> metric_representation(M, cxc.minkowskict) <class 'coordinax._src.metric.matrix.DenseMetric'>
- coordinax.manifolds.metric_representation(M: MinkowskiManifold, chart: MinkowskiCT, /) type[DiagonalMetric]
Minkowski manifold in the canonical CT chart โ
DiagonalMetric.>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_representation >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> M = cxm.MinkowskiManifold() >>> metric_representation(M, cxc.minkowskict) <class 'coordinax._src.metric.matrix.DiagonalMetric'>
- coordinax.manifolds.norm(v, metric, /, *args, **kwargs)#
Compute the Riemannian norm of a vector.
Dispatches on the type of the second argument (metric-related) and the type of
v.Compute the norm of a vector using a general (possibly curved) metric.
This assumes
Gis evaluated at the correct chart and position, andvcomponents match the chartโs ordering.Examples
>>> import jax.numpy as jnp >>> from coordinax.manifolds import norm
Identity metric (Euclidean), 3-4-0 vector:
>>> G = jnp.eye(3) >>> v = jnp.array([3, 4, 0]) >>> norm(G, v) Array(5., dtype=float64)
- coordinax.manifolds.norm(G: Array, v: AbstractQuantity, /) AbstractQuantity
Compute the norm of a vector using a general (possibly curved) metric.
This assumes
Gis evaluated at the correct chart and position, andvcomponents match the chartโs ordering.Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> from coordinax.manifolds import norm
Identity metric (Euclidean), quantity vector:
>>> G = jnp.eye(3) >>> v = u.Q(jnp.array([3.0, 4.0, 0.0]), "m/s") >>> norm(G, v) Q(5., 'm / s')
- coordinax.manifolds.norm(G: unxts.linalg._src._quantity_matrix.QuantityMatrix, v: AbstractQuantity, /) AbstractQuantity
Compute the norm of a vector using a general (possibly curved) metric.
This assumes
Gis evaluated at the correct chart and position, andvcomponents match the chartโs ordering.Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import unxts.linalg as ul >>> from coordinax.manifolds import norm
Dimensionless identity metric, quantity vector:
>>> dmls = ("", "", "") >>> G = ul.QuantityMatrix(jnp.eye(3), unit=(dmls, dmls, dmls)) >>> v = u.Q(jnp.array([3.0, 4.0, 0.0]), "m/s") >>> norm(G, v) Q(5., 'm / s')
- coordinax.manifolds.norm(G: Array, v: dict, /) Array | AbstractQuantity
Compute the norm of a vector using a general (possibly curved) metric.
This assumes
Gis evaluated at the correct chart and position, andvcomponents match the chartโs ordering.Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> from coordinax.manifolds import norm
Identity metric (Euclidean), CDict of quantities:
>>> G = jnp.eye(3) >>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> norm(G, v) Q(5., 'm / s')
Identity metric (Euclidean), CDict of bare arrays โ returns a bare
Array:>>> v = {"x": jnp.array(3.0), "y": jnp.array(4.0), "z": jnp.array(0.0)} >>> norm(G, v) Array(5., dtype=float64)
- coordinax.manifolds.norm(v: dict, metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) Any
Norm of a vector w.r.t. the metric and chart.
Quantity values (any unit combination):
usysis optional. The result is anAbstractQuantitywhose unit reflects the physical dimensions of the norm (e.g.m / swhen components mixm/sand1/s). Mixed-unit components are handled correctly via {class}`~unxts.linalg.QuantityMatrix` arithmetic.Bare jax.Array values:
usysis required (raisesTypeErrorif omitted); a plainjax.Arrayis returned.
Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
CDict of same-unit quantities โ returns
Quantity:>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> cxm.norm(v, metric, cxc.cart3d, at=at) Q(5., 'm / s')
CDict of mixed-unit quantities on a spherical chart โ also returns
Quantity. Atr = 5 m,ฮธ = ฯ/2the metric isG = diag(1, 25 mยฒ, 25 mยฒ), so radial velocity(1 m/s, 0, 0)has norm1 m/s:>>> at_sph = {"r": u.Q(5.0, "m"), "theta": u.Q(jnp.pi / 2, "rad"), ... "phi": u.Q(0.0, "rad")} >>> v_mixed = {"r": u.Q(1.0, "m/s"), "theta": u.Q(0.0, "rad/s"), ... "phi": u.Q(0.0, "rad/s")} >>> cxm.norm(v_mixed, metric, cxc.sph3d, at=at_sph) Q(1., 'm / s')
CDict of bare arrays (usys required) โ returns bare
Array:>>> v_bare = {"x": jnp.array(3.0), "y": jnp.array(4.0), "z": jnp.array(0.0)} >>> cxm.norm(v_bare, metric, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
This was using the Euclidean metric, but it also works on curved metrics.
>>> metric = cxm.RoundMetric(2) >>> at = {"theta": u.Q(jnp.pi / 4, "rad"), "phi": u.Q(1.0, "rad")} >>> v = {"theta": u.Q(0.5, "rad/s"), "phi": u.Q(0.5, "rad/s")} >>> cxm.norm(v, metric, cxc.sph2, at=at) Q(0.61237244, 'rad / s')
An indefinite metric has no real-valued norm and is rejected rather than silently returning
nan:>>> v4 = {"ct": u.Q(5.0, "m"), "x": u.Q(1.0, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> at4 = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")} >>> try: ... cxm.norm(v4, cxm.MinkowskiMetric(), cxc.minkowskict, at=at4) ... except NotImplementedError as e: ... print(str(e)[:52]) norm() supports only positive-definite metrics, but
- coordinax.manifolds.norm(v: AbstractQuantity, metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) AbstractQuantity
Dispatch for a
AbstractQuantity.The quantity is wrapped in a one-element CDict using the chartโs first (and only) component name, then the CDict overload is invoked.
This overload is primarily useful for 1-D manifolds (e.g.
EuclideanManifold(1)with aCart1Dchart).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
Norm of a 1-D displacement on the real line:
>>> M = cxm.FlatMetric(1) >>> at = {"x": jnp.array(0.0)} >>> cxm.norm(u.Q(jnp.array([5.0]), "m/s"), M, cxc.cart1d, at=at) Q(5., 'm / s')
- coordinax.manifolds.norm(v: Array, metric: AbstractMetricField, chart: AbstractChart, /, *, at: dict, usys: AbstractUnitSystem | None = None) Array
Dispatch for a packed 1-D
jax.Array(v first).vis a stacked array whose entries correspond to the chartโs component ordering (chart.components). Because the array carries no unit information,usysis required; passingusys=None(the default) raisesTypeError.Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
3-4-0 triple in Cartesian 3-D:
>>> M = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)} >>> cxm.norm(jnp.array([3.0, 4.0, 0.0]), M, cxc.cart3d, ... at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
Missing
usysraisesTypeError:>>> try: ... cxm.norm(jnp.array([3.0, 4.0, 0.0]), M, cxc.sph3d, at=at) ... except TypeError as e: ... print(e) norm(): `usys` is required when `v` is a bare jax.Array ...
Unit-sphere Sยฒ:
v = (1, 0)at the equator has norm 1:>>> M = cxm.RoundMetric(2) >>> at_eq = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)} >>> cxm.norm(jnp.array([1.0, 0.0]), M, cxc.sph2, ... at=at_eq, usys=u.unitsystems.si) Array(1., dtype=float64)
- coordinax.manifolds.norm(v: dict, chart: AbstractChart, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) Any
Norm of a CDict vector w.r.t. the chart.
Dispatches to the metric-level overload via the chartโs attached metric. For flat (Euclidean) charts
atis optional; for curved metrics it is required and passed through tometric.metric_matrix.- coordinax.manifolds.norm(v: Array, chart: AbstractChart, /, *, at: Any = None, usys: Any = None) Array
Norm of a packed
jax.Arrayw.r.t. the chart.Delegates to the metric-level
norm(v, metric, chart, ...)overload via the chartโs attached metric. For Euclidean Cartesian charts the fast-pathjnp.linalg.normoverload is picked automatically. For curved chartsatandusysare required; missingusysraisesTypeError.Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
Euclidean fast-path (no
atneeded):>>> cxm.norm(jnp.array([3.0, 4.0, 0.0]), cxc.cart3d) Array(5., dtype=float64)
Curved chart (
atandusysrequired):>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)} >>> cxm.norm(jnp.array([1.0, 0.0]), cxc.sph2, at=at, usys=u.unitsystems.si) Array(1., dtype=float64)
- coordinax.manifolds.norm(v: AbstractQuantity, chart: AbstractChart, /, *, at: Any = None, usys: Any = None) AbstractQuantity
Norm of a single
AbstractQuantityw.r.t. the chart.Wraps the quantity in a one-element CDict keyed by the chartโs first component name, then delegates to the metric-level overload. Primarily useful for 1-D charts (
Cart1D).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> at = {"x": jnp.array(0.0)} >>> cxm.norm(u.Q(5.0, "m/s"), cxc.cart1d, at=at) Q(5., 'm / s')
>>> cxm.norm(u.Q(-3.0, "m"), cxc.cart1d, at=at) Q(3., 'm')
- coordinax.manifolds.norm(v: Array, metric: FlatMetric, chart: Cart0D | Cart1D | Cart2D | Cart3D | CartND, /, *, at: Any = None, usys: Any = None) Array
Short-circuit: Euclidean Cartesian chart ->
jnp.linalg.norm.When both the metric is flat (
FlatMetric) and the chart is Cartesian (Cart3D), the metric matrix is the 3x3 identity, soโvโ = โ(vแต I v) = โvโโ. This overload skips the general matrix computation and callsjnp.linalg.normdirectly.atandusysare ignored.Examples
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
3-4-0 triple gives norm 5:
>>> cxm.norm(jnp.array([3.0, 4.0, 0.0]), M, cxc.cart3d, at=at) Array(5., dtype=float64)
atandusysare unused and technically skippable in this fast path:>>> cxm.norm(jnp.array([1.0, 0.0, 0.0]), M, cxc.cart3d) Array(1., dtype=float64)
- coordinax.manifolds.norm(data: dict, metric: FlatMetric, chart: Cart0D | Cart1D | Cart2D | Cart3D, /, *, at: dict | None = None, usys: Any = None) AbstractQuantity | Array
Compute the Euclidean norm of a vector.
Examples
>>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
>>> M = cxm.FlatMetric(3) >>> v = {"x": u.Q(3, "m"), "y": u.Q(4, "m"), "z": u.Q(0, "m")} >>> cxm.norm(v, M, cxc.cart3d) Q(5., 'm')
Works in any dimension:
>>> M = cxm.FlatMetric(2) >>> v2 = {"x": u.Q(3, "km"), "y": u.Q(4, "km")} >>> cxm.norm(v2, M, cxc.cart2d) Q(5., 'km')
- coordinax.manifolds.chord_distance(*args, **kwargs)#
Straight-line distance between two points through their ambient space.
The chord, as opposed to geodesic_distanceโs path along the manifold. Defined wherever the manifold carries an embedding; a manifold that is its own ambient space has no distinct chord and is refused.
- coordinax.manifolds.chord_distance(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Return the ambient straight-line distance, on the chartโs manifold.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
A quarter turn along the equator of the unit sphere: the arc is
pi / 2, the chord through the interior issqrt(2).>>> a = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> b = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(jnp.pi / 2, "rad")} >>> round(float(cxm.chord_distance(cxc.sph2, a, b)), 6) 1.414214 >>> round(float(cxm.geodesic_distance(cxc.sph2, a, b).ustrip("rad")), 6) 1.570796
Antipodes are one diameter apart through the middle, half the great-circle distance around the outside:
>>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")} >>> s = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")} >>> round(float(cxm.chord_distance(cxc.sph2, n, s)), 6) 2.0
- coordinax.manifolds.chord_distance(M: HyperSphericalManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Return the chord of the unit hypersphere, through its canonical embedding.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
Any chart on the sphere gives the same answer:
>>> a = {"lon": u.Angle(0.0, "rad"), "lat": u.Angle(0.0, "rad")} >>> b = {"lon": u.Angle(jnp.pi / 2, "rad"), "lat": u.Angle(0.0, "rad")} >>> round(float(cxm.chord_distance(cxc.lonlat_sph2, a, b)), 6) 1.414214
- coordinax.manifolds.chord_distance(M: EmbeddedManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Return the chord through the manifoldโs own ambient space.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
A sphere of radius 2 m: antipodes are one diameter apart.
>>> M = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")), ... ) >>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")} >>> s = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")} >>> cxm.chord_distance(M, cxc.sph2, n, s).round(6) Distance(4., 'm')
- coordinax.manifolds.chord_distance(M: EuclideanManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Refuse: flat space is its own ambient, so the chord is the geodesic.
Returning the same number under a second name invites the reader to think two things were measured.
>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")} >>> try: cxm.chord_distance(cxc.cart3d, a, b) ... except NotImplementedError as e: print(e) chord_distance is a measurement through an ambient space, and Rn(3) is its own ambient -- its chord is the straight line, which is what `geodesic_distance` already returns.
- coordinax.manifolds.chord_distance(M: AbstractManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Refuse: without an embedding there is no ambient space to cut through.
Straight-line distance between two points, through their ambient space.
The counterpart to ~coordinax.geodesic_distanceโs Point overload: that one measures along the manifold, this one through the space it is embedded in. The result is invariant to the chart each operand happens to use.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax as cx >>> import coordinax.charts as cxc
A quarter turn along the equator: the arc is
pi / 2, the chord through the interior issqrt(2).>>> p = cx.Point({"theta": u.Angle(jnp.pi / 2, "rad"), ... "phi": u.Angle(0.0, "rad")}, chart=cxc.sph2) >>> q = cx.Point({"theta": u.Angle(jnp.pi / 2, "rad"), ... "phi": u.Angle(jnp.pi / 2, "rad")}, chart=cxc.sph2) >>> round(float(cx.chord_distance(p, q)), 6) 1.414214
The operands need not share a chart:
>>> r = cx.Point({"lon": u.Angle(jnp.pi / 2, "rad"), ... "lat": u.Angle(0.0, "rad")}, chart=cxc.lonlat_sph2) >>> round(float(cx.chord_distance(p, r)), 6) 1.414214
Flat space is its own ambient, so it has no chord distinct from its geodesic:
>>> a = cx.Point.from_([3.0, 0.0, 0.0], "m") >>> b = cx.Point.from_([0.0, 4.0, 0.0], "m") >>> try: cx.chord_distance(a, b) ... except NotImplementedError as e: print(str(e)[:48]) chord_distance is a measurement through an ambie
- coordinax.manifolds.geodesic_distance(*args, **kwargs)#
Distance between two points on a manifold.
The length of the shortest path along the manifold: the straight line in flat space, the great circle on a sphere. It is computed from the manifoldโs geometry, not from the coordinate difference, whose norm is asymmetric on a curved manifold and so is not a distance at all. A manifold with no closed-form geodesic raises rather than approximating.
Dispatches on the inputs: two ~coordinax.vectors.Point objects (chart/frame extracted automatically), or a
chart/metrictogether with the two points as component dictionaries, packed quantities, or bare arrays.- coordinax.manifolds.geodesic_distance(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Geodesic distance between two points, on the chartโs manifold.
>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
A 3-4-5 triangle in flat space:
>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")} >>> cxm.geodesic_distance(cxc.cart3d, a, b).round(2) Distance(5., 'm')
The same two points in a curvilinear chart give the same answer, which the norm of their coordinate difference would not:
>>> a_sph = cxc.pt_map(a, cxc.cart3d, cxc.sph3d) >>> b_sph = cxc.pt_map(b, cxc.cart3d, cxc.sph3d) >>> cxm.geodesic_distance(cxc.sph3d, a_sph, b_sph).round(2) Distance(5., 'm')
On the unit sphere it is the great-circle distance, and symmetric:
>>> import jax.numpy as jnp >>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> q = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(1.0, "rad")} >>> round(float(cxm.geodesic_distance(cxc.sph2, p, q).ustrip("rad")), 6) 1.0 >>> round(float(cxm.geodesic_distance(cxc.sph2, q, p).ustrip("rad")), 6) 1.0
- coordinax.manifolds.geodesic_distance(M: EuclideanManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Return the straight-line distance, measured in the Cartesian chart.
Mapping both points into the manifoldโs Cartesian chart first is what makes this chart-invariant and symmetric. Measuring
||b - a||in the chart the caller happened to use is neither, unless that chart is already Cartesian.>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = {"r": u.Q(2.0, "m"), "theta": u.Angle(1.0, "rad"), ... "phi": u.Angle(0.4, "rad")} >>> b = {"r": u.Q(3.0, "m"), "theta": u.Angle(1.3, "rad"), ... "phi": u.Angle(0.9, "rad")} >>> round(float(cxm.geodesic_distance(cxc.sph3d, a, b).ustrip("m")), 6) 1.651376
- coordinax.manifolds.geodesic_distance(chart: AbstractChart, a: AbstractQuantity, b: AbstractQuantity, /, *, usys: AbstractUnitSystem | None = None) Any
Distance between two points given as packed unxt.Quantity vectors.
Each quantityโs trailing axis holds the components in
chart.componentsorder; it is unpacked into a component dictionary and delegated to theCDictoverload.>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = u.Q([3.0, 0.0, 0.0], "m") >>> b = u.Q([0.0, 4.0, 0.0], "m") >>> cxm.geodesic_distance(cxc.cart3d, a, b).round(2) Distance(5., 'm')
- coordinax.manifolds.geodesic_distance(chart: AbstractChart, a: Array, b: Array, /, *, usys: AbstractUnitSystem | None = None) Any
Distance between two points given as packed (unitless) arrays.
The trailing axis holds the components in
chart.componentsorder.>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = jnp.array([3.0, 0.0, 0.0]) >>> b = jnp.array([0.0, 4.0, 0.0]) >>> float(cxm.geodesic_distance(cxc.cart3d, a, b)) 5.0
- coordinax.manifolds.geodesic_distance(M: HyperSphericalManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Return the great-circle distance on the unit hypersphere.
The points are embedded into the ambient Cartesian space as unit vectors and the central angle between them is taken, which for a unit sphere is the arc length. Any chart on the sphere works: the points are routed through the embeddingโs own intrinsic chart first, so the answer does not depend on which one the caller used.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
A quarter turn along the equator is pi / 2:
>>> a = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> b = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(jnp.pi / 2, "rad")} >>> round(float(cxm.geodesic_distance(cxc.sph2, a, b).ustrip("rad")), 6) 1.570796
Antipodes are pi apart โ the coordinate-difference norm cannot reach this, since it has no way to know the sphere closes up:
>>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")} >>> sth = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")} >>> round(float(cxm.geodesic_distance(cxc.sph2, n, sth).ustrip("rad")), 6) 3.141593
- coordinax.manifolds.geodesic_distance(M: EmbeddedManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Embedded sphere: the arc length, i.e. the radius times the central angle.
Delegates the angle to the intrinsic manifoldโs own rule rather than re-deriving it, so there is one implementation of the great circle. Only the radius is applied here, which is what the embedding adds.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "m")), ... ) >>> n = {"theta": u.Angle(0.0, "rad"), "phi": u.Angle(0.0, "rad")} >>> s = {"theta": u.Angle(jnp.pi, "rad"), "phi": u.Angle(0.0, "rad")} >>> cxm.geodesic_distance(M, cxc.sph2, n, s).round(4) Distance(6.2832, 'm')
Reached through an EmbeddedChart too, whose manifold is this one:
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "m"))) >>> cxm.geodesic_distance(chart, n, s).round(4) Distance(6.2832, 'm')
- coordinax.manifolds.geodesic_distance(M: AbstractManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Refuse: no closed-form geodesic is known for this manifold.
Refused rather than approximated: the norm of the coordinate difference, which this used to return, is asymmetric on a curved manifold and so is not a distance at all.
- coordinax.manifolds.geodesic_distance(M: MinkowskiManifold, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Refuse: a pseudo-Riemannian manifold has no Riemannian distance.
The Minkowski metric is indefinite, so
g(v, v)is negative for a timelike pair and its square root is not a length. Returningnanthere while returning a plausible number for a spacelike pair โ which is what taking the root unguarded does โ hides the failure in exactly the half of spacetime a reader is least likely to probe.>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> origin = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")} >>> event = {"ct": u.Q(5.0, "m"), "x": u.Q(1.0, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> try: cxm.geodesic_distance(cxc.minkowskict, origin, event) ... except NotImplementedError as e: print(e) geodesic_distance() requires a Riemannian (positive-definite) metric; MinkowskiManifold() is pseudo-Riemannian, whose indefinite metric admits no distance. Use `interval` for the signed square, `proper_time` for a timelike pair, or `proper_distance` for a spacelike one.
- coordinax.manifolds.geodesic_distance(metric: AbstractMetricField, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Geodesic distance with the metric stated explicitly.
The geodesic is a property of the manifold, so this checks that
metricis the onechartcarries and then defers to the manifold rule. It exists so a caller can be explicit, and so a mismatched metric is refused rather than silently replaced by the chartโs.>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")} >>> cxm.geodesic_distance(cxm.FlatMetric(3), cxc.cart3d, a, b).round(2) Distance(5., 'm')
- coordinax.manifolds.geodesic_distance(chart: Cart0D | Cart1D | Cart2D | Cart3D | CartND, a: Any, b: Any, /, *, usys: AbstractUnitSystem | None = None) Any
Packed operands in a Cartesian chart: measure without unpacking.
Registered as two signatures rather than one with a
Quantity | Arrayunion: each generic overload it overrides is typed for one of them, and a union would be narrower in the chart but wider in the operands, so plum would rank neither more specific and refuse the call as ambiguous.The generic packed overloads split the operands into component dicts so a curvilinear chart can be mapped to Cartesian. Here there is nothing to map, and the components are already on the trailing axis in the right order, so the norm is taken directly.
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = u.Q(jnp.asarray([3.0, 0.0, 0.0]), "m") >>> b = u.Q(jnp.asarray([0.0, 4.0, 0.0]), "m") >>> cxm.geodesic_distance(cxc.cart3d, a, b).round(2) Distance(5., 'm')
>>> float(cxm.geodesic_distance(cxc.cart3d, jnp.asarray([3.0, 0.0, 0.0]), ... jnp.asarray([0.0, 4.0, 0.0]))) 5.0
Distance between two points, via the manifold norm.
The two points are brought into a common Cartesian chart (so the result is invariant to the chart and component units each operand happens to use), and the distance is the manifold ~coordinax.manifolds.norm of their coordinate difference โ the Euclidean distance for a flat manifold. A length result is returned as a Distance; a unitless (dimensionless) result is returned as a bare array.
Dimensionality follows the pointsโ manifold: 2-D points give a 2-D distance, 3-D points a 3-D distance. There is no
separation_3dโ to measure in a particular N-D space, map the points into it first, then call geodesic_distance.Examples
>>> import coordinax as cx >>> import coordinax.charts as cxc
A 3-4-5 right triangle:
>>> p = cx.Point.from_([3.0, 0.0, 0.0], "m") >>> q = cx.Point.from_([0.0, 4.0, 0.0], "m") >>> cx.geodesic_distance(p, q).round(2) Distance(5., 'm')
Chart- and unit-invariant โ the same points expressed differently give the same distance:
>>> cx.geodesic_distance(p, q.cconvert(cxc.sph3d)).round(2) Distance(5., 'm') >>> q_km = cx.Point.from_([0.0, 0.004, 0.0], "km") >>> cx.geodesic_distance(p, q_km).uconvert("m").round(2) Distance(5., 'm')
The distance lives on the pointsโ manifold, so 2-D points give a 2-D distance:
>>> p2 = cx.Point.from_([3.0, 0.0], "m") >>> q2 = cx.Point.from_([0.0, 4.0], "m") >>> cx.geodesic_distance(p2, q2).round(2) Distance(5., 'm')
- coordinax.manifolds.interval(*args, **kwargs)#
Signed squared interval between two points.
intervalis the metric quadratic form of the coordinate difference, without the square root that norm takes. It is therefore defined for every metric, including indefinite ones, where norm has no real value and geodesic_distance refuses outright; for a Lorentzian metric its sign is the pairโs causal character.It is a first-order quantity, not a squared distance: it equals
geodesic_distance**2only where the coordinate difference is itself the geodesic, i.e. on a flat manifold in Cartesian coordinates. On a curved manifold the two differ โ on the unit sphere at a separation of ~0.92 rad, by 13%.- coordinax.manifolds.interval(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Signed squared interval between two points, in the chartโs metric.
Unlike geodesic_distance, this is defined for every metric, because it never takes a square root.
>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
For a Riemannian metric it is the squared geodesic_distance:
>>> a = {"x": u.Q(3.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> b = {"x": u.Q(0.0, "m"), "y": u.Q(4.0, "m"), "z": u.Q(0.0, "m")} >>> cxm.interval(cxc.cart3d, a, b).round(2) Q(25., 'm2')
For Minkowski it is negative for a timelike pair โ the case that used to make geodesic_distance return
nan:>>> o = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")} >>> ev = {"ct": u.Q(5.0, "m"), "x": u.Q(1.0, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> cxm.interval(cxc.minkowskict, o, ev).round(2) Q(-24., 'm2')
- coordinax.manifolds.interval(metric: AbstractMetricField, chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) Any
Signed squared interval with respect to an explicit metric.
>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> o = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")} >>> ev = {"ct": u.Q(1.0, "m"), "x": u.Q(5.0, "m"), ... "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")} >>> cxm.interval(cxm.MinkowskiMetric(), cxc.minkowskict, o, ev).round(2) Q(24., 'm2')
>>> try: ... cxm.interval(cxm.FlatMetric(4), cxc.minkowskict, o, ev) ... except ValueError as e: ... print(str(e)[:56]) interval(): metric-level dispatch needs the chart's own
- coordinax.manifolds.interval(chart: AbstractChart, a: AbstractQuantity, b: AbstractQuantity, /, *, usys: AbstractUnitSystem | None = None) Any
Signed squared interval for packed unxt.Quantity vectors.
>>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> a = u.Q([0.0, 0.0, 0.0, 0.0], "m") >>> b = u.Q([5.0, 1.0, 0.0, 0.0], "m") >>> cxm.interval(cxc.minkowskict, a, b).round(2) Q(-24., 'm2')
- class coordinax.manifolds.AbstractAtlas#
Bases:
objectAtlas protocol for manifolds.
An atlas defines the set of charts that may be used to represent coordinates on a manifold. In differential geometry, a smooth manifold is defined by a pair \((M, \\mathcal{A})\) where \(M\) is a topological space and \(\\mathcal{A}\) is a maximal smooth atlas โ a collection of compatible charts whose domains cover the \(M\).
Responsibilities of an atlas include:
declaring the dimension of the manifold it covers,
determining whether a chart is compatible with the manifold,
providing a default chart used when one is not explicitly specified.
The atlas does not perform coordinate transformations itself. Those are implemented by chart-level transition maps and higher-level transformation machinery (e.g.
coordinax.charts.pt_map()).Notes
Atlas objects are structural descriptors, not numerical objects.
Multiple manifolds may share the same atlas type if their smooth structures coincide.
Charts belonging to the same atlas are assumed to have compatible transition maps.
Some atlas implementations allow charts to register themselves as compatible coordinate systems. For example, Euclidean charts register with coordinax.manifolds.EuclideanAtlas so they can be recognized automatically.
Examples
Constructing a Euclidean atlas
In the Euclidean case the atlas consists of common coordinate systems on \(\mathbb{R}^n\).
>>> import coordinax.manifolds as cxm >>> atlas = cxm.EuclideanAtlas(3)
The atlas records the dimension of the manifold:
>>> atlas.ndim 3
It can provide a canonical chart:
>>> atlas.default_chart() Cart3D(M=Rn(3))
The atlas determines whether a chart belongs to the manifold.
>>> import coordinax.charts as cxc >>> cxc.cart3d in atlas True
>>> cxc.cyl3d in atlas True
Charts with the wrong dimensionality are rejected:
>>> cxc.cart2d in atlas False
Atlas-manifold interaction
A manifold object typically owns an atlas describing its smooth structure.
>>> from coordinax.manifolds import EuclideanManifold >>> M = EuclideanManifold(3)
>>> M.atlas.ndim 3
The manifold uses the atlas to verify chart compatibility:
>>> M.has_chart(cxc.cart3d) True
>>> M.has_chart(cxc.cart2d) False
- abstractmethod default_chart()#
Return a default chart from the atlas.
- Return type:
AbstractChart[Any,Any,Any]
>>> import coordinax.manifolds as cxm >>> atlas = cxm.EuclideanAtlas(2) >>> atlas.default_chart() Cart2D(M=Rn(2))
- abstractmethod has_chart(chart: AbstractChart[Any, Any, Any], /)#
Return whether the atlas supports the given chart.
>>> import coordinax.manifolds as cxm >>> atlas = cxm.EuclideanAtlas(2) >>> atlas.has_chart(cxc.cart2d) True
>>> atlas.has_chart(cxc.cart3d) False
- Parameters:
chart (
AbstractChart[Any,Any,Any])- Return type:
- class coordinax.manifolds.AbstractMetricField#
Bases:
objectAbstract base class for metrics of manifolds.
The metric defines a bilinear form on the tangent space of a chart.
\[g_p: T_p M \times T_p M \to \mathbb{R}\]The metric can be represented as a matrix in the coordinate basis of a chart. Let \((U,\varphi)\) be a chart with coordinates \(q = (q^1, \dots, q^n)\).
The coordinate basis of the tangent space is
\(\left\{ \frac{\partial}{\partial q^i} \right\}_{i=1}^n\).
The metric matrix is defined by evaluating the metric on these basis vectors:
\[g_{ij}(q) = g_p \left( \frac{\partial}{\partial q^i}, \frac{\partial}{\partial q^j} \right).\]This gives an n times n matrix
\[\begin{split}g(q) = \begin{pmatrix} g_{11}(q) & \cdots & g_{1n}(q) \\ \vdots & \ddots & \vdots \\ g_{n1}(q) & \cdots & g_{nn}(q) \end{pmatrix}.\end{split}\]The metric matrix is computed via the standalone dispatch function
coordinax.manifolds.metric_matrix().Examples
>>> import coordinax.manifolds as cxm >>> cxm.FlatMetric(3).ndim 3
- abstract property signature: tuple[int, ...]#
Return the signature of the metric as a tuple of integers.
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- class coordinax.manifolds.AbstractManifold#
Bases:
objectAbstract 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\).
AbstractManifoldis the base class for all manifold objects in coordinax. It couples a manifold to its atlas and exposes a small set of coordinate-level operations:chart introspection โ querying which charts belong to the manifold,
point transition maps โ converting point coordinates between two charts in the same atlas, and
Cartesian realization โ converting point coordinates into (or out of) a canonical ambient Cartesian chart when the manifold admits one.
All geometric and numerical work is delegated to the charts and the atlas; the manifold itself is a lightweight descriptor.
- Variables:
atlas (AbstractAtlas) โ The atlas that defines the smooth structure of this manifold. The atlas records the intrinsic dimension and determines which charts are compatible.
ndim (int) โ Intrinsic dimension \(n\) of the manifold, forwarded from {attr}`atlas.ndim <AbstractAtlas.ndim>`.
default_chart (AbstractChart) โ A canonical chart chosen by the atlas, forwarded from {meth}`atlas.default_chart() <AbstractAtlas.default_chart>`.
Notes
Manifold objects are structural descriptors, not numerical arrays. They carry no point data.
Subclasses are typically frozen dataclasses registered with JAX as static pytree nodes so that they can appear as static metadata inside JIT-compiled functions.
The two-sphere \(S^2\) is not a product manifold: its atlas requires at least two charts with non-trivial overlaps, and its transition maps do not factor. Contrast with the Euclidean case where a single global chart covers all of \(\mathbb{R}^n\).
Examples
AbstractManifoldcannot 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
Truewhen 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
chartbelongs 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:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- class coordinax.manifolds.AbstractDiagonalMetricField#
Bases:
AbstractMetricFieldAbstract 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_matrixdispatch must return aDiagonalMetricat 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
FlatMetricflat Riemannian metric on \(\mathbb{R}^n\); in Cartesian charts \(g = I_n\); in orthogonal curvilinear charts computed by Jacobian pullback \(g = J^\top J\).
RoundMetricround 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\).
MinkowskiMetricLorentzian 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
FlatMetricis anAbstractDiagonalMetricField:>>> isinstance(cxm.FlatMetric(3), AbstractDiagonalMetricField) True
MinkowskiMetricis also anAbstractDiagonalMetricField:>>> isinstance(cxm.MinkowskiMetric(), AbstractDiagonalMetricField) True
General (non-diagonal) metrics such as
PullbackMetricare not:>>> import unxt as u >>> isinstance( ... cxm.PullbackMetric( ... cxm.TwoSphereIn3D(radius=u.Q(1.0, "m")), ... cxm.FlatMetric(3), ... ), ... AbstractDiagonalMetricField, ... ) False
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- class coordinax.manifolds.AbstractLorentzianMetricField#
Bases:
AbstractMetricFieldAbstract base class for metrics with a Lorentzian signature.
Subclassing signals exactly one timelike direction โ signature \((-,+,\ldots,+)\) โ so a tangent vector, or a pair of points, has a causal character: timelike, null, or spacelike.
A marker carrying no implementation. It lets the verbs that need such a signature โ ~coordinax.manifolds.causal_character, ~coordinax.manifolds.proper_time, ~coordinax.manifolds.proper_distance โ state that precondition as a type, and lets a curved spacetime metric join them by inheriting it.
Orthogonal to AbstractDiagonalMetricField, which describes the matrixโs shape rather than its signature. MinkowskiMetric is both.
Examples
>>> import coordinax.manifolds as cxm
>>> isinstance(cxm.MinkowskiMetric(), cxm.AbstractLorentzianMetricField) True
>>> isinstance(cxm.FlatMetric(3), cxm.AbstractLorentzianMetricField) False
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- class coordinax.manifolds.AbstractMetricMatrix#
Bases:
ModuleAbstract 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.
- abstractmethod to_dense()#
Return an equivalent
DenseMetric.For diagonal metrics, off-diagonal entries are zero. For dense metrics, returns
self.- Return type:
- final class coordinax.manifolds.DiagonalMetric(diagonal: QuantityMatrix | Array)#
Bases:
AbstractMetricMatrixDiagonal metric matrix stored as a 1-D array or QuantityMatrix.
Encodes a metric whose coordinate matrix is diagonal โ i.e. orthogonal coordinate charts. Storing only the diagonal avoids materialising the full \(n \times n\) matrix and makes operations like matrix-vector products and inversion run in \(O(n)\).
- Parameters:
diagonal (
QuantityMatrix|Array) โ The diagonal entries \(g_{11}, g_{22}, \\ldots, g_{nn}\).
Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> d = DiagonalMetric(jnp.array([1.0, 4.0, 9.0])) >>> d.ndim 3 >>> d.determinant Array(36., dtype=float64) >>> d.inverse.diagonal Array([1. , 0.25 , 0.11111111], dtype=float64)
- to_dense()#
Convert to a full \(n \times n\) matrix with zeros off the diagonal.
When the diagonal is a
QuantityMatrix, the off-diagonal entry(i, j)is assigned the geometric-mean unitsqrt(diag_unit[i] * diag_unit[j]). This choice ensures thatg[i, j] * v[j]is unit-compatible withg[i, i] * v[i]during matrix-vector contraction, which is required for theQuantityMatrix()dot-product to succeed even when the coordinate components have different physical dimensions (e.g. metres and radians in spherical coordinates).- Return type:
Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DiagonalMetric
Plain array โ off-diagonal entries are zero:
>>> d = DiagonalMetric(jnp.array([1.0, 4.0])) >>> d.to_dense().matrix Array([[1., 0.], [0., 4.]], dtype=float64)
QuantityMatrix diagonal โ diagonal units are preserved and off-diagonal entries get the geometric-mean unit:
>>> import unxts.linalg as ul >>> d = DiagonalMetric( ... ul.QuantityMatrix(jnp.array([1.0, 4.0]), unit=("m2", "s2")) ... ) >>> d.to_dense().matrix.unit[0, 0] Unit("m2") >>> d.to_dense().matrix.unit[1, 1] Unit("s2") >>> d.to_dense().matrix.unit[0, 1] # geometric mean: sqrt(m2 * s2) Unit("m s")
- property inverse: DiagonalMetric#
Inverse diagonal metric โ reciprocal of each diagonal entry.
Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DiagonalMetric
>>> d = DiagonalMetric(jnp.array([2.0, 4.0])) >>> d.inverse.diagonal Array([0.5 , 0.25], dtype=float64)
- property determinant: Array | AbstractQuantity#
Product of the diagonal entries.
Returns a
AbstractQuantitywhen the diagonal is aQuantityMatrix.Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DiagonalMetric
Bare array โ returns a plain
Array:>>> DiagonalMetric(jnp.array([2.0, 3.0])).determinant Array(6., dtype=float64)
QuantityMatrix diagonal โ returns a
Quantity:>>> import unxt as u >>> import unxts.linalg as ul >>> d = DiagonalMetric( ... ul.QuantityMatrix(jnp.array([2.0, 3.0]), unit=("m2", "s2")) ... ) >>> d.determinant Q(6., 'm2 s2')
- final class coordinax.manifolds.DenseMetric(matrix: QuantityMatrix | Array)#
Bases:
AbstractMetricMatrixDense symmetric metric matrix.
Stores the full \(n \\times n\) metric matrix. Used for non-orthogonal charts or metrics that cannot be expressed diagonally.
- Parameters:
matrix (
QuantityMatrix|Array) โ The full metric matrix \(g_{ij}\).
Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DenseMetric
>>> g = DenseMetric(jnp.eye(3)) >>> g.ndim 3 >>> g.determinant Array(1., dtype=float64)
- to_dense()#
Return
selfโ already in dense form.- Return type:
Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DenseMetric
>>> g = DenseMetric(jnp.eye(2)) >>> g.to_dense() is g True
- property inverse: DenseMetric#
Inverse via
jax.numpy.linalg.inv()(positive-definite assumption).Returns a
QuantityMatrix-backedDenseMetricwith units1 / ref_unitwhen the matrix carries units. Assumes all entries share the same unit (physically well-formed metrics from the Cartesian-Jacobian pullback always satisfy this).Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DenseMetric
Bare array:
>>> g = DenseMetric(jnp.array([[2.0, 0.0], [0.0, 4.0]])) >>> g.inverse.matrix Array([[0.5 , 0. ], [0. , 0.25]], dtype=float64)
QuantityMatrix โ inverse carries reciprocal units:
>>> import unxt as u >>> import unxts.linalg as ul >>> g = DenseMetric( ... ul.QuantityMatrix( ... jnp.array([[4.0, 0.0], [0.0, 1.0]]), ... unit=ul.UnitsMatrix(( ... ("m2 / rad2", "m2 / rad2"), ... ("m2 / rad2", "m2 / rad2"), ... )), ... ) ... ) >>> g.inverse.matrix.unit[0, 0] Unit("rad2 / m2") >>> g.inverse.matrix.value Array([[0.25, 0. ], [0. , 1. ]], dtype=float64)
- property determinant: Array | AbstractQuantity#
Determinant via the custom
det_pJAX primitive.Routes through Quax, so a
QuantityMatrixmatrix returns aAbstractQuantitywhile a plain array returns a bareArray. The unit is the product of the main-diagonal units โ valid for diagonal and uniform-unit matrices.Examples
>>> import jax.numpy as jnp >>> from coordinax._src.metric.matrix import DenseMetric
Bare array โ returns a plain
Array:>>> DenseMetric(jnp.eye(3)).determinant Array(1., dtype=float64)
QuantityMatrix โ returns a
Quantity:>>> import unxt as u >>> import unxts.linalg as ul >>> g = DenseMetric( ... ul.QuantityMatrix(jnp.eye(2), unit=(("m2", ""), ("", "s2"))) ... ) >>> g.determinant Q(1., 'm2 s2')
- class coordinax.manifolds.NoManifold#
Bases:
AbstractManifoldA degenerate placeholder manifold with no charts and no geometry.
NoManifoldis a sentinel value used when a manifold object is required by the API but none has been specified by the user.ndim == Falsesignals โno manifold specifiedโ.has_chart(chart)always returnsFalse.
Examples
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> M = cxm.NoManifold() >>> M.ndim 0 >>> M.has_chart(cxc.cart2d) False
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- default_chart()#
Return a default chart from the atlas.
This is a convenience property that proxies to the atlas default chart.
- Return type:
AbstractChart[Any,Any,Any]
>>> import coordinax.manifolds as cxm >>> M = cxm.Rn(2) >>> M.default_chart() Cart2D(M=Rn(2))
- property ndim: int#
Return the dimension of the manifold.
This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.
>>> import coordinax.manifolds as cxm >>> M = cxm.Rn(3) >>> M.ndim 3
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- final class coordinax.manifolds.NoMetric#
Bases:
AbstractMetricFieldA degenerate placeholder metric with no geometry.
NoMetricis a sentinel value used when a metric object is required by the API but none has been specified by the user.ndim == Falsesignals โno metric specifiedโ.
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- class coordinax.manifolds.NoAtlas#
Bases:
AbstractAtlasTrivial atlas that supports no charts.
- default_chart()#
Return a default chart from the atlas.
- Return type:
>>> import coordinax.manifolds as cxm >>> atlas = cxm.EuclideanAtlas(2) >>> atlas.default_chart() Cart2D(M=Rn(2))
- final class coordinax.manifolds.EuclideanAtlas(ndim: int)#
Bases:
AbstractAtlasAtlas 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:
The chart dimensionality matches \(n\) (i.e. \(\varphi\) maps into \(\mathbb{R}^n\)).
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
inoperator (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
- 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:
- 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.
- final class coordinax.manifolds.FlatMetric(ndim: int = <property object>)#
Bases:
AbstractDiagonalMetricFieldEuclidean (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
AbstractDiagonalMetricFieldon that orthogonal chart domain; atlas chart compatibility alone does not imply orthogonality.- Parameters:
ndim (
int) โ Dimension of the Euclidean space.
Examples
>>> import jax.numpy as jnp >>> import coordinaxs.api.manifolds as cxmapi >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> m = cxm.FlatMetric(3) >>> m.signature (1, 1, 1) >>> m.ndim 3
The metric matrix is obtained via the dispatch API on the associated manifold:
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)} >>> cxmapi.metric_matrix(cxm.R3, at, cxc.cart3d).diagonal Array([1., 1., 1.], dtype=float64)
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- final class coordinax.manifolds.EuclideanManifold(ndim: int, /)#
Bases:
AbstractManifoldThe \(n\)-dimensional Euclidean manifold \(\mathbb{R}^n\).
The Euclidean manifold of dimension \(n\) is the smooth manifold \((\mathbb{R}^n, \mathcal{A}_{\mathbb{R}^n})\).
Charts and atlas. The smooth structure is described by an {class}`coordinax.manifolds.EuclideanAtlas` whose charts are local diffeomorphisms
\[\varphi : U \subset \mathbb{R}^n \to \mathbb{R}^n.\]A chart \(C = (U, \varphi)\) is admitted by the atlas when its dimensionality matches \(n\) and either (1) it is explicitly registered with {class}`coordinax.manifolds.EuclideanAtlas`, or (2) it possesses a compatible transition map to the default Cartesian chart. For \(n = 3\) the built-in charts include Cartesian \((x, y, z)\), spherical \((r, \theta, \phi)\), cylindrical \((\rho, \phi, z)\), and several angular variants; see {class}`coordinax.manifolds.EuclideanAtlas` for the full list.
Transition maps. For any two charts \(C_\alpha = (U_\alpha, \varphi_\alpha)\) and \(C_\beta = (U_\beta, \varphi_\beta)\) in the atlas, the transition map is
\[\tau_{\alpha \to \beta} = \varphi_\beta \circ \varphi_\alpha^{-1} : \varphi_\alpha(U_\alpha \cap U_\beta) \to \varphi_\beta(U_\alpha \cap U_\beta).\]Because \(\mathbb{R}^n\) is flat and simply connected, every transition map is a smooth diffeomorphism on a connected open domain. For example, the Cartesian-to-spherical transition on \(\mathbb{R}^3\) is
\[\tau_{C \to S}(x, y, z) = \Bigl(\sqrt{x^2 + y^2 + z^2},\; \arccos\!\tfrac{z}{r},\; \operatorname{atan2}(y, x)\Bigr).\]Pre-built instance. The module exports {obj}`coordinax.manifolds.R3` as a pre-built instance for the common case \(\mathbb{R}^3\).
- Parameters:
ndim (
int) โ Intrinsic dimension \(n \geq 0\) of the manifold โ the number of independent coordinates required to label a point.- Variables:
atlas (EuclideanAtlas) โ The atlas of coordinate charts compatible with this manifold. Its {attr}`~EuclideanAtlas.ndim` equals
ndim.
Examples
Construction
Construct a Euclidean manifold of arbitrary dimension:
>>> import coordinax.manifolds as cxmd >>> M = cxmd.EuclideanManifold(3) >>> M Rn(3)
The intrinsic dimension is accessible via {attr}`~EuclideanManifold.ndim`:
>>> M.ndim 3
The atlas is a {class}`EuclideanAtlas` with matching dimensionality:
>>> M.atlas EuclideanAtlas(ndim=3)
Default chart
The default chart is the standard Cartesian chart for the given dimension:
>>> M.default_chart() Cart3D(M=Rn(3))
>>> cxmd.EuclideanManifold(2).default_chart() Cart2D(M=Rn(2))
>>> cxmd.EuclideanManifold(1).default_chart() Cart1D(M=Rn(1))
Chart membership
Check whether a chart belongs to this manifoldโs atlas:
>>> import coordinax.charts as cxc
>>> M.has_chart(cxc.cart3d) True
>>> M.has_chart(cxc.sph3d) True
Charts with the wrong dimensionality are rejected:
>>> M.has_chart(cxc.cart2d) False
{meth}`check_chart` raises if the chart is not supported:
>>> try: ... M.check_chart(cxc.cart2d) ... except ValueError as e: ... print(e) Chart Cart2D(M=Rn(2)) is not supported by this manifold atlas.
Pre-built instances
For the most common case โ three-dimensional Euclidean space \(\mathbb{R}^3\) โ the module provides a pre-built instance:
>>> cxmd.R3 Rn(3)
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- 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
chartbelongs 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:
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- 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:
AbstractAtlasAtlas 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)
- 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:
- 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
- final class coordinax.manifolds.RoundMetric(ndim: int = <property object>)#
Bases:
AbstractDiagonalMetricFieldRound metric on the unit \(n\)-sphere \(S^{n-1}\) in standard spherical coordinates.
The round metric on \(S^2\) in the \((\theta, \phi)\) spherical chart is
\[\begin{split}g = \begin{pmatrix} 1 & 0 \\ 0 & \sin^2\theta \end{pmatrix}.\end{split}\]- Parameters:
ndim (
int) โ Intrinsic dimension of the sphere (e.g.ndim=2for \(S^2\)).
Examples
>>> import jax.numpy as jnp >>> import coordinaxs.api.manifolds as cxmapi >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> m = cxm.RoundMetric(2) >>> m.signature (1, 1) >>> m.ndim 2
The metric matrix is obtained via the dispatch API on the associated manifold:
>>> at = {"theta": jnp.array(jnp.pi / 2), "phi": jnp.array(0.0)} >>> d = cxmapi.metric_matrix(cxm.S2, at, cxc.sph2).diagonal >>> bool(jnp.allclose(d.value, jnp.array([1.0, 1.0]))) True
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- final class coordinax.manifolds.HyperSphericalManifold(ndim: int = 2, /)#
Bases:
AbstractManifoldThe 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)
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- 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
chartbelongs 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:
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- atlas: AbstractAtlas#
Charts compatible with this manifold. This defines the smooth structure.
- metric: AbstractMetricField#
The manifoldโs metric. This defines the geometric structure.
- coordinax.manifolds.Sn#
alias of
HyperSphericalManifold
- final class coordinax.manifolds.MinkowskiAtlas#
Bases:
AbstractAtlasAtlas 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:
The chart dimensionality is 4.
The chart class is {class}`~coordinax.charts.MinkowskiCT` (or a subclass explicitly registered via {meth}`register`).
Built-in charts:
{class}`~coordinax.charts.MinkowskiCT` โ canonical \((ct, x, y, z)\) chart.
Examples
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc
>>> atlas = cxm.MinkowskiAtlas() >>> atlas.ndim 4
>>> cxc.minkowskict in atlas True
>>> cxc.cart3d in atlas False
>>> atlas.default_chart() MinkowskiCT(M=MinkowskiManifold())
- default_chart()#
Return the default chart (canonical
MinkowskiCT).- Return type:
AbstractChart[Any,Any,Any]
Examples
>>> import coordinax.manifolds as cxm >>> cxm.MinkowskiAtlas().default_chart() MinkowskiCT(M=MinkowskiManifold())
- has_chart(chart: AbstractChart[Any, Any, Any], /)#
Return whether the chart belongs to this atlas.
A chart belongs when its dimensionality is 4 and its class is {class}`~coordinax.charts.MinkowskiCT` (or another class registered via {meth}`register`).
Examples
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc
>>> atlas = cxm.MinkowskiAtlas()
>>> atlas.has_chart(cxc.minkowskict) True
>>> atlas.has_chart(cxc.cart3d) False
- Parameters:
chart (
AbstractChart[Any,Any,Any])- Return type:
- 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
- final class coordinax.manifolds.MinkowskiMetric#
Bases:
AbstractDiagonalMetricField,AbstractLorentzianMetricFieldPseudo-Riemannian (Lorentzian) metric on Minkowski spacetime.
In the canonical {class}`~coordinax.charts.MinkowskiCT` chart \((ct, x, y, z)\), the Minkowski metric is
\[\eta = \operatorname{diag}(-1, 1, 1, 1),\]where the time coordinate \(ct = c\,t\) absorbs the speed of light so that all four components carry the same unit (length). The line element is
\[ds^2 = -(d(ct))^2 + dx^2 + dy^2 + dz^2.\]Signature. The metric is pseudo-Riemannian with Lorentzian signature \((-1, 1, 1, 1)\) meaning one negative and three positive eigenvalues (convention: โmostly plusโ).
Examples
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> from coordinaxs.api.manifolds import metric_matrix
Canonical Cartesian spacetime chart:
>>> m = cxm.MinkowskiMetric() >>> M = cxm.MinkowskiManifold() >>> at = {"ct": jnp.array(0.0), "x": jnp.array(0.0), ... "y": jnp.array(0.0), "z": jnp.array(0.0)} >>> metric_matrix(M, at, cxc.minkowskict).diagonal Array([-1., 1., 1., 1.], dtype=float64)
The signature is Lorentzian (pseudo-Riemannian):
>>> m.signature (-1, 1, 1, 1)
>>> m.ndim 4
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- final class coordinax.manifolds.MinkowskiManifold#
Bases:
AbstractManifoldMinkowski 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
minkowski4das a ready-to-use instance.Examples
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc
>>> M = cxm.MinkowskiManifold() >>> M MinkowskiManifold()
>>> M.ndim 4
>>> M.atlas MinkowskiAtlas()
>>> M.default_chart() MinkowskiCT(M=MinkowskiManifold())
>>> M.has_chart(cxc.minkowskict) True
>>> M.has_chart(cxc.cart3d) False
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- 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
chartbelongs 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:
- property ndim: int#
Return the dimension of the manifold.
This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.
>>> import coordinax.manifolds as cxm >>> M = cxm.Rn(3) >>> M.ndim 3
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- 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:
AbstractAtlasAtlas 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)
- factors: tuple[AbstractAtlas, ...]#
Factor atlases that define the product atlas.
- default_chart()#
Return a default chart for the product atlas.
- Return type:
Examples
>>> import coordinax.manifolds as cxm >>> atlas = cxm.CartesianProductAtlas( ... factors=(cxm.HyperSphericalAtlas(), cxm.EuclideanAtlas(1)), ... factor_names=("S2", "R1")) >>> chart = atlas.default_chart() >>> chart CartesianProductChart( factors=(SphericalTwoSphere(M=Sn(2)), Cart1D(M=Rn(1))), factor_names=('S2', 'R1') )
>>> chart["S2"] == cxm.HyperSphericalAtlas().default_chart() True
>>> chart["R1"] == cxm.EuclideanAtlas(ndim=1).default_chart() True
- has_chart(chart: AbstractChart)#
Check if the atlas supports the given chart.
>>> import coordinax.manifolds as cxm >>> import coordinax.charts as cxc >>> atlas = cxm.CartesianProductAtlas( ... factors=(cxm.HyperSphericalAtlas(), cxm.EuclideanAtlas(1)), ... factor_names=("S2", "R1")) >>> chart = cxc.CartesianProductChart( ... factors=(cxc.sph2, cxc.cart1d), factor_names=("S2", "R1")) >>> atlas.has_chart(chart) True
- Parameters:
chart (
AbstractChart)- Return type:
- final class coordinax.manifolds.ProductMetric(factors: tuple[AbstractMetricField, ...])#
Bases:
AbstractMetricFieldCanonical 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.
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- final class coordinax.manifolds.CartesianProductManifold(factors: tuple[AbstractManifold, ...], factor_names: tuple[str, ...])#
Bases:
AbstractManifoldManifold 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:
- Variables:
atlas (CartesianProductAtlas) โ The product atlas formed from the factor atlases.
metric (ProductMetric) โ The canonical product metric formed from the factor metrics.
ndim (int) โ Total intrinsic dimension \(\sum_i n_i\).
default_chart (CartesianProductChart) โ Product of the default charts from each factor atlas.
Examples
Basic construction
The most common example is the phase-space-like manifold \(S^2 \times \mathbb{R}\), which pairs the 2-sphere (2 angular degrees of freedom) with the real line (1 radial degree of freedom):
>>> import coordinax.manifolds as cxm >>> import wadler_lindig as wl
>>> M = cxm.CartesianProductManifold( ... factors=(cxm.S2, cxm.R1), factor_names=("S2", "R1") ... ) >>> wl.pprint(M, width=76) CartesianProductManifold(factors=(Sn(2), Rn(1)), factor_names=('S2', 'R1'))
Dimension
The dimension equals the sum of the factor dimensions, \(\dim(S^2) + \dim(\mathbb{R}) = 2 + 1 = 3\):
>>> M.ndim 3
Atlas and default chart
The atlas is a {class}`CartesianProductAtlas` whose default chart is the Cartesian product of the default charts of each factor:
>>> M.atlas CartesianProductAtlas(factors=(HyperSphericalAtlas(ndim=2), EuclideanAtlas(ndim=1)), factor_names=('S2', 'R1'))
>>> M.default_chart() CartesianProductChart( factors=(SphericalTwoSphere(M=Sn(2)), Cart1D(M=Rn(1))), factor_names=('S2', 'R1') )
Factor atlases can be retrieved by name:
>>> M.atlas["S2"] HyperSphericalAtlas(ndim=2)
>>> M.atlas["R1"] EuclideanAtlas(ndim=1)
Chart membership
A {class}`~coordinax.charts.CartesianProductChart` belongs to the product atlas when its factor charts belong to the corresponding factor atlases:
>>> import coordinax.charts as cxc
>>> product_chart = cxc.CartesianProductChart( ... factors=(cxc.sph2, cxc.cart1d), factor_names=("S2", "R1") ... ) >>> M.has_chart(product_chart) True
Non-product charts and wrong-factor charts are rejected:
>>> M.has_chart(cxc.sph2) False
Higher-dimensional products
Any number of factors may be combined. The 4-dimensional manifold \(S^2 \times \mathbb{R}^2\) has \(\dim = 2 + 2 = 4\):
>>> M4 = cxm.CartesianProductManifold( ... factors=(cxm.S2, cxm.R2), factor_names=("S2", "R2") ... ) >>> M4.ndim 4
Euclidean-Euclidean products
Factor manifolds need not be non-Euclidean. The product \(\mathbb{R}^2 \times \mathbb{R}\) reproduces 3-dimensional Euclidean space (though as a product structure rather than a single EuclideanManifold):
>>> Mprod = cxm.CartesianProductManifold( ... factors=(cxm.R2, cxm.R1), factor_names=("xy", "z") ... ) >>> Mprod.ndim 3
>>> Mprod.default_chart() CartesianProductChart( factors=(Cart2D(M=Rn(2)), Cart1D(M=Rn(1))), factor_names=('xy', 'z') )
- factors: tuple[AbstractManifold, ...]#
- property atlas: CartesianProductAtlas#
Return the product atlas for the manifold.
>>> import coordinax.manifolds as cxm >>> import wadler_lindig as wl
>>> M = cxm.CartesianProductManifold( ... factors=(cxm.S2, cxm.R1), factor_names=("S2", "R1")) >>> wl.pprint(M.atlas, width=60) CartesianProductAtlas( factors=(HyperSphericalAtlas(), EuclideanAtlas(ndim=1)), factor_names=('S2', 'R1') )
- property metric: ProductMetric#
Return the canonical product metric from the factor metrics.
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- 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
chartbelongs 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:
- property ndim: int#
Return the dimension of the manifold.
This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.
>>> import coordinax.manifolds as cxm >>> M = cxm.Rn(3) >>> M.ndim 3
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- 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^ito ambient coordinatesx^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
SphericalTwoSphereintoSpherical3D: the intrinsic coordinates may be(ฮธ, ฯ)on the unit 2-sphere, while the ambient coordinates are(r, ฮธ, ฯ)with fixed radiusr = R. A concrete subclass can therefore:Map
(ฮธ, ฯ) โฆ (R, ฮธ, ฯ)inSpherical3Dvia embed.Drop the radial component via project.
Realize to Cartesian coordinates by first embedding into
Spherical3Dand then delegating to its Cartesian realization.
Subclasses are responsible for implementing the coordinate-level maps; higher-level metric machinery (e.g. induced metrics) can be built on top of this interface.
This class is deliberately not an equinox.Module itself: the two attributes below are an interface for concrete maps to satisfy with either a field (CustomEmbeddingMap.ambient) or a property (TwoSphereIn3D.intrinsic), and a Module base would turn them into required constructor arguments.
- intrinsic: IntrinsicT#
- ambient: AmbientT#
- abstractmethod embed(point: dict, /, *, usys: AbstractUnitSystem | None = None)#
Embed intrinsic coordinates into ambient coordinates.
- Parameters:
point (
dict) โ A point in intrinsic coordinates.usys (
AbstractUnitSystem|None) โ Optional unit system for the input and output coordinates.
- Return type:
- abstractmethod project(point: dict, /, *, usys: AbstractUnitSystem | None = None)#
Project ambient coordinates to intrinsic coordinates.
- Parameters:
point (
dict) โ A point in ambient coordinates.usys (
AbstractUnitSystem|None) โ Optional unit system for the input and output coordinates.
- Return type:
- final class coordinax.manifolds.CustomEmbeddingMap(intrinsic: IntrinsicT, ambient: AmbientT, embed_fn: EPCallable, project_fn: EPCallable)#
Bases:
AbstractEmbeddingMap[IntrinsicT,AmbientT],ModuleA concrete embedding map defined by user-provided functions.
This class allows users to define an embedding by providing custom embed and project functions, without needing to create a new subclass.
- Parameters:
intrinsic (
TypeVar(IntrinsicT, bound=AbstractChart[Any,Any,Any])) โ The intrinsic chart.ambient (
TypeVar(AmbientT, bound=AbstractChart[Any,Any,Any])) โ The ambient chart.embed_fn (
EPCallable) โ A function that takes a point in intrinsic coordinates and returns the corresponding point in ambient coordinates.project_fn (
EPCallable) โ A function that takes a point in ambient coordinates and returns the corresponding point in intrinsic coordinates.
- intrinsic: IntrinsicT#
- ambient: AmbientT#
- embed_fn: EPCallable#
- project_fn: EPCallable#
- embed(point: dict, /, *, usys: AbstractUnitSystem | None = None)#
Embed intrinsic coordinates into ambient coordinates.
- Parameters:
point (
dict) โ A point in intrinsic coordinates.usys (
AbstractUnitSystem|None) โ Optional unit system for the input and output coordinates.
- Return type:
- project(point: dict, /, *, usys: AbstractUnitSystem | None = None)#
Project ambient coordinates to intrinsic coordinates.
- Parameters:
point (
dict) โ A point in ambient coordinates.usys (
AbstractUnitSystem|None) โ Optional unit system for the input and output coordinates.
- Return type:
- final class coordinax.manifolds.TwoSphereIn3D(radius: AbstractQuantity | float | int, ambient: AbstractChart[Any, Any, Any] = Spherical3D(M=Rn(3)))#
Bases:
AbstractEmbeddingMap[IntrinsicT,AmbientT],ModuleEmbedding of
cxc.SphericalTwoSphereas 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
ambientisSpherical3D, then {meth}`embed` returns spherical coordinates(r, theta, phi)and {meth}`project` expects the same.If
ambientisCart3D, then {meth}`embed` performsSphericalTwoSphere -> Spherical3D -> Cart3Dand returns Cartesian coordinates(x, y, z); {meth}`project` performsCart3D -> Spherical3D -> SphericalTwoSphere.
- Parameters:
radius (
AbstractQuantity|float|int) โ Sphere radiusR.ambient (
AbstractChart[Any,Any,Any]) โ Ambient chart. Defaults toSpherical3D.
Examples
Embed/project
SphericalTwoSpherethrough an ambientSpherical3Dchart:>>> 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
Cart3Dchart (routing viaSpherical3Dinternally):>>> emb = cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"), ambient=cxc.cart3d) >>> chart = cxm.EmbeddedChart(emb) >>> xyz = cxm.pt_embed(p, chart) >>> sorted(xyz) ['x', 'y', 'z'] >>> bool(jnp.allclose(u.ustrip("km", xyz["x"]), 2.0, atol=1e-6)) True
>>> p3 = cxm.pt_project(xyz, chart) >>> bool(jnp.allclose(u.ustrip("rad", p3["phi"]), u.ustrip("rad", p["phi"]))) True
- radius: AbstractQuantity | float | int#
- ambient: AbstractChart[Any, Any, Any] = Spherical3D(M=Rn(3))#
- property intrinsic: AbstractChart[Any, Any, Any]#
The intrinsic chart is always coordinax.charts.SphericalTwoSphere.
- embed(q: dict, /, *, usys: AbstractUnitSystem | None = None)#
Embed
SphericalTwoSphereintrinsic coords into the ambient chart.- Parameters:
q (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- project(x: dict, /, *, usys: AbstractUnitSystem | None = None)#
Project ambient coords onto
SphericalTwoSphereintrinsic coords.- Parameters:
x (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- 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()andembedding=TwoSphereIn3D(radius, ambient).- Parameters:
radius (
float|AbstractQuantity) โ Sphere radius.ambient (
AbstractChart[Any,Any,Any]) โ Ambient chart for the embedding. Defaults to coordinax.charts.Spherical3D.
- Return type:
Examples
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
Default ambient (Spherical3D):
>>> M = cxm.embedded_twosphere(radius=u.Q(2.0, "km")) >>> M EmbeddedManifold(intrinsic=HyperSphericalManifold(...), ambient=Rn(3), embed_map=TwoSphereIn3D(radius=Q(2., 'km'), ambient=Spherical3D(M=Rn(3))))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> sph = cxm.pt_embed(p, M) >>> sph {'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
With Cartesian ambient the embedding returns
(x, y, z):>>> M = cxm.embedded_twosphere(radius=u.Q(2.0, "km"), ambient=cxc.cart3d) >>> xyz = cxm.pt_embed(p, M) >>> sorted(xyz) ['x', 'y', 'z'] >>> bool(jnp.allclose(u.ustrip("km", xyz["x"]), 2.0, atol=1e-6)) True
- final class coordinax.manifolds.EmbeddedManifold(intrinsic: AbstractManifold, ambient: AbstractManifold, embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])#
Bases:
AbstractManifold,Generic[IntrinsicT,AmbientT]Embedded manifold.
Examples
Embed/project {class}`~coordinax.charts.SphericalTwoSphere through an ambient {class}`~coordinax.charts.Spherical3D chart:
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
>>> M = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ... ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> sph = cxm.pt_embed(p, M) >>> sph {'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, M) >>> p2 {'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')} >>> jnp.allclose(p2["theta"].value, p["theta"].value) Array(True, dtype=bool)
- Parameters:
intrinsic (
AbstractManifold)ambient (
AbstractManifold)embed_map (
AbstractEmbeddingMap[TypeVar(IntrinsicT, bound=AbstractChart[Any,Any,Any]),TypeVar(AmbientT, bound=AbstractChart[Any,Any,Any])])
- intrinsic: AbstractManifold#
- ambient: AbstractManifold#
- embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT]#
- embed(intrinsic_point: dict, from_intrinsic_chart: AbstractChart[Any, Any, Any], to_ambient_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)#
- Parameters:
intrinsic_point (
dict)from_intrinsic_chart (
AbstractChart[Any,Any,Any])to_ambient_chart (
AbstractChart[Any,Any,Any])usys (
AbstractUnitSystem|None)
- Return type:
- project(ambient_point: dict, from_ambient_chart: AbstractChart[Any, Any, Any], to_intrinsic_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)#
- Parameters:
ambient_point (
dict)from_ambient_chart (
AbstractChart[Any,Any,Any])to_intrinsic_chart (
AbstractChart[Any,Any,Any])usys (
AbstractUnitSystem|None)
- Return type:
- property metric: PullbackMetric#
Induced (pullback) Riemannian metric from the ambient manifold.
- property atlas: AbstractAtlas#
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- 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
chartbelongs 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:
- property ndim: int#
Return the dimension of the manifold.
This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.
>>> import coordinax.manifolds as cxm >>> M = cxm.Rn(3) >>> M.ndim 3
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- final class coordinax.manifolds.EmbeddedChart(embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])#
Bases:
AbstractParameterizedChart[EmbeddedManifold,Ks,Ds],Generic[IntrinsicT,AmbientT,Ks,Ds]Chart for intrinsic coordinates on an embedding manifold.
This is a convenience wrapper that combines an intrinsic chart with an embedding to an ambient Cartesian chart. It provides the same component and dimension information as the intrinsic chart, but also provides a realization map to Cartesian coordinates via the embedding.
The more correct way to represent an embedding manifold is with {class}`~coordinax.manifolds.EmbeddedManifold`.
Examples
Embed/project {class}`~coordinax.charts.SphericalTwoSphere through an ambient {class}`~coordinax.charts.Spherical3D chart:
>>> import jax.numpy as jnp >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm >>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")} >>> sph = cxm.pt_embed(p, chart) >>> sph {'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, chart) >>> p2 {'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')} >>> jnp.allclose(p2["theta"].value, p["theta"].value) Array(True, dtype=bool)
- Parameters:
embed_map (
AbstractEmbeddingMap[TypeVar(IntrinsicT, bound=AbstractChart[Any,Any,Any]),TypeVar(AmbientT, bound=AbstractChart[Any,Any,Any])])
- embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT]#
The embedding that defines the map to the ambient chart.
This is the core data of the EmbeddedChart, as it defines the ambient chart and the embedding parameters (e.g., radius for a sphere). The intrinsic chart is determined by the embeddingโs
intrinsicproperty, and the ambient chart is determined by the embeddingโsambientproperty.>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.embed_map TwoSphereIn3D(radius=Q(2., 'km'), ambient=Spherical3D(M=Rn(3)))
- property M: EmbeddedManifold#
The manifold associated with this chart.
This is an EmbeddedManifold that combines the intrinsic and ambient manifolds defined by the embedding map.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.M EmbeddedManifold(intrinsic=HyperSphericalManifold(ndim=2), ambient=Rn(3), embed_map=TwoSphereIn3D(radius=Q(2., 'km'), ambient=Spherical3D(M=Rn(3))))
- property intrinsic: IntrinsicT#
The intrinsic chart.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.intrinsic SphericalTwoSphere(M=Sn(2))
- property ambient: AmbientT#
The ambient chart.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.ambient Spherical3D(M=Rn(3))
- property components: Ks#
Return the components of the intrinsic chart.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.components ('theta', 'phi')
- property coord_dimensions: Ds#
Return the coordinate dimensions of the intrinsic chart.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.coord_dimensions ('angle', 'angle')
- property cartesian: AbstractChart#
The ambient Cartesian chart for the embedding.
>>> import coordinax.manifolds as cxm >>> import unxt as u >>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km"))) >>> chart.cartesian Cart3D(M=Rn(3))
- check_data(data: CDictT, /, *, keys: bool = True, values: bool = False)#
Check that the data is compatible with the chart.
- Parameters:
- Return type:
- final class coordinax.manifolds.PullbackMetric(embed_map: AbstractEmbeddingMap, ambient_metric: AbstractMetricField)#
Bases:
AbstractMetricFieldPullback metric induced by an embedding map.
Given an embedding \(\iota : N \hookrightarrow M\), the metric \(g_N\) on the submanifold is the pullback of the ambient metric \(g_M\):
\[g_N = \iota^* g_M, \quad \text{or component-wise}\quad (g_N)_{ij} = (J^T G J)_{ij},\]where \(J = \partial \iota / \partial q\) is the Jacobian of the embedding map and \(G = g_M\) is the ambient metric evaluated at \(\iota(p)\).
- Parameters:
embed_map (
AbstractEmbeddingMap) โ The embedding map from the submanifold into the ambient space.ambient_metric (
AbstractMetricField) โ The Riemannian metric on the ambient manifold.
Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinaxs.api.manifolds as cxmapi >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> embed_map = cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")) >>> ambient_metric = cxm.FlatMetric(3) >>> M = cxm.PullbackMetric(embed_map, ambient_metric) >>> M.signature (1, 1) >>> M.ndim 2
The metric matrix is obtained via the dispatch API on an
EmbeddedManifold:>>> M_emb = cxm.EmbeddedManifold( ... intrinsic=cxm.S2, ambient=cxm.R3, ... embed_map=cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")), ... ) >>> at = {"theta": u.Q(jnp.pi / 2, "rad"), "phi": u.Q(0.0, "rad")} >>> g = cxmapi.metric_matrix(M_emb, at, cxc.sph2) >>> g.matrix.value Array([[1., 0.], [0., 1.]], dtype=float64) >>> g.matrix.unit[0, 0] Unit("km2 / rad2")
- embed_map: AbstractEmbeddingMap#
- ambient_metric: AbstractMetricField#
- property signature: tuple[int, ...]#
Metric signature
(1,) * mwheremis the intrinsic dimension.Embedding into a Riemannian ambient manifold always produces a Riemannian induced metric (
J^T g_M Jis positive-definite whenJhas full column rank). An indefinite ambient carries no such guarantee โ a curve in Minkowski space is timelike or spacelike depending on where it goes, not on its dimension โ so there is no answer to give from the embedding alone.
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- final class coordinax.manifolds.CustomAtlas(charts: tuple[type[AbstractChart[Any, Any, Any]], ...], chart_default: AbstractChart[Any, Any, Any])#
Bases:
AbstractAtlasAtlas of explicitly registered charts for a custom manifold.
CustomAtlasis 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:
Its class is in
charts.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]
- has_chart(chart: AbstractChart[Any, Any, Any])#
Return whether the atlas supports the given chart.
- Parameters:
chart (
AbstractChart[Any,Any,Any])- Return type:
- final class coordinax.manifolds.CustomMetric(metric_matrix: ~collections.abc.Callable[[...], ~typing.Any], signature: tuple[int, ...] = <property object>)#
Bases:
AbstractMetricFieldMetric for a {class}`CustomManifold`, defined by user-provided callables.
CustomMetricallows users to supply their own metric without subclassingAbstractMetricField. Both the metric-matrix callable and the signature must be provided at construction time.- Parameters:
metric_matrix (
Callable[...,Any]) โ Callable(chart, /, *, at) -> Arrayreturning 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,) * nfor 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) -> Arrayreturning the metric matrix.
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> metric = cxm.FlatMetric(3) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> metric.norm(v, cxc.cart3d, at=at) Q(5., 'm / s')
With a stacked
jax.Array(usys required):>>> v = jnp.array([3.0, 4.0, 0.0]) >>> metric.norm(v, cxc.cart3d, at=at, usys=u.unitsystems.si) Array(5., dtype=float64)
- final class coordinax.manifolds.CustomManifold(atlas: AbstractAtlas, metric: AbstractMetricField)#
Bases:
AbstractManifoldSmooth manifold with a caller-defined explicit atlas.
CustomManifoldis 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)metric (
AbstractMetricField)
- atlas: AbstractAtlas#
Atlas defining chart compatibility for this manifold.
- angle_between(chart: AbstractChart[Any, Any, Any], uvec: dict, vvec: dict, /, *, at: dict, usys: AbstractUnitSystem | None = None)#
Return the metric angle between two tangent vectors at
at.This is a thin convenience wrapper over
cxmapi.angle_between(self.metric, chart, uvec, vvec, at=at, usys=usys).- Parameters:
chart (
AbstractChart[Any,Any,Any])uvec (
dict)vvec (
dict)at (
dict)usys (
AbstractUnitSystem|None)
- Return type:
- check_chart(chart: AbstractChart[Any, Any, Any], /)#
Check that
chartbelongs 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:
- 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
chartbelongs 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:
- property ndim: int#
Return the dimension of the manifold.
This is a convenience property that proxies to the atlas dimension, since the atlas defines the smooth structure of the manifold and therefore determines its dimension.
>>> import coordinax.manifolds as cxm >>> M = cxm.Rn(3) >>> M.ndim 3
- norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)#
Compute the norm \(\|v\|_g = \sqrt{g(v, v)}\).
Convenience wrapper that calls
cxmapi.norm(v, self.metric, chart, at=at, usys=usys)directly. Thechartmust be passed as the second positional argument (afterv).Examples
>>> import jax.numpy as jnp >>> import unxt as u >>> import coordinax.charts as cxc >>> import coordinax.manifolds as cxm
>>> M = cxm.EuclideanManifold(3) >>> chart = cxc.Cart3D(M=M) >>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
With a stacked
jax.Array(usys required):>>> usys = u.unitsystems.si >>> v = jnp.array([3.0, 4.0, 0.0]) >>> M.norm(v, chart, at=at, usys=usys) Array(5., dtype=float64)
With a CDict of quantities (usys optional):
>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s"), "z": u.Q(0.0, "m/s")} >>> M.norm(v, chart, at=at) Q(5., 'm / s')
- metric: AbstractMetricField#
Riemannian metric for this manifold, used for norm and distance computations.
coordinax.manifolds.lorentzian#
A sub-namespace for measurements that need a timelike direction โ gated on the metric type AbstractLorentzianMetricField (signature \((-,+,\ldots,+)\)), not on a chart. A metric without one has no method here, rather than a method that accepts and then refuses.
causal_character: classify a pair of events as"timelike","null", or"spacelike". Returns astr, so notjit-able; branch on the sign ofintervalinside a traceproper_time: elapsed proper time between two timelike-separated eventsproper_distance: proper distance between two spacelike-separated eventsrapidity_between: relative rapidity between two timelike tangent vectors โ the hyperbolic counterpart ofangle_between, which refuses that pairinterval: re-exported for convenience โ canonical incoordinax.manifolds, since the signed quadratic form is defined for every metric. It appears here becausecausal_characteris its sign andproper_timeits root
Named for the signature rather than for โspacetimeโ deliberately: charts.galileanct is a 4-D Galilean spacetime and is not Lorentzian, so a spacetime namespace would promise membership these verbs refuse. Named lorentzian rather than minkowski because the gate is the signature โ a curved spacetime metric (Schwarzschild, FLRW) inherits the marker and acquires all of them.
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> birth = {k: u.Q(0.0, "m") for k in ("ct", "x", "y", "z")}
>>> death = {
... "ct": u.Q(5.0, "m"),
... "x": u.Q(1.0, "m"),
... "y": u.Q(0.0, "m"),
... "z": u.Q(0.0, "m"),
... }
>>> cxm.lorentzian.causal_character(cxc.minkowskict, birth, death)
'timelike'
>>> cxm.lorentzian.proper_time(cxc.minkowskict, birth, death).uconvert("ns").round(2)
Q(16.34, 'ns')