coordinax library

coordinax library#

import coordinax as cx.

final class coordinax.Distance(value: Any, unit: Any, *, check_negative: bool = True)

Bases: AbstractDistance

Distance quantities.

The distance is a quantity with dimensions of length.

Examples

>>> import coordinax.distances as cxd
>>> cxd.Distance(10, "km")
Distance(10, 'km')

The units are checked to have length dimensions.

>>> try: cxd.Distance(10, "s")
... except ValueError as e: print(e)
Distance must have dimensions length.
Parameters:
value: Shaped[Array, '*shape']

The distance value.

unit: UnitBase | FunctionUnitBase

The unit associated with this value.

check_negative: bool = True

Whether to check that the distance is strictly non-negative.

property T: AbstractQuantity

Transpose of the array.

Examples

>>> import unxt as u
>>> q = u.Q([[0, 1], [1, 2]], "m")
>>> q.T
Quantity(Array([[0, 1],
                          [1, 2]], dtype=int32), unit='m')
argmax(*args: Any, **kwargs: Any)

Return the indices of the maximum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.argmax()
Array(2, dtype=int32)
Parameters:
Return type:

Array

argmin(*args: Any, **kwargs: Any)

Return the indices of the minimum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.argmin()
Array(0, dtype=int32)
Parameters:
Return type:

Array

astype(*args: Any, **kwargs: Any)

Copy the array and cast to a specified dtype.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.dtype
dtype('int32')
>>> q.astype(float)
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
Parameters:
Return type:

AbstractQuantity

property at: _QuantityIndexUpdateHelper

Helper property for index update functionality.

The at property provides a functionally pure equivalent of in-place array modifications.

In particular:

Alternate syntax

Equivalent In-place expression

x = x.at[idx].set(y)

x[idx] = y

x = x.at[idx].add(y)

x[idx] += y

x = x.at[idx].subtract(y)

x[idx] -= y

x = x.at[idx].multiply(y)

x[idx] *= y

x = x.at[idx].divide(y)

x[idx] /= y

x = x.at[idx].power(y)

x[idx] **= y

x = x.at[idx].min(y)

x[idx] = minimum(x[idx], y)

x = x.at[idx].max(y)

x[idx] = maximum(x[idx], y)

x = x.at[idx].apply(ufunc)

ufunc.at(x, idx)

x = x.at[idx].get()

x = x[idx]

None of the x.at expressions modify the original x; instead they return a modified copy of x. However, inside a jit() compiled function, expressions like x = x.at[idx].set(y) are guaranteed to be applied in-place.

Unlike NumPy in-place operations such as x[idx] += y, if multiple indices refer to the same location, all updates will be applied (NumPy would only apply the last update, rather than applying all updates.) The order in which conflicting updates are applied is implementation-defined and may be nondeterministic (e.g., due to concurrency on some hardware platforms).

By default, JAX assumes that all indices are in-bounds. Alternative out-of-bound index semantics can be specified via the mode parameter (see below).

Parameters:
  • mode –

    string specifying out-of-bound indexing mode. Options are:

    • "promise_in_bounds": (default) The user promises that indices are in bounds. No additional checking will be performed. In practice, this means that out-of-bounds indices in get() will be clipped, and out-of-bounds indices in set(), add(), etc. will be dropped.

    • "clip": clamp out of bounds indices into valid range.

    • "drop": ignore out-of-bound indices.

    • "fill": alias for "drop". For get(), the optional fill_value argument specifies the value that will be returned.

    See jax.lax.GatherScatterMode for more details.

  • wrap_negative_indices – If True (default) then negative indices indicate position from the end of the array, similar to Python and NumPy indexing. If False, then negative indices are considered out-of-bounds and behave according to the mode parameter.

  • fill_value – Only applies to the get() method: the fill value to return for out-of-bounds slices when mode is 'fill'. Ignored otherwise. Defaults to NaN for inexact types, the largest negative value for signed types, the largest positive value for unsigned types, and True for booleans.

  • indices_are_sorted – If True, the implementation will assume that the (normalized) indices passed to at[] are sorted in ascending order, which can lead to more efficient execution on some backends. If True but the indices are not actually sorted, the output is undefined.

  • unique_indices – If True, the implementation will assume that the (normalized) indices passed to at[] are unique, which can result in more efficient execution on some backends. If True but the indices are not actually unique, the output is undefined.

Examples

>>> x = jnp.arange(5.0)
>>> x
Array([0., 1., 2., 3., 4.], dtype=float32)
>>> x.at[2].get()
Array(2., dtype=float32)
>>> x.at[2].add(10)
Array([ 0.,  1., 12.,  3.,  4.], dtype=float32)

By default, out-of-bound indices are ignored in updates, but this behavior can be controlled with the mode parameter:

>>> x.at[10].add(10)  # dropped
Array([0., 1., 2., 3., 4.], dtype=float32)
>>> x.at[20].add(10, mode='clip')  # clipped
Array([ 0.,  1.,  2.,  3., 14.], dtype=float32)

For get(), out-of-bound indices are clipped by default:

>>> x.at[20].get()  # out-of-bounds indices clipped
Array(4., dtype=float32)
>>> x.at[20].get(mode='fill')  # out-of-bounds indices filled with NaN
Array(nan, dtype=float32)
>>> x.at[20].get(mode='fill', fill_value=-1)  # custom fill value
Array(-1., dtype=float32)

Negative indices count from the end of the array, but this behavior can be disabled by setting wrap_negative_indices = False:

>>> x.at[-1].set(99)
Array([ 0.,  1.,  2.,  3., 99.], dtype=float32)
>>> x.at[-1].set(99, wrap_negative_indices=False, mode='drop')  # dropped!
Array([0., 1., 2., 3., 4.], dtype=float32)
block_until_ready()

Block until the array is ready.

Return type:

AbstractQuantity

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.block_until_ready() is q
True
decompose(bases: Sequence[UnitBase | FunctionUnitBase | str], /)

Decompose the quantity into the given bases.

Examples

>>> from unxt import Quantity
>>> q = Quantity(1, "m")
>>> q.decompose(["cm", "s"])
Quantity(Array(100., dtype=float32, ...), unit='cm')
Parameters:

bases (Sequence[UnitBase | FunctionUnitBase | str])

Return type:

AbstractQuantity

property device: Device

Device where the array is located.

Examples

>>> import unxt as u
>>> u.Q(1, "m").device
CpuDevice(id=0)
devices()

Return the devices where the array is located.

Return type:

set[Device]

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.devices()
{CpuDevice(id=0)}
property distance: AbstractDistance

The distance.

Examples

>>> import coordinax.distances as cxd
>>> d = cxd.Distance(10, "km")
>>> d.distance is d
True
>>> import coordinaxs.astro as cxastro
>>> cxastro.DistanceModulus(10, "mag").distance
Distance(1000., 'pc')
>>> p = cxastro.Parallax(1, "mas")
>>> p.distance.to("kpc")
Distance(1., 'kpc')
property dtype: dtype

Data type of the array.

Examples

>>> import unxt as u
>>> u.Q(1, "m").dtype
dtype('int32')
flatten()

Return a flattened version of the array.

Return type:

AbstractQuantity

Examples

>>> import unxt as u
>>> q = u.Q([[1, 2], [3, 4]], "m")
>>> q.flatten()
Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m')
classmethod from_(*args, **kwargs)
from_(cls: type[AbstractQuantity], value: ArrayLike | list[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex] | tuple[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex, ...], unit: Any, /, *, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a unxt.AbstractQuantity from an array-like value and a unit.

Parameters:
  • value – The array-like value.

  • unit – The unit of the value.

  • dtype – The data type of the array (keyword-only).

  • args (Any)

  • kwargs (Any)

Return type:

AbstractQuantity

Examples

For this example we’ll use the Quantity class. The same applies to any subclass of AbstractQuantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> x = jnp.array([1.0, 2, 3])
>>> u.Q.from_(x, "m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
>>> u.Q.from_([1.0, 2, 3], "m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
>>> u.Q.from_((1.0, 2, 3), "m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], value: ArrayLike | list[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex] | tuple[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex, ...], /, *, unit: Any, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Make a unxt.AbstractQuantity from an array-like value and a unit kwarg.

Examples

For this example we’ll use the unxt.Quantity class. The same applies to any subclass of unxt.AbstractQuantity.

>>> import unxt as u
>>> u.Q.from_([1.0, 2, 3], unit="m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], *, value: Any, unit: Any, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a AbstractQuantity from value and unit kwargs.

Examples

For this example we’ll use the Quantity class. The same applies to any subclass of AbstractQuantity.

>>> import unxt as u
>>> u.Q.from_(value=[1.0, 2, 3], unit="m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], mapping: Mapping[str, Any]) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from a Mapping.

Examples

For this example we’ll use the Quantity class. The same applies to any subclass of AbstractQuantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> x = jnp.array([1.0, 2, 3])
>>> q = u.Q.from_({"value": x, "unit": "m"})
>>> q
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
>>> u.Q.from_({"value": q, "unit": "km"})
Quantity(Array([0.001, 0.002, 0.003], dtype=float32), unit='km')
from_(cls: type[AbstractQuantity], value: AbstractQuantity, unit: Any, /, *, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from another quantity.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> u.Q.from_(q, "cm")
Quantity(Array(100., dtype=float32, ...), unit='cm')
from_(cls: type[AbstractQuantity], value: AbstractQuantity, unit: NoneType, /, *, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from another quantity.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> u.Q.from_(q, None)
Quantity(Array(1, dtype=int32...), unit='m')
from_(cls: type[AbstractQuantity], value: AbstractQuantity, /, *, unit: Any | None = None, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from another quantity.

The unit is unchanged.

from_(cls: type[StaticQuantity], value: ArrayLike | list[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex] | tuple[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex, ...], unit: Any, /, *, dtype: Any = None) → StaticQuantity
Parameters:
Return type:

AbstractQuantity

Construct a StaticQuantity, keeping the value on NumPy dtypes.

The generic AbstractQuantity.from_ routes the value through jnp.asarray, which applies JAX’s x64-disabled dtype rules and silently downcasts int64 / float64 to int32 / float32. A StaticQuantity stores its value verbatim, so hand the value straight to __init__ and let the StaticValue.from_ converter convert it – that preserves the NumPy dtype. Delegating (rather than calling np.asarray here) also keeps .from_ and __init__ under the same policy for JAX inputs, instead of materialising an array the constructor would reject. (The keyword-unit overload delegates here, so it is covered too.)

Examples

>>> import numpy as np
>>> import unxt as u
>>> u.StaticQuantity.from_(
...     np.array([1, 2, 3], dtype=np.int64), "m"
... ).value.array.dtype
dtype('int64')

.from_ applies the same JAX-input policy as __init__ – it delegates rather than converting first, so the two cannot drift:

>>> import jax.numpy as jnp
>>> u.StaticQuantity.from_(jnp.array([1.0, 2.0]), "m")
StaticQuantity(array([1., 2.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], value: Quantity, /, **kwargs: Any) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from an astropy Quantity.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> import astropy.units as apyu
>>> u.Q.from_(apyu.Quantity(1, "m"))
Quantity(Array(1., dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], value: Quantity, u: Any, /, **kwargs: Any) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from an astropy Quantity, converting to a target unit.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> import astropy.units as apyu
>>> u.Q.from_(apyu.Quantity(1, "m"), "cm")
Quantity(Array(100., dtype=float32), unit='cm')
from_(cls: type[Distance], value: ArrayLike, unit: Any, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Construct a distance.

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> cxd.Distance.from_(1, "kpc")
Distance(1, 'kpc')
from_(cls: type[Distance], d: Distance, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Compute distance from distance.

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> d = cxd.Distance(1, "kpc")
>>> cxd.Distance.from_(d) is d
True
>>> cxd.Distance.from_(d, dtype=float)
Distance(1., 'kpc')
from_(cls: type[Distance], q: AbstractQuantity, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Construct a distance from a quantity, dispatching on its dimensions.

From a length quantity:

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> q = u.Q(1, "kpc")
>>> cxd.Distance.from_(q, dtype=float)
Distance(1., 'kpc')

From a parallax angle:

>>> q = u.Q(1, "mas")
>>> cxd.Distance.from_(q).uconvert("pc").round(2)
Distance(1000., 'pc')

From a distance modulus:

>>> q = u.Q(10, "mag")
>>> cxd.Distance.from_(q).uconvert("pc").round(2)
Distance(1000., 'pc')
from_(cls: type[DistanceModulus], value: ArrayLike, unit: Any, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Construct a distance.

>>> import unxt as u
>>> from coordinaxs.astro import DistanceModulus
>>> DistanceModulus.from_(1, "mag")
DistanceModulus(1, 'mag')
from_(cls: type[DistanceModulus], dm: DistanceModulus, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Compute distance modulus from distance modulus.

>>> import unxt as u
>>> from coordinaxs.astro import DistanceModulus
>>> dm = DistanceModulus(1, "mag")
>>> DistanceModulus.from_(dm) is dm
True
>>> DistanceModulus.from_(dm, dtype=float)
DistanceModulus(1., 'mag')
from_(cls: type[DistanceModulus], q: AbstractQuantity, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Construct a distance modulus from a quantity, dispatching on dimensions.

From a distance:

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import DistanceModulus
>>> d = cxd.Distance(1, "pc")
>>> DistanceModulus.from_(d)
DistanceModulus(-5., 'mag')
>>> q = u.Q(1, "pc")
>>> DistanceModulus.from_(q)
DistanceModulus(-5., 'mag')

From a parallax angle:

>>> q = u.Q(1, "mas")
>>> DistanceModulus.from_(q)
DistanceModulus(10., 'mag')

From a distance modulus (magnitude):

>>> q = u.Q(1, "mag")
>>> DistanceModulus.from_(q)
DistanceModulus(1, 'mag')
from_(cls: type[Distance], dm: DistanceModulus, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Compute distance from distance modulus.

>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import DistanceModulus
>>> dm = DistanceModulus(10, "mag")
>>> cxd.Distance.from_(dm).uconvert("pc").round(2)
Distance(1000., 'pc')
from_(cls: type[Parallax], value: ArrayLike, unit: Any, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Construct a distance.

>>> import unxt as u
>>> from coordinaxs.astro import Parallax
>>> Parallax.from_(1, "mas")
Parallax(1, 'mas')
from_(cls: type[Parallax], p: Parallax, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Compute parallax from parallax.

>>> import unxt as u
>>> from coordinaxs.astro import Parallax
>>> p = Parallax(1, "mas")
>>> Parallax.from_(p) is p
True
>>> Parallax.from_(p, dtype=float)
Parallax(1., 'mas')
from_(cls: type[Parallax], q: AbstractQuantity, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Construct a parallax from a quantity, dispatching on its dimensions.

From a parallax angle:

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import Parallax
>>> q = u.Q(1, "mas")
>>> Parallax.from_(q, dtype=float)
Parallax(1., 'mas')

From a distance:

>>> d = cxd.Distance(10, "pc")
>>> Parallax.from_(d).uconvert("mas").round(2)
Parallax(100., 'mas')
>>> q = u.Q(10, "pc")
>>> Parallax.from_(q).uconvert("mas").round(2)
Parallax(100., 'mas')

From a distance modulus:

>>> dm = u.Q(10, "mag")
>>> Parallax.from_(dm).uconvert("mas").round(2)
Parallax(1., 'mas')
from_(cls: type[Distance], p: Parallax, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Compute distance from parallax.

>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import Parallax
>>> p = Parallax(1, "mas")
>>> cxd.Distance.from_(p).uconvert("pc").round(2)
Distance(1000., 'pc')
from_(cls: type[DistanceModulus], p: Parallax, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Compute distance modulus from parallax.

>>> from coordinaxs.astro import DistanceModulus, Parallax
>>> p = Parallax(1, "mas")
>>> DistanceModulus.from_(p)
DistanceModulus(10., 'mag')
from_(cls: type[Parallax], dm: DistanceModulus, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Convert distance modulus to parallax.

>>> import unxt as u
>>> from coordinaxs.astro import DistanceModulus, Parallax
>>> dm = DistanceModulus(10, "mag")
>>> Parallax.from_(dm).uconvert("mas").round(2)
Parallax(1., 'mas')
Parameters:
Return type:

AbstractQuantity

is_equivalent(other: AbstractQuantity, /)

Whether self and other are physically equal (unit-aware).

The method form of unxt.equivalent; unlike == (which is unit-blind for StaticValue-backed quantities) this accounts for unit conversion.

Examples

>>> import unxt as u
>>> u.Q(1000.0, "m").is_equivalent(u.Q(1.0, "km"))
Quantity(Array(True, dtype=bool...), unit='')
Parameters:

other (AbstractQuantity)

Return type:

Any

property mT: AbstractQuantity

Matrix transpose of the array.

Examples

>>> import unxt as u
>>> q = u.Q([[0, 1], [2, 3]], "m")
>>> q.mT
Quantity(Array([[0, 2],
                          [1, 3]], dtype=int32), unit='m')

It also works for a StaticQuantity:

>>> u.StaticQuantity([[0, 1], [2, 3]], "m").mT.value.tolist()
[[0, 2], [1, 3]]
max(*args: Any, **kwargs: Any)

Return the maximum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.max()
Quantity(Array(3, dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

mean(*args: Any, **kwargs: Any)

Return the mean value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.mean()
Quantity(Array(2., dtype=float32), unit='m')
Parameters:
Return type:

AbstractQuantity

min(*args: Any, **kwargs: Any)

Return the minimum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.min()
Quantity(Array(1, dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

property ndim: int

Number of dimensions.

Examples

>>> import unxt as u
>>> q = u.Q([[1]], "m")
>>> q.ndim
2
ravel()

Return a flattened version of the array.

Return type:

AbstractQuantity

Examples

>>> import unxt as u
>>> q = u.Q([[1, 2], [3, 4]], "m")
>>> q.ravel()
Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m')
reshape(*args: Any, order: str = 'C')

Return a reshaped version of the array.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3, 4], "m")
>>> q.reshape(2, 2)
Quantity(Array([[1, 2],
                          [3, 4]], dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

round(*args: Any, **kwargs: Any)

Round the array to the given number of decimals.

Examples

>>> import unxt as u
>>> q = u.Q([1.1, 2.2, 3.3], "m")
>>> q.round(0)
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
Parameters:
Return type:

AbstractQuantity

property shape: tuple[int, ...]

Shape of the array.

property sharding: Any

Return the sharding configuration of the array.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.sharding
SingleDeviceSharding(device=..., memory_kind=...)
property size: int

Total number of elements.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.size
3
squeeze(*args: Any, **kwargs: Any)

Return the array with all single-dimensional entries removed.

Examples

>>> import unxt as u
>>> q = u.Q([[[1], [2], [3]]], "m")
>>> q.squeeze()
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

to(u: Any, /)

Convert the quantity to the given units.

See unxt.quantity.AbstractQuantity.uconvert.

Examples

>>> from unxt import Quantity
>>> q = Quantity(1, "m")
>>> q.to("cm")
Quantity(Array(100., dtype=float32, ...), unit='cm')
Parameters:

u (Any)

Return type:

AbstractQuantity

to_device(device: None | Device = None)

Move the array to a new device.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.to_device(None)
Quantity(Array(1, dtype=int32...), unit='m')
Parameters:

device (None | Device)

Return type:

AbstractQuantity

to_value(u: Any, /)

Return the value in the given units.

See unxt.AbstractQuantity.ustrip.

Examples

>>> from unxt import Quantity
>>> q = Quantity(1, "m")
>>> q.to_value("cm")
Array(100., dtype=float32, weak_type=True)
Parameters:

u (Any)

Return type:

Union[Array, ndarray, bool, number, bool, int, float, complex]

uconvert(u: Any, /)

Convert the quantity to the given units.

See also

None

convert a quantity to a new unit.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.uconvert("cm")
Quantity(Array(100., dtype=float32, ...), unit='cm')
Parameters:

u (Any)

Return type:

AbstractQuantity

ustrip(u: Any, /)

Return the value in the given units.

See also

None

strip the units from a quantity.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.ustrip("cm")
Array(100., dtype=float32, weak_type=True)
Parameters:

u (Any)

Return type:

Array

final class coordinax.Angle(value: Any, unit: Any)

Bases: AbstractAngle

Angular quantity.

Examples

>>> import unxt as u

Create an Angle:

>>> q = u.Angle(1, "rad")
>>> q
Angle(Array(1, dtype=int32...), unit='rad')

Wrap an Angle to a range:

>>> q = u.Angle(370, "deg")
>>> q.wrap_to(u.Q(0, "deg"), u.Q(360, "deg"))
Angle(Array(10, dtype=int32...), unit='deg')

Create an Angle array:

>>> q = u.Angle([1, 2, 3], "deg")
>>> q
Angle(Array([1, 2, 3], dtype=int32), unit='deg')

Do math on an Angle:

>>> 2 * q
Angle(Array([2, 4, 6], dtype=int32), unit='deg')
>>> q % u.Q(4, "deg")
Angle(Array([1, 2, 3], dtype=int32), unit='deg')
Parameters:
value: Real[Array, '*shape'] | Real[StaticValue, '*shape']

The value of the unxt.AbstractQuantity.

property T: AbstractQuantity

Transpose of the array.

Examples

>>> import unxt as u
>>> q = u.Q([[0, 1], [1, 2]], "m")
>>> q.T
Quantity(Array([[0, 1],
                          [1, 2]], dtype=int32), unit='m')
argmax(*args: Any, **kwargs: Any)

Return the indices of the maximum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.argmax()
Array(2, dtype=int32)
Parameters:
Return type:

Array

argmin(*args: Any, **kwargs: Any)

Return the indices of the minimum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.argmin()
Array(0, dtype=int32)
Parameters:
Return type:

Array

astype(*args: Any, **kwargs: Any)

Copy the array and cast to a specified dtype.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.dtype
dtype('int32')
>>> q.astype(float)
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
Parameters:
Return type:

AbstractQuantity

property at: _QuantityIndexUpdateHelper

Helper property for index update functionality.

The at property provides a functionally pure equivalent of in-place array modifications.

In particular:

Alternate syntax

Equivalent In-place expression

x = x.at[idx].set(y)

x[idx] = y

x = x.at[idx].add(y)

x[idx] += y

x = x.at[idx].subtract(y)

x[idx] -= y

x = x.at[idx].multiply(y)

x[idx] *= y

x = x.at[idx].divide(y)

x[idx] /= y

x = x.at[idx].power(y)

x[idx] **= y

x = x.at[idx].min(y)

x[idx] = minimum(x[idx], y)

x = x.at[idx].max(y)

x[idx] = maximum(x[idx], y)

x = x.at[idx].apply(ufunc)

ufunc.at(x, idx)

x = x.at[idx].get()

x = x[idx]

None of the x.at expressions modify the original x; instead they return a modified copy of x. However, inside a jit() compiled function, expressions like x = x.at[idx].set(y) are guaranteed to be applied in-place.

Unlike NumPy in-place operations such as x[idx] += y, if multiple indices refer to the same location, all updates will be applied (NumPy would only apply the last update, rather than applying all updates.) The order in which conflicting updates are applied is implementation-defined and may be nondeterministic (e.g., due to concurrency on some hardware platforms).

By default, JAX assumes that all indices are in-bounds. Alternative out-of-bound index semantics can be specified via the mode parameter (see below).

Parameters:
  • mode –

    string specifying out-of-bound indexing mode. Options are:

    • "promise_in_bounds": (default) The user promises that indices are in bounds. No additional checking will be performed. In practice, this means that out-of-bounds indices in get() will be clipped, and out-of-bounds indices in set(), add(), etc. will be dropped.

    • "clip": clamp out of bounds indices into valid range.

    • "drop": ignore out-of-bound indices.

    • "fill": alias for "drop". For get(), the optional fill_value argument specifies the value that will be returned.

    See jax.lax.GatherScatterMode for more details.

  • wrap_negative_indices – If True (default) then negative indices indicate position from the end of the array, similar to Python and NumPy indexing. If False, then negative indices are considered out-of-bounds and behave according to the mode parameter.

  • fill_value – Only applies to the get() method: the fill value to return for out-of-bounds slices when mode is 'fill'. Ignored otherwise. Defaults to NaN for inexact types, the largest negative value for signed types, the largest positive value for unsigned types, and True for booleans.

  • indices_are_sorted – If True, the implementation will assume that the (normalized) indices passed to at[] are sorted in ascending order, which can lead to more efficient execution on some backends. If True but the indices are not actually sorted, the output is undefined.

  • unique_indices – If True, the implementation will assume that the (normalized) indices passed to at[] are unique, which can result in more efficient execution on some backends. If True but the indices are not actually unique, the output is undefined.

Examples

>>> x = jnp.arange(5.0)
>>> x
Array([0., 1., 2., 3., 4.], dtype=float32)
>>> x.at[2].get()
Array(2., dtype=float32)
>>> x.at[2].add(10)
Array([ 0.,  1., 12.,  3.,  4.], dtype=float32)

By default, out-of-bound indices are ignored in updates, but this behavior can be controlled with the mode parameter:

>>> x.at[10].add(10)  # dropped
Array([0., 1., 2., 3., 4.], dtype=float32)
>>> x.at[20].add(10, mode='clip')  # clipped
Array([ 0.,  1.,  2.,  3., 14.], dtype=float32)

For get(), out-of-bound indices are clipped by default:

>>> x.at[20].get()  # out-of-bounds indices clipped
Array(4., dtype=float32)
>>> x.at[20].get(mode='fill')  # out-of-bounds indices filled with NaN
Array(nan, dtype=float32)
>>> x.at[20].get(mode='fill', fill_value=-1)  # custom fill value
Array(-1., dtype=float32)

Negative indices count from the end of the array, but this behavior can be disabled by setting wrap_negative_indices = False:

>>> x.at[-1].set(99)
Array([ 0.,  1.,  2.,  3., 99.], dtype=float32)
>>> x.at[-1].set(99, wrap_negative_indices=False, mode='drop')  # dropped!
Array([0., 1., 2., 3., 4.], dtype=float32)
block_until_ready()

Block until the array is ready.

Return type:

AbstractQuantity

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.block_until_ready() is q
True
decompose(bases: Sequence[UnitBase | FunctionUnitBase | str], /)

Decompose the quantity into the given bases.

Examples

>>> from unxt import Quantity
>>> q = Quantity(1, "m")
>>> q.decompose(["cm", "s"])
Quantity(Array(100., dtype=float32, ...), unit='cm')
Parameters:

bases (Sequence[UnitBase | FunctionUnitBase | str])

Return type:

AbstractQuantity

property device: Device

Device where the array is located.

Examples

>>> import unxt as u
>>> u.Q(1, "m").device
CpuDevice(id=0)
devices()

Return the devices where the array is located.

Return type:

set[Device]

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.devices()
{CpuDevice(id=0)}
property dtype: dtype

Data type of the array.

Examples

>>> import unxt as u
>>> u.Q(1, "m").dtype
dtype('int32')
flatten()

Return a flattened version of the array.

Return type:

AbstractQuantity

Examples

>>> import unxt as u
>>> q = u.Q([[1, 2], [3, 4]], "m")
>>> q.flatten()
Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m')
classmethod from_(*args, **kwargs)
from_(cls: type[AbstractQuantity], value: ArrayLike | list[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex] | tuple[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex, ...], unit: Any, /, *, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a unxt.AbstractQuantity from an array-like value and a unit.

Parameters:
  • value – The array-like value.

  • unit – The unit of the value.

  • dtype – The data type of the array (keyword-only).

  • args (Any)

  • kwargs (Any)

Return type:

AbstractQuantity

Examples

For this example we’ll use the Quantity class. The same applies to any subclass of AbstractQuantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> x = jnp.array([1.0, 2, 3])
>>> u.Q.from_(x, "m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
>>> u.Q.from_([1.0, 2, 3], "m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
>>> u.Q.from_((1.0, 2, 3), "m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], value: ArrayLike | list[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex] | tuple[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex, ...], /, *, unit: Any, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Make a unxt.AbstractQuantity from an array-like value and a unit kwarg.

Examples

For this example we’ll use the unxt.Quantity class. The same applies to any subclass of unxt.AbstractQuantity.

>>> import unxt as u
>>> u.Q.from_([1.0, 2, 3], unit="m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], *, value: Any, unit: Any, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a AbstractQuantity from value and unit kwargs.

Examples

For this example we’ll use the Quantity class. The same applies to any subclass of AbstractQuantity.

>>> import unxt as u
>>> u.Q.from_(value=[1.0, 2, 3], unit="m")
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], mapping: Mapping[str, Any]) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from a Mapping.

Examples

For this example we’ll use the Quantity class. The same applies to any subclass of AbstractQuantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> x = jnp.array([1.0, 2, 3])
>>> q = u.Q.from_({"value": x, "unit": "m"})
>>> q
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
>>> u.Q.from_({"value": q, "unit": "km"})
Quantity(Array([0.001, 0.002, 0.003], dtype=float32), unit='km')
from_(cls: type[AbstractQuantity], value: AbstractQuantity, unit: Any, /, *, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from another quantity.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> u.Q.from_(q, "cm")
Quantity(Array(100., dtype=float32, ...), unit='cm')
from_(cls: type[AbstractQuantity], value: AbstractQuantity, unit: NoneType, /, *, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from another quantity.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> u.Q.from_(q, None)
Quantity(Array(1, dtype=int32...), unit='m')
from_(cls: type[AbstractQuantity], value: AbstractQuantity, /, *, unit: Any | None = None, dtype: Any = None) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from another quantity.

The unit is unchanged.

from_(cls: type[StaticQuantity], value: ArrayLike | list[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex] | tuple[jaxtyping.Shaped[Array, ''] | jaxtyping.Shaped[ndarray, ''] | bool | number | bool | int | float | complex, ...], unit: Any, /, *, dtype: Any = None) → StaticQuantity
Parameters:
Return type:

AbstractQuantity

Construct a StaticQuantity, keeping the value on NumPy dtypes.

The generic AbstractQuantity.from_ routes the value through jnp.asarray, which applies JAX’s x64-disabled dtype rules and silently downcasts int64 / float64 to int32 / float32. A StaticQuantity stores its value verbatim, so hand the value straight to __init__ and let the StaticValue.from_ converter convert it – that preserves the NumPy dtype. Delegating (rather than calling np.asarray here) also keeps .from_ and __init__ under the same policy for JAX inputs, instead of materialising an array the constructor would reject. (The keyword-unit overload delegates here, so it is covered too.)

Examples

>>> import numpy as np
>>> import unxt as u
>>> u.StaticQuantity.from_(
...     np.array([1, 2, 3], dtype=np.int64), "m"
... ).value.array.dtype
dtype('int64')

.from_ applies the same JAX-input policy as __init__ – it delegates rather than converting first, so the two cannot drift:

>>> import jax.numpy as jnp
>>> u.StaticQuantity.from_(jnp.array([1.0, 2.0]), "m")
StaticQuantity(array([1., 2.], dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], value: Quantity, /, **kwargs: Any) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from an astropy Quantity.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> import astropy.units as apyu
>>> u.Q.from_(apyu.Quantity(1, "m"))
Quantity(Array(1., dtype=float32), unit='m')
from_(cls: type[AbstractQuantity], value: Quantity, u: Any, /, **kwargs: Any) → AbstractQuantity
Parameters:
Return type:

AbstractQuantity

Construct a quantity from an astropy Quantity, converting to a target unit.

The value is converted to the new unit.

Examples

>>> import unxt as u
>>> import astropy.units as apyu
>>> u.Q.from_(apyu.Quantity(1, "m"), "cm")
Quantity(Array(100., dtype=float32), unit='cm')
from_(cls: type[Distance], value: ArrayLike, unit: Any, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Construct a distance.

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> cxd.Distance.from_(1, "kpc")
Distance(1, 'kpc')
from_(cls: type[Distance], d: Distance, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Compute distance from distance.

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> d = cxd.Distance(1, "kpc")
>>> cxd.Distance.from_(d) is d
True
>>> cxd.Distance.from_(d, dtype=float)
Distance(1., 'kpc')
from_(cls: type[Distance], q: AbstractQuantity, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Construct a distance from a quantity, dispatching on its dimensions.

From a length quantity:

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> q = u.Q(1, "kpc")
>>> cxd.Distance.from_(q, dtype=float)
Distance(1., 'kpc')

From a parallax angle:

>>> q = u.Q(1, "mas")
>>> cxd.Distance.from_(q).uconvert("pc").round(2)
Distance(1000., 'pc')

From a distance modulus:

>>> q = u.Q(10, "mag")
>>> cxd.Distance.from_(q).uconvert("pc").round(2)
Distance(1000., 'pc')
from_(cls: type[DistanceModulus], value: ArrayLike, unit: Any, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Construct a distance.

>>> import unxt as u
>>> from coordinaxs.astro import DistanceModulus
>>> DistanceModulus.from_(1, "mag")
DistanceModulus(1, 'mag')
from_(cls: type[DistanceModulus], dm: DistanceModulus, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Compute distance modulus from distance modulus.

>>> import unxt as u
>>> from coordinaxs.astro import DistanceModulus
>>> dm = DistanceModulus(1, "mag")
>>> DistanceModulus.from_(dm) is dm
True
>>> DistanceModulus.from_(dm, dtype=float)
DistanceModulus(1., 'mag')
from_(cls: type[DistanceModulus], q: AbstractQuantity, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Construct a distance modulus from a quantity, dispatching on dimensions.

From a distance:

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import DistanceModulus
>>> d = cxd.Distance(1, "pc")
>>> DistanceModulus.from_(d)
DistanceModulus(-5., 'mag')
>>> q = u.Q(1, "pc")
>>> DistanceModulus.from_(q)
DistanceModulus(-5., 'mag')

From a parallax angle:

>>> q = u.Q(1, "mas")
>>> DistanceModulus.from_(q)
DistanceModulus(10., 'mag')

From a distance modulus (magnitude):

>>> q = u.Q(1, "mag")
>>> DistanceModulus.from_(q)
DistanceModulus(1, 'mag')
from_(cls: type[Distance], dm: DistanceModulus, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Compute distance from distance modulus.

>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import DistanceModulus
>>> dm = DistanceModulus(10, "mag")
>>> cxd.Distance.from_(dm).uconvert("pc").round(2)
Distance(1000., 'pc')
from_(cls: type[Parallax], value: ArrayLike, unit: Any, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Construct a distance.

>>> import unxt as u
>>> from coordinaxs.astro import Parallax
>>> Parallax.from_(1, "mas")
Parallax(1, 'mas')
from_(cls: type[Parallax], p: Parallax, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Compute parallax from parallax.

>>> import unxt as u
>>> from coordinaxs.astro import Parallax
>>> p = Parallax(1, "mas")
>>> Parallax.from_(p) is p
True
>>> Parallax.from_(p, dtype=float)
Parallax(1., 'mas')
from_(cls: type[Parallax], q: AbstractQuantity, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Construct a parallax from a quantity, dispatching on its dimensions.

From a parallax angle:

>>> import unxt as u
>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import Parallax
>>> q = u.Q(1, "mas")
>>> Parallax.from_(q, dtype=float)
Parallax(1., 'mas')

From a distance:

>>> d = cxd.Distance(10, "pc")
>>> Parallax.from_(d).uconvert("mas").round(2)
Parallax(100., 'mas')
>>> q = u.Q(10, "pc")
>>> Parallax.from_(q).uconvert("mas").round(2)
Parallax(100., 'mas')

From a distance modulus:

>>> dm = u.Q(10, "mag")
>>> Parallax.from_(dm).uconvert("mas").round(2)
Parallax(1., 'mas')
from_(cls: type[Distance], p: Parallax, /, **kw: Any) → Distance
Parameters:
Return type:

AbstractQuantity

Compute distance from parallax.

>>> import coordinax.distances as cxd
>>> from coordinaxs.astro import Parallax
>>> p = Parallax(1, "mas")
>>> cxd.Distance.from_(p).uconvert("pc").round(2)
Distance(1000., 'pc')
from_(cls: type[DistanceModulus], p: Parallax, /, **kw: Any) → DistanceModulus
Parameters:
Return type:

AbstractQuantity

Compute distance modulus from parallax.

>>> from coordinaxs.astro import DistanceModulus, Parallax
>>> p = Parallax(1, "mas")
>>> DistanceModulus.from_(p)
DistanceModulus(10., 'mag')
from_(cls: type[Parallax], dm: DistanceModulus, /, **kw: Any) → Parallax
Parameters:
Return type:

AbstractQuantity

Convert distance modulus to parallax.

>>> import unxt as u
>>> from coordinaxs.astro import DistanceModulus, Parallax
>>> dm = DistanceModulus(10, "mag")
>>> Parallax.from_(dm).uconvert("mas").round(2)
Parallax(1., 'mas')
Parameters:
Return type:

AbstractQuantity

is_equivalent(other: AbstractQuantity, /)

Whether self and other are physically equal (unit-aware).

The method form of unxt.equivalent; unlike == (which is unit-blind for StaticValue-backed quantities) this accounts for unit conversion.

Examples

>>> import unxt as u
>>> u.Q(1000.0, "m").is_equivalent(u.Q(1.0, "km"))
Quantity(Array(True, dtype=bool...), unit='')
Parameters:

other (AbstractQuantity)

Return type:

Any

property mT: AbstractQuantity

Matrix transpose of the array.

Examples

>>> import unxt as u
>>> q = u.Q([[0, 1], [2, 3]], "m")
>>> q.mT
Quantity(Array([[0, 2],
                          [1, 3]], dtype=int32), unit='m')

It also works for a StaticQuantity:

>>> u.StaticQuantity([[0, 1], [2, 3]], "m").mT.value.tolist()
[[0, 2], [1, 3]]
max(*args: Any, **kwargs: Any)

Return the maximum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.max()
Quantity(Array(3, dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

mean(*args: Any, **kwargs: Any)

Return the mean value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.mean()
Quantity(Array(2., dtype=float32), unit='m')
Parameters:
Return type:

AbstractQuantity

min(*args: Any, **kwargs: Any)

Return the minimum value.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.min()
Quantity(Array(1, dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

property ndim: int

Number of dimensions.

Examples

>>> import unxt as u
>>> q = u.Q([[1]], "m")
>>> q.ndim
2
ravel()

Return a flattened version of the array.

Return type:

AbstractQuantity

Examples

>>> import unxt as u
>>> q = u.Q([[1, 2], [3, 4]], "m")
>>> q.ravel()
Quantity(Array([1, 2, 3, 4], dtype=int32), unit='m')
reshape(*args: Any, order: str = 'C')

Return a reshaped version of the array.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3, 4], "m")
>>> q.reshape(2, 2)
Quantity(Array([[1, 2],
                          [3, 4]], dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

round(*args: Any, **kwargs: Any)

Round the array to the given number of decimals.

Examples

>>> import unxt as u
>>> q = u.Q([1.1, 2.2, 3.3], "m")
>>> q.round(0)
Quantity(Array([1., 2., 3.], dtype=float32), unit='m')
Parameters:
Return type:

AbstractQuantity

property shape: tuple[int, ...]

Shape of the array.

property sharding: Any

Return the sharding configuration of the array.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.sharding
SingleDeviceSharding(device=..., memory_kind=...)
property size: int

Total number of elements.

Examples

>>> import unxt as u
>>> q = u.Q([1, 2, 3], "m")
>>> q.size
3
squeeze(*args: Any, **kwargs: Any)

Return the array with all single-dimensional entries removed.

Examples

>>> import unxt as u
>>> q = u.Q([[[1], [2], [3]]], "m")
>>> q.squeeze()
Quantity(Array([1, 2, 3], dtype=int32), unit='m')
Parameters:
Return type:

AbstractQuantity

to(u: Any, /)

Convert the quantity to the given units.

See unxt.quantity.AbstractQuantity.uconvert.

Examples

>>> from unxt import Quantity
>>> q = Quantity(1, "m")
>>> q.to("cm")
Quantity(Array(100., dtype=float32, ...), unit='cm')
Parameters:

u (Any)

Return type:

AbstractQuantity

to_device(device: None | Device = None)

Move the array to a new device.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.to_device(None)
Quantity(Array(1, dtype=int32...), unit='m')
Parameters:

device (None | Device)

Return type:

AbstractQuantity

to_value(u: Any, /)

Return the value in the given units.

See unxt.AbstractQuantity.ustrip.

Examples

>>> from unxt import Quantity
>>> q = Quantity(1, "m")
>>> q.to_value("cm")
Array(100., dtype=float32, weak_type=True)
Parameters:

u (Any)

Return type:

Union[Array, ndarray, bool, number, bool, int, float, complex]

uconvert(u: Any, /)

Convert the quantity to the given units.

See also

None

convert a quantity to a new unit.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.uconvert("cm")
Quantity(Array(100., dtype=float32, ...), unit='cm')
Parameters:

u (Any)

Return type:

AbstractQuantity

ustrip(u: Any, /)

Return the value in the given units.

See also

None

strip the units from a quantity.

Examples

>>> import unxt as u
>>> q = u.Q(1, "m")
>>> q.ustrip("cm")
Array(100., dtype=float32, weak_type=True)
Parameters:

u (Any)

Return type:

Array

wrap_to(min: AbstractQuantity, max: AbstractQuantity)

Wrap the angle to the range [min, max).

Parameters:
Return type:

AbstractAngle

See also

None

functional version of this method.

Return type:

AbstractAngle

Parameters:

Examples

>>> import unxt as u
>>> angle = u.Angle(370, "deg")
>>> angle.wrap_to(min=u.Q(0, "deg"), max=u.Q(360, "deg"))
Angle(Array(10, dtype=int32...), unit='deg')
unit: UnitBase | FunctionUnitBase

The unit associated with this value.

exception coordinax.NoGlobalCartesianChartError

Bases: Exception

Raised when a chart has no global Cartesian representation.

Some charts represent coordinates on curved manifolds (e.g., 2-sphere) that cannot be globally mapped to a flat Cartesian space without singularities or discontinuities.

Examples

2-sphere charts (intrinsic coordinates on a spherical surface) have no global Cartesian 2D representation. To work with these charts:

  • Use an EmbeddedChart to embed in 3D Euclidean space

  • Use local projections when available

  • Work directly in the intrinsic coordinates

add_note()

Exception.add_note(note) – add a note to the exception

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

final class coordinax.CartesianProductChart(factors: tuple[AbstractChart[Any, Any, Any], ...], factor_names: tuple[str, ...])

Bases: AbstractCartesianProductChart[Ks, Ds]

Concrete Cartesian product chart with dot-delimited component keys.

Constructs a product chart from a tuple of factor charts and factor names. Components are dot-delimited string keys "factor_name.component_name" to avoid collisions (e.g., phase space with repeated Cart3D factors).

Parameters:

Examples

>>> import coordinax.charts as cxc
>>> chart = cxc.CartesianProductChart((cxc.cart3d, cxc.cart3d), ("q", "p"))
>>> chart.components
('q.x', 'q.y', 'q.z', 'p.x', 'p.y', 'p.z')
>>> chart.ndim
6
>>> chart["p"]
Cart3D(M=Rn(3))
>>> chart.M
CartesianProductManifold(factors=(Rn(3), Rn(3)), factor_names=('q', 'p'))
factors: tuple[AbstractChart[Any, Any, Any], ...]

Ordered tuple of factor charts.

factor_names: tuple[str, ...]

Factor names for namespaced keys. Must be unique and aligned with factors.

property M: CartesianProductManifold

Return the product manifold of the factor charts’ manifolds.

Examples

>>> import coordinax.charts as cxc
>>> chart = cxc.CartesianProductChart((cxc.cart3d, cxc.cart3d), ("q", "p"))
>>> chart.M
CartesianProductManifold(factors=(Rn(3), Rn(3)), factor_names=('q', 'p'))
check_data(data: CDictT, /, *, keys: bool = True, values: bool = False)

Check that the data is compatible with the chart.

Parameters:
  • data (TypeVar(CDictT, bound= dict)) – The data to check.

  • keys (bool) – Whether to check that the keys of data match chart.components. If False, this check is skipped. Default is True.

  • values (bool) – Whether to check that the dimensions of the values in data match chart.coord_dimensions. If False, this check is skipped. Default is False.

Return type:

TypeVar(CDictT, bound= dict)

property components: Ks

Component keys are dot-delimited strings "factor_name.component".

Components are dot-delimited string keys to avoid collisions:

(f”{name_0}.{c}” for c in factors[0].components, …)

property coord_dimensions: Ds

Concatenation of factor coordinate dimensions.

merge_components(parts: tuple[Mapping[str, V], ...], /)

Merge factor CDicts into a single CDict.

For namespaced products: re-attach dot-delimited prefix "name_i.c" for each factor key.

Parameters:

parts (tuple[Mapping[str, TypeVar(V)], ...]) – Tuple of dictionaries, one per factor, with factor-native keys.

Returns:

Merged dictionary in this chart’s component order.

Return type:

dict[str, TypeVar(V)]

property ndim: int

Total dimension, the sum of factor dimensions.

property ndims: tuple[int, ...]

Per-factor dimensions, as a tuple aligned with factors.

split_components(p: dict, /)

Partition a CDict by factor components.

For namespaced products: select keys "name_i.c" and strip prefix to yield factor dict keyed by c.

Parameters:

p (dict) – Point dictionary with keys matching this chart’s components.

Returns:

Tuple of dictionaries, one per factor, with factor-native keys.

Return type:

tuple[dict, ...]

property cartesian: CartesianProductChart

Get Cartesian version of a namespaced product chart (factorwise).

Returns a CartesianProductChart with each factor replaced by its cartesian_chart version, preserving factor_names.

Examples

>>> import coordinax.charts as cxc
>>> product = cxc.CartesianProductChart((cxc.sph3d, cxc.sph3d), ("q", "p"))
>>> cart_product = cxc.cartesian_chart(product)
>>> cart_product
CartesianProductChart(
    factors=(Cart3D(M=Rn(3)), Cart3D(M=Rn(3))), factor_names=('q', 'p')
)
coordinax.cartesian_chart(obj, /)

Return the corresponding Cartesian chart for a given chart.

This function provides the canonical flat-space Cartesian chart associated with any coordinate system. It maps each chart to its natural Cartesian equivalent in the same dimensional space.

Mathematical Definition:

For a coordinate chart \(\mathcal{R}\) in \(n\)-dimensional space, this returns the Cartesian chart \(\mathcal{C}_n\) such that:

\[\mathrm{cartesian\_chart}(\mathcal{R}) = \mathcal{C}_n\]

where \(\mathcal{C}_n \in \{\text{Cart1D}, \text{Cart2D}, \text{Cart3D}, \text{CartND}\}\) depending on \(n = \text{ndim}\).

The Cartesian chart uses orthonormal basis vectors with components typically denoted \((x)\), \((x, y)\), \((x, y, z)\), or \((q_1, \ldots, q_n)\) for arbitrary dimension.

Parameters:

obj (Any) – A coordinate chart instance (e.g., coordinax.charts.sph3d, coordinax.charts.polar2d) or any object for which a Cartesian equivalent is defined.

Returns:

The Cartesian chart in the same dimensional space:

  • 1D charts → Cart1D (component: x)

  • 2D charts → Cart2D (components: x, y)

  • 3D charts → Cart3D (components: x, y, z)

  • N-D charts → CartND (components: q)

Return type:

AbstractChart

Raises:

NotImplementedError – If no Cartesian chart is defined for the input object.

Notes

  • Cartesian charts use the Euclidean metric with orthonormal bases.

  • This function does not perform coordinate transformation; it only returns the chart type. Use {func}`~coordinax.charts.pt_map` for actual coordinate conversion.

  • All standard Euclidean coordinate systems (spherical, cylindrical, polar) map to their dimensional Cartesian equivalent.

  • For embedded manifolds, this returns the Cartesian form of the ambient space, not the intrinsic coordinates.

  • Intrinsic 2-sphere charts do not have a global Cartesian 2D chart and raise NoGlobalCartesianChartError.

See also

coordinax.charts.pt_map

Transform coordinates between charts

coordinax.charts.AbstractChart

Base class for coordinate charts

Examples

>>> import coordinax.charts as cxc

1D coordinate systems map to Cart1D:

>>> cxc.cartesian_chart(cxc.cart1d)
Cart1D(M=Rn(1))
>>> cxc.cartesian_chart(cxc.radial1d)
Cart1D(M=Rn(1))

2D coordinate systems map to Cart2D:

>>> cxc.cartesian_chart(cxc.cart2d)
Cart2D(M=Rn(2))
>>> cxc.cartesian_chart(cxc.polar2d)
Cart2D(M=Rn(2))

3D coordinate systems map to Cart3D:

>>> cxc.cartesian_chart(cxc.cart3d)
Cart3D(M=Rn(3))
>>> cxc.cartesian_chart(cxc.sph3d)
Cart3D(M=Rn(3))
>>> cxc.cartesian_chart(cxc.cyl3d)
Cart3D(M=Rn(3))

N-dimensional systems map to CartND:

>>> cxc.cartesian_chart(cxc.cartnd)
CartND(M=Rn(True))
coordinax.cartesian_chart(chart: AbstractChart, /) → AbstractChart
Parameters:

obj (Any)

Return type:

AbstractChart

Return the canonical Cartesian chart for a 0D chart.

>>> import coordinax.charts as cxc
>>> cxc.cartesian_chart(cxc.cart0d) is cxc.cart0d
True
coordinax.guess_chart(*_)

Infer a chart from keys or from array/quantity trailing shape.

This is a lightweight heuristic API.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> cxc.guess_chart(frozenset(("x", "y", "z")))
Cart3D(M=Rn(3))
>>> cxc.guess_chart({"x": 1.0, "y": 2.0, "z": 3.0})
Cart3D(M=Rn(3))
>>> q = u.Q([1.0, 2.0, 3.0], "m")
>>> cxc.guess_chart(q)
Cart3D(M=Rn(3))
>>> x = jnp.array([1.0, 2.0, 3.0])
>>> cxc.guess_chart(x)
Cart3D(M=Rn(3))
coordinax.guess_chart(obj: frozenset[str], /) → AbstractChart
Parameters:

_ (Any)

Return type:

AbstractChart

Infer a chart from the keys of a component dictionary.

Note that many charts may share the same component names (e.g., Spherical3D and MathSpherical3D both use ‘r’, ‘theta’, ‘phi’). These are completely indistinguishable from component names alone, so CANONICAL_CHART_CLASSES names the one to infer – the physics convention in both current cases. Pass the chart explicitly when the convention matters.

>>> import coordinax.charts as cxc
>>> d = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> chart = cxc.guess_chart(d)
>>> chart
Cart3D(M=Rn(3))
coordinax.guess_chart(obj: dict, /) → AbstractChart
Parameters:

_ (Any)

Return type:

AbstractChart

Infer a chart from the keys of a component dictionary.

Note that many charts may share the same component names (e.g., Spherical3D and MathSpherical3D both use ‘r’, ‘theta’, ‘phi’). These are completely indistinguishable from component names alone, so CANONICAL_CHART_CLASSES names the one to infer – the physics convention in both current cases. Pass the chart explicitly when the convention matters.

>>> import coordinax.charts as cxc
>>> d = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> chart = cxc.guess_chart(d)
>>> chart
Cart3D(M=Rn(3))
coordinax.guess_chart(_: jaxtyping.Shaped[Array, '*batch 1'] | jaxtyping.Shaped[ndarray, '*batch 1'] | jaxtyping.Shaped[AbstractQuantity, '*batch 1'], /) → AbstractChart
Parameters:

_ (Any)

Return type:

AbstractChart

Infer a 1D Cartesian chart from last dimension of a value / quantity.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> q = u.Q([1.0], "m")
>>> cxc.guess_chart(q)
Cart1D(M=Rn(1))
coordinax.guess_chart(_: jaxtyping.Shaped[Array, '*batch 2'] | jaxtyping.Shaped[ndarray, '*batch 2'] | jaxtyping.Shaped[AbstractQuantity, '*batch 2'], /) → AbstractChart
Parameters:

_ (Any)

Return type:

AbstractChart

Infer a 2D Cartesian chart from last dimension of a value / quantity.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> q = u.Q([1.0, 2.0], "m")
>>> cxc.guess_chart(q)
Cart2D(M=Rn(2))
coordinax.guess_chart(_: jaxtyping.Shaped[Array, '*batch 3'] | jaxtyping.Shaped[ndarray, '*batch 3'] | jaxtyping.Shaped[AbstractQuantity, '*batch 3'], /) → AbstractChart
Parameters:

_ (Any)

Return type:

AbstractChart

Infer a 3D Cartesian chart from last dimension of a value / quantity.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> q = u.Q([1.0, 2.0, 3.0], "m")
>>> cxc.guess_chart(q)
Cart3D(M=Rn(3))
coordinax.guess_chart(_: jaxtyping.Shaped[Array, '*batch N'] | jaxtyping.Shaped[ndarray, '*batch N'] | jaxtyping.Shaped[AbstractQuantity, '*batch N'], /) → AbstractChart
Parameters:

_ (Any)

Return type:

AbstractChart

Infer a N-dimensional Cartesian chart from last dimension of a value / quantity.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> q = u.Q([1.0, 2.0, 3.0, 4.0], "m")
>>> cxc.guess_chart(q)
CartND(M=Rn(True))
coordinax.guess_chart(obj: RadialRepresentation) → Radial1D
Parameters:

_ (Any)

Return type:

AbstractChart

Guess astropy.coordinates.RadialRepresentation -> Radial1D.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.RadialRepresentation(distance=1 * apyu.kpc)
>>> cxc.guess_chart(vec)
Radial1D(M=Rn(1))
coordinax.guess_chart(obj: UnitSphericalRepresentation) → LonLatSphericalTwoSphere
Parameters:

_ (Any)

Return type:

AbstractChart

Guess astropy.UnitSphericalRepresentation -> LonLatSphericalTwoSphere.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.UnitSphericalRepresentation(lon=90 * apyu.deg, lat=45 * apyu.deg)
>>> cxc.guess_chart(vec)
LonLatSphericalTwoSphere(M=Sn(2))
coordinax.guess_chart(obj: CartesianRepresentation) → Cart3D
Parameters:

_ (Any)

Return type:

AbstractChart

Guess astropy.coordinates.CartesianRepresentation -> Cart3D.

>>> import astropy.coordinates as apyc
>>> import coordinax.charts as cxc
>>> cxc.guess_chart(apyc.CartesianRepresentation(1, 2, 3))
Cart3D(M=Rn(3))
coordinax.guess_chart(obj: CylindricalRepresentation) → Cylindrical3D
Parameters:

_ (Any)

Return type:

AbstractChart

Guess astropy.coordinates.CylindricalRepresentation -> Cylindrical3D.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.CylindricalRepresentation(1 * apyu.km, 2 * apyu.deg, 3 * apyu.km)
>>> cxc.guess_chart(vec)
Cylindrical3D(M=Rn(3))
coordinax.guess_chart(obj: PhysicsSphericalRepresentation) → Spherical3D
Parameters:

_ (Any)

Return type:

AbstractChart

Guess astropy.coordinates.PhysicsSphericalRepresentation -> Spherical3D.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.PhysicsSphericalRepresentation(
...     r=1 * apyu.kpc, theta=45 * apyu.deg, phi=90 * apyu.deg)
>>> cxc.guess_chart(vec)
Spherical3D(M=Rn(3))
coordinax.guess_chart(obj: SphericalRepresentation) → LonLatSpherical3D
Parameters:

_ (Any)

Return type:

AbstractChart

Guess astropy.coordinates.SphericalRepresentation -> LonLatSpherical3D.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.SphericalRepresentation(
...     lon=90 * apyu.deg, lat=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxc.guess_chart(vec)
LonLatSpherical3D(M=Rn(3))
Parameters:

_ (Any)

Return type:

AbstractChart

coordinax.cdict(_, /)

Normalize an input object into a component dictionary.

Keys are component names and values are corresponding scalars/arrays or quantities.

Parameters:
  • obj –

    An object to extract a component dictionary from. Dispatch rules include:

    • collections.abc.Mapping: returned as-is

    • unxt.Quantity: treated as Cartesian coordinates with components in the last dimension, matched to the appropriate Cartesian chart

    • Point: extracted from obj.data

  • _ (Any)

Returns:

A mapping from component names to values.

Return type:

dict

Examples

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

Extract from a Mapping:

>>> d = {"x": u.Q(1.0, "m"), "y": u.Q(2.0, "m"), "z": u.Q(3.0, "m")}
>>> cx.cdict(d)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}
>>> d = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> cx.cdict(d)
{'x': 1.0, 'y': 2.0, 'z': 3.0}

Extract from a unxt.Quantity treated as Cartesian:

>>> q = u.Q([1, 2, 3], "m")
>>> cx.cdict(q)
{'x': Q(1, 'm'), 'y': Q(2, 'm'), 'z': Q(3, 'm')}

Specify the chart for a unxt.Quantity. For homogeneous unit Quantities, this must be Cartesian:

>>> cx.cdict(q, cx.cart3d)
{'x': Q(1, 'm'), 'y': Q(2, 'm'), 'z': Q(3, 'm')}

Extract from an Array-like object with a registered chart:

>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cx.cdict(arr, cx.cart3d)
{'x': Array(1., dtype=float64), 'y': Array(2., dtype=float64),
 'z': Array(3., dtype=float64)}
coordinax.cdict(obj: dict, /) → dict
Parameters:

_ (Any)

Return type:

dict

Return a dictionary as-is.

>>> import coordinax as cx
>>> d = {"x": 1.0, "y": 2.0}
>>> cx.cdict(d)
{'x': 1.0, 'y': 2.0}
coordinax.cdict(obj: dict, chart: AbstractChart, /) → dict
Parameters:

_ (Any)

Return type:

dict

Return a dictionary as-is.

>>> import coordinax.charts as cxc
>>> d = {"x": 1.0, "y": 2.0}
>>> cxc.cdict(d, cxc.cart2d)
{'x': 1.0, 'y': 2.0}
coordinax.cdict(obj: AbstractQuantity, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from a Quantity.

Treats the Quantity as a Cartesian vector with components in the last dimension. The appropriate Cartesian chart is determined from the last dimension of the quantity.

>>> import coordinax as cx
>>> import unxt as u
>>> q = u.Q([1.0, 2.0, 3.0], "m")
>>> cx.cdict(q)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}
coordinax.cdict(obj: AbstractQuantity, keys: tuple, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from a Quantity using specified keys.

Treats the Quantity as a vector with components in the last dimension, splitting along that axis according to the provided keys.

This function requires that:

  1. The last dimension of the quantity matches the number of chart components

  2. The chart has homogeneous coordinate dimensions (all components have the same physical dimension, like Cartesian charts)

Raises:

ValueError – If the last dimension of the quantity doesn’t match the chart’s component count, or if dimensions don’t match.

Parameters:

_ (Any)

Return type:

dict

Examples

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> q = u.Q([1.0, 2.0, 3.0], "m")
>>> cxc.cdict(q, ('x', 'y', 'z'))
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}
coordinax.cdict(obj: unxts.linalg._src._quantity_matrix.QuantityMatrix, keys: tuple, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from a 1D QuantityMatrix.

This overload supports heterogeneous per-component units by constructing one quantity per chart component from the corresponding numeric slice and unit in the QuantityMatrix.

Raises:

ValueError – If obj is not 1D, or if the last dimension does not match the number of provided keys.

Parameters:

_ (Any)

Return type:

dict

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import unxts.linalg as ul
>>> q = ul.QuantityMatrix(jnp.array([1.0, 2.0, 3.0]),
...                    unit=("m", "km/s", "rad"))
>>> cxc.cdict(q, ('x', 'y', 'z'))
{'x': Q(1., 'm'), 'y': Q(2., 'km / s'), 'z': Q(3., 'rad')}
coordinax.cdict(obj: AbstractQuantity, chart: AbstractChart, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from a Quantity.

Treats the Quantity as a vector with components in the last dimension, splitting along that axis according to the chart’s component names.

This function requires that:

  1. The last dimension of the quantity matches the number of chart components

  2. The chart has homogeneous coordinate dimensions (all components have the same physical dimension, like Cartesian charts)

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> q = u.Q([1.0, 2.0, 3.0], "m")
>>> cxc.cdict(q, cxc.cart3d)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}

A chart whose components have different dimensions cannot be filled from a single unit – every component would take that one unit:

>>> try:
...     cxc.cdict(q, cxc.sph3d)
... except ValueError as e:
...     print(e)
Cannot split a Quantity in 'm' across Spherical3D, whose components have
different dimensions ('length', 'angle', 'angle'): every component would take
that unit. Pass per-component values instead -- a QuantityMatrix, a component
dict, or a Point.
coordinax.cdict(obj: unxts.linalg._src._quantity_matrix.QuantityMatrix, chart: AbstractChart, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from a 1D QuantityMatrix.

This overload supports heterogeneous per-component units by constructing one quantity per chart component from the corresponding numeric slice and unit in the QuantityMatrix.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import unxts.linalg as ul
>>> q = ul.QuantityMatrix(
...     jnp.array([1.0, 2.0, 3.0]),
...     unit=(u.unit("m"), u.unit("km/s"), u.unit("rad")),
... )
>>> cxc.cdict(q, cxc.cart3d)
{'x': Q(1., 'm'), 'y': Q(2., 'km / s'), 'z': Q(3., 'rad')}
coordinax.cdict(obj: ArrayLike, keys: tuple, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from an array.

Raises:

ValueError – If the last dimension of the quantity doesn’t match a known Cartesian chart (0D, 1D, 2D, or 3D).

Parameters:

_ (Any)

Return type:

dict

Examples

>>> import coordinax as cx
>>> import jax.numpy as jnp
>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cx.cdict(arr, ('x', 'y', 'z'))
{'x': Array(1., dtype=float64), 'y': Array(2., dtype=float64),
 'z': Array(3., dtype=float64)}
coordinax.cdict(obj: ArrayLike, chart: AbstractChart, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from an array.

>>> import coordinax as cx
>>> import jax.numpy as jnp
>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cx.cdict(arr, cx.cart3d)
{'x': Array(1., dtype=float64), 'y': Array(2., dtype=float64),
 'z': Array(3., dtype=float64)}
coordinax.cdict(obj: ArrayLike, unit: UnitBase | FunctionUnitBase | str | unxts.linalg._src._units_matrix.UnitsMatrix | None, keys: tuple, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from an array.

>>> import coordinax as cx
>>> import jax.numpy as jnp
>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cx.cdict(arr, "m", ('x', 'y', 'z'))
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}

A unxts.linalg.UnitsMatrix gives each component its own unit:

>>> import unxts.linalg as ul
>>> cx.cdict(arr, ul.UnitsMatrix(("kpc", "rad", "rad")), ('r', 'theta', 'phi'))
{'r': Q(1., 'kpc'), 'theta': Q(2., 'rad'), 'phi': Q(3., 'rad')}
coordinax.cdict(obj: ArrayLike, unit: UnitBase | FunctionUnitBase | str | unxts.linalg._src._units_matrix.UnitsMatrix | None, chart: AbstractChart, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from an array.

>>> import coordinax as cx
>>> import jax.numpy as jnp
>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cx.cdict(arr, "m", cx.cart3d)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}

A unxts.linalg.UnitsMatrix gives each component its own unit:

>>> import coordinax.charts as cxc
>>> import unxts.linalg as ul
>>> cx.cdict(arr, ul.UnitsMatrix(("kpc", "rad", "rad")), cxc.sph3d)
{'r': Q(1., 'kpc'), 'theta': Q(2., 'rad'), 'phi': Q(3., 'rad')}
coordinax.cdict(obj: ArrayLike, unit: UnitBase | FunctionUnitBase | str | unxts.linalg._src._units_matrix.UnitsMatrix, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from an array.

Treats the array as a Cartesian vector with components in the last dimension. The appropriate Cartesian chart is determined from the last dimension of the quantity.

>>> import coordinax.charts as cxc
>>> import jax.numpy as jnp
>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cxc.cdict(arr, "m")
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}

A unxts.linalg.UnitsMatrix gives each component its own unit:

>>> import unxts.linalg as ul
>>> cxc.cdict(arr, ul.UnitsMatrix(("m", "km/s", "rad")))
{'x': Q(1., 'm'), 'y': Q(2., 'km / s'), 'z': Q(3., 'rad')}
coordinax.cdict(obj: ArrayLike, usys: AbstractUnitSystem, chart: AbstractChart, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract component dictionary from an array.

Raises:

ValueError – If the last dimension of the quantity doesn’t match a known Cartesian chart (0D, 1D, 2D, or 3D).

Parameters:

_ (Any)

Return type:

dict

Examples

>>> import coordinax as cx
>>> import unxt as u
>>> import jax.numpy as jnp
>>> arr = jnp.array([1.0, 2.0, 3.0])
>>> cx.cdict(arr, u.unitsystems.si, cx.cart3d)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}

Angular components arrive as unxt.Angle, per the chart’s declaration:

>>> import coordinax.charts as cxc
>>> cx.cdict(arr, u.unitsystems.si, cxc.sph3d)
{'r': Q(1., 'm'), 'theta': Angle(2., 'rad'), 'phi': Angle(3., 'rad')}
coordinax.cdict(obj: Point | Tangent, /) → dict
Parameters:

_ (Any)

Return type:

dict

Extract the component dictionary from a Point or Tangent.

>>> import coordinax as cx
>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> list(cx.cdict(cx.Point.from_(u.Q([1, 2, 3], "m"))).keys())
['x', 'y', 'z']
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> list(cx.cdict(cx.Tangent.from_(d, cxc.cart3d, cxr.coord_vel)).keys())
['x', 'y', 'z']
coordinax.cdict(r: CartesianRepresentation) → dict
Parameters:

_ (Any)

Return type:

dict

Convert an astropy CartesianRepresentation to a CDict.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.CartesianRepresentation(1 * apyu.km, 2 * apyu.km, 3 * apyu.km)
>>> cxc.cdict(vec)
{'x': Q(1., 'km'), 'y': Q(2., 'km'), 'z': Q(3., 'km')}
coordinax.cdict(r: CylindricalRepresentation) → dict
Parameters:

_ (Any)

Return type:

dict

Convert an astropy CylindricalRepresentation to a CDict.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.CylindricalRepresentation(1 * apyu.m, 45 * apyu.deg, 3 * apyu.m)
>>> cxc.cdict(vec)
{'rho': Q(1., 'm'), 'phi': Q(45., 'deg'), 'z': Q(3., 'm')}
coordinax.cdict(r: PhysicsSphericalRepresentation) → dict
Parameters:

_ (Any)

Return type:

dict

Convert an astropy PhysicsSphericalRepresentation to a CDict.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.PhysicsSphericalRepresentation(
...     phi=45 * apyu.deg, theta=30 * apyu.deg, r=1 * apyu.m)
>>> cxc.cdict(vec)
{'r': Q(1., 'm'), 'theta': Q(30., 'deg'), 'phi': Q(45., 'deg')}
coordinax.cdict(r: SphericalRepresentation) → dict
Parameters:

_ (Any)

Return type:

dict

Convert an astropy SphericalRepresentation to a CDict.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.SphericalRepresentation(
...     lon=90 * apyu.deg, lat=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxc.cdict(vec)
{'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')}
coordinax.cdict(r: UnitSphericalRepresentation) → dict
Parameters:

_ (Any)

Return type:

dict

Convert an astropy UnitSphericalRepresentation to a CDict.

A sky position without a distance – what a catalogue gives you, and what astropy.coordinates.SkyCoord builds when no distance is supplied.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> vec = apyc.UnitSphericalRepresentation(lon=90 * apyu.deg, lat=45 * apyu.deg)
>>> cxc.cdict(vec)
{'lon': Q(90., 'deg'), 'lat': Q(45., 'deg')}
coordinax.cdict(r: RadialRepresentation) → dict
Parameters:

_ (Any)

Return type:

dict

Convert an astropy RadialRepresentation to a CDict.

Astropy calls the component distance; coordinax.charts.radial1d calls it r.

>>> import astropy.coordinates as apyc
>>> import astropy.units as apyu
>>> import coordinax.charts as cxc
>>> cxc.cdict(apyc.RadialRepresentation(distance=1 * apyu.kpc))
{'r': Q(1., 'kpc')}
Parameters:

_ (Any)

Return type:

dict

coordinax.carray(p, /, *args)

Pack a component dictionary into a QuantityMatrix (complement of cdict).

Parameters:
  • p (Any) – A component dictionary (keys are component names, values are scalars/arrays or quantities).

  • *args (Any) –

    Either keys (a tuple of component names) or a chart, optionally followed by a unit or unit system controlling the packed units:

    • carray(p, keys) / carray(p, chart) — one unit per component (dimensionless where a component carries no unit)

    • carray(p, keys, usys) / carray(p, chart, usys) — units resolved from a unit system by each component’s dimension

    • carray(p, keys, unit) / carray(p, chart, unit) — a single shared unit for every component

Returns:

The packed 1-D quantity matrix, stacked along the trailing axis, with one unit per component.

Return type:

QuantityMatrix

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(2.0, "km"), "z": u.Q(3.0, "km")}
>>> cx.carray(p, ("x", "y", "z"))
QM([1., 2., 3.], '(km, km, km)')

Unitless components pack as dimensionless:

>>> cx.carray({"x": 1.0, "y": 2.0}, cx.cart2d)
QM([1., 2.], '(, )')
coordinax.carray(p: dict, keys: tuple, /) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

unxts.linalg.QuantityMatrix

Pack a component dict into a 1-D QM with per-component native units.

Unitless components are treated as dimensionless, so quantity- and array-valued components can be packed together.

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(2.0, "km"), "z": u.Q(3.0, "km")}
>>> cx.carray(p, ("x", "y", "z"))
QM([1., 2., 3.], '(km, km, km)')
coordinax.carray(p: dict, chart: AbstractChart, /) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

unxts.linalg.QuantityMatrix

Pack a component dict using chart.components as the keys.

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(2.0, "km"), "z": u.Q(3.0, "km")}
>>> cx.carray(p, cx.cart3d)
QM([1., 2., 3.], '(km, km, km)')
coordinax.carray(p: dict, keys: tuple, usys: AbstractUnitSystem, /) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

unxts.linalg.QuantityMatrix

Pack a component dict, resolving each component’s unit from usys.

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(2.0, "km")}
>>> cx.carray(p, ("x", "y"), u.unitsystems.si)
QM([1000., 2000.], '(m, m)')
coordinax.carray(p: dict, chart: AbstractChart, usys: AbstractUnitSystem, /) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

unxts.linalg.QuantityMatrix

Pack a component dict, using chart.components and units from usys.

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(2.0, "km"), "z": u.Q(3.0, "km")}
>>> cx.carray(p, cx.cart3d, u.unitsystems.si)
QM([1000., 2000., 3000.], '(m, m, m)')
coordinax.carray(p: dict, keys: tuple, unit: UnitBase | FunctionUnitBase, /) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

unxts.linalg.QuantityMatrix

Pack a component dict into a single shared unit (all components converted).

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(200.0, "m")}
>>> cx.carray(p, ("x", "y"), u.unit("km"))
QM([1. , 0.2], '(km, km)')
coordinax.carray(p: dict, chart: AbstractChart, unit: UnitBase | FunctionUnitBase, /) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

unxts.linalg.QuantityMatrix

Pack a component dict, using chart.components and a shared unit.

>>> import unxt as u
>>> import coordinax as cx
>>> p = {"x": u.Q(1.0, "km"), "y": u.Q(200.0, "m"), "z": u.Q(0.0, "m")}
>>> cx.carray(p, cx.cart3d, u.unit("km"))
QM([1. , 0.2, 0. ], '(km, km, km)')
coordinax.pt_map(*args, **kwargs)

Transform position coordinates from one chart to another.

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

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

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

Mathematical Definition:

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

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

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

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

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

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

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

Parameters:
Return type:

dict

Notes

  • This is a position-only transformation.

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

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

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

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

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

See also

coordinax.charts.pt_map

transform position coordinates between charts on the

same

Examples

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

Transform from 2D polar to Cartesian:

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

Transform from 3D spherical to Cartesian:

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

Transform from Cartesian to cylindrical:

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

dict

ABC CartesianProductChart -> CartesianProductChart (factorwise).

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

Mathematical definition:

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

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

Examples

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

Transform a Cartesian product chart between spatial representations:

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

dict

AbstractChart -> Cartesian -> AbstractCartesianProductChart.

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

dict

AbstractCartesianProductChart -> Cartesian -> AbstractChart.

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

dict

Return a partial function for point transformation.

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

Coordinates without units are the default.

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

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

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

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

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

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

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

dict

Return a partial function for point transformation.

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

Coordinates without units are the default.

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

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

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

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

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

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

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

dict

Point transformation from chart to chart, using their manifolds.

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

dict

AbstractChart -> Cartesian -> AbstractChart.

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

dict

Identity conversion for matching charts.

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

dict

Radial1D -> Cart1D.

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

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

dict

Cart1D -> Radial1D.

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

Assumptions:

  • Cart1D and Radial1D are

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

dict

Polar2D -> Cart2D.

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

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

dict

Cart2D -> Polar2D.

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

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

dict

Cylindrical3D -> Cart3D.

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

dict

Spherical3D -> Cart3D.

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

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

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

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

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

dict

LonLatSpherical3D -> Cart3D.

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

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

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

A point on the equator at lon=0:

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

dict

LonCosLatSpherical3D -> Cart3D.

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

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

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

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

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

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

dict

MathSpherical3D -> Cart3D.

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

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

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

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

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

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

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

dict

ProlateSpheroidal3D -> Cart3D.

We calculate through cylindrical coordinates first:

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

Then convert to Cartesian:

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

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

dict

Cart3D -> Cylindrical3D.

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

dict

Cart3D -> Spherical3D -> AbstractSpherical3D.

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

dict

Cart3D -> Spherical3D.

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

A point on the +z axis:

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

A point on the +x axis:

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

dict

Cylindrical3D -> Spherical3D.

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

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

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

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

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

dict

Spherical3D -> Cylindrical3D.

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

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

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

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

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

dict

Spherical3D -> LonLatSpherical3D.

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

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

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

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

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

dict

Spherical3D -> LonCosLatSpherical3D.

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

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

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

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

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

dict

Spherical3D -> MathSpherical3D.

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

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

dict

MathSpherical3D -> Spherical3D.

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

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

dict

ProlateSpheroidal3D -> Cylindrical3D.

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

Validity constraints (enforced by the representation) are:

  • \(\Delta > 0\),

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

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

The conversion proceeds via

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

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

dict

Cylindrical3D -> ProlateSpheroidal3D.

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

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

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

Then

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

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

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

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

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

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

Without units:

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

dict

Cart3D -> Cylindrical3D -> ProlateSpheroidal3D.

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

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

dict

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

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

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

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

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

Same focal length (identity transformation):

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

Different focal lengths (converts via cylindrical):

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

dict

CartND -> AbstractChart.

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

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

Convert 3D CartND to Spherical:

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

Convert 2D CartND to Polar:

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

Convert 1D CartND to Radial:

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

Convert CartND to Cart3D:

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

dict

AbstractChart -> CartND.

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

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

Convert Cart3D to CartND:

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

Convert Cart2D to CartND:

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

Convert Radial to CartND:

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

Convert Cylindrical to CartND (z-axis point):

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

dict

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

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

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

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

1D Cartesian (identity):

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

2D Cartesian (identity):

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

3D Cartesian (identity):

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

N-D Cartesian (identity):

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

dict

Transform a QuantityMatrix between charts.

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

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

2D Cartesian to Polar:

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

3D Cartesian to Spherical:

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

Batched transformation:

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

dict

Point transform for array input.

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

Returns:

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

Return type:

dict

Parameters:

Examples

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

Cartesian to Spherical (3D):

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

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

Spherical to Cartesian (3D):

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

Cartesian to Cylindrical:

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

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

Batched transformation:

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

2D Cartesian to Polar:

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

dict

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

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

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

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

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

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

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

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

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

dict

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

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

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

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

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

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

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

dict

Convert between embedded manifolds with a shared ambient space.

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

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

Example 1: Two spheres with different radii

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

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

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

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

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

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

dict

Project an ambient position into an embedded chart.

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

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

From Cartesian ambient to SphericalTwoSphere intrinsic:

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

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

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

From Spherical ambient to SphericalTwoSphere intrinsic:

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

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

dict

Embed intrinsic coordinates into an ambient representation.

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

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

From SphericalTwoSphere intrinsic to Cartesian ambient:

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

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

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

From SphericalTwoSphere intrinsic to Spherical ambient:

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

dict

Convert between embedded manifolds with a shared ambient space.

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

dict

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

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

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

dict

Identity conversion for matching charts.

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

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

dict

Route between two-sphere charts via SphericalTwoSphere.

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

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

dict

SphericalTwoSphere -> LonLatSphericalTwoSphere.

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

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

dict

LonLatSphericalTwoSphere -> SphericalTwoSphere.

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

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

dict

SphericalTwoSphere -> LonCosLatSphericalTwoSphere.

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

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

dict

LonCosLatSphericalTwoSphere -> SphericalTwoSphere.

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

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

dict

SphericalTwoSphere -> MathSphericalTwoSphere.

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

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

dict

MathSphericalTwoSphere -> SphericalTwoSphere.

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

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

dict

Convert point data between charts.

Convert a point from Cartesian coordinates to spherical coordinates:

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

Define a point in Cartesian coordinates:

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

Convert it to spherical coordinates:

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

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

The representation remains unchanged; only the chart changes:

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

Let’s work through more examples.

Cartesian to Spherical (with units):

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

Cylindrical to Cartesian (without units):

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

Polar to Cartesian (2D):

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

Between Spherical variants (Spherical to LonLatSpherical):

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

Identity conversion (same chart):

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

dict

Convert point data between charts.

Convert a point from Cartesian coordinates to spherical coordinates:

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

Define a point in Cartesian coordinates:

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

Convert it to spherical coordinates:

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

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

The representation remains unchanged; only the chart changes:

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

dict

Convert point data between charts.

Convert a point from Cartesian coordinates to spherical coordinates:

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

Define a point in Cartesian coordinates:

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

Convert it to spherical coordinates:

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

dict

Convert a point from one chart to another.

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

dict

Convert a vector from one chart to another.

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

dict

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

Examples

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

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

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

dict

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

Examples

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

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

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

dict

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

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

Examples

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

dict

coordinax.jac_pt_map(*args, **kwargs)

Compute the Jacobian of the chart transition map at a base point.

Examples

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> J = cxc.jac_pt_map(
...     {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")},
...     cxc.cart3d, cxc.sph3d,
... )
>>> J.value.shape
(3, 3)
coordinax.jac_pt_map(at: NoneType, /, *fixed_args: Any, **fixed_kw: Any) → Any
Parameters:
Return type:

Any

Higher-order function for fixed-arg Jacobian point map.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> map = cxc.jac_pt_map(None, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> map(at)
QM(
    [[ 1.,  0.,  0.],
     [ 0.,  0., -1.],
     [-0.,  1.,  0.]],
    '((, , ), (rad / m, rad / m, rad / m), (rad / m, rad / m, rad / m))'
)
>>> import jax
>>> J = jax.vmap(map)(jax.tree.map(lambda x: x[None], at))
>>> J.shape
(1, 3, 3)
coordinax.jac_pt_map(from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None) → Callable[[object], Any]
Parameters:
Return type:

Any

Higher-order function for fixed-arg Jacobian point map.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> map = cxc.jac_pt_map(cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> map(at)
QM(
    [[ 1.,  0.,  0.],
     [ 0.,  0., -1.],
     [-0.,  1.,  0.]],
    '((, , ), (rad / m, rad / m, rad / m), (rad / m, rad / m, rad / m))'
)
>>> import jax
>>> J = jax.vmap(map)(jax.tree.map(lambda x: x[None], at))
>>> J.shape
(1, 3, 3)
coordinax.jac_pt_map(at: Array, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem) → Array
Parameters:
Return type:

Any

Compute the Jacobian at a plain-array base point.

Treats at as a flat numeric array whose elements are the from_chart coordinates expressed. Returns the JAX array Jacobian \(J^j{}_i = \partial \tau^j / \partial q^i\), without unit annotation.

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

This is the fallback. A pair in _CLOSED_FORM_PAIRS is served by its own closed form instead – including the ones that reach their answer by chaining two others, which is a choice each of those makes internally and not something resolved here. Every other pair is differentiated.

Cylindrical3D -> MathSpherical3D has no closed form, so it comes here.

>>> jac_fn = cxc.jac_pt_map(None, cxc.cyl3d, cxc.math_sph3d, usys=u.unitsystems.si)
>>> at = jnp.array([1.0, 0.0, 0.0])
>>> jac_fn(at)
Array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0., -1.]], dtype=float64)
>>> import jax
>>> J = jax.vmap(jac_fn)(at[None])
>>> J.shape
(1, 3, 3)
coordinax.jac_pt_map(at: dict, from_chart: AbstractChart, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → Array | unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Compute the Jacobian at a coordinate-dictionary base point.

The primary dict-input dispatch. Takes the first route that applies:

Closed form registered for the chart pair

_jac_from_dict_via_closed_form canonicalises the components, evaluates the closed form, and restores the units – for unitful and plain-array dicts alike.

Array-valued branch (no units in any value)

Stacks the dict values into a plain array via jnp.stack, then forwards to jac_pt_map(at_arr, from_chart, to_chart, usys=usys). A pair with a closed form reads bare angles as radians; any other needs usys to say what the numbers mean, and raises ValueError without one.

Quantity-valued branch (a unitful value, no closed form for the pair)

Packs at into a 1-D QuantityMatrix via carray(at, from_chart.components), promotes any integer or boolean leaves to the default floating-point dtype (other dtypes, including complex, are left unchanged and will raise a TypeError from jax.jacfwd if passed), then computes J_qq = jax.jacfwd(pt_map_fn)(at_in). Because jacfwd applied to a QuantityMatrix-in / QuantityMatrix-out function yields a nested QuantityMatrix, _repack_q_from_jac is called to extract the correct 2-D unit structure.

Returns:

Plain array when at is array-valued; QuantityMatrix of shape (n_out, n_in) with per-element units otherwise.

Return type:

Any

Raises:

ValueError – If at keys do not match from_chart.components (via check_data), or – from pt_map – “usys must be provided for array input” when bare values reach a pair with no closed form.

Parameters:

Examples

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

Quantity-valued dict (no usys needed):

>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> J = cxc.jac_pt_map(at, cxc.cart3d, cxc.sph3d)
>>> J.value.shape
(3, 3)

Plain-array dict (usys required):

>>> import jax.numpy as jnp
>>> at_arr = {"x": jnp.array(1.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> J2 = cxc.jac_pt_map(at_arr, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> J2.shape
(3, 3)
coordinax.jac_pt_map(at: Array, from_chart: Cart2D, to_chart: Polar2D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of the transition function between two charts.

\[J = \frac{\partial(r,\theta)}{\partial(x,y)} = ( x/r & y/r \ -y/r^2 & x/r^2 ) = ( \cos\theta & \sin\theta \ -\sin\theta/r & \cos\theta/r )\]

as written, in radians per unit length. The result carries no units, so it has to mean the same thing pt_map(…, usys=usys) does: the angular row is scaled by \(d\theta_{usys}/d\theta_{rad}\), which is 1 for radians and \(180/\pi\) for degrees. A Quantity point goes to the overload below instead, which labels the row rad / length and needs no such scaling.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> x = jnp.array([1.0, 1.0])
>>> cxc.jac_pt_map(cxc.cart2d, cxc.polar2d, usys=u.unitsystems.si)(x)
Array([[ 0.70710678,  0.70710678],
       [-0.5       ,  0.5       ]], dtype=float64)

The same point under a degree system: the radial row is unchanged, the angular row is the same derivative expressed per degree.

>>> degrees = u.unitsystem("m", "deg", "kg", "s")
>>> cxc.jac_pt_map(cxc.cart2d, cxc.polar2d, usys=degrees)(x)
Array([[  0.70710678,   0.70710678],
       [-28.64788976,  28.64788976]], dtype=float64)
coordinax.jac_pt_map(at: AbstractQuantity, from_chart: Cart2D, to_chart: Polar2D, /, *, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Compute the Jacobian of the transition function between two charts.

\[J = \frac{\partial(r,\theta)}{\partial(x,y)} = ( x/r & y/r \ -y/r^2 & x/r^2 ) = ( \cos\theta & \sin\theta \ -\sin\theta/r & \cos\theta/r )\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> x = u.Q(jnp.array([1.0, 1.0]), "m")
>>> cxc.jac_pt_map(cxc.cart2d, cxc.polar2d, usys=u.unitsystems.si)(x)
QM([[ 0.70710678,  0.70710678],
    [-0.5       ,  0.5       ]], '((, ), (rad / m, rad / m))')
coordinax.jac_pt_map(at: Array, from_chart: Cart3D, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cart3D -> Cylindrical3D.

\[\begin{split}J = \frac{\partial(\rho,\phi,z)}{\partial(x,y,z)} = \begin{pmatrix} x/\rho & y/\rho & 0 \\ -y/\rho^2 & x/\rho^2 & 0 \\ 0 & 0 & 1 \end{pmatrix}\end{split}\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at = jnp.array([1.0, 0.0, 3.0])
>>> cxc.jac_pt_map(at, cxc.cart3d, cxc.cyl3d, usys=u.unitsystems.si)
Array([[ 1.,  0.,  0.],
       [-0.,  1.,  0.],
       [ 0.,  0.,  1.]], dtype=float64)
coordinax.jac_pt_map(at: AbstractQuantity, from_chart: Cart3D, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Compute the Jacobian of Cart3D -> Cylindrical3D at a unitful point.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at = u.Q(jnp.array([1.0, 0.0, 3.0]), "m")
>>> cxc.jac_pt_map(at, cxc.cart3d, cxc.cyl3d).unit
UnitsMatrix("((, , ), (rad / m, rad / m, rad / m), (, , ))")
coordinax.jac_pt_map(at: Array, from_chart: Cylindrical3D, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cylindrical3D -> Cart3D.

\[\begin{split}J = \frac{\partial(x,y,z)}{\partial(\rho,\phi,z)} = \begin{pmatrix} \cos\phi & -\rho\sin\phi & 0 \\ \sin\phi & \rho\cos\phi & 0 \\ 0 & 0 & 1 \end{pmatrix}\end{split}\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at = jnp.array([2.0, 0.0, 3.0])
>>> cxc.jac_pt_map(at, cxc.cyl3d, cxc.cart3d, usys=u.unitsystems.si)
Array([[ 1., -0.,  0.],
       [ 0.,  2.,  0.],
       [ 0.,  0.,  1.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Cart3D, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cart3D -> Spherical3D.

\[\begin{split}J = \frac{\partial(r,\theta,\phi)}{\partial(x,y,z)} = \begin{pmatrix} x/r & y/r & z/r \\ xz/(r^2\rho) & yz/(r^2\rho) & -\rho/r^2 \\ -y/\rho^2 & x/\rho^2 & 0 \end{pmatrix}\end{split}\]

with \(\rho=\sqrt{x^2+y^2}\) the cylindrical radius.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at = jnp.array([1.0, 0.0, 0.0])
>>> cxc.jac_pt_map(at, cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
Array([[ 1.,  0.,  0.],
       [ 0.,  0., -1.],
       [-0.,  1.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: AbstractQuantity, from_chart: Cart3D, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Compute the Jacobian of Cart3D -> Spherical3D at a unitful point.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at = u.Q(jnp.array([1.0, 0.0, 0.0]), "m")
>>> cxc.jac_pt_map(at, cxc.cart3d, cxc.sph3d).value
Array([[ 1.,  0.,  0.],
       [ 0.,  0., -1.],
       [-0.,  1.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Spherical3D, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Spherical3D -> Cart3D.

\[\begin{split}J = \frac{\partial(x,y,z)}{\partial(r,\theta,\phi)} = \begin{pmatrix} s_\theta c_\phi & r c_\theta c_\phi & -r s_\theta s_\phi \\ s_\theta s_\phi & r c_\theta s_\phi & r s_\theta c_\phi \\ c_\theta & -r s_\theta & 0 \end{pmatrix}\end{split}\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at = jnp.array([1.0, 0.0, 0.0])
>>> cxc.jac_pt_map(at, cxc.sph3d, cxc.cart3d, usys=u.unitsystems.si)
Array([[ 0.,  1., -0.],
       [ 0.,  0.,  0.],
       [ 1., -0.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: LonLatSpherical3D, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonLatSpherical3D -> LonCosLatSpherical3D.

\[\begin{split}J = \frac{\partial(\lambda\cos\phi, \phi, d)}{\partial(\lambda, \phi, d)} = \begin{pmatrix} \cos\phi & -\lambda\sin\phi & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix}\end{split}\]

Both angles are converted to radians for the trigonometry. The entries then need no further scaling: each is an angle per angle, so the caller’s unit cancels between numerator and denominator.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.0, 0.0, 2.0])
>>> cxc.jac_pt_map(at, cxc.lonlat_sph3d, cxc.loncoslat_sph3d,
...                usys=u.unitsystems.si)
Array([[ 1., -0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: LonCosLatSpherical3D, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonCosLatSpherical3D -> LonLatSpherical3D.

\[\begin{split}J = \frac{\partial(\lambda, \phi, d)}{\partial(\lambda\cos\phi, \phi, d)} = \begin{pmatrix} 1/\cos\phi & \lambda\cos\phi\,\sin\phi/\cos^2\phi & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix}\end{split}\]

Singular at the poles, where cos(lat) vanishes and longitude is not recoverable from lon * cos(lat) – the same place the map itself is.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.0, 0.0, 2.0])
>>> cxc.jac_pt_map(at, cxc.loncoslat_sph3d, cxc.lonlat_sph3d,
...                usys=u.unitsystems.si)
Array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Cart3D, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cart3D -> LonLatSpherical3D.

\[\begin{split}J = \frac{\partial(\lambda, \phi, d)}{\partial(x,y,z)} = \begin{pmatrix} -y/\rho^2 & x/\rho^2 & 0 \\ -xz/(r^2\rho) & -yz/(r^2\rho) & \rho/r^2 \\ x/r & y/r & z/r \end{pmatrix}\end{split}\]

The latitude row is the colatitude’s negated, lat increasing towards +z where theta decreases.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([1.0, 0.0, 0.0])
>>> cxc.jac_pt_map(at, cxc.cart3d, cxc.lonlat_sph3d, usys=u.unitsystems.si)
Array([[-0.,  1.,  0.],
       [-0., -0.,  1.],
       [ 1.,  0.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: LonLatSpherical3D, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonLatSpherical3D -> Cart3D.

\[\begin{split}J = \frac{\partial(x,y,z)}{\partial(\lambda, \phi, d)} = \begin{pmatrix} -d\cos\phi\sin\lambda & -d\sin\phi\cos\lambda & \cos\phi\cos\lambda \\ d\cos\phi\cos\lambda & -d\sin\phi\sin\lambda & \cos\phi\sin\lambda \\ 0 & d\cos\phi & \sin\phi \end{pmatrix}\end{split}\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.0, 0.0, 2.0])
>>> cxc.jac_pt_map(at, cxc.lonlat_sph3d, cxc.cart3d, usys=u.unitsystems.si)
Array([[-0., -0.,  1.],
       [ 2., -0.,  0.],
       [ 0.,  2.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Cylindrical3D, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cylindrical3D -> Spherical3D.

\[\begin{split}J = \frac{\partial(r,\theta,\phi)}{\partial(\rho,\phi,z)} = \begin{pmatrix} \rho/r & 0 & z/r \\ z/r^2 & 0 & -\rho/r^2 \\ 0 & 1 & 0 \end{pmatrix}\end{split}\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([1.0, 0.0, 0.0])
>>> cxc.jac_pt_map(at, cxc.cyl3d, cxc.sph3d, usys=u.unitsystems.si)
Array([[ 1.,  0.,  0.],
       [ 0.,  0., -1.],
       [ 0.,  1.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Spherical3D, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Spherical3D -> Cylindrical3D.

\[\begin{split}J = \frac{\partial(\rho,\phi,z)}{\partial(r,\theta,\phi)} = \begin{pmatrix} \sin\theta & r\cos\theta & 0 \\ 0 & 0 & 1 \\ \cos\theta & -r\sin\theta & 0 \end{pmatrix}\end{split}\]
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([2.0, 0.0, 0.0])
>>> cxc.jac_pt_map(at, cxc.sph3d, cxc.cyl3d, usys=u.unitsystems.si)
Array([[ 0.,  2.,  0.],
       [ 0.,  0.,  1.],
       [ 1., -0.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Cart3D, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cart3D -> LonCosLatSpherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([1.0, 0.0, 0.0])
>>> cxc.jac_pt_map(at, cxc.cart3d, cxc.loncoslat_sph3d, usys=u.unitsystems.si)
Array([[0., 1., 0.],
       [0., 0., 1.],
       [1., 0., 0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: LonCosLatSpherical3D, to_chart: Cart3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonCosLatSpherical3D -> Cart3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.0, 0.0, 2.0])
>>> cxc.jac_pt_map(at, cxc.loncoslat_sph3d, cxc.cart3d, usys=u.unitsystems.si)
Array([[0., 0., 1.],
       [2., 0., 0.],
       [0., 2., 0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Spherical3D, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Spherical3D -> LonLatSpherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([3.0, 0.6, 1.1])
>>> cxc.jac_pt_map(at, cxc.sph3d, cxc.lonlat_sph3d, usys=u.unitsystems.si)
Array([[ 0.,  0.,  1.],
       [ 0., -1.,  0.],
       [ 1.,  0.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: LonLatSpherical3D, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonLatSpherical3D -> Spherical3D.

The same matrix as the forward direction: the relabel is an involution.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.9, 0.35, 2.5])
>>> cxc.jac_pt_map(at, cxc.lonlat_sph3d, cxc.sph3d, usys=u.unitsystems.si)
Array([[ 0.,  0.,  1.],
       [ 0., -1.,  0.],
       [ 1.,  0.,  0.]], dtype=float64)
coordinax.jac_pt_map(at: Array, from_chart: Cylindrical3D, to_chart: LonLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cylindrical3D -> LonLatSpherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([2.0, 0.7, 3.0])
>>> J = cxc.jac_pt_map(at, cxc.cyl3d, cxc.lonlat_sph3d, usys=u.unitsystems.si)
>>> J.shape
(3, 3)
coordinax.jac_pt_map(at: Array, from_chart: LonLatSpherical3D, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonLatSpherical3D -> Cylindrical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.9, 0.35, 2.5])
>>> J = cxc.jac_pt_map(at, cxc.lonlat_sph3d, cxc.cyl3d, usys=u.unitsystems.si)
>>> J.shape
(3, 3)
coordinax.jac_pt_map(at: Array, from_chart: Spherical3D, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Spherical3D -> LonCosLatSpherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([3.0, 0.6, 1.1])
>>> J = cxc.jac_pt_map(at, cxc.sph3d, cxc.loncoslat_sph3d, usys=u.unitsystems.si)
>>> J.shape
(3, 3)
coordinax.jac_pt_map(at: Array, from_chart: LonCosLatSpherical3D, to_chart: Spherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonCosLatSpherical3D -> Spherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.8, 0.35, 2.5])
>>> J = cxc.jac_pt_map(at, cxc.loncoslat_sph3d, cxc.sph3d, usys=u.unitsystems.si)
>>> J.shape
(3, 3)
coordinax.jac_pt_map(at: Array, from_chart: Cylindrical3D, to_chart: LonCosLatSpherical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of Cylindrical3D -> LonCosLatSpherical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([2.0, 0.7, 3.0])
>>> J = cxc.jac_pt_map(at, cxc.cyl3d, cxc.loncoslat_sph3d, usys=u.unitsystems.si)
>>> J.shape
(3, 3)
coordinax.jac_pt_map(at: Array, from_chart: LonCosLatSpherical3D, to_chart: Cylindrical3D, /, *, usys: AbstractUnitSystem | None = None) → Array
Parameters:
Return type:

Any

Compute the Jacobian of LonCosLatSpherical3D -> Cylindrical3D.

>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import jax.numpy as jnp
>>> at = jnp.array([0.8, 0.35, 2.5])
>>> J = cxc.jac_pt_map(at, cxc.loncoslat_sph3d, cxc.cyl3d, usys=u.unitsystems.si)
>>> J.shape
(3, 3)
Parameters:
Return type:

Any

final class coordinax.EuclideanManifold(ndim: int, /)

Bases: AbstractManifold

The \(n\)-dimensional Euclidean manifold \(\mathbb{R}^n\).

The Euclidean manifold of dimension \(n\) is the smooth manifold \((\mathbb{R}^n, \mathcal{A}_{\mathbb{R}^n})\).

Charts and atlas. The smooth structure is described by an {class}`coordinax.manifolds.EuclideanAtlas` whose charts are local diffeomorphisms

\[\varphi : U \subset \mathbb{R}^n \to \mathbb{R}^n.\]

A chart \(C = (U, \varphi)\) is admitted by the atlas when its dimensionality matches \(n\) and either (1) it is explicitly registered with {class}`coordinax.manifolds.EuclideanAtlas`, or (2) it possesses a compatible transition map to the default Cartesian chart. For \(n = 3\) the built-in charts include Cartesian \((x, y, z)\), spherical \((r, \theta, \phi)\), cylindrical \((\rho, \phi, z)\), and several angular variants; see {class}`coordinax.manifolds.EuclideanAtlas` for the full list.

Transition maps. For any two charts \(C_\alpha = (U_\alpha, \varphi_\alpha)\) and \(C_\beta = (U_\beta, \varphi_\beta)\) in the atlas, the transition map is

\[\tau_{\alpha \to \beta} = \varphi_\beta \circ \varphi_\alpha^{-1} : \varphi_\alpha(U_\alpha \cap U_\beta) \to \varphi_\beta(U_\alpha \cap U_\beta).\]

Because \(\mathbb{R}^n\) is flat and simply connected, every transition map is a smooth diffeomorphism on a connected open domain. For example, the Cartesian-to-spherical transition on \(\mathbb{R}^3\) is

\[\tau_{C \to S}(x, y, z) = \Bigl(\sqrt{x^2 + y^2 + z^2},\; \arccos\!\tfrac{z}{r},\; \operatorname{atan2}(y, x)\Bigr).\]

Pre-built instance. The module exports {obj}`coordinax.manifolds.R3` as a pre-built instance for the common case \(\mathbb{R}^3\).

Parameters:

ndim (int) – Intrinsic dimension \(n \geq 0\) of the manifold — the number of independent coordinates required to label a point.

Variables:

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

Examples

Construction

Construct a Euclidean manifold of arbitrary dimension:

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

The intrinsic dimension is accessible via {attr}`~EuclideanManifold.ndim`:

>>> M.ndim
3

The atlas is a {class}`EuclideanAtlas` with matching dimensionality:

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

Default chart

The default chart is the standard Cartesian chart for the given dimension:

>>> M.default_chart()
Cart3D(M=Rn(3))
>>> cxmd.EuclideanManifold(2).default_chart()
Cart2D(M=Rn(2))
>>> cxmd.EuclideanManifold(1).default_chart()
Cart1D(M=Rn(1))

Chart membership

Check whether a chart belongs to this manifold’s atlas:

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

Charts with the wrong dimensionality are rejected:

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

{meth}`check_chart` raises if the chart is not supported:

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

Pre-built instances

For the most common case — three-dimensional Euclidean space \(\mathbb{R}^3\) — the module provides a pre-built instance:

>>> cxmd.R3
Rn(3)
ndim: int

Intrinsic dimension of the manifold.

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

Return the metric angle between two tangent vectors at at.

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

Parameters:
Return type:

AbstractAngle

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

Check that chart belongs to this manifold atlas.

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

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()

Return a default chart from the atlas.

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

Return type:

AbstractChart[Any, Any, Any]

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

Return whether chart belongs to this manifold atlas.

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

chart (AbstractChart[Any, Any, Any])

Return type:

bool

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

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

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

Examples

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

With a stacked jax.Array (usys required):

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

With a CDict of quantities (usys optional):

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

Any

atlas: AbstractAtlas

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

metric: AbstractMetricField

The manifold’s metric. This defines the geometric structure.

coordinax.Rn

alias of EuclideanManifold

final class coordinax.FlatMetric(ndim: int = <property object>)

Bases: AbstractDiagonalMetricField

Euclidean (flat) Riemannian metric on \(\mathbb{R}^n\).

In Cartesian coordinates the metric is the identity matrix \(g = I_n\). In any other chart, the metric matrix is computed via the pullback

\[g_{ij} = \sum_k \frac{\partial x^k}{\partial q^i} \frac{\partial x^k}{\partial q^j} = (J^T J)_{ij},\]

where \(J = \partial x / \partial q\) is the Jacobian of the chart-to-Cartesian transition map.

This pullback is diagonal precisely for orthogonal coordinate charts. FlatMetric is treated as AbstractDiagonalMetricField on that orthogonal chart domain; atlas chart compatibility alone does not imply orthogonality.

Parameters:

ndim (int) – Dimension of the Euclidean space.

Examples

>>> import jax.numpy as jnp
>>> import coordinaxs.api.manifolds as cxmapi
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> m = cxm.FlatMetric(3)
>>> m.signature
(1, 1, 1)
>>> m.ndim
3

The metric matrix is obtained via the dispatch API on the associated manifold:

>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0), "z": jnp.array(0.0)}
>>> cxmapi.metric_matrix(cxm.R3, at, cxc.cart3d).diagonal
Array([1., 1., 1.], dtype=float64)
ndim: int

Dimension of the Euclidean space.

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

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

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

Examples

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

With a CDict of quantities (usys optional):

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

With a stacked jax.Array (usys required):

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

Any

property signature: tuple[int, ...]

Signature of the metric as a tuple of 1’s.

coordinax.embedded_twosphere(radius: float | AbstractQuantity, ambient: AbstractChart[Any, Any, Any] = Spherical3D(M=Rn(3)))

Create an coordinax.manifolds.EmbeddedManifold for the two-sphere.

This is a convenience helper that constructs an coordinax.manifolds.EmbeddedManifold with intrinsic=HyperSphericalManifold() and embedding=TwoSphereIn3D(radius, ambient).

Parameters:
Return type:

EmbeddedManifold

Examples

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

Default ambient (Spherical3D):

>>> M = cxm.embedded_twosphere(radius=u.Q(2.0, "km"))
>>> M
EmbeddedManifold(intrinsic=HyperSphericalManifold(...),
                 ambient=Rn(3),
                 embed_map=TwoSphereIn3D(radius=Q(2., 'km'),
                                         ambient=Spherical3D(M=Rn(3))))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, M)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}

With Cartesian ambient the embedding returns (x, y, z):

>>> M = cxm.embedded_twosphere(radius=u.Q(2.0, "km"), ambient=cxc.cart3d)
>>> xyz = cxm.pt_embed(p, M)
>>> sorted(xyz)
['x', 'y', 'z']
>>> bool(jnp.allclose(u.ustrip("km", xyz["x"]), 2.0, atol=1e-6))
True
final class coordinax.EmbeddedChart(embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])

Bases: AbstractParameterizedChart[EmbeddedManifold, Ks, Ds], Generic[IntrinsicT, AmbientT, Ks, Ds]

Chart for intrinsic coordinates on an embedding manifold.

This is a convenience wrapper that combines an intrinsic chart with an embedding to an ambient Cartesian chart. It provides the same component and dimension information as the intrinsic chart, but also provides a realization map to Cartesian coordinates via the embedding.

The more correct way to represent an embedding manifold is with {class}`~coordinax.manifolds.EmbeddedManifold`.

Examples

Embed/project {class}`~coordinax.charts.SphericalTwoSphere through an ambient {class}`~coordinax.charts.Spherical3D chart:

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, chart)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, chart)
>>> p2
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> jnp.allclose(p2["theta"].value, p["theta"].value)
Array(True, dtype=bool)
Parameters:

embed_map (AbstractEmbeddingMap[TypeVar(IntrinsicT, bound= AbstractChart[Any, Any, Any]), TypeVar(AmbientT, bound= AbstractChart[Any, Any, Any])])

embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT]

The embedding that defines the map to the ambient chart.

This is the core data of the EmbeddedChart, as it defines the ambient chart and the embedding parameters (e.g., radius for a sphere). The intrinsic chart is determined by the embedding’s intrinsic property, and the ambient chart is determined by the embedding’s ambient property.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.embed_map
TwoSphereIn3D(radius=Q(2., 'km'), ambient=Spherical3D(M=Rn(3)))
property M: EmbeddedManifold

The manifold associated with this chart.

This is an EmbeddedManifold that combines the intrinsic and ambient manifolds defined by the embedding map.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.M
EmbeddedManifold(intrinsic=HyperSphericalManifold(ndim=2),
                 ambient=Rn(3),
                 embed_map=TwoSphereIn3D(radius=Q(2., 'km'),
                                         ambient=Spherical3D(M=Rn(3))))
property intrinsic: IntrinsicT

The intrinsic chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.intrinsic
SphericalTwoSphere(M=Sn(2))
property ambient: AmbientT

The ambient chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.ambient
Spherical3D(M=Rn(3))
property components: Ks

Return the components of the intrinsic chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.components
('theta', 'phi')
property coord_dimensions: Ds

Return the coordinate dimensions of the intrinsic chart.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.coord_dimensions
('angle', 'angle')
property cartesian: AbstractChart

The ambient Cartesian chart for the embedding.

>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> chart = cxm.EmbeddedChart(cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> chart.cartesian
Cart3D(M=Rn(3))
check_data(data: CDictT, /, *, keys: bool = True, values: bool = False)

Check that the data is compatible with the chart.

Parameters:
  • data (TypeVar(CDictT, bound= dict)) – The data to check.

  • keys (bool) – Whether to check that the keys of data match chart.components. If False, this check is skipped. Default is True.

  • values (bool) – Whether to check that the dimensions of the values in data match chart.coord_dimensions. If False, this check is skipped. Default is False.

Return type:

TypeVar(CDictT, bound= dict)

property ndim: int

Number of coordinate components (chart dimension).

final class coordinax.EmbeddedManifold(intrinsic: AbstractManifold, ambient: AbstractManifold, embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT])

Bases: AbstractManifold, Generic[IntrinsicT, AmbientT]

Embedded manifold.

Examples

Embed/project {class}`~coordinax.charts.SphericalTwoSphere through an ambient {class}`~coordinax.charts.Spherical3D chart:

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import unxt as u
>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2,
...     ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(2.0, "km")))
>>> p = {"theta": u.Angle(jnp.pi / 2, "rad"), "phi": u.Angle(0.0, "rad")}
>>> sph = cxm.pt_embed(p, M)
>>> sph
{'r': Q(2., 'km'), 'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> p2 = cxm.pt_project(sph, M)
>>> p2
{'theta': Angle(1.57079633, 'rad'), 'phi': Angle(0., 'rad')}
>>> jnp.allclose(p2["theta"].value, p["theta"].value)
Array(True, dtype=bool)
Parameters:
intrinsic: AbstractManifold
ambient: AbstractManifold
embed_map: AbstractEmbeddingMap[IntrinsicT, AmbientT]
embed(intrinsic_point: dict, from_intrinsic_chart: AbstractChart[Any, Any, Any], to_ambient_chart: AbstractChart[Any, Any, Any], /, *, usys: AbstractUnitSystem | None = None)
Parameters:
Return type:

dict

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

dict

property metric: PullbackMetric

Induced (pullback) Riemannian metric from the ambient manifold.

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

Return the metric angle between two tangent vectors at at.

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

Parameters:
Return type:

AbstractAngle

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

Check that chart belongs to this manifold atlas.

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

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()

Return a default chart from the atlas.

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

Return type:

AbstractChart[Any, Any, Any]

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

Return whether chart belongs to this manifold atlas.

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

chart (AbstractChart[Any, Any, Any])

Return type:

bool

property ndim: int

Return the dimension of the manifold.

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

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)

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

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

Examples

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

With a stacked jax.Array (usys required):

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

With a CDict of quantities (usys optional):

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

Any

final class coordinax.CustomAtlas(charts: tuple[type[AbstractChart[Any, Any, Any]], ...], chart_default: AbstractChart[Any, Any, Any])

Bases: AbstractAtlas

Atlas of explicitly registered charts for a custom manifold.

CustomAtlas is an explicit atlas: chart membership is determined only by the set of chart classes provided at construction time.

A chart belongs to the atlas iff:

  1. Its class is in charts.

  2. Its dimensionality matches the atlas ndim.

The default chart must be one of the registered classes and defines the atlas dimension.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> atlas = cxm.CustomAtlas(
...     charts=(cxc.Cart2D, cxc.Polar2D),
...     chart_default=cxc.cart2d,
... )
>>> atlas.ndim
2
>>> atlas.default_chart()
Cart2D(M=Rn(2))
>>> atlas.has_chart(cxc.polar2d)
True
>>> atlas.has_chart(cxc.cart3d)
False
Parameters:
charts: tuple[type[AbstractChart[Any, Any, Any]], ...]

Explicitly registered chart classes for this atlas.

chart_default: AbstractChart[Any, Any, Any]

Stored default chart instance provided at construction.

default_chart()

Return the default chart for this atlas.

Return type:

AbstractChart[Any, Any, Any]

property ndim: int

Intrinsic dimension of the manifold.

has_chart(chart: AbstractChart[Any, Any, Any])

Return whether the atlas supports the given chart.

Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

bool

final class coordinax.CustomManifold(atlas: AbstractAtlas, metric: AbstractMetricField)

Bases: AbstractManifold

Smooth manifold with a caller-defined explicit atlas.

CustomManifold is a thin wrapper around {class}`CustomAtlas` and inherits all chart validation and transition wrappers from {class}`~coordinax.manifolds.AbstractManifold`.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> atlas = cxm.CustomAtlas(
...     charts=(cxc.Cart2D, cxc.Polar2D),
...     chart_default=cxc.cart2d,
... )
>>> M = cxm.CustomManifold(atlas=atlas, metric=cxm.FlatMetric(2))
>>> M.ndim
2
>>> M.default_chart()
Cart2D(M=Rn(2))
>>> M.has_chart(cxc.polar2d)
True
Parameters:
atlas: AbstractAtlas

Atlas defining chart compatibility for this manifold.

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

Return the metric angle between two tangent vectors at at.

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

Parameters:
Return type:

AbstractAngle

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

Check that chart belongs to this manifold atlas.

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

chart (AbstractChart[Any, Any, Any])

Return type:

None

default_chart()

Return a default chart from the atlas.

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

Return type:

AbstractChart[Any, Any, Any]

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

Return whether chart belongs to this manifold atlas.

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

chart (AbstractChart[Any, Any, Any])

Return type:

bool

property ndim: int

Return the dimension of the manifold.

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

>>> import coordinax.manifolds as cxm
>>> M = cxm.Rn(3)
>>> M.ndim
3
norm(v: Any, *args: Any, at: Any, usys: AbstractUnitSystem | None = None, **kwargs: Any)

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

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

Examples

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

With a stacked jax.Array (usys required):

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

With a CDict of quantities (usys optional):

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

Any

metric: AbstractMetricField

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

coordinax.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.chord_distance(chart: AbstractChart, a: dict, b: dict, /, *, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Return the ambient straight-line distance, on the chart’s manifold.

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

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

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

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

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

Any

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

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

Any chart on the sphere gives the same answer:

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

Any

Return the chord through the manifold’s own ambient space.

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

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

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

Any

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

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

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

Any

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

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

Any

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

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

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

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

>>> p = cx.Point({"theta": u.Angle(jnp.pi / 2, "rad"),
...               "phi": u.Angle(0.0, "rad")}, chart=cxc.sph2)
>>> q = cx.Point({"theta": u.Angle(jnp.pi / 2, "rad"),
...               "phi": u.Angle(jnp.pi / 2, "rad")}, chart=cxc.sph2)
>>> round(float(cx.chord_distance(p, q)), 6)
1.414214

The operands need not share a chart:

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

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

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

Any

coordinax.act(*args, **kwargs)

Apply a transform action to coordinates.

This is the core dispatch function for transform application. Each transform type registers its own implementation via multiple dispatch. Transforms act on various input types (Array, Quantity, Vector, CDict) according to their semantics.

Mathematical Definition:

For a transform \(\mathcal{T}\) parameterized by \(\tau\), this computes:

\[x' = \mathcal{T}(\tau)(x)\]

For tau-independent transforms, \(\tau\) is ignored. For composite transforms (e.g., Composed), the component transforms are applied sequentially. Time dependence itself is carried by exactly one wrapper, TimeDep(builder), where builder(tau) -> AbstractTransform: every other transform holds only constant parameters, and act on a TimeDep evaluates builder(tau) before applying it.

Parameters:
  • op (AbstractTransform) –

    The transform to apply. This can be any transform type:

    • Translate: Spatial translation (point geometry)

    • Rotate: Spatial rotation

    • Identity: No-op

    • Composed: Sequential composition

  • tau (Any) – Parameter for tau-dependent transforms. Pass None for tau-independent transforms. Only TimeDep (and composites containing one) actually consume tau; it evaluates the wrapped builder(tau) before applying it.

  • x (Any) –

    The input to transform. Supported types depend on the transform:

    • Array/ArrayLike: Interpreted as Cartesian point data

    • Quantity: Unitful array, treated as Cartesian point

    • Vector: Role-aware transformation with chart preservation

    • CDict: Low-level component dict

  • *args (Any) – Additional positional/keyword arguments passed to concrete dispatches, e.g. chart, rep, usys.

  • **kwargs (Any) – Additional positional/keyword arguments passed to concrete dispatches, e.g. chart, rep, usys.

Returns:

The transformed input, same type as x.

Return type:

Any

Raises:

NotImplementedError – If no dispatch is registered for the given (transform, input) types.

Notes

  • Transform.__call__: The __call__ method of transforms delegates to this function: op(tau, x) is equivalent to act(op, tau, x).

  • Chart inference: When no chart is provided and the input is an Array or Quantity, the chart is inferred via coordinax.charts.guess_chart.

  • Composite transforms: For Composed, the component transforms are applied in sequence (left-to-right).

See also

coordinax.transforms.act

Concrete dispatch entrypoint used in practice

coordinax.transforms.compose

Compose two transforms into one

coordinax.transforms.simplify

Simplify a transform to canonical form

Examples

>>> import unxt as u
>>> import coordinax.transforms as cxfm

Apply a rotation to a Quantity:

>>> op = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> q = u.Q([1, 0, 0], "km")
>>> cxfm.act(op, None, q).round(3)
Q([0., 1., 0.], 'km')

Apply a translation to a Quantity (usys required):

>>> import jax.numpy as jnp
>>> op = cxfm.Translate.from_([1, 0, 0], "km")
>>> x = jnp.asarray([1.0, 0.0, 0.0])  # metres (dimensionless array)
>>> cxfm.act(op, None, x, usys=u.unitsystems.si).round(3)
Array([1001.,    0.,    0.], dtype=float64)

Composite transform:

>>> R = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> T = cxfm.Translate.from_([1, 0, 0], "km")
>>> op = R | T  # rotate then translate
>>> cxfm.act(op, None, q).round(3)
Q([1., 1., 0.], 'km')
coordinax.act(op: Identity, tau: Any, x: Any, /, *args: Any, **kw: Any) → Any
Parameters:
Return type:

Any

Identity operator - returns input unchanged.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> op = cxfm.identity
>>> q = [1, 2, 3]
>>> cxfm.act(op, None, q) is q
True
>>> q = u.Q([1, 2, 3], "km")
>>> cxfm.act(op, None, q) is q
True
>>> data = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
>>> cxfm.act(op, None, data) is data
True
>>> v = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> cxfm.act(op, None, v) is v
True
coordinax.act(op: AbstractTransform, tau: Any, x: dict, chart: AbstractChart, rep: Representation, /, **kw: Any) → dict
Parameters:
Return type:

Any

Redispatch on the representation’s geometry kind (generic funnel).

coordinax.act(op: AbstractTransform, tau: Any, x: dict, chart: AbstractChart, geom: PointGeometry, rep: Representation, /, **kw: Any) → dict
Parameters:
Return type:

Any

Raise: a transform must register its own point action (the primitive).

coordinax.act(op: AbstractTransform, tau: Any, x: dict, chart: AbstractChart, geom: TangentGeometry, rep: Representation, /, *, at: dict | None = None, at_jet: dict[int, dict | None] | None = None, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Transform tangent data: pushforward or kinematic prolongation.

  • Order-0 (displacement) data and all data under time-independent transforms transform by the frozen-\(\tau\) pushforward.

  • Under a time-dependent transform, order-\(m\) data (\(m \geq 1\)) transforms by the \(m\)-th prolongation, which requires jet slots \(0 \ldots m-1\).

Supply those slots with at_jet, a dict keyed by slot order. at is shorthand for slot 0, which it shares with pushforward; every higher slot is at_jet’s alone. Giving slot 0 both ways raises rather than picking one, and so does giving a slot \(\geq 1\) on a pushforward path that cannot read it — use act_jet for the full prolongation there.

Examples

>>> import quaxed.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.transforms as cxfm

A uniformly rotating frame (angular speed 1 rad/s about z):

>>> zhat = jnp.asarray([0., 0., 1.])
>>> op = cxfm.TimeDep(cxfm.builders.RotationAboutAxis(u.Q(1.0, "rad/s"), axis=zhat))

At tau=0 the rotation is the identity but the velocity still gains the \(\dot R x\) (angular) term:

>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> v = {"x": u.Q(0.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> out = cxfm.act(op, u.Q(0.0, "s"), v, cxc.cart3d, cxr.tangent_geom,
...                cxr.coord_vel, at=at)
>>> out["y"].round(3)
Q(1., 'm / s')
coordinax.act(op: Composed, tau: Any, x: ArrayLike, chart: AbstractChart, rep: Representation, /, **kw: object) → Array
Parameters:
Return type:

Any

Apply Composed to an ArrayLike by sequentially applying each transform.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm
>>> import coordinax.representations as cxr
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> rot = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> pipe = cxfm.Composed((shift,))
>>> x = jnp.array([0.0, 0.0, 0.0])
>>> usys = u.unitsystems.si
>>> cxfm.act(pipe, None, x, cxc.cart3d, cxr.point, usys=usys)
Array([1000., 2000., 3000.], dtype=float64)
coordinax.act(op: Composed, tau: Any, x: dict, chart: AbstractChart, rep: Representation, /, **kw: object) → dict
Parameters:
Return type:

Any

Apply Composed to a CDict by sequentially applying each transform.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm
>>> import coordinax.representations as cxr
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> pipe = cxfm.Composed((shift,))
>>> data = {"x": u.Q(0, "km"), "y": u.Q(0, "km"), "z": u.Q(0, "km")}
>>> cxfm.act(pipe, None, data, cxc.cart3d, cxr.point)
{'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}
coordinax.act(op: Composed, tau: Any, x: AbstractQuantity, chart: AbstractChart, rep: Representation, /, **kw: object) → AbstractQuantity
Parameters:
Return type:

Any

Apply Composed to a Quantity by sequentially applying each transform.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm
>>> import coordinax.representations as cxr
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> pipe = cxfm.Composed((shift,))
>>> q = u.Q([0, 0, 0], "km")
>>> cxfm.act(pipe, None, q, cxc.cart3d, cxr.point)
Q([1, 2, 3], 'km')
coordinax.act(op: Composed, tau: Any, x: AbstractQuantity, /, **kw: object) → AbstractQuantity
Parameters:
Return type:

Any

Apply Composed to a Quantity by sequentially applying each transform.

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> pipe = cxfm.Composed((shift,))
>>> q = u.Q([0, 0, 0], "km")
>>> cxfm.act(pipe, None, q)
Q([1, 2, 3], 'km')
coordinax.act(op: TimeDep, tau: Any, x: dict, chart: AbstractChart, geom: PointGeometry, rep: Representation, /, **kw: Any) → dict
Parameters:
Return type:

Any

Point action: evaluate the family at tau, then act.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.transforms as cxfm
>>> zhat = jnp.array([0.0, 0.0, 1.0])
>>> op = cxfm.TimeDep(cxfm.builders.RotationAboutAxis(u.Q(90, "deg/s"), axis=zhat))
>>> q = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> out = cxfm.act(op, u.Q(1.0, "s"), q, cxc.cart3d, cxr.point)
>>> out["y"].round(3)
Q(1., 'm')
coordinax.act(op: TimeDep, tau: Any, x: dict, chart: AbstractChart, geom: TangentGeometry, rep: Representation, /, *, at: dict | None = None, at_jet: dict[int, dict | None] | None = None, usys: Any = None, **kw: Any) → dict
Parameters:
Return type:

Any

Tangent action of a TimeDep, with the fibre-offset ladder rule.

A TimeDep family whose value is a fibre offset (ladder order \(k \geq 1\)) applies the ladder rule directly: order-\(m\) data gains \(d^{m-k}\delta/d\tau^{m-k}\). Every other TimeDep — in particular every point-acting one — defers to the generic funnel, which recovers its time dependence by differentiating the point action.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.transforms as cxfm

A velocity kick that grows at 5 km/s2 shifts accelerations by its rate:

>>> kick = cxfm.TimeDep.from_(lambda t: cxfm.Translate(
...     {"x": u.Q(5.0, "km/s2") * t, "y": u.Q(0.0, "km/s"),
...      "z": u.Q(0.0, "km/s")},
...     chart=cxc.cart3d, semantic_kind=cxr.vel))
>>> a = {"x": u.Q(1.0, "km/s2"), "y": u.Q(1.0, "km/s2"), "z": u.Q(1.0, "km/s2")}
>>> out = cxfm.act(kick, u.Q(2.0, "s"), a, cxc.cart3d, cxr.coord_acc)
>>> out["x"]
Q(6., 'km / s2')
coordinax.act(op: coordinax.transforms._src.actions.linear.AbstractLinearTransform, tau: Any, x: ArrayLike, chart: AbstractChart, rep: Representation, /, **kw: Any) → Array
Parameters:
Return type:

Any

Apply a linear transform to an Array(like) object.

A bare array with no explicit rep defaults to ~coordinax.point, which this fast path serves directly: a linear map on Cartesian point coordinates is just M x, with no units to track.

An explicit non-point rep says what the data is, so it is not ambiguous and is served by the CDict ladder (#972). Unlike a Translate – where a position shift is the identity on a velocity – a linear map genuinely acts on a tangent, and the CDict path already knows the rule (its pushforward is M v, through the chart Jacobian when the chart is not flat):

>>> import quaxed.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.transforms as cxfm
>>> op = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> v = jnp.asarray([1.0, 0.0, 0.0])
>>> cxfm.act(op, None, v, cxc.cart3d, cxr.coord_vel,
...          usys=u.unitsystems.galactic).round(3)
Array([0., 1., 0.], dtype=float64)
coordinax.act(op: coordinax.transforms._src.actions.linear.AbstractLinearTransform, tau: Any, x: AbstractQuantity, chart: AbstractChart, rep: Representation, /, **kw: Any) → AbstractQuantity
Parameters:
Return type:

Any

Apply a linear transform to a PointGeometry-roled Quantity.

coordinax.act(op: coordinax.transforms._src.actions.linear.AbstractLinearTransform, tau: Any, x: dict, chart: AbstractChart, geom: PointGeometry, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Apply a linear map to a Point-valued coordinate dictionary.

The point is mapped by converting to the chart’s canonical Cartesian chart, applying the matrix in Cartesian components, then converting back. Units are handled by packing Cartesian components into a common unit before the map and restoring it afterward.

coordinax.act(op: coordinax.transforms._src.actions.linear.AbstractLinearTransform, tau: Any, x: dict, chart: AbstractCartesianProductChart, geom: PointGeometry, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Apply a linear map factorwise on Cartesian-product charts.

coordinax.act(op: Affine, tau: Any, x: dict, chart: AbstractChart, geom: PointGeometry, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Apply A x + b in one kernel, in the chart’s Cartesian components.

coordinax.act(op: Translate, tau: Any, x: ArrayLike, chart: AbstractChart, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → Array
Parameters:
Return type:

Any

Apply Translate to an ArrayLike.

The array is interpreted as Cartesian coordinates. The delta is converted to the same unit system to perform the addition.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> x = jnp.array([0.0, 0.0, 0.0])
>>> usys = u.unitsystems.si
>>> cxfm.act(shift, None, x,  cxc.cart3d, cxr.point, usys=usys)
Array([1000., 2000., 3000.], dtype=float64)

A fibre kick (e.g. semantic_kind=vel) cannot infer whether a bare, unitless array is a position (kick is identity) or the matching tangent data (kick applies) — that ambiguity is rejected loudly:

>>> import coordinax.representations as cxr
>>> from dataclassish import replace
>>> vel_shift = replace(shift, semantic_kind=cxr.vel)
>>> try:
...     cxfm.act(vel_shift, None, x, cxc.cart3d, cxr.point, usys=usys)
... except TypeError as e:
...     print(str(e)[:44])
A fibre offset (Translate with semantic_kind

An explicit tangent rep is not ambiguous, so it is served by the CDict ladder: a position translate leaves velocities alone.

>>> cxfm.act(shift, None, x, cxc.cart3d, cxr.coord_vel, usys=usys)
Array([0., 0., 0.], dtype=float64)
coordinax.act(op: Translate, tau: Any, x: AbstractQuantity, chart: AbstractChart, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → AbstractQuantity
Parameters:
Return type:

Any

Apply Translate to a Quantity.

The array is interpreted as Cartesian coordinates. The delta is converted to the same unit system to perform the addition.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> import coordinax.representations as cxr
>>> from dataclassish import replace
>>> import unxt as u
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> x = u.Q([0.0, 0.0, 0.0], "m")
>>> cxfm.act(shift, None, x, cxc.cart3d, cxr.point)
Q([1000., 2000., 3000.], 'm')

Velocity-semantic translate is identity on point quantities:

>>> vel_shift = replace(shift, semantic_kind=cxr.vel)
>>> cxfm.act(vel_shift, None, x, cxc.cart3d, cxr.point)
Q([0., 0., 0.], 'm')

A Quantity’s rep is inferred from its dimensions, so a velocity Quantity arrives with a tangent rep — served by the CDict ladder (#942):

>>> v = u.Q([1.0, 0.0, 0.0], "km/s")
>>> cxfm.act(shift, None, v, cxc.cart3d, cxr.coord_vel)
Q([1., 0., 0.], 'km / s')

A velocity kick does move them:

>>> kick = replace(cxfm.Translate.from_([1, 2, 3], "km/s"), semantic_kind=cxr.vel)
>>> cxfm.act(kick, None, v, cxc.cart3d, cxr.coord_vel)
Q([2., 2., 3.], 'km / s')
coordinax.act(op: Translate, tau: Any, x: dict, chart: AbstractChart, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Apply Translate to a component dictionary (ladder rule).

The behavior follows the time-derivative ladder: with \(k\) the operator’s semantic_kind order and \(m\) the input’s ladder order, the input gains \(d^{m-k}\delta/d\tau^{m-k}\) for \(m \geq k\) (points act as the curve position for \(k = 0\)), and is unaffected for \(m < k\) or for Displacement data (\(m = 0\)).

>>> import coordinax.transforms as cxfm
>>> import unxt as u

Default (displacement-semantic) translate shifts points:

>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> x = {"x": u.Q(0, "km"), "y": u.Q(0, "km"), "z": u.Q(0, "km")}
>>> cxfm.act(shift, None, x, cxc.cart3d, cxr.point)
{'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}

A static translate does not affect velocities:

>>> v = {"x": u.Q(1.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> cxfm.act(shift, None, v, cxc.cart3d, cxr.coord_vel)
{'x': Q(1., 'km / s'), 'y': Q(0., 'km / s'), 'z': Q(0., 'km / s')}

But a time-dependent translate — a ~coordinax.transforms.TimeDep family — boosts velocities by its rate (the kinematic prolongation):

>>> rate = {"x": u.Q(3.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> moving = cxfm.TimeDep(cxfm.builders.UniformTranslation(rate, chart=cxc.cart3d))
>>> at = {"x": u.Q(0.0, "km"), "y": u.Q(0.0, "km"), "z": u.Q(0.0, "km")}
>>> out = cxfm.act(moving, u.Q(2.0, "s"), v, cxc.cart3d, cxr.coord_vel, at=at)
>>> out["x"].round(3)
Q(4., 'km / s')
coordinax.act(op: Boost, tau: Any, x: dict, chart: AbstractChart, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Apply a Galilean boost to a component dictionary.

The point action is \(x \mapsto x + \Delta v\,\tau\); tangent data of ladder order \(m\) gains \(d^m(\Delta v\,\tau)/d\tau^m\) (so \(\Delta v\) for velocities and, for constant \(\Delta v\), nothing for accelerations). Displacements are invariant.

Examples

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.transforms as cxfm
>>> dv = {"x": u.Q(1.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> boost = cxfm.Boost(dv, chart=cxc.cart3d)

Boost shifts velocity components:

>>> v = {"x": u.Q(2.0, "km/s"), "y": u.Q(3.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> cxfm.act(boost, u.Q(0.0, "s"), v, cxc.cart3d, cxr.coord_vel)
{'x': Q(3., 'km / s'), 'y': Q(3., 'km / s'), 'z': Q(0., 'km / s')}

A static boost leaves accelerations unchanged:

>>> a = {"x": u.Q(1.0, "km/s2"), "y": u.Q(0.0, "km/s2"), "z": u.Q(0.0, "km/s2")}
>>> cxfm.act(boost, u.Q(0.0, "s"), a, cxc.cart3d, cxr.coord_acc)
{'x': Q(1., 'km / s2'), 'y': Q(0., 'km / s2'), 'z': Q(0., 'km / s2')}
coordinax.act(op: Boost, tau: Any, x: dict, chart: AbstractChart, geom: TangentGeometry, rep: Representation, /, *, usys: AbstractUnitSystem | None = None, **kw: Any) → dict
Parameters:
Return type:

Any

Boost tangent action (geometry-form): defer to the 5-arg act.

Boost’s 5-arg act implements the kinematic prolongation directly (its closed forms avoid the general jet machinery); the geometry-form delegates to it so the two act forms are identical by construction.

coordinax.act(op: AbstractTransform, tau: Any, x: ArrayLike | AbstractQuantity | unxts.linalg._src._quantity_matrix.QuantityMatrix | dict, /, **kw: Any) → Any
Parameters:
Return type:

Any

Infer the chart and representation, then apply the operator.

A bare input is interpreted as the data for a coordinax.Point in its guessed (Cartesian) chart.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> usys = u.unitsystems.si
>>> x = jnp.asarray([1, 0, 0])  # [m]
>>> T = cxfm.Translate.from_([1, 0, 0], "km")
>>> cxfm.act(T, None, x, usys=usys).round(3)  # needs usys
Array([1001.,    0.,    0.], dtype=float64)
>>> R = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> cxfm.act(R, None, x).round(3)  # no usys required
Array([0., 1., 0.], dtype=float64)
>>> op = R | T  # rotate then translate
>>> cxfm.act(op, None, x, usys=usys).round(3)
Array([1000.,    1.,    0.], dtype=float64)

A Quantity carries units, so its role is inferred from them:

>>> q = u.Q([1, 0, 0], "km")
>>> cxfm.act(R, None, q).round(3)
Q([0., 1., 0.], 'km')
coordinax.act(op: AbstractTransform, tau: Any, x: ArrayLike | AbstractQuantity | unxts.linalg._src._quantity_matrix.QuantityMatrix | dict, chart: AbstractChart, /, **kw: Any) → Any
Parameters:
Return type:

Any

Infer the representation, then apply the operator.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm
>>> R = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> q = u.Q([1, 0, 0], "km")
>>> cxfm.act(R, None, q, cxc.cart3d).round(3)
Q([0., 1., 0.], 'km')
coordinax.act(op: AbstractTransform, tau: Any, x: ArrayLike, chart: AbstractChart, rep: Representation, /, **kw: Any) → Array
Parameters:
Return type:

Any

Apply an operator to an Array(like) object.

The Array is interpreted as coordinates in chart, in the units that usys gives for rep — an array carries none of its own — and the result is written back in those same units.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> op = cx.Rotate.from_euler("z", u.Q(90, "deg"))
>>> x = jnp.asarray([1.0, 0.0, 0.0])
>>> cx.act(op, None, x, cx.cart3d, cx.point).round(3)
Array([0., 1., 0.], dtype=float64)

Operators with no typed Array fast path land here, and are served by coercion to a CDict rather than by re-dispatch (#948 — every arity-6 act takes a CDict, so re-dispatching an array found no method):

>>> import coordinax.transforms as cxfm
>>> import coordinax.charts as cxc
>>> rate = {k: u.Q(v, "kpc/Myr") for k, v in [("x", 1.0), ("y", 0.0), ("z", 0.0)]}
>>> boost = cxfm.Boost(rate, chart=cxc.cart3d)
>>> cxfm.act(boost, u.Q(1.0, "Myr"), x, usys=u.unitsystems.galactic).round(3)
Array([2., 0., 0.], dtype=float64)
coordinax.act(op: AbstractTransform, tau: Any, x: AbstractQuantity, chart: AbstractChart, rep: Representation, /, **kw: Any) → AbstractQuantity
Parameters:
Return type:

Any

Apply operator, routing through the CDict-based implementation.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm
>>> import coordinax.representations as cxr
>>> op = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> q = u.Q([1, 0, 0], "km")

Directly access this registered method, bypassing more efficient methods.

>>> func = cxfm.act.invoke(cxfm.Rotate, None, u.Q, cxc.Cart3D, cxr.Representation)
>>> func(op, None, q, cxc.cart3d, cxr.point).round(3)
Q([0., 1., 0.], 'km')
coordinax.act(op: AbstractTransform, tau: Any, x: unxts.linalg._src._quantity_matrix.QuantityMatrix, chart: AbstractChart, rep: Representation, /, **kw: Any) → unxts.linalg._src._quantity_matrix.QuantityMatrix
Parameters:
Return type:

Any

Apply an operator to a QuantityMatrix with explicit chart and rep.

Routes through the CDict-based implementation, then repacks the result into a QuantityMatrix.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm
>>> import coordinax.representations as cxr
>>> import unxts.linalg as ul
>>> op = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> qm = ul.QuantityMatrix(
...     jnp.array([1.0, 0.0, 0.0]),
...     unit=("km", "km", "km"),
... )
>>> result = cxfm.act(op, None, qm, cxc.cart3d, cxr.point)
>>> result.value.round(3)
Array([0., 1., 0.], dtype=float64)
coordinax.act(op: AbstractTransform, tau: Any, x: Tangent, /, *, at: Any = None, at_jet: dict[int, Any] | None = None, **kw: Any) → Tangent
Parameters:
Return type:

Any

Act a frame transform on a tangent Tangent.

at (the base point) anchors the transformation where one is needed — for Jacobian pushforwards in non-Cartesian charts, and as slot 0 of the kinematic prolongation under time-dependent transforms. at_jet is the general form, a dict keyed by jet slot, and the only spelling that reaches slot 1 and above. Either may hold Point/Tangent instances (whose .data is used, after a chart check) or raw CDict data — at_jet’s values slot by slot.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.frames as cxf
>>> Rz = jnp.asarray([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
>>> op = cx.Rotate(Rz)
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cx.cart3d, cx.coord_vel,
... )
>>> transformed = cx.act(op, None, v)
>>> print(transformed)
<Tangent: chart=Cart3D (x, y, z) [m / s]
    [0. 1. 0.]>
coordinax.act(op: AbstractTransform, tau: Any, x: Point, /, **kw: Any) → Point
Parameters:
Return type:

Any

Act a frame transform on a Point.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> Rz = jnp.asarray([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
>>> op = cx.Rotate(Rz)
>>> q = u.Q([1, 0, 0], "km")
>>> vec = cx.Point.from_(q)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [km]
    [1 0 0]>
>>> transformed_vec = cx.act(op, None, vec)
>>> print(transformed_vec)
<Point: chart=Cart3D (x, y, z) [km]
    [0 1 0]>
coordinax.act(op: AbstractTransform, tau: Any, x: Coordinate, /, **kw: Any) → Coordinate
Parameters:
Return type:

Any

Act a frame transform on a Coordinate (point + all fibres).

>>> import coordinax as cx
>>> import coordinax.frames as cxf
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> point = cx.Point.from_([1.0, 0.0, 0.0], "m", cxf.alice)
>>> vel = cx.Tangent(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel, frame=cxf.alice,
... )
>>> pv = cx.Coordinate(point=point, velocity=vel)
>>> pv_alex = pv.to_frame(cxf.alex)
>>> pv_alex.frame
Alex()
>>> pv_alex["velocity"].frame
Alex()

A time-dependent transform prolongs the whole bundle jointly: a uniformly moving translation boosts the velocity fibre by its rate:

>>> import coordinax.transforms as cxfm
>>> rate = {"x": u.Q(3.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> moving = cxfm.TimeDep(cxfm.builders.UniformTranslation(rate, chart=cxc.cart3d))
>>> out = cx.act(moving, u.Q(2.0, "s"), pv)
>>> out.point.data["x"], out["velocity"].data["x"]
(Q(7., 'm'), Q(4., 'm / s'))
coordinax.frame_transition(*args, **kwargs)

Return the transform operator that maps coordinates from one frame to another.

Given a source frame and a target frame, frame_transition computes the AbstractTransform (or composed chain of transforms) that, when applied to coordinates expressed in from_frame, yields coordinates expressed in to_frame.

Notes

  • Each pair of concrete frame types registers its own dispatch. Calling frame_transition(frame_a, frame_a) returns Identity().

  • For TransformedReferenceFrame, the transition is constructed automatically by composing the base-frame transition with the stored xop.

  • The returned transform is invertible: op.inverse gives the to_frame → from_frame direction.

  • Build the operator outside ``jit``, then pass it in as an argument. frame_transition is pure Python: it walks the dispatch table, composes the chain and simplifies it. That work does not belong inside a traced function – do it once, up front, and hand the operator to the jitted function that applies it. Frames and transforms are pytrees, so changing frame parameters reuses the same compiled code; closing over the operator instead makes it static and buys a fresh compile per operator. The payoff is large: applying a precomputed operator under jit runs in microseconds rather than milliseconds (roughly 30x at N=1, and over 100x against to_frame, which rebuilds the operator on every call). Eager application costs a few milliseconds of fixed Python plus tens of nanoseconds per element, so below ~1e5 elements you are paying almost entirely for Python.

  • This function uses multiple dispatch. To inspect all registered pairs:

    >>> import coordinax.frames as cxf
    >>> cxf.frame_transition.methods
    List of 20 method(s):
        [0] frame_transition(from_frame: ...)
    

See also

coordinax.transforms.act

Apply a transform to coordinates

coordinax.transforms.compose

Compose two transforms into one

Examples

>>> import coordinax.frames as cxf

Same-to-same frame (identity):

>>> cxf.frame_transition(cxf.alice, cxf.alice)
Identity()

Alice → Alex:

>>> op = cxf.frame_transition(cxf.alice, cxf.alex)
>>> op
Composed(( Translate(...), Rotate(...) ))

Alex → Alice (inverse direction):

>>> op = cxf.frame_transition(cxf.alex, cxf.alice)
>>> op
Composed(( Rotate(...), Translate(...) ))

Using a TransformedReferenceFrame:

>>> import coordinax.transforms as cxfm
>>> import quaxed.numpy as jnp
>>> from coordinaxs.astro import ICRS
>>> R = cxfm.Rotate(jnp.asarray([[0., -1, 0], [1, 0, 0], [0, 0, 1]]))
>>> frame = cxf.TransformedReferenceFrame(ICRS(), R)
>>> op = cxf.frame_transition(ICRS(), frame)
>>> type(op).__name__
'Composed'

Under ``jit`` – operator built outside, passed in:

>>> import equinox as eqx
>>> import coordinax as cx
>>> import coordinax.transforms as cxfm
>>> op = cxf.frame_transition(cxf.alice, cxf.alex)
>>> @eqx.filter_jit
... def to_alex(op, p):
...     return cxfm.act(op, None, p)
>>> p = cx.Point.from_([1, 2, 3], "kpc", cxf.alice)
>>> print(to_alex(op, p))
<Point: chart=Cart3D (x, y, z) [kpc]
    [-2.  1.  3.]>
coordinax.frame_transition(from_frame: AbstractReferenceFrame, to_frame: AbstractReferenceFrame, /) → Identity
Parameters:
Return type:

Any

Return an identity operator for frames that are the same.

>>> import coordinax.frames as cxf
>>> cxf.frame_transition(cxf.alice, cxf.alice)
Identity()
>>> cxf.frame_transition(cxf.alex, cxf.alex)
Identity()
>>> cxf.frame_transition(cxf.bob, cxf.bob)
Identity()
>>> cxf.frame_transition(cxf.carol, cxf.carol)
Identity()
coordinax.frame_transition(from_frame: Alice, to_frame: Alex, /) → Composed
Parameters:
Return type:

Any

Transform from Alice’s frame to Alex’s frame.

>>> import unxt as u
>>> import coordinax as cx
>>> op = cxf.frame_transition(cxf.alice, cxf.alex)
>>> print(op)
Composed(( Translate(...), Rotate(...) ))
coordinax.frame_transition(from_frame: Alice, to_frame: Bob, /) → Composed
Parameters:
Return type:

Any

Transform from Alice’s frame to Bob’s frame.

A spacetime translation followed by a Lorentz boost of \(\beta = 0.9\) along Alice’s x-axis. Bob moves too fast for a Galilean velocity kick: adding 0.9 c to a particle already at 0.3 c would give 1.2 c. See Bob for that comparison worked through, and Carol for the slow frame where the kick is the right physics.

Because a boost mixes time into the spatial components, this transition acts on ~coordinax.charts.minkowskict rather than a 3-D chart.

Examples

>>> import coordinax.frames as cxf
>>> op = cxf.frame_transition(cxf.Alice(), cxf.Bob())
>>> [type(t).__name__ for t in op.transforms]
['Translate', 'LorentzBoost']
coordinax.frame_transition(from_frame: Alice, to_frame: Carol, /) → Composed
Parameters:
Return type:

Any

Transform from Alice’s frame to Carol’s frame.

A spatial translation followed by a velocity kick – the transformation Bob carried before he became relativistic. The kick has well-defined actions on each kinematic role:

  • Point: identity (a velocity kick does not move points)

  • Displacement: identity (Galilean invariant)

  • Velocity: adds \(v_0\)

  • Acceleration: identity (for a constant kick)

Examples

>>> import coordinax.frames as cxf
>>> op = cxf.frame_transition(cxf.alice, cxf.carol)
>>> [type(t).__name__ for t in op.transforms]
['Translate', 'Translate']
coordinax.frame_transition(from_frame: AbstractReferenceFrame, to_frame: AbstractReferenceFrame, /) → Composed
Parameters:
Return type:

Any

Transform back.

>>> import unxt as u
>>> import coordinax as cx
>>> cxf.frame_transition(cxf.alex, cxf.alice)
Composed(( Rotate(...), Translate(...) ))
>>> [type(t).__name__ for t in cxf.frame_transition(cxf.bob, cxf.alice).transforms]
['LorentzBoost', 'Translate']
>>> op = cxf.frame_transition(cxf.carol, cxf.alice)
>>> [type(t).__name__ for t in op.transforms]
['Translate', 'Translate']
coordinax.frame_transition(from_frame: NoFrame, to_frame: NoFrame, /) → Identity
Parameters:
Return type:

Any

Null-to-null frame transition is always the identity.

When both source and target frames are noframe (i.e. the vector is frame-agnostic) there is nothing to transform, so the result is the identity operation.

>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> op = cxf.frame_transition(cxf.noframe, cxf.noframe)
>>> isinstance(op, cxfm.Identity)
True
coordinax.frame_transition(from_frame: NoFrame, to_frame: AbstractReferenceFrame, /) → NoReturn
Parameters:
Return type:

Any

Cannot transform from the null frame.

>>> import coordinax.frames as cxf
>>> try:
...     cxf.frame_transition(cxf.noframe, cxf.alice)
... except cxf.FrameTransformError as e:
...     print(e)
Cannot transform from the null frame.
coordinax.frame_transition(from_frame: AbstractReferenceFrame, to_frame: NoFrame, /) → NoReturn
Parameters:
Return type:

Any

Cannot transform to the null frame.

Carries the same precedence as its “from the null frame” sibling above, and for the same reason: without it a rule keyed on the source type – frame_transition(TransformedReferenceFrame, AbstractReferenceFrame) – matches equally well, and plum has no ground to choose. Any transformed frame going to noframe then raised AmbiguousLookupError instead of saying what was wrong, while the mirror direction, already at precedence 1, answered properly.

>>> import coordinax.frames as cxf
>>> try:
...     cxf.frame_transition(cxf.alice, cxf.noframe)
... except cxf.FrameTransformError as e:
...     print(e)
Cannot transform to the null frame.
coordinax.frame_transition(from_frame: AbstractReferenceFrame, to_frame: AbstractTransformedReferenceFrame) → AbstractTransform
Parameters:
Return type:

Any

Return a frame transform operator to a transformed frame.

>>> import quaxed.numpy as jnp
>>> import coordinax.vectors as cxv
>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> from coordinaxs.astro import ICRS
>>> R = cxfm.Rotate(jnp.asarray([[0., -1, 0], [1, 0, 0], [0, 0, 1]]))
>>> frame = cxf.TransformedReferenceFrame(ICRS(), R)
>>> frame
TransformedReferenceFrame(base_frame=ICRS(), xop=Rotate(R=f64[3,3]))

Let’s transform a position from the base frame to the transformed frame:

>>> op = cxf.frame_transition(ICRS(), frame)
>>> q_icrs = cxv.Point.from_([1, 0, 0], "kpc")
>>> q_frame = op(q_icrs)
>>> print(q_frame)
<Point: chart=Cart3D (x, y, z) [kpc]
    [0. 1. 0.]>
coordinax.frame_transition(from_frame: AbstractTransformedReferenceFrame, to_frame: AbstractReferenceFrame) → AbstractTransform
Parameters:
Return type:

Any

Return a frame transform operator from a transformed frame.

>>> import quaxed.numpy as jnp
>>> import coordinax.vectors as cxv
>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> from coordinaxs.astro import ICRS
>>> R = cxfm.Rotate(jnp.asarray([[0., -1, 0], [1, 0, 0], [0, 0, 1]]))
>>> frame = cxf.TransformedReferenceFrame(ICRS(), R)
>>> frame
TransformedReferenceFrame(base_frame=ICRS(), xop=Rotate(R=f64[3,3]))

Let’s transform a position from the base frame to the transformed frame:

>>> op = cxf.frame_transition(frame, ICRS())
>>> q_icrs = cxv.Point.from_([0, 1, 0], "kpc")
>>> q_frame = op(q_icrs)
>>> print(q_frame)
<Point: chart=Cart3D (x, y, z) [kpc]
    [1. 0. 0.]>
coordinax.frame_transition(from_frame: AbstractTransformedReferenceFrame, to_frame: AbstractTransformedReferenceFrame) → AbstractTransform
Parameters:
Return type:

Any

Return a frame transform operator between two transformed frames.

When sameness is statically knowable – the same object, or equal-but-distinct with concrete leaves – the result is the identity transform. Frames arriving as jax.jit arguments are neither: tracing hands this function two distinct tracer-backed frames, so the shortcut is skipped and the general transform is built instead. That transform is correct either way; only the shortcut is lost.

>>> import quaxed.numpy as jnp
>>> import coordinax.vectors as cxv
>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> from coordinaxs.astro import ICRS
>>> R = cxfm.Rotate(jnp.asarray([[0., -1, 0], [1, 0, 0], [0, 0, 1]]))
>>> frame1 = cxf.TransformedReferenceFrame(ICRS(), R)

Same frame → identity:

>>> cxf.frame_transition(frame1, frame1)
Identity()

An equal-but-distinct frame is the same frame, so it too is the identity rather than a Rotate | Identity | Rotate chain:

>>> frame1b = cxf.TransformedReferenceFrame(
...     ICRS(), cxfm.Rotate(jnp.asarray([[0., -1, 0], [1, 0, 0], [0, 0, 1]]))
... )
>>> cxf.frame_transition(frame1, frame1b)
Identity()
>>> shift = cxfm.Translate.from_([1, 0, 0], "kpc")
>>> frame2 = cxf.TransformedReferenceFrame(frame1, shift)
>>> op1to2 = cxf.frame_transition(frame1, frame2)
>>> q_frame1 = cxv.Point.from_([0, -1, 0], "kpc")
>>> q_frame2 = op1to2(q_frame1)
>>> print(q_frame2)
<Point: chart=Cart3D (x, y, z) [kpc]
    [ 1. -1.  0.]>
coordinax.frame_transition(from_frame: AbstractSpaceFrame, to_frame: AbstractSpaceFrame, /) → AbstractTransform
Parameters:
Return type:

Any

Compute frame transformations with ICRS as the intermediary.

>>> import plum
>>> import unxt as u
>>> import coordinax.frames as cxf
>>> import coordinaxs.astro as cxastro

Routing through ICRS needs both legs registered: one direction alone leaves the other, and the frame’s self-transition, unroutable.

>>> class MySpaceFrame(cxastro.AbstractSpaceFrame):
...     pass
>>> @plum.dispatch
... def frame_transition(from_frame: MySpaceFrame, to_frame: ICRS, /) -> cxfm.AbstractTransform:
...     return cxfm.Rotate.from_euler("z", u.Q(10, "deg"))
>>> @plum.dispatch
... def frame_transition(from_frame: ICRS, to_frame: MySpaceFrame, /) -> cxfm.AbstractTransform:
...     return cxfm.Rotate.from_euler("z", u.Q(-10, "deg"))

We can transform from MySpaceFrame to a Galactocentric frame, even though we don’t have a direct transformation defined:

>>> my_frame = MySpaceFrame()
>>> gcf_frame = cxastro.Galactocentric()
>>> op = cxf.frame_transition(my_frame, gcf_frame)
>>> op
Composed((
  Affine(
    f64[3,3](jax),
    {...},
    Cart3D(M=Rn(3)),
    coordinax.transforms._src.groups.EuclideanGroup
  ),
  Translate(
      {...},
      chart=Cart3D(M=Rn(3)),
      semantic_kind=vel
  )
))

The two rotations and the spatial translation fuse into one Affine kernel; the velocity offset stays separate because it acts on the tangent fibre rather than on the point.

The return leg routes the same way, off the (ICRS, MySpaceFrame) registration:

>>> type(cxf.frame_transition(gcf_frame, my_frame)).__name__
'Composed'
coordinax.frame_transition(from_frame: AbstractSpaceFrame, to_frame: ICRS, /) → NoReturn
Parameters:
Return type:

Any

No registered transformation to ICRS.

>>> import coordinax.frames as cxf
>>> import coordinaxs.astro as cxastro
>>> class UnregisteredFrame(cxastro.AbstractSpaceFrame):
...     pass
>>> try:
...     cxf.frame_transition(UnregisteredFrame(), cxastro.icrs)
... except cxf.FrameTransformError as e:
...     print(e)
No `frame_transition` registered from UnregisteredFrame to ICRS.
coordinax.frame_transition(from_frame: ICRS, to_frame: AbstractSpaceFrame, /) → NoReturn
Parameters:
Return type:

Any

No registered transformation from ICRS.

>>> import coordinax.frames as cxf
>>> import coordinaxs.astro as cxastro
>>> class UnregisteredFrame(cxastro.AbstractSpaceFrame):
...     pass
>>> try:
...     cxf.frame_transition(cxastro.icrs, UnregisteredFrame())
... except cxf.FrameTransformError as e:
...     print(e)
No `frame_transition` registered from ICRS to UnregisteredFrame.
coordinax.frame_transition(from_frame: ICRS, to_frame: ICRS, /) → Identity
Parameters:
Return type:

Any

Return an identity operator for the ICRS->ICRS transformation.

>>> import coordinax.frames as cxf
>>> import coordinaxs.astro as cxastro
>>> icrs_frame = cxastro.ICRS()
>>> frame_op = cxf.frame_transition(icrs_frame, icrs_frame)
>>> frame_op
Identity()
coordinax.frame_transition(from_frame: Galactic, to_frame: Galactic, /) → Identity
Parameters:
Return type:

Any

Return an identity operator for the Galactic->Galactic transformation.

>>> import coordinax.frames as cxf
>>> import coordinaxs.astro as cxastro
>>> cxf.frame_transition(cxastro.galactic, cxastro.galactic)
Identity()
coordinax.frame_transition(from_frame: ICRS, to_frame: Galactic, /) → Rotate
Parameters:
Return type:

Any

Return an ICRS to Galactic frame transformation operator.

The Galactic frame is related to ICRS by a fixed rotation (the IAU 1958 definition, via Astropy’s FK4-B1950-derived matrix including the ICRS/FK5 frame bias). Being a pure static rotation, it acts on positions, displacements, velocities, and accelerations alike by the rotation matrix.

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinaxs.astro as cxastro
>>> frame_op = cx.frame_transition(cxastro.icrs, cxastro.galactic)
>>> frame_op
Rotate(f64[3,3](jax))

The North Galactic Pole (in ICRS) maps to the +z axis:

>>> ngp = cx.Point.from_(
...     {"lon": u.Q(192.8594812065348, "deg"), "lat": u.Q(27.12825118085622, "deg"),
...      "distance": u.Q(1, "kpc")}, cx.lonlat_sph3d)
>>> print(frame_op(ngp).cconvert(cx.cart3d).round(6))
<Point: chart=Cart3D (x, y, z) [kpc]
    [0. 0. 1.]>

Velocities rotate too:

>>> v = cx.Tangent.from_([100.0, 0.0, 0.0], "km/s")
>>> print(frame_op(None, v).round(3))
<Tangent: chart=Cart3D (x, y, z) [km / s]
    [ -5.488  49.411 -86.767]>
coordinax.frame_transition(from_frame: Galactic, to_frame: ICRS, /) → Rotate
Parameters:
Return type:

Any

Return a Galactic to ICRS frame transformation operator.

The inverse of the ICRS -> Galactic rotation (the matrix transpose).

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinaxs.astro as cxastro
>>> fwd = cx.frame_transition(cxastro.icrs, cxastro.galactic)
>>> bwd = cx.frame_transition(cxastro.galactic, cxastro.icrs)
>>> q = u.Q([1.0, 2.0, 3.0], "kpc")
>>> bwd(fwd(q)).round(6)
Q([1., 2., 3.], 'kpc')
coordinax.frame_transition(from_frame: Galactocentric, to_frame: Galactocentric, /) → AbstractTransform
Parameters:
Return type:

Any

Return the transform between two Galactocentric frames.

The identity when the two are the same frame; otherwise the simplified composition of the routes through ICRS.

>>> import unxt as u
>>> import coordinax.frames as cxf
>>> import coordinaxs.astro as cxastro
>>> gcf_frame = cxastro.Galactocentric()
>>> frame_op = cxf.frame_transition(gcf_frame, gcf_frame)
>>> frame_op
Identity()

An equal-but-distinct frame is the same frame too:

>>> cxf.frame_transition(gcf_frame, cxastro.Galactocentric())
Identity()
>>> gcf_frame2 = cxastro.Galactocentric(roll=u.Q(10, "deg"))
>>> frame_op2 = cxf.frame_transition(gcf_frame, gcf_frame2)
>>> frame_op2
Composed((
  Translate(
      {...},
      chart=Cart3D(M=Rn(3)),
      semantic_kind=vel
  ),
  Affine(
    f64[3,3](jax),
    {...},
    Cart3D(M=Rn(3)),
    coordinax.transforms._src.groups.EuclideanGroup
  ),
  Translate(
      {...},
      chart=Cart3D(M=Rn(3)),
      semantic_kind=vel
  )
))

Seven operators become three: the five spatial ones – three rotations and two translations, interleaved so no pairwise rule could reach them – fuse into a single Affine. The velocity offsets bracket it untouched, acting on the tangent fibre rather than the point.

coordinax.frame_transition(from_frame: ICRS, to_frame: Galactocentric, /) → Composed
Parameters:
Return type:

Any

Return an ICRS to Galactocentric frame transformation operator.

This transformation applies a series of Galilean transformations to convert coordinates from the ICRS frame to a Galactocentric frame. The transformation accounts for:

  1. Rotation to align with the Galactic coordinate system

  2. Translation to the Galactic center

  3. Tilt correction for the Sun’s height above the Galactic plane

  4. Velocity boost to the Galactocentric rest frame

Notes

The transformation is composed of:

  • R: Combined rotation matrix (longitude x latitude x roll)

  • offset_q: Translation by the Galactic center distance

  • H: Rotation to account for Sun’s height above plane

  • offset_v: Solar-velocity kick to the Galactocentric rest frame (a fibre-only Translate(semantic_kind=vel))

The default Galactocentric frame uses parameters from the Astropy default values (as of v4.0), which are based on various literature sources.

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinaxs.astro as cxastro

Create the frames:

>>> icrs_frame = cxastro.ICRS()
>>> gcf_frame = cxastro.Galactocentric()

Define the transformation operator:

>>> frame_op = cx.frame_transition(icrs_frame, gcf_frame)
>>> frame_op
Composed((
  Rotate(f64[3,3](jax)),
  Translate( {...}, chart=Cart3D(M=Rn(3)) ),
  Rotate(f64[3,3](jax)),
  Translate( {...}, chart=Cart3D(M=Rn(3)), semantic_kind=vel )
))

Transform a position at the origin of ICRS to Galactocentric:

>>> q = cx.Point.from_([0, 0, 0], "pc")
>>> print(frame_op(q))
<Point: chart=Cart3D (x, y, z) [pc]
    [-8121.973     0.       20.8  ]>

The result shows the Sun’s position in Galactocentric coordinates: the Sun is about 8.1 kpc from the Galactic center along the x-axis and about 21 pc above the Galactic plane.

Velocities are transformed too: a star at rest in ICRS moves with the solar velocity in the Galactocentric frame:

>>> point = cx.Point.from_([0, 0, 0], "pc")
>>> vel = cx.Tangent.from_([0.0, 0.0, 0.0], "km/s")
>>> pv = cx.Coordinate(point=point, velocity=vel)
>>> out = frame_op(None, pv)
>>> print(out["velocity"])
<Tangent: chart=Cart3D (x, y, z) [km / s]
    [ 12.9  245.6    7.78]>

Works with unxt Quantities too:

>>> q = u.Q([0, 0, 0], "pc")
>>> frame_op(q)
Q([-8121.97336612,     0.        ,    20.8       ], 'pc')

Transform a star position in spherical coordinates:

>>> star_q = cx.Point.from_(
...     {"lon": u.Q(279.23, "deg"), "lat": u.Q(38.78, "deg"),
...      "distance": u.Q(25, "pc")}, cx.lonlat_sph3d)
>>> gcf_q = frame_op(star_q)
>>> gcf_q = gcf_q.cconvert(cx.cart3d)
>>> print(gcf_q)
<Point: chart=Cart3D (x, y, z) [pc]
    [-8112.898    21.798    29.015]>
coordinax.frame_transition(from_frame: Galactocentric, to_frame: ICRS, /) → AbstractTransform
Parameters:
Return type:

Any

Return a Galactocentric to ICRS frame transformation operator.

This transformation inverts the ICRS→Galactocentric transformation, converting coordinates from a Galactocentric frame back to ICRS.

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinaxs.astro as cxastro

Create the frames:

>>> icrs_frame = cxastro.ICRS()
>>> gcf_frame = cxastro.Galactocentric()

Define the transformation operator:

>>> frame_op = cx.frame_transition(gcf_frame, icrs_frame)

Transform from Galactocentric origin to ICRS:

>>> q = cx.Point.from_([0, 0, 0], "pc")
>>> print(frame_op(q).round(0))
<Point: chart=Cart3D (x, y, z) [pc]
    [ -446. -7094. -3930.]>

This shows the Galactic center’s position in ICRS coordinates from the Sun’s perspective.

Works with unxt Quantities:

>>> q = u.Q([0, 0, 0], "pc")
>>> frame_op(q).round(0)
Q([ -446., -7094., -3930.], 'pc')

Transform a star in Galactocentric coordinates back to ICRS:

>>> star_q = cx.Point.from_([-8112.9, 21.8, 29.0], "pc")
>>> icrs_q = frame_op(star_q)
>>> icrs_q = icrs_q.cconvert(cx.lonlat_sph3d)
>>> print(icrs_q.uconvert({u.dimension("angle"): "deg", u.dimension("length"): "pc"}))
<Point: chart=LonLatSpherical3D (lon[deg], lat[deg], distance[pc])
    [-80.728  38.775  24.996]>

Notes

This transformation is implemented by computing the inverse of the ICRS→Galactocentric transformation. The operator pipeline is simplified automatically for computational efficiency.

coordinax.frame_transition(from_frame: AbstractReferenceFrame, to_frame: AbstractParallelTransportFrame) → AbstractTransform
Parameters:
Return type:

Any

Return the composite transform operator to a curve frame.

Composes the transition from from_frame to the curve frame’s base_frame with the curve-frame’s forward transform xop:

\[\mathcal{A} \to \mathcal{F}_\gamma = (\mathcal{A} \to \mathcal{B}) \circ (\mathcal{B} \to \mathcal{F}_\gamma)\]

where \(\mathcal{B}\) is to_frame.base_frame.

Parameters:
Returns:

The composed frame-transition operator.

Return type:

Any

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> 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")
>>> fs_frame = cxfc.FrenetSerretFrame.from_curve(cxf.Alice(), circle, "s")
>>> op = cxf.frame_transition(cxf.Alice(), fs_frame)
>>> isinstance(op, cxfm.AbstractTransform)
True
coordinax.frame_transition(from_frame: AbstractParallelTransportFrame, to_frame: AbstractReferenceFrame) → AbstractTransform
Parameters:
Return type:

Any

Return the composite transform operator from a curve frame.

Composes the inverse curve-frame transform xop_inv with the transition from the base frame to to_frame:

\[\mathcal{F}_\gamma \to \mathcal{A} = (\mathcal{F}_\gamma \to \mathcal{B}) \circ (\mathcal{B} \to \mathcal{A})\]

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> 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")
>>> fs_frame = cxfc.FrenetSerretFrame.from_curve(cxf.Alice(), circle, "s")
>>> op = cxf.frame_transition(fs_frame, cxf.Alice())
>>> isinstance(op, cxfm.AbstractTransform)
True
coordinax.frame_transition(from_frame: AbstractParallelTransportFrame, to_frame: AbstractParallelTransportFrame) → AbstractTransform
Parameters:
Return type:

Any

Return the composite transform operator between two curve frames.

When both source and target are curve frames, the transition composes three stages:

\[\mathcal{F}_1 \to \mathcal{F}_2 = (\mathcal{F}_1 \to \mathcal{B}_1) \circ (\mathcal{B}_1 \to \mathcal{B}_2) \circ (\mathcal{B}_2 \to \mathcal{F}_2)\]

This dispatch has precedence=1 so it takes priority over the more general to-curve-frame and from-curve-frame dispatches when both arguments are AbstractParallelTransportFrame.

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.frames as cxf
>>> import coordinax.transforms as cxfm
>>> 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")
>>> fs1 = cxfc.FrenetSerretFrame.from_curve(cxf.Alice(), circle, "s")
>>> fs2 = cxfc.FrenetSerretFrame.from_curve(cxf.Alex(), circle, "s")
>>> op = cxf.frame_transition(fs1, fs2)
>>> isinstance(op, cxfm.AbstractTransform)
True
Parameters:
Return type:

Any

coordinax.simplify(*args, **kwargs)

Simplify a transform to a canonical form.

This function takes a transform and attempts to simplify it, returning a new, potentially simpler transform. For example, a Translate with zero delta simplifies to Identity.

Notes

In general this cannot be called in a JIT’ed context because it generally requires inspecting values to determine if simplifications are possible.

This function uses multiple dispatch. Each operator type registers its own simplification rules.

To see all available dispatches:

>>> import coordinax.transforms as cxfm
>>> cxfm.simplify.methods
List of 7 method(s):
    [0] simplify(...)

Examples

>>> import coordinax.transforms as cxfm

Identity (already simple):

>>> op = cxfm.Identity()
>>> cxfm.simplify(op) is op
True

Translate with zero delta:

>>> op = cxfm.Translate.from_([0, 0, 0], "m")
>>> cxfm.simplify(op)
Identity()

Translate with non-zero delta (no simplification):

>>> op = cxfm.Translate.from_([1, 2, 3], "m")
>>> simplified = cxfm.simplify(op)
>>> type(simplified).__name__
'Translate'

Rotate with identity matrix:

>>> import unxt as u
>>> op = cxfm.Rotate.from_euler("z", u.Q(0, "deg"))
>>> cxfm.simplify(op)
Identity()
coordinax.simplify(op: Identity, /, **__: Any) → Identity
Parameters:
Return type:

Any

Simplify a {class}`coordinax.transforms.Identity` operator.

The coordinax.transforms.Identity operator is the simplest operator and cannot be simplified further:

>>> import coordinax.transforms as cxfm
>>> op = cxfm.identity
>>> cxfm.simplify(op) is op
True
coordinax.simplify(op: Composed, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify a Composed transform.

Recursively simplifies each sub-operator, strips Identity elements, and merges adjacent same-type operators in a single left-to-right pass: two rotations collapse to one (R1 @ R2), two same-role static translations to one, and — via the merge plus per-operator identity detection — an inverse pair cancels to Identity.

With approx=True (default) the value-inspecting per-operator collapses run too (an identity rotation matrix, a zero translation). With approx=False only value-free structural simplification is done, which is always safe to call inside jax.jit.

>>> import unxt as u
>>> import coordinax.transforms as cxfm

Identity elements are stripped:

>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.simplify(cxfm.Composed((shift, cxfm.Identity())))
Translate(...)

Adjacent rotations merge into one:

>>> R1 = cxfm.Rotate.from_euler("z", u.Q(30, "deg"))
>>> R2 = cxfm.Rotate.from_euler("z", u.Q(60, "deg"))
>>> cxfm.simplify(cxfm.Composed((R1, R2)))
Rotate(...)

An inverse pair cancels to Identity:

>>> cxfm.simplify(cxfm.Composed((R1, R1.inverse)))
Identity()
coordinax.simplify(op: TimeDep, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Pass a TimeDep through unchanged (its value is unknown until tau).

Examples

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> zhat = jnp.array([0.0, 0.0, 1.0])
>>> op = cxfm.TimeDep(cxfm.builders.RotationAboutAxis(u.Q(1.0, "rad/s"), axis=zhat))
>>> cxfm.simplify(op) is op
True
coordinax.simplify(op: coordinax.transforms._src.actions.add.AbstractAdd, /, *, approx: bool = True, **kw: Any) → coordinax.transforms._src.actions.add.AbstractAdd | Identity
Parameters:
Return type:

Any

Simplify a AbstractAdd operator.

A translation with zero delta simplifies to Identity. This is a value-inspecting rule, so it is skipped when approx=False, and when any leaf of delta is traced – under jax.jit the values are not known, which is exactly when the answer is “do not simplify”.

>>> import coordinax.transforms as cxfm
>>> op = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.simplify(op)
Translate(...)
>>> op = cxfm.Translate.from_([0, 0, 0], "km")
>>> cxfm.simplify(op)
Identity()
coordinax.simplify(op: Affine, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Collapse to Identity when the map is one; otherwise keep the fusion.

Both checks inspect values, so both are skipped when approx=False, and when A or b is traced – the same trace-safety contract the sibling operators honour.

coordinax.simplify(op: Rotate, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify the Galilean rotation operator.

>>> import quaxed.numpy as jnp
>>> import coordinax as cx

An operator with a non-identity rotation matrix is not simplified:

>>> Rz = jnp.asarray([[0, -1, 0], [1, 0,  0], [0, 0, 1]])
>>> op = cxfm.Rotate(Rz)
>>> cxfm.simplify(op)
Rotate(i64[3,3](jax))

An operator with an identity rotation matrix is simplified:

>>> op = cxfm.Rotate(jnp.eye(3))
>>> cxfm.simplify(op)
Identity()

When two rotations are combined that cancel each other out, the result simplifies to an {class}`coordinax.ops.Identity`:

>>> op = (  cxfm.Rotate.from_euler("z", u.Q(45, "deg"))
...       @ cxfm.Rotate.from_euler("z", u.Q(-45, "deg")))
>>> cxfm.simplify(op)
Identity()

The identity check inspects values, so it is skipped when approx=False, and when the matrix is traced – under jax.jit the values are not known, which is exactly when the answer is “do not simplify” rather than an error.

coordinax.simplify(op: Linear, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify a general linear transform to identity when its matrix is one.

The identity-matrix check inspects values, so it is skipped when approx=False, and when the matrix is traced – under jax.jit the values are not known, which is exactly when the answer is “do not simplify” rather than an error.

coordinax.simplify(op: LorentzBoost, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify a Lorentz boost to identity when its velocity is zero.

Every other transform has had this rule; LorentzBoost did not, and simplify dispatches per operator with no generic fallback – so simplify of a boost, or of any ~coordinax.transforms.Composed containing one, raised NotFoundLookupError rather than returning the operator unchanged.

The zero-velocity check inspects values, so it is skipped when approx=False, and when beta is traced; the point of the rule is that those paths return op instead of raising.

Examples

>>> import coordinax.transforms as cxfm

A boost with velocity is left alone:

>>> cxfm.simplify(cxfm.LorentzBoost([0.6, 0.0, 0.0]))
LorentzBoost(...)

A zero boost is the identity:

>>> cxfm.simplify(cxfm.LorentzBoost([0.0, 0.0, 0.0]))
Identity()
coordinax.simplify(op: Reflect, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify a reflection, collapsing the identity matrix when present.

The identity-matrix check inspects values, so it is skipped when approx=False, and when the matrix is traced – under jax.jit the values are not known, which is exactly when the answer is “do not simplify” rather than an error.

coordinax.simplify(op: Scale, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify a scaling transform to identity when matrix is identity.

The identity-matrix check inspects values, so it is skipped when approx=False, and when the factor is traced – under jax.jit the values are not known, which is exactly when the answer is “do not simplify” rather than an error.

coordinax.simplify(op: Shear, /, *, approx: bool = True, **kw: Any) → AbstractTransform
Parameters:
Return type:

Any

Simplify a shear transform to identity when matrix is identity.

The identity-matrix check inspects values, so it is skipped when approx=False, and when the matrix is traced – under jax.jit the values are not known, which is exactly when the answer is “do not simplify” rather than an error.

Parameters:
Return type:

Any

final class coordinax.Composed(transforms: Any)

Bases: AbstractTransform, Generic[Unpack[Ts]]

Composition of Transforms.

Piping refers to a process in which the output of one operation is directly passed as the input to another. This is a composite operator that represents a sequence of operations to be applied in order.

Composed transformations can be created using the ‘pipe’ syntax op1 | op2. A Composed transformation created as FG = F | G, when evaluated, is equivalent to evaluating \(g \circ f = g(f(x))\). Note the order of the transformations!

Note

The | operator works differently from the functional composition operator \(\circ\), which is sadly not supported in Python. The | operator is like the Unix Shell pipe operator, where output is passed left-to-right. This order can be seen in the indexing of the transformations in the Composed object.

Parameters:

transforms (Any) – The sequence of transformations to apply.

Examples

>>> import coordinax.transforms as cxfm
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> rotate = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> pipe = cxfm.Composed((shift, rotate))
>>> pipe
Composed(( Translate(...), Rotate(...) ))

A pipe can also be constructed by |:

>>> pipe2 = shift | rotate
>>> pipe2
Composed(( Translate(...), Rotate(...) ))

The pipe can be simplified. Adding an identity operator shows it being stripped; the remaining translation and rotation are both affine, so they then fuse into a single ~coordinax.transforms.Affine kernel.

>>> pipe3 = pipe2 | cxfm.Identity()
>>> pipe3
Composed(( Translate(...), Rotate(...), Identity() ))
>>> cxfm.simplify(pipe3)
Affine(...)
transforms: tuple[Unpack[Ts]]
property inverse: Composed

The inverse of the operator.

This is the sequence of the inverse of each operator in reverse order.

Examples

>>> import coordinax.transforms as cxfm
>>> import unxt as u
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> rotate = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> pipe = cxfm.Composed((shift, rotate))
>>> pipe.inverse
Composed((...))
property is_time_dependent: bool

Whether any component transform is time-dependent.

Examples

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> pipe = cxfm.Composed((shift, cxfm.Identity()))
>>> pipe.is_time_dependent
False

A TimeDep component makes the whole composite time-dependent:

>>> moving = cxfm.TimeDep.from_(
...     lambda t: cxfm.Translate.from_(jnp.asarray([1.0, 0.0, 0.0]) * t, "km")
... )
>>> cxfm.Composed((shift, moving)).is_time_dependent
True
groups()

Return the least common supergroup of the component transforms.

Return type:

frozenset[type]

classmethod from_(*args, **kwargs)

Construct from a set of arguments.

from_(cls: type[AbstractTransform], *args: object, **kwargs: object) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a set of arguments.

This is a low-priority dispatch that will be called if no other dispatch is found. It just tries to pass the arguments to the constructor.

from_(cls: type[AbstractTransform], obj: Mapping[str, Any], /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a mapping.

>>> import coordinax.transforms as cxfm
>>> cxfm.Composed.from_({"transforms": (cxfm.Identity(), cxfm.Identity())})
Composed((Identity(), Identity()))
from_(cls: type[AbstractTransform], x: ArrayLike | list[float | int], unit: str, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a Quantity’s value and unit.

>>> import coordinax.transforms as cxfm
>>> op = cxfm.Translate.from_([1, 1, 1], "km")
>>> print(op)
Translate(
    {'x': Q(1, 'km'), 'y': Q(1, 'km'), 'z': Q(1, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[AbstractTransform], obj: AbstractTransform, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct an operator from another operator.

Raises:

TypeError – If the input object is not a subclass of the target class.

Parameters:
Return type:

AbstractTransform

Examples

>>> import coordinax as cx

If the object is the same type, it should return the object itself.

>>> op = cxfm.Identity()
>>> cxfm.Identity.from_(op) is op
True

If the object is a different type, it will error.

>>> try:
...     cxfm.Translate.from_(op)
... except TypeError as e:
...     print(e)
Cannot construct <class '...Translate'> from <class '...Identity'>.

Unless the object is a subclass of the target class.

>>> class MyOperator(cxfm.Identity):
...     pass
>>> op = MyOperator()
>>> op
MyOperator()
>>> newop = cxfm.Identity.from_(op)
>>> newop is op, isinstance(newop, cxfm.Identity)
(False, True)
from_(cls: type[TimeDep], fn: Callable[..., Any], /, *args: Any, **kw: Any) → TimeDep
Parameters:
Return type:

AbstractTransform

Build from a tau -> transform callable.

tau is a call-time argument, never a stored parameter, so a user-defined function’s tau dependence is fully differentiated by the kinematic-prolongation engine – even for a bare lambda. act on tangent data and act_jet pick up the resulting d/dtau terms automatically.

Storage matters only for the builder’s other parameters. With no extra arguments: a bare function/lambda cannot be a pytree leaf, so it is stored STATIC – anything it closes over is a trace-time constant and a fresh closure forces a jit retrace. A callable that is already a pytree (an equinox.Module, notably equinox.Partial) is used as the builder as-is, keeping its leaves dynamic – differentiable and jit-cached. (Wrapping it would set those leaves static, silently destroying them.)

Given extra *args/**kw, they are bound onto fn with equinox.Partial, which keeps them as dynamic leaves. ``fn`` must take ``tau`` LAST – fn(param..., tau, **kw) – because equinox.Partial prepends its bound positionals: partial(tau) calls fn(*bound, tau, **kw). Writing fn(tau, param) instead silently passes the parameter as tau. The resulting builder is a equinox.Partial, so the operator needs equinox.filter_jit, not plain jax.jit.

Examples

>>> import equinox as eqx
>>> import jax
>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm

A bare lambda is enough for the tau derivatives to flow: this drift at 3 km/s, acted on data at rest, transforms the velocity to exactly the drift rate.

>>> rate = {"x": u.Q(3.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> drift = cxfm.TimeDep.from_(
...     lambda t: cxfm.Translate({k: v * t for k, v in rate.items()},
...                              chart=cxc.cart3d)
... )
>>> at_rest = cx.Coordinate(
...     point=cx.Point.from_([0.0, 0.0, 0.0], "km"),
...     velocity=cx.Tangent.from_([0.0, 0.0, 0.0], "km/s"),
... )
>>> cxfm.act(drift, u.Q(0.0, "s"), at_rest)["velocity"]["x"]
Q(3., 'km / s')

Extra arguments are bound as leaves – keeping them differentiable and jit-cached. Note t comes last:

>>> def scaled(factor, t) -> cxfm.Scale:
...     return cxfm.Scale.from_factors(jnp.full(3, factor))
>>> op = cxfm.TimeDep.from_(scaled, jnp.asarray(2.0))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]

A pre-built equinox.Partial is equivalent:

>>> op = cxfm.TimeDep.from_(eqx.Partial(scaled, jnp.asarray(2.0)))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], obj: coordinax.transforms._src.actions.add.AbstractAdd, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct a AbstractAdd from another AbstractAdd.

>>> import coordinax as cx
>>> shift1 = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.Translate.from_(shift1) is shift1
True
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], q: AbstractQuantity, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an AbstractAdd subclass from a Quantity.

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_(u.Q([1, 2, 3], "km"))
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], x: ArrayLike, unit: str) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an Add operator from an array-like offset and unit.

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km")
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[Rotate], obj: Rotate, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from another Rotate.

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> R = cxfm.Rotate(jnp.eye(3))
>>> cxfm.Rotate.from_(R) is R
True
from_(cls: type[Rotate], obj: AbstractQuantity, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from a Quantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(u.Q(jnp.eye(3), ""))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: ArrayLike, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from an Array.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(jnp.eye(3))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: Rotation, /) → Rotate
Parameters:
Return type:

AbstractTransform

Initialize from a jax.scipy.spatial.transform.Rotation.

>>> import jax.numpy as jnp
>>> from jax.scipy.spatial.transform import Rotation
>>> import coordinax as cx
>>> R = Rotation.from_euler("z", 90, degrees=True)
>>> op = cxfm.Rotate.from_(R)
>>> jnp.allclose(op.R, R.as_matrix())
Array(True, dtype=bool)
from_(cls: type[Linear], obj: ArrayLike, /) → Linear
Parameters:
Return type:

AbstractTransform

Construct a Linear from an array matrix.

Registered on the class’s own dispatcher, as the sibling transforms do. Via the global plum.dispatch the ArrayLike union is unfaithful, which would turn off plum’s method cache for every from_ in the library – caught by test_no_new_unfaithful_signatures.

from_(cls: type[Reflect], obj: Reflect, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from another Reflect.

from_(cls: type[Reflect], obj: AbstractQuantity, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from a dimensionless quantity matrix.

from_(cls: type[Reflect], obj: ArrayLike, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from an array matrix.

from_(cls: type[Scale], obj: Scale, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from another Scale.

from_(cls: type[Scale], obj: AbstractQuantity, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from a dimensionless quantity matrix.

from_(cls: type[Scale], obj: ArrayLike, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from an array matrix.

from_(cls: type[Shear], obj: Shear, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from another Shear.

from_(cls: type[Shear], obj: AbstractQuantity, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from a dimensionless quantity matrix.

from_(cls: type[Shear], obj: ArrayLike, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from an array matrix.

Parameters:
Return type:

AbstractTransform

simplify()

Simplify the operator.

This method calls coordinax.ops.simplify to simplify the operator.

Return type:

AbstractTransform

Examples

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> op = cxfm.Identity()
>>> op.simplify() is op
True
>>> pipe = cxfm.Composed((cxfm.Identity(), cxfm.Identity()))
>>> pipe
Composed((Identity(), Identity()))
>>> pipe.simplify()
Identity()
final class coordinax.Rotate(R: Any)

Bases: AbstractLinearTransform

Operator for Galilean rotations.

The coordinate transform is given by:

\[(t,\mathbf{x}) \mapsto (t, R \mathbf{x})\]

where \(R\) is the rotation matrix. Note this is intrinsically time dependent.

Parameters:
  • rotation (Array[float, (3, 3)]) – The rotation matrix.

  • R (Shaped[Array, 'N N'])

Raises:

ValueError – If the rotation matrix is not orthogonal.

Notes

The Galilean rotation is intrinsically a time-dependent transformation. This is part of the inhomogeneous Galilean group, which is the group of transformations that leave the space-time interval invariant.

Examples

We start with the required imports:

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.transforms as cxfm

We can then create a rotation operator:

>>> Rz = jnp.asarray([[0, -1, 0], [1, 0,  0], [0, 0, 1]])
>>> op = cxfm.Rotate(Rz)
>>> op
Rotate(i64[3,3](jax))

Rotation operators can be applied to {class}`~coordinax.Point` and other higher-level objects, with behavior depending on the role:

>>> v = cx.Point.from_([1, 0, 0], "m")  # A cxr.Point vector
>>> t = u.Q(1, "s")
>>> print(op(t, v))  # equivalent to `cx.act(op, t, v)`
<Point: chart=Cart3D (x, y, z) [m]
    [0 1 0]>

This also works for a batch of vectors (as a note, it is more efficient to jax.vmap over the jax.jit-ed operator):

>>> v = cx.Point.from_([[1, 0, 0], [0, 1, 0]], "m")  # A Point vector
>>> print(op(t, v))
<Point: chart=Cart3D (x, y, z) [m]
    [[ 0  1  0]
     [-1  0  0]]>

Rotations can also be applied to low-level coordinate dictionaries:

>>> q = {"x": u.Q(1, "m"), "y": u.Q(0, "m"), "z": u.Q(0, "m")}
>>> nq = op(t, q)  # inferred chart & rep -> cxr.Point
>>> nq
{'x': Q(0, 'm'), 'y': Q(1, 'm'), 'z': Q(0, 'm')}

In addition to the standard low-level objects, Rotation operators can be applied to {class}`~unxt.Quantity` and Array-like objects, taken to represent a Cartesian vectors. For Quantity, the role is inferred from the units, while Arrays are always points:

>>> q = u.Q([1, 0, 0], "m")
>>> t = u.Q(1, "s")
>>> op(t, q)
Q([0, 1, 0], 'm')

This also works for a batch of vectors:

>>> q = u.Q([[1, 0, 0], [0, 1, 0]], "m")
>>> op(t, q)
Q([[ 0,  1,  0],
   [-1,  0,  0]], 'm')

R is always a constant matrix. A time-dependent rotation is a ~coordinax.transforms.TimeDep family of Rotate operators — e.g. built by ~coordinax.transforms.builders.RotationAboutAxis:

>>> zhat = jnp.array([0.0, 0.0, 1.0])
>>> b = cxfm.builders.RotationAboutAxis(u.Q(45, "deg/s"), axis=zhat)
>>> R_op = cxfm.TimeDep(b)
>>> t = u.Q(4, "s")  # 180 degrees rotation
>>> R_op(t, q).round(3)
Q([[-1.,  0.,  0.],
   [-0., -1.,  0.]], 'm')
Parameters:

R (Any)

R: Shaped[Array, 'N N']

The rotation matrix.

classmethod groups()

Return the groups to which this map belongs.

Return type:

frozenset[type]

classmethod from_euler(seq: str, angles: Quantity | Angle, /)

Initialize from Euler angles.

See jax.scipy.spatial.transform.Rotation.from_euler. XYZ are intrinsic rotations, xyz are extrinsic rotations.

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> op = cxfm.Rotate.from_euler("z", u.Q(90, "deg"))
>>> op.R.round(2)
Array([[ 0., -1.,  0.],
       [ 1.,  0.,  0.],
       [ 0.,  0.,  1.]], dtype=float64)
Parameters:
Return type:

Rotate

property inverse: Rotate

The inverse of the operator.

Examples

>>> import quaxed.numpy as jnp
>>> import coordinax as cx
>>> Rz = jnp.asarray([[0, -1, 0], [1, 0,  0], [0, 0, 1]])
>>> op = cxfm.Rotate(Rz)
>>> op.inverse
Rotate(i64[3,3](jax))
>>> jnp.allclose(op.R, op.inverse.R.T)
Array(True, dtype=bool)
classmethod from_(*args, **kwargs)

Construct from a set of arguments.

from_(cls: type[AbstractTransform], *args: object, **kwargs: object) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a set of arguments.

This is a low-priority dispatch that will be called if no other dispatch is found. It just tries to pass the arguments to the constructor.

from_(cls: type[AbstractTransform], obj: Mapping[str, Any], /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a mapping.

>>> import coordinax.transforms as cxfm
>>> cxfm.Composed.from_({"transforms": (cxfm.Identity(), cxfm.Identity())})
Composed((Identity(), Identity()))
from_(cls: type[AbstractTransform], x: ArrayLike | list[float | int], unit: str, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a Quantity’s value and unit.

>>> import coordinax.transforms as cxfm
>>> op = cxfm.Translate.from_([1, 1, 1], "km")
>>> print(op)
Translate(
    {'x': Q(1, 'km'), 'y': Q(1, 'km'), 'z': Q(1, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[AbstractTransform], obj: AbstractTransform, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct an operator from another operator.

Raises:

TypeError – If the input object is not a subclass of the target class.

Parameters:
Return type:

AbstractTransform

Examples

>>> import coordinax as cx

If the object is the same type, it should return the object itself.

>>> op = cxfm.Identity()
>>> cxfm.Identity.from_(op) is op
True

If the object is a different type, it will error.

>>> try:
...     cxfm.Translate.from_(op)
... except TypeError as e:
...     print(e)
Cannot construct <class '...Translate'> from <class '...Identity'>.

Unless the object is a subclass of the target class.

>>> class MyOperator(cxfm.Identity):
...     pass
>>> op = MyOperator()
>>> op
MyOperator()
>>> newop = cxfm.Identity.from_(op)
>>> newop is op, isinstance(newop, cxfm.Identity)
(False, True)
from_(cls: type[TimeDep], fn: Callable[..., Any], /, *args: Any, **kw: Any) → TimeDep
Parameters:
Return type:

AbstractTransform

Build from a tau -> transform callable.

tau is a call-time argument, never a stored parameter, so a user-defined function’s tau dependence is fully differentiated by the kinematic-prolongation engine – even for a bare lambda. act on tangent data and act_jet pick up the resulting d/dtau terms automatically.

Storage matters only for the builder’s other parameters. With no extra arguments: a bare function/lambda cannot be a pytree leaf, so it is stored STATIC – anything it closes over is a trace-time constant and a fresh closure forces a jit retrace. A callable that is already a pytree (an equinox.Module, notably equinox.Partial) is used as the builder as-is, keeping its leaves dynamic – differentiable and jit-cached. (Wrapping it would set those leaves static, silently destroying them.)

Given extra *args/**kw, they are bound onto fn with equinox.Partial, which keeps them as dynamic leaves. ``fn`` must take ``tau`` LAST – fn(param..., tau, **kw) – because equinox.Partial prepends its bound positionals: partial(tau) calls fn(*bound, tau, **kw). Writing fn(tau, param) instead silently passes the parameter as tau. The resulting builder is a equinox.Partial, so the operator needs equinox.filter_jit, not plain jax.jit.

Examples

>>> import equinox as eqx
>>> import jax
>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm

A bare lambda is enough for the tau derivatives to flow: this drift at 3 km/s, acted on data at rest, transforms the velocity to exactly the drift rate.

>>> rate = {"x": u.Q(3.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> drift = cxfm.TimeDep.from_(
...     lambda t: cxfm.Translate({k: v * t for k, v in rate.items()},
...                              chart=cxc.cart3d)
... )
>>> at_rest = cx.Coordinate(
...     point=cx.Point.from_([0.0, 0.0, 0.0], "km"),
...     velocity=cx.Tangent.from_([0.0, 0.0, 0.0], "km/s"),
... )
>>> cxfm.act(drift, u.Q(0.0, "s"), at_rest)["velocity"]["x"]
Q(3., 'km / s')

Extra arguments are bound as leaves – keeping them differentiable and jit-cached. Note t comes last:

>>> def scaled(factor, t) -> cxfm.Scale:
...     return cxfm.Scale.from_factors(jnp.full(3, factor))
>>> op = cxfm.TimeDep.from_(scaled, jnp.asarray(2.0))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]

A pre-built equinox.Partial is equivalent:

>>> op = cxfm.TimeDep.from_(eqx.Partial(scaled, jnp.asarray(2.0)))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], obj: coordinax.transforms._src.actions.add.AbstractAdd, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct a AbstractAdd from another AbstractAdd.

>>> import coordinax as cx
>>> shift1 = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.Translate.from_(shift1) is shift1
True
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], q: AbstractQuantity, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an AbstractAdd subclass from a Quantity.

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_(u.Q([1, 2, 3], "km"))
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], x: ArrayLike, unit: str) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an Add operator from an array-like offset and unit.

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km")
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[Rotate], obj: Rotate, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from another Rotate.

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> R = cxfm.Rotate(jnp.eye(3))
>>> cxfm.Rotate.from_(R) is R
True
from_(cls: type[Rotate], obj: AbstractQuantity, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from a Quantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(u.Q(jnp.eye(3), ""))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: ArrayLike, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from an Array.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(jnp.eye(3))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: Rotation, /) → Rotate
Parameters:
Return type:

AbstractTransform

Initialize from a jax.scipy.spatial.transform.Rotation.

>>> import jax.numpy as jnp
>>> from jax.scipy.spatial.transform import Rotation
>>> import coordinax as cx
>>> R = Rotation.from_euler("z", 90, degrees=True)
>>> op = cxfm.Rotate.from_(R)
>>> jnp.allclose(op.R, R.as_matrix())
Array(True, dtype=bool)
from_(cls: type[Linear], obj: ArrayLike, /) → Linear
Parameters:
Return type:

AbstractTransform

Construct a Linear from an array matrix.

Registered on the class’s own dispatcher, as the sibling transforms do. Via the global plum.dispatch the ArrayLike union is unfaithful, which would turn off plum’s method cache for every from_ in the library – caught by test_no_new_unfaithful_signatures.

from_(cls: type[Reflect], obj: Reflect, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from another Reflect.

from_(cls: type[Reflect], obj: AbstractQuantity, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from a dimensionless quantity matrix.

from_(cls: type[Reflect], obj: ArrayLike, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from an array matrix.

from_(cls: type[Scale], obj: Scale, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from another Scale.

from_(cls: type[Scale], obj: AbstractQuantity, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from a dimensionless quantity matrix.

from_(cls: type[Scale], obj: ArrayLike, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from an array matrix.

from_(cls: type[Shear], obj: Shear, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from another Shear.

from_(cls: type[Shear], obj: AbstractQuantity, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from a dimensionless quantity matrix.

from_(cls: type[Shear], obj: ArrayLike, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from an array matrix.

Parameters:
Return type:

AbstractTransform

property is_time_dependent: bool

Whether the point action depends on the time parameter tau.

A declared trait, not a structural scan: subclasses whose point action varies with tau (TimeDep, Boost) override this to True; composites (Composed) override it to the disjunction of their components. The default is False.

Examples

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km").is_time_dependent
False
property matrix: Array

The matrix \(M\) this transform applies, as \(x \mapsto M x\).

The one spelling shared by every subclass, which otherwise keep \(M\) under their own letter (R, H, S) or, for ~coordinax.transforms.LorentzBoost, derive it from a stored velocity.

Validated square, but not against a chart – a chart is what fixes the dimension and this takes none, so the act paths keep that check.

Examples

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm

For a stored matrix it is the field under a common name:

>>> Rz = jnp.asarray([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
>>> bool(jnp.array_equal(cxfm.Rotate(Rz).matrix, Rz))
True

For a derived one it is the only way to see it:

>>> cxfm.LorentzBoost([0.6, 0.0, 0.0]).matrix.round(2)
Array([[1.25, 0.75, 0.  , 0.  ],
       [0.75, 1.25, 0.  , 0.  ],
       [0.  , 0.  , 1.  , 0.  ],
       [0.  , 0.  , 0.  , 1.  ]], dtype=float64)
simplify()

Simplify the operator.

This method calls coordinax.ops.simplify to simplify the operator.

Return type:

AbstractTransform

Examples

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> op = cxfm.Identity()
>>> op.simplify() is op
True
>>> pipe = cxfm.Composed((cxfm.Identity(), cxfm.Identity()))
>>> pipe
Composed((Identity(), Identity()))
>>> pipe.simplify()
Identity()
final class coordinax.Translate(delta: dict, chart: AbstractChart, semantic_kind: AbstractTangentSemanticKind = dpl, *, right_add: bool = True)

Bases: AbstractAdd

Operator for translating points.

A Translate operator represents addition of a constant displacement \(\Delta\) in the ambient Euclidean space (or in a chart whose metric is Euclidean and whose canonical Cartesian chart exists).

Think of \(\Delta\) as a displacement vector field that is constant in space and time (unless explicitly time-dependent).

Formally, in a Cartesian chart on \(\mathbb{R}^n\): \(T_\Delta:\; x \mapsto x+\Delta\).

In that flat setting its differential (pushforward) is the identity, \((dT_\Delta)_x = I\). When delta lives in a non-flat chart, or acts on data in a different or non-flat chart, the point action is base-point dependent and the differential is NOT the identity — see the Notes.

Parameters:
  • delta (dict) – The offset to apply. Its physical dimension follows semantic_kind: length for the default displacement kind (dpl), speed for a velocity kick (vel), and so on up the time-derivative ladder.

  • chart (AbstractChart)

  • semantic_kind (AbstractTangentSemanticKind)

  • right_add (bool)

Notes

The semantic_kind field sets the ladder order \(k\) of the offset (dpl: \(k=0\), vel: \(k=1\), …). Acting on data of ladder order \(m\) (points behave as the curve position for \(k=0\)):

  • \(k = 0\) with delta in a Cartesian-type (flat) chart: shifts points by \(\delta\) and leaves every tangent order unchanged (the Jacobian of a flat translation is the identity, and a constant \(\delta\) has no \(\tau\)-derivatives).

  • \(k = 0\) with delta in a non-flat chart: the point action pushes delta through the chart Jacobian at the point, so it is base-point dependent. All tangent data — including displacements — transforms by the generic pushforward/prolongation of the point action, which is generally not the identity and requires the base point (at=, or a ~coordinax.Coordinate bundle).

  • \(k \geq 1\): identity on points and on all orders \(m \neq k\); order \(m = k\) gains \(\delta\). The componentwise rule is definitional (the point action is the identity), independent of chart flatness.

A time-dependent translation is a ~coordinax.transforms.TimeDep family of Translate operators (e.g. built by ~coordinax.transforms.builders.UniformTranslation); its \(d^{m-k}\delta/d\tau^{m-k}\) prolongation terms are recovered by the generic tangent funnel, which differentiates the point action.

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.transforms as cxfm

Create a translation operator:

>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> shift
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)

The inverse negates the displacement:

>>> shift.inverse
Translate(
    {'x': Q(-1, 'km'), 'y': Q(-2, 'km'), 'z': Q(-3, 'km')}, chart=Cart3D(M=Rn(3))
)

Time-dependent translation — a ~coordinax.transforms.TimeDep family:

>>> rate = {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")}
>>> moving = cxfm.TimeDep(cxfm.builders.UniformTranslation(rate, chart=cxc.cart3d))
>>> t = u.Q(10, "s")
>>> x = cx.cdict(u.Q([0.0, 0.0, 0.0], "m"))
>>> moving(t, x)
{'x': Q(10., 'm'), 'y': Q(0., 'm'), 'z': Q(0., 'm')}
Parameters:
semantic_kind: AbstractTangentSemanticKind = dpl

Semantic kind of tangent data this operator acts on (default Displacement).

classmethod groups()

Return the groups to which this map belongs.

Return type:

frozenset[type]

classmethod from_(*args, **kwargs)

Construct from a set of arguments.

from_(cls: type[AbstractTransform], *args: object, **kwargs: object) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a set of arguments.

This is a low-priority dispatch that will be called if no other dispatch is found. It just tries to pass the arguments to the constructor.

from_(cls: type[AbstractTransform], obj: Mapping[str, Any], /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a mapping.

>>> import coordinax.transforms as cxfm
>>> cxfm.Composed.from_({"transforms": (cxfm.Identity(), cxfm.Identity())})
Composed((Identity(), Identity()))
from_(cls: type[AbstractTransform], x: ArrayLike | list[float | int], unit: str, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a Quantity’s value and unit.

>>> import coordinax.transforms as cxfm
>>> op = cxfm.Translate.from_([1, 1, 1], "km")
>>> print(op)
Translate(
    {'x': Q(1, 'km'), 'y': Q(1, 'km'), 'z': Q(1, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[AbstractTransform], obj: AbstractTransform, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct an operator from another operator.

Raises:

TypeError – If the input object is not a subclass of the target class.

Parameters:
Return type:

AbstractTransform

Examples

>>> import coordinax as cx

If the object is the same type, it should return the object itself.

>>> op = cxfm.Identity()
>>> cxfm.Identity.from_(op) is op
True

If the object is a different type, it will error.

>>> try:
...     cxfm.Translate.from_(op)
... except TypeError as e:
...     print(e)
Cannot construct <class '...Translate'> from <class '...Identity'>.

Unless the object is a subclass of the target class.

>>> class MyOperator(cxfm.Identity):
...     pass
>>> op = MyOperator()
>>> op
MyOperator()
>>> newop = cxfm.Identity.from_(op)
>>> newop is op, isinstance(newop, cxfm.Identity)
(False, True)
from_(cls: type[TimeDep], fn: Callable[..., Any], /, *args: Any, **kw: Any) → TimeDep
Parameters:
Return type:

AbstractTransform

Build from a tau -> transform callable.

tau is a call-time argument, never a stored parameter, so a user-defined function’s tau dependence is fully differentiated by the kinematic-prolongation engine – even for a bare lambda. act on tangent data and act_jet pick up the resulting d/dtau terms automatically.

Storage matters only for the builder’s other parameters. With no extra arguments: a bare function/lambda cannot be a pytree leaf, so it is stored STATIC – anything it closes over is a trace-time constant and a fresh closure forces a jit retrace. A callable that is already a pytree (an equinox.Module, notably equinox.Partial) is used as the builder as-is, keeping its leaves dynamic – differentiable and jit-cached. (Wrapping it would set those leaves static, silently destroying them.)

Given extra *args/**kw, they are bound onto fn with equinox.Partial, which keeps them as dynamic leaves. ``fn`` must take ``tau`` LAST – fn(param..., tau, **kw) – because equinox.Partial prepends its bound positionals: partial(tau) calls fn(*bound, tau, **kw). Writing fn(tau, param) instead silently passes the parameter as tau. The resulting builder is a equinox.Partial, so the operator needs equinox.filter_jit, not plain jax.jit.

Examples

>>> import equinox as eqx
>>> import jax
>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm

A bare lambda is enough for the tau derivatives to flow: this drift at 3 km/s, acted on data at rest, transforms the velocity to exactly the drift rate.

>>> rate = {"x": u.Q(3.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> drift = cxfm.TimeDep.from_(
...     lambda t: cxfm.Translate({k: v * t for k, v in rate.items()},
...                              chart=cxc.cart3d)
... )
>>> at_rest = cx.Coordinate(
...     point=cx.Point.from_([0.0, 0.0, 0.0], "km"),
...     velocity=cx.Tangent.from_([0.0, 0.0, 0.0], "km/s"),
... )
>>> cxfm.act(drift, u.Q(0.0, "s"), at_rest)["velocity"]["x"]
Q(3., 'km / s')

Extra arguments are bound as leaves – keeping them differentiable and jit-cached. Note t comes last:

>>> def scaled(factor, t) -> cxfm.Scale:
...     return cxfm.Scale.from_factors(jnp.full(3, factor))
>>> op = cxfm.TimeDep.from_(scaled, jnp.asarray(2.0))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]

A pre-built equinox.Partial is equivalent:

>>> op = cxfm.TimeDep.from_(eqx.Partial(scaled, jnp.asarray(2.0)))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], obj: coordinax.transforms._src.actions.add.AbstractAdd, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct a AbstractAdd from another AbstractAdd.

>>> import coordinax as cx
>>> shift1 = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.Translate.from_(shift1) is shift1
True
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], q: AbstractQuantity, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an AbstractAdd subclass from a Quantity.

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_(u.Q([1, 2, 3], "km"))
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], x: ArrayLike, unit: str) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an Add operator from an array-like offset and unit.

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km")
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[Rotate], obj: Rotate, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from another Rotate.

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> R = cxfm.Rotate(jnp.eye(3))
>>> cxfm.Rotate.from_(R) is R
True
from_(cls: type[Rotate], obj: AbstractQuantity, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from a Quantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(u.Q(jnp.eye(3), ""))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: ArrayLike, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from an Array.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(jnp.eye(3))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: Rotation, /) → Rotate
Parameters:
Return type:

AbstractTransform

Initialize from a jax.scipy.spatial.transform.Rotation.

>>> import jax.numpy as jnp
>>> from jax.scipy.spatial.transform import Rotation
>>> import coordinax as cx
>>> R = Rotation.from_euler("z", 90, degrees=True)
>>> op = cxfm.Rotate.from_(R)
>>> jnp.allclose(op.R, R.as_matrix())
Array(True, dtype=bool)
from_(cls: type[Linear], obj: ArrayLike, /) → Linear
Parameters:
Return type:

AbstractTransform

Construct a Linear from an array matrix.

Registered on the class’s own dispatcher, as the sibling transforms do. Via the global plum.dispatch the ArrayLike union is unfaithful, which would turn off plum’s method cache for every from_ in the library – caught by test_no_new_unfaithful_signatures.

from_(cls: type[Reflect], obj: Reflect, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from another Reflect.

from_(cls: type[Reflect], obj: AbstractQuantity, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from a dimensionless quantity matrix.

from_(cls: type[Reflect], obj: ArrayLike, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from an array matrix.

from_(cls: type[Scale], obj: Scale, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from another Scale.

from_(cls: type[Scale], obj: AbstractQuantity, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from a dimensionless quantity matrix.

from_(cls: type[Scale], obj: ArrayLike, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from an array matrix.

from_(cls: type[Shear], obj: Shear, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from another Shear.

from_(cls: type[Shear], obj: AbstractQuantity, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from a dimensionless quantity matrix.

from_(cls: type[Shear], obj: ArrayLike, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from an array matrix.

Parameters:
Return type:

AbstractTransform

property inverse: AbstractAdd

The inverse operator (negated offset).

Examples

>>> import coordinax.transforms as cxfm
>>> shift = cxfm.Translate.from_([1, 2, 3], "km")
>>> shift.inverse
Translate(
    {'x': Q(-1, 'km'), 'y': Q(-2, 'km'), 'z': Q(-3, 'km')},
    chart=Cart3D(M=Rn(3))
)
property is_time_dependent: bool

Whether the point action depends on the time parameter tau.

A declared trait, not a structural scan: subclasses whose point action varies with tau (TimeDep, Boost) override this to True; composites (Composed) override it to the disjunction of their components. The default is False.

Examples

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km").is_time_dependent
False
right_add: bool = True

Whether to add on the right (x + offset) or left (offset + x).

simplify()

Simplify the operator.

This method calls coordinax.ops.simplify to simplify the operator.

Return type:

AbstractTransform

Examples

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> op = cxfm.Identity()
>>> op.simplify() is op
True
>>> pipe = cxfm.Composed((cxfm.Identity(), cxfm.Identity()))
>>> pipe
Composed((Identity(), Identity()))
>>> pipe.simplify()
Identity()
delta: dict

The additive offset (displacement for Translate, velocity for Boost).

chart: AbstractChart

Chart in which the offset is expressed.

final class coordinax.Scale(S: Any)

Bases: AbstractLinearTransform

Operator for Cartesian linear scaling.

A scaling transform applies

\[x \mapsto Sx,\]

where S is an invertible diagonal matrix: anisotropic scaling with one factor per axis.

Diagonality is the whole content of the type. A Scale holding an off-diagonal matrix would be a general linear map wearing a name that promises otherwise, and isinstance(op, Scale) would stop meaning anything. ~coordinax.transforms.Linear is the type for a general matrix.

Examples

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> jnp.diagonal(cxfm.Scale.from_factors(jnp.asarray([2.0, 3.0, 4.0])).matrix)
Array([2., 3., 4.], dtype=float64)

A matrix that is already diagonal is accepted:

>>> jnp.diagonal(cxfm.Scale(jnp.diag(jnp.asarray([2.0, 1.0, 0.5]))).matrix)
Array([2. , 1. , 0.5], dtype=float64)

One that is not is refused, and points at the type that fits:

>>> try:
...     cxfm.Scale(jnp.asarray([[1.0, 0.5], [0.0, 1.0]])).matrix
... except Exception as e:
...     print("Scale requires a diagonal matrix" in str(e))
True
Parameters:

S (Any)

s: Shaped[Array, 'N']

The scaling factors – the diagonal of \(S\), one per axis.

The diagonal rather than the whole matrix, so diagonality is structural instead of re-checked: a vector cannot have an off-diagonal entry. The matrix is rebuilt on demand by _raw_matrix, the same way ~coordinax.transforms.LorentzBoost derives its own from a stored velocity.

classmethod groups()

Return the groups to which this map belongs.

Return type:

frozenset[type]

classmethod from_factors(factors: Any, /)

Construct a diagonal scaling transform from axis factors.

Parameters:

factors (Any)

Return type:

Scale

property inverse: Scale

Return the inverse scaling transform.

Reciprocals of the factors, not jnp.linalg.inv: O(n) rather than O(n^3), and exact where the general solve is not.

Nothing is re-checked here, and s carries no deferred check of its own: __init__ validates the matrix when it takes the diagonal, so an s that exists has already passed. A malformed input therefore reports from construction – eagerly at the call, under jit when the traced graph runs – never from this property.

classmethod from_(*args, **kwargs)

Construct from a set of arguments.

from_(cls: type[AbstractTransform], *args: object, **kwargs: object) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a set of arguments.

This is a low-priority dispatch that will be called if no other dispatch is found. It just tries to pass the arguments to the constructor.

from_(cls: type[AbstractTransform], obj: Mapping[str, Any], /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a mapping.

>>> import coordinax.transforms as cxfm
>>> cxfm.Composed.from_({"transforms": (cxfm.Identity(), cxfm.Identity())})
Composed((Identity(), Identity()))
from_(cls: type[AbstractTransform], x: ArrayLike | list[float | int], unit: str, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a Quantity’s value and unit.

>>> import coordinax.transforms as cxfm
>>> op = cxfm.Translate.from_([1, 1, 1], "km")
>>> print(op)
Translate(
    {'x': Q(1, 'km'), 'y': Q(1, 'km'), 'z': Q(1, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[AbstractTransform], obj: AbstractTransform, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct an operator from another operator.

Raises:

TypeError – If the input object is not a subclass of the target class.

Parameters:
Return type:

AbstractTransform

Examples

>>> import coordinax as cx

If the object is the same type, it should return the object itself.

>>> op = cxfm.Identity()
>>> cxfm.Identity.from_(op) is op
True

If the object is a different type, it will error.

>>> try:
...     cxfm.Translate.from_(op)
... except TypeError as e:
...     print(e)
Cannot construct <class '...Translate'> from <class '...Identity'>.

Unless the object is a subclass of the target class.

>>> class MyOperator(cxfm.Identity):
...     pass
>>> op = MyOperator()
>>> op
MyOperator()
>>> newop = cxfm.Identity.from_(op)
>>> newop is op, isinstance(newop, cxfm.Identity)
(False, True)
from_(cls: type[TimeDep], fn: Callable[..., Any], /, *args: Any, **kw: Any) → TimeDep
Parameters:
Return type:

AbstractTransform

Build from a tau -> transform callable.

tau is a call-time argument, never a stored parameter, so a user-defined function’s tau dependence is fully differentiated by the kinematic-prolongation engine – even for a bare lambda. act on tangent data and act_jet pick up the resulting d/dtau terms automatically.

Storage matters only for the builder’s other parameters. With no extra arguments: a bare function/lambda cannot be a pytree leaf, so it is stored STATIC – anything it closes over is a trace-time constant and a fresh closure forces a jit retrace. A callable that is already a pytree (an equinox.Module, notably equinox.Partial) is used as the builder as-is, keeping its leaves dynamic – differentiable and jit-cached. (Wrapping it would set those leaves static, silently destroying them.)

Given extra *args/**kw, they are bound onto fn with equinox.Partial, which keeps them as dynamic leaves. ``fn`` must take ``tau`` LAST – fn(param..., tau, **kw) – because equinox.Partial prepends its bound positionals: partial(tau) calls fn(*bound, tau, **kw). Writing fn(tau, param) instead silently passes the parameter as tau. The resulting builder is a equinox.Partial, so the operator needs equinox.filter_jit, not plain jax.jit.

Examples

>>> import equinox as eqx
>>> import jax
>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm

A bare lambda is enough for the tau derivatives to flow: this drift at 3 km/s, acted on data at rest, transforms the velocity to exactly the drift rate.

>>> rate = {"x": u.Q(3.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> drift = cxfm.TimeDep.from_(
...     lambda t: cxfm.Translate({k: v * t for k, v in rate.items()},
...                              chart=cxc.cart3d)
... )
>>> at_rest = cx.Coordinate(
...     point=cx.Point.from_([0.0, 0.0, 0.0], "km"),
...     velocity=cx.Tangent.from_([0.0, 0.0, 0.0], "km/s"),
... )
>>> cxfm.act(drift, u.Q(0.0, "s"), at_rest)["velocity"]["x"]
Q(3., 'km / s')

Extra arguments are bound as leaves – keeping them differentiable and jit-cached. Note t comes last:

>>> def scaled(factor, t) -> cxfm.Scale:
...     return cxfm.Scale.from_factors(jnp.full(3, factor))
>>> op = cxfm.TimeDep.from_(scaled, jnp.asarray(2.0))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]

A pre-built equinox.Partial is equivalent:

>>> op = cxfm.TimeDep.from_(eqx.Partial(scaled, jnp.asarray(2.0)))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], obj: coordinax.transforms._src.actions.add.AbstractAdd, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct a AbstractAdd from another AbstractAdd.

>>> import coordinax as cx
>>> shift1 = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.Translate.from_(shift1) is shift1
True
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], q: AbstractQuantity, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an AbstractAdd subclass from a Quantity.

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_(u.Q([1, 2, 3], "km"))
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], x: ArrayLike, unit: str) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an Add operator from an array-like offset and unit.

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km")
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[Rotate], obj: Rotate, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from another Rotate.

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> R = cxfm.Rotate(jnp.eye(3))
>>> cxfm.Rotate.from_(R) is R
True
from_(cls: type[Rotate], obj: AbstractQuantity, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from a Quantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(u.Q(jnp.eye(3), ""))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: ArrayLike, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from an Array.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(jnp.eye(3))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: Rotation, /) → Rotate
Parameters:
Return type:

AbstractTransform

Initialize from a jax.scipy.spatial.transform.Rotation.

>>> import jax.numpy as jnp
>>> from jax.scipy.spatial.transform import Rotation
>>> import coordinax as cx
>>> R = Rotation.from_euler("z", 90, degrees=True)
>>> op = cxfm.Rotate.from_(R)
>>> jnp.allclose(op.R, R.as_matrix())
Array(True, dtype=bool)
from_(cls: type[Linear], obj: ArrayLike, /) → Linear
Parameters:
Return type:

AbstractTransform

Construct a Linear from an array matrix.

Registered on the class’s own dispatcher, as the sibling transforms do. Via the global plum.dispatch the ArrayLike union is unfaithful, which would turn off plum’s method cache for every from_ in the library – caught by test_no_new_unfaithful_signatures.

from_(cls: type[Reflect], obj: Reflect, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from another Reflect.

from_(cls: type[Reflect], obj: AbstractQuantity, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from a dimensionless quantity matrix.

from_(cls: type[Reflect], obj: ArrayLike, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from an array matrix.

from_(cls: type[Scale], obj: Scale, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from another Scale.

from_(cls: type[Scale], obj: AbstractQuantity, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from a dimensionless quantity matrix.

from_(cls: type[Scale], obj: ArrayLike, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from an array matrix.

from_(cls: type[Shear], obj: Shear, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from another Shear.

from_(cls: type[Shear], obj: AbstractQuantity, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from a dimensionless quantity matrix.

from_(cls: type[Shear], obj: ArrayLike, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from an array matrix.

Parameters:
Return type:

AbstractTransform

property is_time_dependent: bool

Whether the point action depends on the time parameter tau.

A declared trait, not a structural scan: subclasses whose point action varies with tau (TimeDep, Boost) override this to True; composites (Composed) override it to the disjunction of their components. The default is False.

Examples

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km").is_time_dependent
False
property matrix: Array

The matrix \(M\) this transform applies, as \(x \mapsto M x\).

The one spelling shared by every subclass, which otherwise keep \(M\) under their own letter (R, H, S) or, for ~coordinax.transforms.LorentzBoost, derive it from a stored velocity.

Validated square, but not against a chart – a chart is what fixes the dimension and this takes none, so the act paths keep that check.

Examples

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm

For a stored matrix it is the field under a common name:

>>> Rz = jnp.asarray([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
>>> bool(jnp.array_equal(cxfm.Rotate(Rz).matrix, Rz))
True

For a derived one it is the only way to see it:

>>> cxfm.LorentzBoost([0.6, 0.0, 0.0]).matrix.round(2)
Array([[1.25, 0.75, 0.  , 0.  ],
       [0.75, 1.25, 0.  , 0.  ],
       [0.  , 0.  , 1.  , 0.  ],
       [0.  , 0.  , 0.  , 1.  ]], dtype=float64)
simplify()

Simplify the operator.

This method calls coordinax.ops.simplify to simplify the operator.

Return type:

AbstractTransform

Examples

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> op = cxfm.Identity()
>>> op.simplify() is op
True
>>> pipe = cxfm.Composed((cxfm.Identity(), cxfm.Identity()))
>>> pipe
Composed((Identity(), Identity()))
>>> pipe.simplify()
Identity()
final class coordinax.Shear(H: Any)

Bases: AbstractLinearTransform

Operator for Cartesian linear shear.

A shear transform applies

\[x \mapsto Hx,\]

where H is an invertible shear matrix.

Parameters:

H (Any)

H: Shaped[Array, 'N N']

The shear matrix.

classmethod groups()

Return the groups to which this map belongs.

Return type:

frozenset[type]

property inverse: Shear

Return the inverse shear transform.

classmethod from_(*args, **kwargs)

Construct from a set of arguments.

from_(cls: type[AbstractTransform], *args: object, **kwargs: object) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a set of arguments.

This is a low-priority dispatch that will be called if no other dispatch is found. It just tries to pass the arguments to the constructor.

from_(cls: type[AbstractTransform], obj: Mapping[str, Any], /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a mapping.

>>> import coordinax.transforms as cxfm
>>> cxfm.Composed.from_({"transforms": (cxfm.Identity(), cxfm.Identity())})
Composed((Identity(), Identity()))
from_(cls: type[AbstractTransform], x: ArrayLike | list[float | int], unit: str, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct from a Quantity’s value and unit.

>>> import coordinax.transforms as cxfm
>>> op = cxfm.Translate.from_([1, 1, 1], "km")
>>> print(op)
Translate(
    {'x': Q(1, 'km'), 'y': Q(1, 'km'), 'z': Q(1, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[AbstractTransform], obj: AbstractTransform, /) → AbstractTransform
Parameters:
Return type:

AbstractTransform

Construct an operator from another operator.

Raises:

TypeError – If the input object is not a subclass of the target class.

Parameters:
Return type:

AbstractTransform

Examples

>>> import coordinax as cx

If the object is the same type, it should return the object itself.

>>> op = cxfm.Identity()
>>> cxfm.Identity.from_(op) is op
True

If the object is a different type, it will error.

>>> try:
...     cxfm.Translate.from_(op)
... except TypeError as e:
...     print(e)
Cannot construct <class '...Translate'> from <class '...Identity'>.

Unless the object is a subclass of the target class.

>>> class MyOperator(cxfm.Identity):
...     pass
>>> op = MyOperator()
>>> op
MyOperator()
>>> newop = cxfm.Identity.from_(op)
>>> newop is op, isinstance(newop, cxfm.Identity)
(False, True)
from_(cls: type[TimeDep], fn: Callable[..., Any], /, *args: Any, **kw: Any) → TimeDep
Parameters:
Return type:

AbstractTransform

Build from a tau -> transform callable.

tau is a call-time argument, never a stored parameter, so a user-defined function’s tau dependence is fully differentiated by the kinematic-prolongation engine – even for a bare lambda. act on tangent data and act_jet pick up the resulting d/dtau terms automatically.

Storage matters only for the builder’s other parameters. With no extra arguments: a bare function/lambda cannot be a pytree leaf, so it is stored STATIC – anything it closes over is a trace-time constant and a fresh closure forces a jit retrace. A callable that is already a pytree (an equinox.Module, notably equinox.Partial) is used as the builder as-is, keeping its leaves dynamic – differentiable and jit-cached. (Wrapping it would set those leaves static, silently destroying them.)

Given extra *args/**kw, they are bound onto fn with equinox.Partial, which keeps them as dynamic leaves. ``fn`` must take ``tau`` LAST – fn(param..., tau, **kw) – because equinox.Partial prepends its bound positionals: partial(tau) calls fn(*bound, tau, **kw). Writing fn(tau, param) instead silently passes the parameter as tau. The resulting builder is a equinox.Partial, so the operator needs equinox.filter_jit, not plain jax.jit.

Examples

>>> import equinox as eqx
>>> import jax
>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.transforms as cxfm

A bare lambda is enough for the tau derivatives to flow: this drift at 3 km/s, acted on data at rest, transforms the velocity to exactly the drift rate.

>>> rate = {"x": u.Q(3.0, "km/s"), "y": u.Q(0.0, "km/s"), "z": u.Q(0.0, "km/s")}
>>> drift = cxfm.TimeDep.from_(
...     lambda t: cxfm.Translate({k: v * t for k, v in rate.items()},
...                              chart=cxc.cart3d)
... )
>>> at_rest = cx.Coordinate(
...     point=cx.Point.from_([0.0, 0.0, 0.0], "km"),
...     velocity=cx.Tangent.from_([0.0, 0.0, 0.0], "km/s"),
... )
>>> cxfm.act(drift, u.Q(0.0, "s"), at_rest)["velocity"]["x"]
Q(3., 'km / s')

Extra arguments are bound as leaves – keeping them differentiable and jit-cached. Note t comes last:

>>> def scaled(factor, t) -> cxfm.Scale:
...     return cxfm.Scale.from_factors(jnp.full(3, factor))
>>> op = cxfm.TimeDep.from_(scaled, jnp.asarray(2.0))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]

A pre-built equinox.Partial is equivalent:

>>> op = cxfm.TimeDep.from_(eqx.Partial(scaled, jnp.asarray(2.0)))
>>> [float(x) for x in jax.tree.leaves(eqx.filter(op, eqx.is_array))]
[2.0]
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], obj: coordinax.transforms._src.actions.add.AbstractAdd, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct a AbstractAdd from another AbstractAdd.

>>> import coordinax as cx
>>> shift1 = cxfm.Translate.from_([1, 2, 3], "km")
>>> cxfm.Translate.from_(shift1) is shift1
True
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], q: AbstractQuantity, /) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an AbstractAdd subclass from a Quantity.

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_(u.Q([1, 2, 3], "km"))
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[coordinax.transforms._src.actions.add.AbstractAdd], x: ArrayLike, unit: str) → coordinax.transforms._src.actions.add.AbstractAdd
Parameters:
Return type:

AbstractTransform

Construct an Add operator from an array-like offset and unit.

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km")
Translate(
    {'x': Q(1, 'km'), 'y': Q(2, 'km'), 'z': Q(3, 'km')}, chart=Cart3D(M=Rn(3))
)
from_(cls: type[Rotate], obj: Rotate, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from another Rotate.

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> R = cxfm.Rotate(jnp.eye(3))
>>> cxfm.Rotate.from_(R) is R
True
from_(cls: type[Rotate], obj: AbstractQuantity, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from a Quantity.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(u.Q(jnp.eye(3), ""))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: ArrayLike, /) → Rotate
Parameters:
Return type:

AbstractTransform

Construct a Rotate from an Array.

>>> import jax.numpy as jnp
>>> import coordinax.transforms as cxfm
>>> cxfm.Rotate.from_(jnp.eye(3))
Rotate(f64[3,3](jax))
from_(cls: type[Rotate], obj: Rotation, /) → Rotate
Parameters:
Return type:

AbstractTransform

Initialize from a jax.scipy.spatial.transform.Rotation.

>>> import jax.numpy as jnp
>>> from jax.scipy.spatial.transform import Rotation
>>> import coordinax as cx
>>> R = Rotation.from_euler("z", 90, degrees=True)
>>> op = cxfm.Rotate.from_(R)
>>> jnp.allclose(op.R, R.as_matrix())
Array(True, dtype=bool)
from_(cls: type[Linear], obj: ArrayLike, /) → Linear
Parameters:
Return type:

AbstractTransform

Construct a Linear from an array matrix.

Registered on the class’s own dispatcher, as the sibling transforms do. Via the global plum.dispatch the ArrayLike union is unfaithful, which would turn off plum’s method cache for every from_ in the library – caught by test_no_new_unfaithful_signatures.

from_(cls: type[Reflect], obj: Reflect, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from another Reflect.

from_(cls: type[Reflect], obj: AbstractQuantity, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from a dimensionless quantity matrix.

from_(cls: type[Reflect], obj: ArrayLike, /) → Reflect
Parameters:
Return type:

AbstractTransform

Construct a Reflect from an array matrix.

from_(cls: type[Scale], obj: Scale, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from another Scale.

from_(cls: type[Scale], obj: AbstractQuantity, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from a dimensionless quantity matrix.

from_(cls: type[Scale], obj: ArrayLike, /) → Scale
Parameters:
Return type:

AbstractTransform

Construct a Scale from an array matrix.

from_(cls: type[Shear], obj: Shear, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from another Shear.

from_(cls: type[Shear], obj: AbstractQuantity, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from a dimensionless quantity matrix.

from_(cls: type[Shear], obj: ArrayLike, /) → Shear
Parameters:
Return type:

AbstractTransform

Construct a Shear from an array matrix.

Parameters:
Return type:

AbstractTransform

property is_time_dependent: bool

Whether the point action depends on the time parameter tau.

A declared trait, not a structural scan: subclasses whose point action varies with tau (TimeDep, Boost) override this to True; composites (Composed) override it to the disjunction of their components. The default is False.

Examples

>>> import coordinax.transforms as cxfm
>>> cxfm.Translate.from_([1, 2, 3], "km").is_time_dependent
False
property matrix: Array

The matrix \(M\) this transform applies, as \(x \mapsto M x\).

The one spelling shared by every subclass, which otherwise keep \(M\) under their own letter (R, H, S) or, for ~coordinax.transforms.LorentzBoost, derive it from a stored velocity.

Validated square, but not against a chart – a chart is what fixes the dimension and this takes none, so the act paths keep that check.

Examples

>>> import quaxed.numpy as jnp
>>> import coordinax.transforms as cxfm

For a stored matrix it is the field under a common name:

>>> Rz = jnp.asarray([[0, -1, 0], [1, 0, 0], [0, 0, 1]])
>>> bool(jnp.array_equal(cxfm.Rotate(Rz).matrix, Rz))
True

For a derived one it is the only way to see it:

>>> cxfm.LorentzBoost([0.6, 0.0, 0.0]).matrix.round(2)
Array([[1.25, 0.75, 0.  , 0.  ],
       [0.75, 1.25, 0.  , 0.  ],
       [0.  , 0.  , 1.  , 0.  ],
       [0.  , 0.  , 0.  , 1.  ]], dtype=float64)
simplify()

Simplify the operator.

This method calls coordinax.ops.simplify to simplify the operator.

Return type:

AbstractTransform

Examples

>>> import unxt as u
>>> import coordinax.transforms as cxfm
>>> op = cxfm.Identity()
>>> op.simplify() is op
True
>>> pipe = cxfm.Composed((cxfm.Identity(), cxfm.Identity()))
>>> pipe
Composed((Identity(), Identity()))
>>> pipe.simplify()
Identity()
coordinax.cconvert(*args, **kwargs)

Transform the current vector to the target chart.

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

Examples

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

>>> cxr.cconvert(p, cxc.cart3d, cxr.point, cxc.sph3d, cxr.point)
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}
coordinax.cconvert(obj: NoneType, /, *fixed_args: Any, **fixed_kw: Any) → Any
Parameters:
Return type:

Any

Return a partial function for vector conversion.

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:

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

Convert it to spherical coordinates:

>>> map = cxr.cconvert(None, cxc.cart3d, cxr.point, cxc.sph3d)
>>> map(q)
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}
coordinax.cconvert(x: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, to_rep: Representation, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

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 = cxr.cconvert(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:

>>> cxr.cconvert(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")}
>>> cxr.cconvert(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}
>>> cxr.cconvert(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")}
>>> cxr.cconvert(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")}
>>> cxr.cconvert(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")}
>>> cxr.cconvert(p, cxc.cart2d, cxr.point, cxc.cart2d, cxr.point) is p
True
coordinax.cconvert(x: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

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 = cxr.cconvert(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:

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

Any

Convert point data between charts.

This function delegates to coordinax.charts.pt_map. The representation arguments are checked to ensure they correspond to canonical point data:

\[(\mathrm{PointGeometry},\, \mathrm{NoBasis},\, \mathrm{Location}).\]

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:

>>> cxr.cconvert(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.cconvert(x: Any, from_chart: AbstractChart, from_geom: TangentGeometry, from_rep: Representation, to_chart: AbstractChart, to_geom: TangentGeometry, to_rep: Representation, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → Any
Parameters:
Return type:

Any

Convert tangent data between charts via Jacobian pushforward.

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"r": jnp.array(5.0), "theta": jnp.array(1.0), "phi": jnp.array(2.0)}
>>> at = {"r": jnp.array(3.0), "theta": jnp.array(0.5), "phi": jnp.array(0.0)}
>>> cxr.cconvert(v, cxc.sph3d, cxr.tangent_geom, cxr.coord_disp,
...              cxc.sph3d, cxr.tangent_geom, cxr.phys_disp, at=at)
{'r': Array(5., dtype=float64, ...),
 'theta': Array(3., dtype=float64, ...),
 'phi': Array(..., dtype=float64, ...)}
>>> v = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> at = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> cxr.cconvert(v, cxc.cart2d, cxr.coord_disp, cxc.polar2d, cxr.coord_disp, at=at)
{'r': Array(1., ...), 'theta': Array(0., ...)}
coordinax.cconvert(from_vec: Point, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → Point
Parameters:
Return type:

Any

Convert a point from one chart to another.

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

Any

Convert a vector from one chart to another.

>>> import unxt as u
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 1, 1], "m")
>>> sph_vec = cx.cconvert(vec, cx.cart3d, cx.sph3d)
>>> print(sph_vec)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [1.732 0.955 0.785]>
coordinax.cconvert(from_vec: Tangent, to_chart: AbstractChart, /, *, at: Any = None, usys: AbstractUnitSystem | None = None) → Tangent
Parameters:
Return type:

Any

Convert a tangent Tangent from one chart to another.

The at parameter provides the base point at which the tangent map (Jacobian pushforward) is evaluated. It may be a Point instance (whose .data is used) or a raw CDict.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel,
... )
>>> pt = cx.Point.from_([1.0, 0.0, 0.0], "m")
>>> v_sph = cx.cconvert(v, cxc.sph3d, at=pt)
>>> v_sph.chart
Spherical3D(M=Rn(3))
coordinax.cconvert(from_vec: Tangent, from_chart: AbstractChart, to_chart: AbstractChart, /, *, at: Any = None, usys: AbstractUnitSystem | None = None) → Tangent
Parameters:
Return type:

Any

Convert a tangent Tangent from one chart to another (explicit from-chart).

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel,
... )
>>> pt = cx.Point.from_([1.0, 0.0, 0.0], "m")
>>> v_sph = cx.cconvert(v, cxc.cart3d, cxc.sph3d, at=pt)
>>> v_sph.chart
Spherical3D(M=Rn(3))
coordinax.cconvert(pv: Coordinate, to_chart: AbstractChart, /, *, usys: AbstractUnitSystem | None = None) → Coordinate
Parameters:
Return type:

Any

Convert a Coordinate to a new chart.

Delegates to Coordinate.cconvert.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> pt = cx.Point.from_([1.0, 0.0, 0.0], "m")
>>> pv = cx.Coordinate(point=pt)
>>> pv_sph = cx.cconvert(pv, cxc.sph3d)
>>> pv_sph.point.chart
Spherical3D(M=Rn(3))
Parameters:
Return type:

Any

coordinax.add(*args, **kwargs)

Add two coordinate data objects.

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

coordinax.add(lhs: Any, lhs_chart: AbstractChart, lhs_rep: Representation, rhs: Any, rhs_chart: AbstractChart, rhs_rep: Representation, /) → Any
Parameters:
Return type:

Any

Add two coordinate data objects via Cartesian round-trip.

Both operands are converted to the ambient Cartesian chart of lhs_chart, added component-wise, then converted back to lhs_chart. If lhs_chart is already Cartesian (or has no global Cartesian), rhs is converted into lhs_chart and added directly.

>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p1 = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> p2 = {"x": u.Q(4, "m"), "y": u.Q(5, "m"), "z": u.Q(6, "m")}
>>> cxr.add(p1, cxc.cart3d, cxr.point, p2, cxc.cart3d, cxr.point)
{'x': Q(5, 'm'), 'y': Q(7, 'm'), 'z': Q(9, 'm')}
coordinax.add(lhs: Point, rhs: Point, /) → Point
Parameters:
Return type:

Any

Add two points.

For non-Cartesian charts the operation converts both operands to the ambient Cartesian chart, adds there, and converts the result back to the lhs chart. For Cartesian charts the addition is direct.

The result keeps the lhs chart and representation.

>>> import coordinax as cx
>>> v1 = cx.Point.from_([1, 2, 3], "m")
>>> v2 = cx.Point.from_([4, 5, 6], "m")
>>> print(cxr.add(v1, v2))
<Point: chart=Cart3D (x, y, z) [m]
    [5 7 9]>
coordinax.add(lhs: Tangent, rhs: Tangent, /) → Tangent
Parameters:
Return type:

Any

Add two tangent vectors component-wise.

Tangent spaces are genuine vector spaces: addition is component-wise in any chart basis (no Cartesian round-trip is needed or correct). Both operands must share the same chart and representation (basis + semantic) – adding components from two different charts is meaningless even when the charts happen to name their components the same way.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v1 = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel,
... )
>>> v2 = cx.Tangent.from_(
...     {"x": u.Q(4.0, "m/s"), "y": u.Q(5.0, "m/s"), "z": u.Q(6.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel,
... )
>>> result = cxr.add(v1, v2)
>>> result["x"]
Q(5., 'm / s')
Parameters:
Return type:

Any

coordinax.subtract(*args, **kwargs)

Subtract two coordinate data objects.

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

coordinax.subtract(lhs: Any, lhs_chart: AbstractChart, lhs_rep: Representation, rhs: Any, rhs_chart: AbstractChart, rhs_rep: Representation, /) → Any
Parameters:
Return type:

Any

Subtract two coordinate data objects via Cartesian round-trip.

Both operands are converted to the ambient Cartesian chart of lhs_chart, subtracted component-wise, then converted back to lhs_chart. If lhs_chart is already Cartesian (or has no global Cartesian), rhs is converted into lhs_chart and subtracted directly.

>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p1 = {"x": u.Q(4, "m"), "y": u.Q(5, "m"), "z": u.Q(6, "m")}
>>> p2 = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> cxr.subtract(p1, cxc.cart3d, cxr.point, p2, cxc.cart3d, cxr.point)
{'x': Q(3, 'm'), 'y': Q(3, 'm'), 'z': Q(3, 'm')}
coordinax.subtract(lhs: Point, rhs: Point, /) → Point
Parameters:
Return type:

Any

Subtract two vectors.

For non-Cartesian charts the operation converts both operands to the ambient Cartesian chart, subtracts there, and converts the result back to the lhs chart. For Cartesian charts the subtraction is direct.

The result keeps the lhs chart and representation.

>>> import coordinax as cx
>>> v1 = cx.Point.from_([4, 5, 6], "m")
>>> v2 = cx.Point.from_([1, 2, 3], "m")
>>> print(cxr.subtract(v1, v2))
<Point: chart=Cart3D (x, y, z) [m]
    [3 3 3]>
coordinax.subtract(lhs: Tangent, rhs: Tangent, /) → Tangent
Parameters:
Return type:

Any

Subtract two tangent vectors component-wise.

Tangent spaces are genuine vector spaces: subtraction is component-wise in any chart basis (no Cartesian round-trip is needed or correct). Both operands must share the same chart and representation (basis + semantic) – subtracting components from two different charts is meaningless even when the charts happen to name their components the same way.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v1 = cx.Tangent.from_(
...     {"x": u.Q(4.0, "m/s"), "y": u.Q(5.0, "m/s"), "z": u.Q(6.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel,
... )
>>> v2 = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel,
... )
>>> result = cxr.subtract(v1, v2)
>>> result["x"]
Q(3., 'm / s')
Parameters:
Return type:

Any

final class coordinax.PointGeometry

Bases: AbstractGeometry

Point geometric kind.

A point geometry indicates that component data should be interpreted as the coordinates of a point on a manifold, rather than as components of a vector- or covector-like object.

Mathematical Definition:

Let \(M\) be a smooth manifold. A point is an element \(p \in M\).

Point data is therefore affine, not linear: points do not in general form a vector space, so operations such as adding two points are not geometrically defined. Under a change of chart, point coordinates transform by the ordinary chart transition map.

Examples

Construct the point geometry object directly:

>>> import coordinax.representations as cxr
>>> geom = cxr.PointGeometry()

Use it inside a full representation for point data:

>>> rep = cxr.Representation(geom, cxr.no_basis, cxr.loc)

The representation can then be used with cconvert to convert point data between charts while preserving the represented geometric object:

>>> import coordinax.charts as cxc
>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> cxr.cconvert(p, cxc.cart3d, rep, cxc.sph3d, rep)
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}

The output is still point data, but expressed in the target chart.

Notes

PointGeometry describes only the geometric kind. The chart still determines the coordinate system, component names, and coordinate domains.

canonical_name: ClassVar = 'point_geom'

Canonical name for the point geometry kind.

final class coordinax.TangentGeometry

Bases: AbstractGeometry

Tangent-vector geometric kind.

A tangent geometry indicates that component data should be interpreted as the components of a tangent vector at a point on a manifold, rather than as coordinates of a point.

Mathematical Definition:

Let \(M\) be a smooth manifold and \(p \in M\) a point. A tangent vector is an element \(v \in T_p M\) of the tangent space at \(p\).

Tangent data is linear: tangent vectors form a vector space, so adding two tangent vectors at the same point is geometrically well-defined. Under a change of chart, tangent vector components transform by the Jacobian (pushforward) of the chart transition map.

Examples

Construct the tangent geometry object directly:

>>> import coordinax.representations as cxr
>>> geom = cxr.TangentGeometry()

Use it inside a full representation for tangent data:

>>> rep = cxr.Representation(geom, cxr.coord_basis, cxr.dpl)

Notes

This is a static dispatch object and carries no runtime numerical data. TangentGeometry describes only the geometric kind. The chart still determines the coordinate system, component names, and coordinate domains.

canonical_name: ClassVar = 'tangent_geom'

Canonical name for the tangent geometry kind.

final class coordinax.NoBasis

Bases: AbstractBasis

No-basis kind.

A no-basis kind indicates that the represented data does not live in a basis-dependent linear space, so there is no meaningful choice of components with respect to a basis.

Mathematical Definition:

NoBasis is used for geometric objects whose representation is not given by expansion in a vector-space basis. The canonical example is a point on a manifold.

Let \(M\) be a smooth manifold. A point is an element \(p \in M\), not a vector in a tangent space \(T_p M\). Accordingly, point data has no associated basis: it is represented by chart coordinates, and under a change of chart those coordinates transform by the ordinary chart transition map, not by a change of basis.

Examples

Construct the no-basis object directly:

>>> import coordinax.representations as cxr
>>> basis = cxr.NoBasis()

Use it inside a full representation for point data:

>>> rep = cxr.Representation(cxr.point_geom, basis, cxr.loc)

The representation can then be used with coordinax.representations.cconvert to convert point data between charts while preserving the fact that point data has no basis-dependent components:

>>> import coordinax.charts as cxc
>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> q = cxr.cconvert(p, cxc.cart3d, rep, cxc.sph3d)

The output q is still point data, but expressed in the target chart.

>>> import wadler_lindig as wl
>>> wl.pprint(basis, canonical=False)
NoBasis()
>>> wl.pprint(basis, canonical=True)
no_basis

Notes

NoBasis does not mean “no coordinates”. It means that the represented object is not described by components in a basis-dependent linear space.

canonical_name: ClassVar = 'no_basis'

Canonical name for the no-basis kind.

class coordinax.AbstractLinearBasis

Bases: AbstractBasis

Abstract base class for linear (basis-dependent) basis kinds.

A linear basis kind indicates that component data is expressed as components with respect to a specific choice of basis vectors in a tangent space or other linear space.

In the representation model used by coordinax, AbstractLinearBasis refines AbstractBasis by indicating that the object lives in a basis-dependent linear space, in contrast to NoBasis which is used for affine point data.

Examples

>>> import coordinax.representations as cxr
>>> isinstance(cxr.CoordinateBasis(), cxr.AbstractLinearBasis)
True
canonical_name: ClassVar[str | None] = None

Canonical name for the kind, or None to always render verbosely.

final class coordinax.CoordinateBasis

Bases: AbstractLinearBasis

Coordinate basis kind.

A coordinate basis kind indicates that tangent-vector components are expressed in the coordinate basis induced by the current chart. In a coordinate basis, the basis vectors are the partial derivative operators \(\partial/\partial x^i\) associated with the chart coordinates \(x^i\).

Mathematical Definition:

Let \((U, \varphi)\) be a chart on a smooth manifold \(M\). The coordinate basis at a point \(p \in U\) consists of the tangent vectors \(\partial_i = \partial/\partial x^i|_p\). A tangent vector \(v \in T_p M\) expressed in the coordinate basis has components \(v^i\) such that \(v = v^i \partial_i\).

Under a change of chart, coordinate-basis components transform by the Jacobian matrix of the chart transition map.

Examples

Construct the coordinate basis object directly:

>>> import coordinax.representations as cxr
>>> basis = cxr.CoordinateBasis()

Use it inside a full representation for tangent data:

>>> rep = cxr.Representation(cxr.tangent_geom, basis, cxr.dpl)

Notes

CoordinateBasis does not carry unit-length information. Components in a coordinate basis are not necessarily dimensionless: for example, the \(\partial_r\) component of a vector in spherical coordinates carries units of inverse length relative to the physical basis.

canonical_name: ClassVar = 'coord_basis'

Canonical name for the coordinate basis kind.

final class coordinax.PhysicalBasis

Bases: AbstractLinearBasis

Physical (orthonormal) basis kind.

A physical basis kind indicates that tangent-vector components are expressed in an orthonormal physical basis. In a physical basis, the basis vectors are unit-length and mutually orthogonal with respect to the metric.

Mathematical Definition:

Let \(M\) carry a Riemannian metric \(g\) and let \((U, \varphi)\) be a chart on \(M\). The physical basis at a point \(p \in U\) consists of unit vectors \(\hat{e}_i = \partial_i / \sqrt{g_{ii}}\) (for orthogonal charts). A tangent vector \(v \in T_p M\) expressed in the physical basis has components \(\hat{v}^i\) such that \(v = \hat{v}^i \hat{e}_i\).

Under a change of chart, physical-basis components transform by both the Jacobian and the normalization factors of the chart transition.

Examples

Construct the physical basis object directly:

>>> import coordinax.representations as cxr
>>> basis = cxr.PhysicalBasis()

Use it inside a full representation for tangent data:

>>> rep = cxr.Representation(cxr.tangent_geom, basis, cxr.vel)

Notes

PhysicalBasis components have consistent physical dimensions across all charts, unlike coordinate-basis components.

canonical_name: ClassVar = 'phys_basis'

Canonical name for the physical basis kind.

final class coordinax.Location

Bases: AbstractSemanticKind

Location semantic kind.

A location semantic kind indicates that the represented data specifies where a geometric object is, rather than how it is displaced, how fast it moves, or how it accelerates.

Mathematical Definition:

Location is the canonical semantic kind for point data. Let \(M\) be a smooth manifold. A point is an element \(p \in M\), and Location indicates that the represented data should be interpreted as the coordinates of that point in some chart.

The semantic kind Location therefore does not change the underlying geometric transformation law: for point data, coordinates still transform by the ordinary chart transition map. Instead, it records the interpretation of the point-like data as an actual position on the manifold.

Examples

Construct the location semantic object directly:

>>> import coordinax.representations as cxr
>>> semantic = cxr.Location()

Use it inside a full representation for point data:

>>> rep = cxr.Representation(cxr.point_geom, cxr.no_basis, semantic)

The representation can then be used with cconvert to convert point data between charts while preserving the fact that the data represents a location:

>>> import coordinax.charts as cxc
>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> cxr.cconvert(p, cxc.cart3d, rep, cxc.sph3d, rep)
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}

The output is still point data representing the same location, but expressed in the target chart.

Notes

Location does not by itself imply that the represented object is a point, but in the current coordinax design it is primarily used as the semantic kind paired with PointGeometry.

canonical_name: ClassVar = 'loc'

Canonical name for the location semantic kind.

classmethod coord_dimensions(cls, chart: AbstractChart[Any, Any, Any], /)

Return the physical dimensions of each component for a location.

For a location semantic kind, the dimensions are determined solely by the chart: each component’s dimension is taken directly from chart.coord_dimensions.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> cxr.Location.coord_dimensions(cxc.cart3d)
(PhysicalType('length'), PhysicalType('length'), PhysicalType('length'))
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

tuple[PhysicalType | None, ...]

order: ClassVar[int | None] = None

Time-derivative ladder order, or None if not on the ladder.

Tangent semantic kinds (AbstractTangentSemanticKind) override this with an int (0=displacement, 1=velocity, 2=acceleration, …); non-ladder kinds such as Location leave it as None.

class coordinax.AbstractTangentSemanticKind

Bases: AbstractSemanticKind

Abstract base class for tangent-vector semantic kinds.

A tangent semantic kind specifies the meaning of a tangent-vector object, distinguishing objects that are geometrically identical (both elements of a tangent space \(T_p M\)) but semantically different in how they are used or interpreted.

Examples include:

  • Displacement: a finite difference between two nearby points,

  • Velocity: the rate of change of position with respect to time,

  • Acceleration: the second derivative of position with respect to time.

All share the same coordinate-transformation law (Jacobian pushforward) but differ in physical dimension and physical interpretation.

Examples

>>> import coordinax.representations as cxr
>>> isinstance(cxr.Displacement(), cxr.AbstractTangentSemanticKind)
True
>>> isinstance(cxr.Velocity(), cxr.AbstractTangentSemanticKind)
True
order: ClassVar[int] = None

Time-derivative order of the role (e.g. 0=pos, 1=vel, 2=acc, …).

classmethod coord_dimensions(cls, chart: AbstractChart[Any, Any, Any], /)

Return the physical dimensions of each component for this tangent kind.

Each component’s base dimension is taken from chart.coord_dimensions and scaled by the appropriate power of time according to cls.order: dimension / time^order.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.cart3d)]
['length', 'length', 'length']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.cart3d)]
['speed/...', 'speed/...', 'speed/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.cart3d)]
['acceleration', 'acceleration', 'acceleration']
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.sph3d)]
['length', 'angle', 'angle']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.sph3d)]
['speed/...', 'angular frequency/...', 'angular frequency/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.sph3d)]
['acceleration', 'angular acceleration', 'angular acceleration']
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

tuple[PhysicalType | None, ...]

derivative()

Return the semantic kind one step up the time-derivative ladder.

Looks up self.order + 1 in the internal order registry and returns a fresh instance of the registered class. Raises ValueError if no class is registered at that order.

This design is open for extension: registering a new AbstractTangentSemanticKind subclass at order N automatically makes kind_at_N_minus_1.derivative() return an instance of that class.

Raises:

ValueError – If no tangent semantic kind is registered at self.order + 1.

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Displacement().derivative()
vel
>>> cxr.Velocity().derivative()
acc
>>> try:
...     cxr.Acceleration().derivative()   # no Jerk registered yet
... except ValueError as e:
...     print(type(e).__name__)
ValueError
antiderivative()

Return the semantic kind one step down the time-derivative ladder.

Looks up self.order - 1 in the internal order registry and returns a fresh instance of the registered class. Raises ValueError if no class is registered at that order.

This design is open for extension: registering a new AbstractTangentSemanticKind subclass at order N automatically makes kind_at_N_plus_1.antiderivative() return an instance of that class. For example, once an Absement class is registered at order -1, Displacement().antiderivative() will return Absement().

Raises:

ValueError – If no tangent semantic kind is registered at self.order - 1.

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Acceleration().antiderivative()
vel
>>> cxr.Velocity().antiderivative()
dpl
>>> try:
...     cxr.Displacement().antiderivative()   # no Absement registered yet
... except ValueError as e:
...     print(type(e).__name__)
ValueError
canonical_name: ClassVar[str | None] = None

Canonical name for the kind, or None to always render verbosely.

final class coordinax.Displacement

Bases: AbstractTangentSemanticKind

Displacement semantic kind.

A displacement semantic kind indicates that the represented tangent data is a spatial displacement — a finite difference between two nearby points on a manifold, expressed as a tangent vector.

Mathematical Definition:

Let \(M\) be a smooth manifold and \(p, q \in M\) two nearby points. In a chart \((U, \varphi)\), the displacement from \(p\) to \(q\) is the vector \(\Delta x = \varphi(q) - \varphi(p) \in \mathbb{R}^n\). As a tangent vector, this element of \(T_p M\) transforms by the Jacobian of the chart transition map.

Examples

>>> import coordinax.representations as cxr
>>> semantic = cxr.Displacement()
>>> semantic.canonical_name
'dpl'
canonical_name: ClassVar = 'dpl'

Canonical name for the displacement semantic kind.

order: ClassVar[int] = 0

Time-derivative order of the role (0 for displacement).

derivative()

Return the Velocity semantic kind.

Displacement has time-order 0; its time derivative is always Velocity (order 1). This override avoids a dict lookup and makes the intent explicit.

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Displacement().derivative()
vel
antiderivative()

Return the semantic kind one step down the time-derivative ladder.

Looks up self.order - 1 in the internal order registry and returns a fresh instance of the registered class. Raises ValueError if no class is registered at that order.

This design is open for extension: registering a new AbstractTangentSemanticKind subclass at order N automatically makes kind_at_N_plus_1.antiderivative() return an instance of that class. For example, once an Absement class is registered at order -1, Displacement().antiderivative() will return Absement().

Raises:

ValueError – If no tangent semantic kind is registered at self.order - 1.

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Acceleration().antiderivative()
vel
>>> cxr.Velocity().antiderivative()
dpl
>>> try:
...     cxr.Displacement().antiderivative()   # no Absement registered yet
... except ValueError as e:
...     print(type(e).__name__)
ValueError
classmethod coord_dimensions(cls, chart: AbstractChart[Any, Any, Any], /)

Return the physical dimensions of each component for this tangent kind.

Each component’s base dimension is taken from chart.coord_dimensions and scaled by the appropriate power of time according to cls.order: dimension / time^order.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.cart3d)]
['length', 'length', 'length']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.cart3d)]
['speed/...', 'speed/...', 'speed/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.cart3d)]
['acceleration', 'acceleration', 'acceleration']
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.sph3d)]
['length', 'angle', 'angle']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.sph3d)]
['speed/...', 'angular frequency/...', 'angular frequency/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.sph3d)]
['acceleration', 'angular acceleration', 'angular acceleration']
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

tuple[PhysicalType | None, ...]

final class coordinax.Velocity

Bases: AbstractTangentSemanticKind

Velocity semantic kind.

A velocity semantic kind indicates that the represented tangent data is a velocity vector — the first time derivative of position on a manifold.

Mathematical Definition:

Let \(\gamma: \mathbb{R} \to M\) be a smooth curve on manifold \(M\). The velocity at time \(t\) is the tangent vector \(\dot{\gamma}(t) \in T_{\gamma(t)} M\). In a chart, velocity components \(\dot{x}^i = d(x^i \circ \gamma)/dt\) transform by the Jacobian of the chart transition map.

Examples

>>> import coordinax.representations as cxr
>>> semantic = cxr.Velocity()
>>> semantic.canonical_name
'vel'
canonical_name: ClassVar = 'vel'

Canonical name for the velocity semantic kind.

classmethod coord_dimensions(cls, chart: AbstractChart[Any, Any, Any], /)

Return the physical dimensions of each component for this tangent kind.

Each component’s base dimension is taken from chart.coord_dimensions and scaled by the appropriate power of time according to cls.order: dimension / time^order.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.cart3d)]
['length', 'length', 'length']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.cart3d)]
['speed/...', 'speed/...', 'speed/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.cart3d)]
['acceleration', 'acceleration', 'acceleration']
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.sph3d)]
['length', 'angle', 'angle']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.sph3d)]
['speed/...', 'angular frequency/...', 'angular frequency/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.sph3d)]
['acceleration', 'angular acceleration', 'angular acceleration']
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

tuple[PhysicalType | None, ...]

order: ClassVar[int] = 1

Time-derivative order of the role (1 for velocity).

derivative()

Return the semantic kind for the time derivative of this velocity.

Velocity has time-order 1. Its time derivative is acceleration (time-order 2).

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Velocity().derivative()
acc
antiderivative()

Return the semantic kind for the time antiderivative of this velocity.

Velocity has time-order 1. Its time antiderivative is displacement (time-order 0).

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Velocity().antiderivative()
dpl
final class coordinax.Acceleration

Bases: AbstractTangentSemanticKind

Acceleration semantic kind.

An acceleration semantic kind indicates that the represented tangent data is an acceleration vector — the covariant second derivative of position along a curve on a manifold.

Mathematical Definition:

Let \(\gamma: \mathbb{R} \to M\) be a smooth curve on manifold \(M\). The acceleration at time \(t\) is the tangent vector \(\nabla_{\dot{\gamma}}\dot{\gamma} \in T_{\gamma(t)} M\).

It coincides with the plain second derivative \(\ddot{q}^i\) of the coordinates only in an affine chart – the condition is the chart, not flatness – so in a curvilinear chart the two differ by the Christoffel term \(\Gamma^i_{jk}\dot{q}^j\dot{q}^k\), nonzero even on flat \(\mathbb{R}^3\). This decides what conversion returns: being a tangent vector, an acceleration pushes forward linearly as \(J a\), so converting a trajectory’s Cartesian \(\ddot{q}\) to a curvilinear chart yields the acceleration vector’s components there, not that chart’s \(\ddot{q}\).

Examples

>>> import coordinax.representations as cxr
>>> semantic = cxr.Acceleration()
>>> semantic.canonical_name
'acc'
classmethod coord_dimensions(cls, chart: AbstractChart[Any, Any, Any], /)

Return the physical dimensions of each component for this tangent kind.

Each component’s base dimension is taken from chart.coord_dimensions and scaled by the appropriate power of time according to cls.order: dimension / time^order.

Examples

>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.cart3d)]
['length', 'length', 'length']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.cart3d)]
['speed/...', 'speed/...', 'speed/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.cart3d)]
['acceleration', 'acceleration', 'acceleration']
>>> [str(x) for x in cxr.Displacement.coord_dimensions(cxc.sph3d)]
['length', 'angle', 'angle']
>>> [str(x) for x in cxr.Velocity.coord_dimensions(cxc.sph3d)]
['speed/...', 'angular frequency/...', 'angular frequency/...']
>>> [str(x) for x in cxr.Acceleration.coord_dimensions(cxc.sph3d)]
['acceleration', 'angular acceleration', 'angular acceleration']
Parameters:

chart (AbstractChart[Any, Any, Any])

Return type:

tuple[PhysicalType | None, ...]

derivative()

Return the semantic kind one step up the time-derivative ladder.

Looks up self.order + 1 in the internal order registry and returns a fresh instance of the registered class. Raises ValueError if no class is registered at that order.

This design is open for extension: registering a new AbstractTangentSemanticKind subclass at order N automatically makes kind_at_N_minus_1.derivative() return an instance of that class.

Raises:

ValueError – If no tangent semantic kind is registered at self.order + 1.

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Displacement().derivative()
vel
>>> cxr.Velocity().derivative()
acc
>>> try:
...     cxr.Acceleration().derivative()   # no Jerk registered yet
... except ValueError as e:
...     print(type(e).__name__)
ValueError
canonical_name: ClassVar = 'acc'

Canonical name for the acceleration semantic kind.

order: ClassVar[int] = 2

Time-derivative order of the role (2 for acceleration).

antiderivative()

Return the semantic kind for the time antiderivative of this acceleration.

Acceleration has time-order 2. Its time antiderivative is velocity (time-order 1).

Return type:

AbstractTangentSemanticKind

Examples

>>> import coordinax.representations as cxr
>>> cxr.Acceleration().antiderivative()
vel
final class coordinax.Representation(geom_kind: GeomT, basis: BasisT, semantic_kind: SemanticT)

Bases: Generic[GeomT, BasisT, SemanticT]

Representation of geometric component data.

A representation specifies what kind of geometric object component data is meant to represent, independently of the chart used to write down the coordinates or components.

In coordinax, a representation is the ordered triple

\[R = (K, B, S),\]

where:

  • \(K\) is the geometric kind (coordinax.representations.AbstractGeometry),

  • \(B\) is the basis kind (coordinax.representations.AbstractBasis), and

  • \(S\) is the semantic kind (coordinax.representations.AbstractSemanticKind).

Thus a Representation answers three distinct questions:

  1. What sort of geometric object is this? For example, a point.

  2. In what basis are its components written? For example, no basis for affine point data.

  3. What does the object mean? For example, a location on a manifold.

A representation is therefore not the same thing as a chart.

  • A chart specifies how a manifold is coordinatized locally: component names, ordering, dimensionalities, and the coordinate map into \(\mathbb{R}^n\).

  • A representation specifies how the data should be interpreted geometrically.

Equivalently: the chart determines the coordinate system, while the representation determines the geometric role of the data written in that coordinate system.

For the current point-focused design, the canonical representation is

\[(\mathrm{PointGeometry},\, \mathrm{NoBasis},\, \mathrm{Location}).\]

This indicates that the data represents a point on a manifold, that it does not live in a basis-dependent linear space, and that its semantic meaning is a location.

Parameters:

Examples

Construct the canonical point representation directly:

>>> import coordinax.representations as cxr
>>> rep = cxr.Representation(cxr.PointGeometry(), cxr.NoBasis(), cxr.Location())

coordinax also provides the predefined point representation:

>>> rep == cxr.point
True

Use the representation with cconvert to convert point data between charts while preserving its geometric interpretation:

>>> import coordinax.charts as cxc
>>> p = {"x": 1.0, "y": 2.0, "z": 3.0}
>>> cxr.cconvert(p, cxc.cart3d, cxr.point, cxc.sph3d, cxr.point)
{'r': Array(3.74165739, dtype=float64, ...),
 'theta': Array(0.64052231, dtype=float64, ...),
 'phi': Array(1.10714872, dtype=float64, ...)}

The output represents the same point, but in the target chart.

Notes

Representation is a static, immutable descriptor object. It carries no runtime numerical data itself; it only describes how such data should be interpreted.

geom_kind: GeomT

Geometric kind of the represented object.

basis: BasisT

Basis kind in which components are expressed.

semantic_kind: SemanticT

Semantic interpretation attached to the represented object.

coordinax.tangent_map(*args, **kwargs)

Compute the tangent map (Jacobian) of a chart transition.

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

Examples

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
coordinax.tangent_map(v: Any, from_chart: AbstractChart, basis: CoordinateBasis, to_chart: AbstractChart, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Push a tangent vector forward from one chart to another.

Applies the Jacobian of the chart transition map to the tangent vector components v, evaluated at the base point at.

Convert a tangent vector from Cartesian to polar 2D at the point (1, 0):

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> at = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> cxr.tangent_map(v, cxc.cart2d, cxr.coord_disp, cxc.polar2d, at=at)
{'r': Array(1., dtype=float64), 'theta': Array(0., dtype=float64)}
coordinax.tangent_map(v: dict, from_chart: AbstractChart, basis: PhysicalBasis, to_chart: AbstractChart, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Push a tangent vector forward in physical-basis components.

This dispatch applies the tangent-map pushforward while preserving the physical-basis convention by composing three steps:

  1. convert source components from physical basis to coordinate basis,

  2. apply the chart Jacobian pushforward,

  3. convert target components back to physical basis.

Convert a physical-basis tangent vector from Cartesian to spherical 3D:

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"x": u.Q(1, "m/s"), "y": u.Q(0, "m/s"), "z": u.Q(0, "m/s")}
>>> at = {"x": u.Q(1, "m"), "y": u.Q(0, "m"), "z": u.Q(0, "m")}
>>> cxr.tangent_map(v, cxc.cart3d, cxr.phys_basis, cxc.sph3d, at=at)
{'r': Q(1., 'm / s'), 'theta': Q(0., 'm / s'), 'phi': Q(0., 'm / s')}

The same call can be made using a physical representation:

>>> v = {"x": 1, "y": 0, "z": 0}
>>> at = {"x": 1, "y": 0, "z": 0}
>>> usys = u.unitsystems.si
>>> cxr.tangent_map(v, cxc.cart3d, cxr.phys_disp, cxc.sph3d, at=at, usys=usys)
{'r': Array(1., dtype=float64), 'theta': Array(0., dtype=float64),
 'phi': Array(0., dtype=float64)}
coordinax.tangent_map(v: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Push a tangent vector forward from one chart to another.

Applies the Jacobian of the chart transition map to the tangent vector components v, evaluated at the base point at.

Convert a tangent vector from Cartesian to polar 2D at the point (1, 0):

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> at = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> cxr.tangent_map(v, cxc.cart2d, cxr.coord_disp, cxc.polar2d, at=at)
{'r': Array(1., dtype=float64), 'theta': Array(0., dtype=float64)}
coordinax.tangent_map(v: Any, from_chart: AbstractChart, from_rep: Representation, to_chart: AbstractChart, to_rep: Representation, /, *, at: dict | None = None, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Push a tangent vector forward from one chart to another.

Applies the Jacobian of the chart transition map to the tangent vector components v, evaluated at the base point at.

Convert a tangent vector from Cartesian to polar 2D at the point (1, 0):

>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> at = {"x": jnp.array(1.0), "y": jnp.array(0.0)}
>>> cxr.tangent_map(v, cxc.cart2d, cxr.coord_disp,
...                 cxc.polar2d, cxr.coord_disp, at=at)
{'r': Array(1., dtype=float64), 'theta': Array(0., dtype=float64)}
Parameters:
Return type:

Any

coordinax.change_basis(*args, **kwargs)

Change the basis of a tangent vector’s components.

Examples

>>> import coordinax.representations as cxr
>>> import coordinax.charts as cxc
>>> v = {"x": 1.0, "y": 0.0}
>>> at = {"x": 1.0, "y": 0.0}
>>> cxr.change_basis(v, cxc.cart2d, cxr.coord_basis, cxr.phys_basis, at=at)
{'x': 1.0, 'y': 0.0}
coordinax.change_basis(v: dict, chart: AbstractChart, M: AbstractManifold, from_basis: CoordinateBasis, to_basis: PhysicalBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from coordinate basis to physical basis using a manifold.

Retrieves the manifold’s metric and applies the appropriate transformation. For diagonal metrics (e.g. coordinax.manifolds.FlatMetric in orthogonal charts) the fast scale-factor path is taken; for general metrics (e.g. coordinax.manifolds.PullbackMetric) the Cholesky vielbein \(E = L^\top\) is used.

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

Euclidean 3-D manifold in spherical coordinates (diagonal metric):

>>> M3 = cxm.R3
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(1, "rad/s"), "phi": u.Q(2, "rad/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, M3, cxr.coord_basis, cxr.phys_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(3, 'm / s'), 'phi': Q(2.87655323, 'm / s')}

Embedded two-sphere manifold — non-diagonal PullbackMetric:

>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")),
... )
>>> v = {"theta": u.Q(1.0, "rad/s"), "phi": u.Q(2.0, "rad/s")}
>>> at = {"theta": u.Q(jnp.pi / 3, "rad"), "phi": u.Q(0.0, "rad")}
>>> cxr.change_basis(v, cxc.sph2, M, cxr.coord_basis, cxr.phys_basis, at=at)
{'theta': Q(..., 'km / s'), 'phi': Q(..., 'km / s')}
coordinax.change_basis(v: dict, chart: AbstractChart, M: AbstractManifold, from_basis: PhysicalBasis, to_basis: CoordinateBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from physical basis to coordinate basis using a manifold.

Retrieves the manifold’s metric and applies the inverse transformation. For diagonal metrics the fast scale-factor path is taken; for general metrics the Cholesky vielbein \(E = L^\top\) is solved as a triangular system \(v = E^{-1}\hat{v}\).

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

Euclidean 3-D manifold in spherical coordinates (diagonal metric):

>>> M3 = cxm.R3
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(3, "m/s"), "phi": u.Q(2.876553, "m/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0.5, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, M3, cxr.phys_basis, cxr.coord_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(1., 'rad / s'), 'phi': Q(1.99999..., 'rad / s')}

Embedded two-sphere manifold — non-diagonal PullbackMetric:

>>> M = cxm.EmbeddedManifold(
...     intrinsic=cxm.S2, ambient=cxm.R3,
...     embed_map=cxm.TwoSphereIn3D(radius=u.Q(1.0, "km")),
... )
>>> v = {"theta": u.Q(1.0, "km/s"), "phi": u.Q(2.0, "km/s")}
>>> at = {"theta": u.Q(jnp.pi / 3, "rad"), "phi": u.Q(0.0, "rad")}
>>> cxr.change_basis(v, cxc.sph2, M, cxr.phys_basis, cxr.coord_basis, at=at)
{'theta': Q(..., 'rad / s'), 'phi': Q(..., 'rad / s')}
coordinax.change_basis(v: dict, chart: AbstractChart, from_basis: CoordinateBasis, to_basis: PhysicalBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from coordinate basis to physical basis using the chart’s manifold.

Falls back to chart.M when no explicit manifold is supplied, mirroring the physical-to-coordinate rule below. Without it only the charts with a bespoke rule – the Cartesian family and ~coordinax.charts.Spherical3D – could make this change without being handed a manifold, and every other orthogonal chart raised NotFoundLookupError.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"rho": u.Q(1.0, "m/s"), "phi": u.Q(2.0, "rad/s"), "z": u.Q(3.0, "m/s")}
>>> at = {"rho": u.Q(2.0, "m"), "phi": u.Q(0.4, "rad"), "z": u.Q(1.0, "m")}
>>> cxr.change_basis(v, cxc.cyl3d, cxr.coord_basis, cxr.phys_basis, at=at)
{'rho': Q(1., 'm / s'), 'phi': Q(4., 'm / s'), 'z': Q(3., 'm / s')}
coordinax.change_basis(v: dict, chart: AbstractChart, from_basis: PhysicalBasis, to_basis: CoordinateBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from physical basis to coordinate basis using the chart’s manifold.

Falls back to chart.M when no explicit manifold is supplied.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(3, "m/s"), "phi": u.Q(2.876553, "m/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0.5, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, cxr.phys_basis, cxr.coord_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(1., 'rad / s'), 'phi': Q(1.99999984, 'rad / s')}
coordinax.change_basis(v: dict, chart: AbstractChart, from_basis: NoBasis | CoordinateBasis, to_basis: CoordinateBasis, /, **kw: Any) → dict
Parameters:
Return type:

Any

Reinterpret unknown basis components as coordinate-basis components.

This is an identity on component values: no numeric basis transform is possible from an unknown basis, so values are preserved and only the representation-level basis label changes.

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

The conversion is an identity map on values:

>>> v = {"x": jnp.array(1.0), "y": jnp.array(-2.0)}
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0)}
>>> cxr.change_basis(v, cxc.cart2d, cxr.no_basis, cxr.coord_basis, at=at)
{'x': Array(1., dtype=float64, ...), 'y': Array(-2., dtype=float64, ...)}

“at” is accepted and ignored for this basis-only reinterpretation:

>>> cxr.change_basis(v, cxc.cart2d, cxr.no_basis, cxr.coord_basis)
{'x': Array(1., dtype=float64, ...), 'y': Array(-2., dtype=float64, ...)}
coordinax.change_basis(v: dict, chart: AbstractChart, from_basis: NoBasis | PhysicalBasis, to_basis: PhysicalBasis, /, **kw: Any) → dict
Parameters:
Return type:

Any

Reinterpret unknown basis components as physical-basis components.

This conversion is only well-defined when all components share the same physical dimension. No numeric transform is applied; values are preserved.

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

Same-dimension components (even with different units) are accepted:

>>> v = {"x": u.Q(1.0, "m / s"), "y": u.Q(2.0, "km / s")}
>>> at = {"x": jnp.array(0.0), "y": jnp.array(0.0)}
>>> cxr.change_basis(v, cxc.cart2d, cxr.no_basis, cxr.phys_basis, at=at)
{'x': Q(1., 'm / s'), 'y': Q(2., 'km / s')}

Mixed dimensions are rejected:

>>> v_bad = {"x": u.Q(1.0, "m / s"), "y": u.Q(2.0, "m")}
>>> cxr.change_basis(v_bad, cxc.cart2d, cxr.no_basis, cxr.phys_basis)
Traceback (most recent call last):
...
ValueError: change_basis from NoBasis to PhysicalBasis requires all
components to have the same dimension, got ...
coordinax.change_basis(v: dict, chart: Cart0D | Cart1D | Cart2D | Cart3D | CartND, from_basis: CoordinateBasis | PhysicalBasis, to_basis: CoordinateBasis | PhysicalBasis, /, **kw: Any) → dict
Parameters:
Return type:

Any

Change the basis used to interpret tangent components.

In a Cartesian chart every scale factor equals one,

\[h_x = h_y = h_z = 1,\]

so the coordinate basis vectors are already unit vectors (\(\hat{e}_i = \partial_i\)) and the transformation matrix is the identity (\(H = I\)). The coordinate basis is the physical basis, so this conversion is always the identity map and v is returned unchanged regardless of the direction of the conversion.

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

Coordinate basis to physical basis in a 2-D Cartesian chart — identity:

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s")}
>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(2.0, "m")}
>>> cxr.change_basis(v, cxc.cart2d, cxr.coord_basis, cxr.phys_basis, at=at)
{'x': Q(3., 'm / s'), 'y': Q(4., 'm / s')}

The reverse direction is equally a no-op:

>>> cxr.change_basis(v, cxc.cart2d, cxr.phys_basis, cxr.coord_basis, at=at)
{'x': Q(3., 'm / s'), 'y': Q(4., 'm / s')}

Works for any Cartesian dimensionality; at is optional:

>>> v3 = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> cxr.change_basis(v3, cxc.cart3d, cxr.coord_basis, cxr.phys_basis)
{'x': Q(1., 'm / s'), 'y': Q(2., 'm / s'), 'z': Q(3., 'm / s')}
coordinax.change_basis(v: dict, chart: Cart0D | Cart1D | Cart2D | Cart3D | CartND, M: AbstractManifold, from_basis: AbstractLinearBasis, to_basis: AbstractLinearBasis, /, **kw: Any) → dict
Parameters:
Return type:

Any

Change the basis used to interpret tangent components.

In a Cartesian chart every scale factor equals one,

\[h_x = h_y = h_z = 1,\]

so the coordinate basis vectors are already unit vectors (\(\hat{e}_i = \partial_i\)) and the transformation matrix is the identity (\(H = I\)). The coordinate basis is the physical basis, so this conversion is always the identity map and v is returned unchanged regardless of the direction of the conversion.

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

Coordinate basis to physical basis in a 2-D Cartesian chart — identity:

>>> v = {"x": u.Q(3.0, "m/s"), "y": u.Q(4.0, "m/s")}
>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(2.0, "m")}
>>> M2 = cxm.R2
>>> cxr.change_basis(v, cxc.cart2d, M2, cxr.coord_basis, cxr.phys_basis, at=at)
{'x': Q(3., 'm / s'), 'y': Q(4., 'm / s')}

The reverse direction is equally a no-op:

>>> cxr.change_basis(v, cxc.cart2d, M2, cxr.phys_basis, cxr.coord_basis, at=at)
{'x': Q(3., 'm / s'), 'y': Q(4., 'm / s')}

Works for any Cartesian dimensionality; at is optional:

>>> v3 = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> cxr.change_basis(v3, cxc.cart3d, cxm.R3, cxr.coord_basis, cxr.phys_basis)
{'x': Q(1., 'm / s'), 'y': Q(2., 'm / s'), 'z': Q(3., 'm / s')}
coordinax.change_basis(v: dict, chart: Spherical3D, from_basis: CoordinateBasis, to_basis: PhysicalBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from coordinate basis to physical basis in a 3-D spherical chart.

In spherical coordinates \((r, \theta, \phi)\) the scale factors are

\[h_r = 1, \quad h_\theta = r, \quad h_\phi = r \sin\theta,\]

so the transformation matrix is

\[\begin{split}H = \begin{pmatrix} 1 & 0 & 0 \\ 0 & r & 0 \\ 0 & 0 & r\sin\theta \end{pmatrix}.\end{split}\]

Given coordinate-basis components \((v^r, v^\theta, v^\phi)\), the physical-basis components are

\[\hat{v} = H v \implies \hat{v}^r = v^r, \quad \hat{v}^\theta = r\, v^\theta, \quad \hat{v}^\phi = r\sin\theta\, v^\phi.\]

Examples

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(1, "rad/s"), "phi": u.Q(2, "rad/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, cxr.coord_basis, cxr.phys_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(3, 'm / s'), 'phi': Q(2.87655323, 'm / s')}
>>> v = {"r": 5, "theta": 1, "phi": 2}  # unitless
>>> at = {"r": 3, "theta": 0.5, "phi": 0}  # unitless
>>> cxr.change_basis(v, cxc.sph3d, cxr.coord_basis, cxr.phys_basis, at=at)
{'r': 5, 'theta': 3, 'phi': Array(2.87655323, dtype=float64, ...)}
coordinax.change_basis(v: dict, chart: Spherical3D, M: EuclideanManifold, from_basis: CoordinateBasis, to_basis: PhysicalBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from coordinate basis to physical basis in a 3-D spherical chart.

Delegates to the chart-specific implementation for coordinax.manifolds.EuclideanManifold.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import coordinax.representations as cxr
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(1, "rad/s"), "phi": u.Q(2, "rad/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, cxm.R3, cxr.coord_basis, cxr.phys_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(3, 'm / s'), 'phi': Q(2.87655323, 'm / s')}
>>> v = {"r": 5, "theta": 1, "phi": 2}  # unitless
>>> at = {"r": 3, "theta": 0.5, "phi": 0}  # unitless
>>> cxr.change_basis(v, cxc.sph3d, cxm.R3, cxr.coord_basis, cxr.phys_basis, at=at)
{'r': 5, 'theta': 3, 'phi': Array(2.87655323, dtype=float64, ...)}
coordinax.change_basis(v: dict, chart: Spherical3D, from_basis: PhysicalBasis, to_basis: CoordinateBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from physical basis to coordinate basis in a 3-D spherical chart.

In spherical coordinates \((r, \theta, \phi)\) the inverse transformation matrix is

\[\begin{split}H^{-1} = \begin{pmatrix} 1 & 0 & 0 \\ 0 & 1/r & 0 \\ 0 & 0 & 1/(r\sin\theta) \end{pmatrix}.\end{split}\]

Given physical-basis components \((\hat{v}^r, \hat{v}^\theta, \hat{v}^\phi)\), the coordinate-basis components are

\[v = H^{-1} \hat{v} \implies v^r = \hat{v}^r, \quad v^\theta = \hat{v}^\theta / r, \quad v^\phi = \hat{v}^\phi / (r\sin\theta).\]

Examples

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(3, "m/s"), "phi": u.Q(2.876553, "m/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0.5, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, cxr.phys_basis, cxr.coord_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(1., 'rad / s'), 'phi': Q(1.99999984, 'rad / s')}
>>> v = {"r": 5, "theta": 3, "phi": 2.876553}  # unitless
>>> at = {"r": 3, "theta": 0.5, "phi": 0.5}  # unitless
>>> cxr.change_basis(v, cxc.sph3d, cxr.phys_basis, cxr.coord_basis, at=at)
{'r': 5, 'theta': 1.0, 'phi': Array(1.99999984, dtype=float64, ...)}
coordinax.change_basis(v: dict, chart: Spherical3D, M: EuclideanManifold, from_basis: PhysicalBasis, to_basis: CoordinateBasis, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change from physical basis to coordinate basis in a 3-D spherical chart.

Delegates to the chart-specific implementation for EuclideanManifold.

>>> import unxt as u
>>> import coordinax.charts as cxc
>>> import coordinax.manifolds as cxm
>>> import coordinax.representations as cxr
>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(3, "m/s"), "phi": u.Q(2.876553, "m/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0.5, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, cxm.R3, cxr.phys_basis, cxr.coord_basis, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(1., 'rad / s'), 'phi': Q(1.99999984, 'rad / s')}
>>> v = {"r": 5, "theta": 3, "phi": 2.876553}  # unitless
>>> at = {"r": 3, "theta": 0.5, "phi": 0.5}  # unitless
>>> cxr.change_basis(v, cxc.sph3d, cxm.R3, cxr.phys_basis, cxr.coord_basis, at=at)
{'r': 5, 'theta': 1.0, 'phi': Array(1.99999984, dtype=float64, ...)}
coordinax.change_basis(v: dict, chart: AbstractChart, from_rep: Representation, to_rep: Representation, /, *, at: dict, usys: AbstractUnitSystem | None = None) → dict
Parameters:
Return type:

Any

Change basis using source and/or target Representation objects.

This is a convenience overload: the caller may pass full Representation objects for from_rep/to_rep instead of bare AbstractBasis instances. The basis is extracted from each argument and the appropriate change_basis() overload is called.

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

Coordinate-basis displacement to physical-basis displacement in a spherical chart, passing full Representation objects:

>>> v = {"r": u.Q(5, "m/s"), "theta": u.Q(1, "rad/s"), "phi": u.Q(2, "rad/s")}
>>> at = {"r": u.Q(3, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0, "rad")}
>>> cxr.change_basis(v, cxc.sph3d, cxr.coord_disp, cxr.phys_disp, at=at)
{'r': Q(5, 'm / s'), 'theta': Q(3, 'm / s'), 'phi': Q(2.87655323, 'm / s')}
>>> v = {"r": 5, "theta": 1, "phi": 2}  # unitless
>>> at = {"r": 3, "theta": 0.5, "phi": 0}  # unitless
>>> cxr.change_basis(v, cxc.sph3d, cxr.coord_disp, cxr.phys_disp, at=at)
{'r': 5, 'theta': 3, 'phi': Array(2.87655323, dtype=float64, ...)}
coordinax.change_basis(v: Tangent, to_basis: AbstractLinearBasis, /, *, at: Any = None, usys: AbstractUnitSystem | None = None) → Tangent
Parameters:
Return type:

Any

Change the basis of a Tangent vector.

Converts the component data from the current basis to to_basis using the registered change_basis overload for dicts, then returns a new Tangent with the updated data and basis.

The at parameter provides the base point at which the scale factors are evaluated. It may be a Point instance (whose .data is used) or a raw CDict.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr

Convert a coordinate-basis spherical velocity to the physical basis:

>>> point = cx.Point.from_(
...     {"r": u.Q(1.0, "m"), "theta": u.Q(0.5, "rad"), "phi": u.Q(0.0, "rad")},
...     cxc.sph3d,
... )
>>> vel = cx.Tangent.from_({"r": u.Q(1.0, "m/s"), "theta": u.Q(0.0, "rad/s"),
...     "phi": u.Q(0.0, "rad/s")}, cxc.sph3d, cxr.coord_vel)
>>> vel_phys = cxr.change_basis(vel, cxr.phys_basis, at=point)
>>> vel_phys.basis
phys_basis
>>> vel_phys.rep
phys_vel
coordinax.change_basis(v: Point, to_basis: AbstractLinearBasis, /, *, at: Any = None, usys: Any = None) → Tangent
Parameters:
Return type:

Any

Promote a Point to a Tangent with Displacement semantics.

The component data are unchanged; only the geometric interpretation is recast from a manifold point (PointGeometry) to a tangent-space displacement vector (TangentGeometry, Displacement). The resulting Tangent carries the same chart and frame as the input Point, and its basis is to_basis.

The at and usys parameters are accepted for API consistency but are not used.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.representations as cxr
>>> pt = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> disp = cxr.change_basis(pt, cxr.coord_basis)
>>> disp.semantic
dpl
>>> disp.basis
coord_basis
>>> disp.chart == pt.chart
True
Parameters:
Return type:

Any

final class coordinax.Point(data: dict[str, Any], chart: ChartT, frame: Any = NoFrame())

Bases: AstropyRepresentationAPIMixin, NumpyInvertMixin[Any], LaxLenMixin, AbstractVector[ChartT, PointGeometry, NoBasis, Location, V], Generic[ChartT, V]

A coordinate-carrying geometric point.

A Point stores three pieces of information:

  • data: a mapping from component name to scalar-like value (typically unxt.Quantity),

  • chart: a chart object describing the coordinate system and component schema, and

  • rep: a representation describing the geometric meaning of the components and therefore the correct transformation law.

The design goal is to make the public API simple (construct, convert, index) while keeping the mathematics correct and the numerical kernels JAX-friendly (operate on scalar leaves; rely on jit/vmap).

Mathematical background:

Let \(M\) be a manifold and let \((U,\varphi)\) be a chart with coordinate map \(\varphi: U \to \mathbb{R}^n\). Coordinax distinguishes:

Point (representation cxr.point)

A point \(p \in M\) represented by its chart coordinates \(q = \varphi(p)\). A point transforms by coordinate change: \(q' = (\varphi' \circ \varphi^{-1})(q)\).

In Euclidean charts, point coordinates may have heterogeneous physical dimensions (e.g. spherical \((r,\theta,\phi)\) mixes length and angle). This is expected.

Parameters:
  • data (dict[str, Any]) – Mapping from chart component name to scalar value. Each leaf may be a unxt.Quantity (recommended) or an array-like. Components are expected to be scalar leaves; batching happens via broadcasting of these leaves.

  • chart (TypeVar(ChartT, bound= AbstractChart[Any, Any, Any])) – A chart instance (e.g. cxc.cart3d, cxc.sph3d) that defines component names and per-component physical dimensions.

  • frame (AbstractReferenceFrame)

Examples

Construct a point in Cartesian 3D and convert to spherical:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> cart = cx.Point.from_({"x": u.Q(1, "m"), "y": u.Q(1, "m"), "z": u.Q(1, "m")},
...                  cxc.cart3d)
>>> sph = cart.cconvert(cxc.sph3d)
>>> sph["r"]
Q(1.73205081, 'm')

Notes

Notes on units and array shape:

  • A Point does not require that all components share one unit. This is essential for charts like spherical coordinates where point components naturally mix dimensions.

  • Batching is represented by broadcasting the component leaves; the conceptual shape of the Point is broadcast_shapes(*(v.shape for v in data.values())).

Core operations:

  • Indexing: vec["x"] returns a component leaf.

  • Conversion: vec.cconvert(target_chart, at=...) converts the vector to target_chart. For Point this is a coordinate transform.

Parameters:
data: dict[str, Any]

The data for each component.

chart: ChartT

The chart of the vector, e.g. cxc.cart3d.

frame: AbstractReferenceFrame = NoFrame()

The reference frame of the point. Defaults to cxf.noframe.

property M: AbstractManifold

The manifold of the vector, from the chart.

astype(dtype: Any, /, **kwargs: Any)

Cast the vector to a new dtype.

Examples

>>> import quaxed.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx

We can cast a vector to a new dtype:

>>> vec = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> print(vec.astype(jnp.float32))
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
>>> print(jnp.astype(vec, jnp.float32))
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
Parameters:
Return type:

Self

cconvert(*args, **kwargs)

Represent the vector as another type.

This forwards to coordinax.representations.cconvert.

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> print(vec.cconvert(cx.sph3d))
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
Parameters:
Return type:

Any

copy()

Return a copy of the vector.

Return type:

Self

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m")
>>> print(vec.copy())
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
property dtype: Any
flatten()

Flatten the vector.

Return type:

Self

classmethod from_(*args, **kwargs)

Create a vector-like object from arguments.

from_(cls: type[Point], obj: Point, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from another point.

>>> import coordinax as cx
>>> vec1 = cx.Point.from_([1, 2, 3], "m")
>>> vec2 = cx.Point.from_(vec1)
>>> print(vec2)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
from_(cls: type[Point], obj: Any, chart: AbstractChart, rep: Representation, /) → Point
Parameters:
Return type:

AbstractVector

Construct a vector from an object, and chart and rep info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, chart: AbstractChart, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, and chart info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = {"x": u.Q([1, 2], "m"), "y": u.Q([3, 4], "m"), "z": u.Q([5, 6], "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 3 5]
     [2 4 6]]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, rep: Representation, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, and rep info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = {"x": u.Q([1, 2], "m"), "y": u.Q([3, 4], "m"), "z": u.Q([5, 6], "m")}
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 3 5]
     [2 4 6]]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, /) → Any
Parameters:
Return type:

AbstractVector

Construct a point from an object.

Note that this is a pretty limited constructor since it often lacks the necessary information to do a proper construction.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> vec = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, /) → Any
Parameters:
Return type:

AbstractVector

Construct a cartesian vector from an array and unit.

The ArrayLike[Any, (*#batch, N), "..."] is expected to have the components as the last dimension.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "meter")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "meter")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, /) → Any
Parameters:
Return type:

AbstractVector

Construct a vector from an array, unit, and chart.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m", cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "m", cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, /) → Any
Parameters:
Return type:

AbstractVector

Construct a vector from an array, unit, chart, and rep.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m", cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "m", cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Point, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from another point, replacing its frame.

Every constructor above also accepts a trailing frame, which is attached to the constructed point:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.frames as cxf
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
>>> for args in [(cx.Point.from_(d),), (d,), (d, cxc.cart3d),
...              (d, cxc.cart3d, cxr.point), ([1, 2, 3], "km")]:
...     print(cx.Point.from_(*args, cxf.alice).frame)
Alice()
Alice()
Alice()
Alice()
Alice()

An existing frame is replaced, not merged:

>>> p_alice = cx.Point.from_(d, cxf.alice)
>>> cx.Point.from_(p_alice, cxf.noframe).frame == cxf.noframe
True
from_(cls: type[Point], obj: Any, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from any object, with a frame.

from_(cls: type[Point], obj: Any, chart: AbstractChart, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object and chart, with a frame.

from_(cls: type[Point], obj: Any, chart: AbstractChart, rep: Representation, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, chart, and representation, with a frame.

from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an array and unit, with a frame.

from_(cls: type[Tangent], obj: Tangent, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from another Tangent (identity / fast path).

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel,
... )
>>> v2 = cx.Tangent.from_(v)
>>> v2 is v
True
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, basis, and semantic.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d, cxr.coord_basis, cxr.vel)
>>> v.chart
Cart3D(M=Rn(3))
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, rep: Representation, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, and a tangent Representation.

Extracts basis and semantic from the representation. Raises TypeError if the representation’s geometry kind is not TangentGeometry.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d, cxr.coord_vel)
>>> v.basis == cxr.coord_basis
True
>>> v.semantic == cxr.vel
True
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data and chart (rep inferred from data).

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: Any, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data alone (chart and rep inferred).

>>> import coordinax as cx
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array and unit (chart inferred).

>>> import coordinax as cx
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s")
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, and chart.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s", cxc.cart3d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, chart, and Representation.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_vel)
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, chart, Representation, and frame.

from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from array, unit, chart, basis, and semantic.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_(
...     [1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_basis, cxr.vel
... )
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: AbstractQuantity, unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from a Quantity, unit, chart, basis, and semantic.

The Quantity is converted to the given unit before construction.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     u.Q([1.0, 2.0, 3.0], "m/s"), "m/s", cxc.cart3d, cxr.coord_basis, cxr.vel
... )
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: Tangent, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from another Tangent, replacing its frame.

Every constructor above also accepts a trailing frame, which is attached to the constructed tangent:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.frames as cxf
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> for args in [(cx.Tangent.from_(d),), (d,), (d, cxc.cart3d),
...              (d, cxc.cart3d, cxr.coord_basis, cxr.vel),
...              ([1.0, 2.0, 3.0], "m/s"),
...              ([1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_vel)]:
...     print(cx.Tangent.from_(*args, cxf.alice).frame)
Alice()
Alice()
Alice()
Alice()
Alice()
Alice()
from_(cls: type[Tangent], obj: Any, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data with a frame (chart and rep inferred).

from_(cls: type[Tangent], obj: Any, chart: AbstractChart, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data and chart, with a frame.

from_(cls: type[Tangent], obj: Any, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, basis, and semantic, with a frame.

from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array and unit, with a frame.

from_(cls: type[Coordinate], pv: Coordinate, /) → Coordinate
Parameters:
Return type:

AbstractVector

Identity: return the same Coordinate unchanged.

>>> import coordinax as cx
>>> pv = cx.Coordinate(point=cx.Point.from_([1.0, 2.0, 3.0], "m"))
>>> cx.Coordinate.from_(pv) is pv
True
from_(cls: type[Coordinate], p: Point, /) → Coordinate
Parameters:
Return type:

AbstractVector

Wrap a single Point as a point-only bundle (no field vectors).

>>> import coordinax as cx
>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> pv = cx.Coordinate.from_(p)
>>> pv.point is p
True
from_(cls: type[Coordinate], data: Mapping[str, Any], /, *, point: Point | None = None) → Coordinate
Parameters:
Return type:

AbstractVector

Create a Coordinate from a mapping of named objects.

The mapping may contain a "point" key for the base; the explicit point keyword argument takes precedence if both are supplied.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> vel = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel)
>>> pv = cx.Coordinate.from_({"point": p, "velocity": vel})
>>> pv.point is p
True
from_(cls: type[Point], obj: CartesianRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy CartesianRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import CartesianRepresentation
>>> vec = CartesianRepresentation(1, 2, 3, unit="km")
>>> cxv.Point.from_(vec)
Point({'x': Q(1., 'km'), 'y': Q(2., 'km'), 'z': Q(3., 'km')}, chart=Cart3D(M=Rn(3)))
from_(cls: type[Point], obj: CylindricalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy CylindricalRepresentation.

>>> import astropy.units as apyu
>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import CylindricalRepresentation
>>> vec = CylindricalRepresentation(rho=1 * apyu.km, phi=90 * apyu.deg,
...                                 z=3 * apyu.km)
>>> cxv.Point.from_(vec)
Point(
  {'rho': Q(1., 'km'), 'phi': Q(90., 'deg'), 'z': Q(3., 'km')},
  chart=Cylindrical3D(M=Rn(3))
)
from_(cls: type[Point], obj: PhysicsSphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy PhysicsSphericalRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import PhysicsSphericalRepresentation
>>> import astropy.units as apyu
>>> vec = PhysicsSphericalRepresentation(
...     r=1 * apyu.kpc, theta=45 * apyu.deg, phi=90 * apyu.deg)
>>> cxv.Point.from_(vec)
Point(
  {'r': Q(1., 'kpc'), 'theta': Q(45., 'deg'), 'phi': Q(90., 'deg')},
  chart=Spherical3D(M=Rn(3))
)
from_(cls: type[Point], obj: SphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy SphericalRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import SphericalRepresentation
>>> import astropy.units as apyu
>>> vec = SphericalRepresentation(
...     lon=90 * apyu.deg, lat=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3))
)
from_(cls: type[Point], obj: UnitSphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy UnitSphericalRepresentation.

A direction with no distance, so the point lives on the two-sphere rather than in R^3.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import UnitSphericalRepresentation
>>> import astropy.units as apyu
>>> vec = UnitSphericalRepresentation(lon=90 * apyu.deg, lat=45 * apyu.deg)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg')},
  chart=LonLatSphericalTwoSphere(M=Sn(2))
)
from_(cls: type[Point], obj: RadialRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy RadialRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import RadialRepresentation
>>> import astropy.units as apyu
>>> cxv.Point.from_(RadialRepresentation(distance=1 * apyu.kpc))
Point({'r': Q(1., 'kpc')}, chart=Radial1D(M=Rn(1)))
from_(cls: type[Point], obj: BaseCoordinateFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy frame with data.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.ICRS(ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3)), frame=ICRS()
)
>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc
... )
>>> cxv.Point.from_(vec)
Point(
    {'x': Q(1., 'kpc'), 'y': Q(2., 'kpc'), 'z': Q(3., 'kpc')},
    chart=Cart3D(M=Rn(3)), frame=Galactocentric(...)
)

A ~coordinax.vectors.Point is a position, so any velocity the frame carries is not part of the result. ~coordinax.vectors.Tangent.from_ takes the other half:

>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)
from_(cls: type[Tangent], obj: BaseCoordinateFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from the velocity of an Astropy frame.

The position half is ~coordinax.vectors.Point.from_; this is the other half, so a frame carrying both splits into the two coordinax objects.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)

Angular rates come across too, when the frame states them as plain d_lon rather than astropy’s cos(lat)-scaled convention:

>>> vec = apyc.ICRS(
...     ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc,
...     pm_ra=3 * apyu.mas / apyu.yr, pm_dec=2 * apyu.mas / apyu.yr,
...     radial_velocity=10 * apyu.km / apyu.s,
...     differential_type=apyc.SphericalDifferential,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'lon': Q(3., 'mas / yr'), 'lat': Q(2., 'mas / yr'),
   'distance': Q(10., 'km / s')},
  chart=LonLatSpherical3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)

A frame without velocities says so rather than handing back a zero:

>>> try:
...     cxv.Tangent.from_(apyc.ICRS(ra=1 * apyu.deg, dec=2 * apyu.deg))
... except ValueError as e:
...     print(e)
ICRS carries no velocity; there is nothing to convert to a Tangent.
from_(cls: type[Tangent], obj: SkyCoord, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from the velocity of an Astropy SkyCoord.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv

A ~astropy.coordinates.SkyCoord defaults to the cos(lat)-scaled proper motion convention, which coordinax has no chart for, so the usual astronomical spelling is refused rather than silently reinterpreted:

>>> sc = apyc.SkyCoord(
...     ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc,
...     pm_ra_cosdec=3 * apyu.mas / apyu.yr, pm_dec=2 * apyu.mas / apyu.yr,
...     radial_velocity=10 * apyu.km / apyu.s,
... )
>>> try:
...     cxv.Tangent.from_(sc)
... except ValueError as e:
...     print(e)
astropy's SphericalCosLatDifferential is a rate convention ...

Cartesian velocities carry across unchanged:

>>> sc = apyc.SkyCoord(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
...     representation_type="cartesian", differential_type="cartesian",
... )
>>> cxv.Tangent.from_(sc)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)
from_(cls: type[Point], obj: SkyCoord, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy SkyCoord.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.SkyCoord(ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3)), frame=ICRS()
)
>>> vec = vec.transform_to(apyc.Galactocentric())
>>> cxv.Point.from_(vec)
Point(
  {'x': Q(-9.08123957, 'kpc'), 'y': Q(0.21365468, 'kpc'), 'z': Q(0.2056243, 'kpc')},
  chart=Cart3D(M=Rn(3)),
  frame=Galactocentric(
    galcen=Point(
      {
        'lon': Q(266.4051, 'deg'),
        'lat': Q(-28.936175, 'deg'),
        'distance': Q(8.122, 'kpc')
      },
      chart=LonLatSpherical3D(M=Rn(3)),
      frame=ICRS()
    ),
    roll=Angle(0., 'deg'),
    z_sun=Q(20.8, 'pc'),
    galcen_v_sun=Tangent(
      {'x': Q(12.9, 'km / s'), 'y': Q(245.6, 'km / s'), 'z': Q(7.78, 'km / s')},
      chart=Cart3D(M=Rn(3)),
      basis=coord_basis,
      semantic=vel
    )
  )
)
Parameters:
Return type:

AbstractVector

classmethod is_like(obj: Any, /)

Check if the object is a AbstractVector object.

Examples

>>> import coordinax.vectors as cxv
>>> vec = cxv.Point.from_([1, 2, 3], "m")
>>> cxv.AbstractVector.is_like(vec)
True
>>> cxv.AbstractVector.is_like(42)
False
Parameters:

obj (Any)

Return type:

TypeIs[Self]

property ndim: int
ravel()

Return a flattened vector.

Return type:

Self

property rep: Representation[PointGeometry, NoBasis, Location]

The representation of the vector.

represent_as(target: Any, *args: Any, **kwargs: Any)

Represent the vector as another type.

This just forwards to coordinax.cconvert.

Parameters:
  • target (Any) – The representation type to convert to, e.g. cxc.sph3d.

  • *args (Any) – Extra arguments. These are passed to coordinax.cconvert and might be used, depending on the dispatched method. E.g. for transforming a velocity or acceleration vector, generally the first argument is the position vector at which the differential is defined. In general this is a required argument, though it is not for Cartesian-to-Cartesian transforms – see https://en.wikipedia.org/wiki/Tensors_in_curvilinear_coordinates for more information.

  • **kwargs (Any) – Extra arguments. These are passed to coordinax.cconvert and might be used, depending on the dispatched method. E.g. for transforming a velocity or acceleration vector, generally the first argument is the position vector at which the differential is defined. In general this is a required argument, though it is not for Cartesian-to-Cartesian transforms – see https://en.wikipedia.org/wiki/Tensors_in_curvilinear_coordinates for more information.

Return type:

Any

Examples

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

Transforming a Position:

>>> q_cart = cx.Point.from_([1, 2, 3], "m")
>>> q_sph = q_cart.represent_as(cxc.sph3d)
>>> print(q_sph)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
reshape(*shape: int)

Return a reshaped vector.

Parameters:

shape (int)

Return type:

Self

round(decimals: int = 0)

Return a rounded vector.

Parameters:

decimals (int)

Return type:

Self

property shape: tuple[int, ...]

Return the batch shape of the vector.

property size: int
to_cartesian()

Return the vector in a Cartesian chart.

This just forwards to coordinax.cartesian_chart and cconvert.

Return type:

AbstractVector

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m").cconvert(cx.sph3d)
>>> print(vec)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
>>> print(vec.to_cartesian())
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
to_device(device: None | Device = None)

Move the vector to a new device.

Parameters:

device (None | Device)

Return type:

Self

to_frame(toframe: AbstractReferenceFrame, /, t: Quantity | None = None)

Transform the vector to a specified reference frame.

Parameters:
Returns:

New vector with the data transformed into toframe and frame=toframe.

Return type:

AbstractVector

Notes

This is a convenience: it rebuilds the frame-transition operator on every call, which is pure Python and dominates the cost. On a hot path or under jit, build the operator once outside with coordinax.frames.frame_transition and pass it into the jitted function that applies it – see that function’s notes.

Examples

>>> import coordinax as cx
>>> import coordinax.frames as cxf
>>> p = cx.Point.from_([1, 2, 3], "kpc", cxf.alice)
>>> p.to_frame(cxf.alice) is p
True
uconvert(*args, **kwargs)

Convert the vector to the given units.

This just forwards to unxt.uconvert, reversing the order of the arguments to match the unxt API.

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "km")
>>> print(vec.uconvert({"length": "km"}))
<Point: chart=Cart3D (x, y, z) [km]
    [1 2 3]>
uconvert(self, usys: AbstractUnitSystem, /) → Any
Parameters:
Return type:

Any

Convert the vector to the given units.

Parameters:
  • usys – The units to convert to according to the physical type of the components. This is passed to [unxt.unitsystem][].

  • args (Any)

  • kwargs (Any)

Return type:

Any

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> usys = u.unitsystem("m", "s", "kg", "rad")
>>> vec = cx.Point.from_([1, 2, 3], "km")
>>> print(vec.uconvert(usys))
<Point: chart=Cart3D (x, y, z) [m]
    [1000. 2000. 3000.]>
>>> print(vec.uconvert("galactic"))
<Point: chart=Cart3D (x, y, z) [kpc]
    [3.241e-17 6.482e-17 9.722e-17]>
Parameters:
Return type:

Any

final class coordinax.Coordinate(point: Any, **fields: Any)

Bases: AbstractVector

A vector bundle anchored at a base point.

A Coordinate stores:

  • A base point \(q \in M\) (a ~coordinax.vectors.Point).

  • A collection of named fibre vectors \(\{v_i\}\) anchored at \(q\) (each a ~coordinax.vectors.Tangent with TangentGeometry rep, e.g. velocity, displacement, acceleration).

On construction every fibre vector is automatically frame-aligned to the reference frame of the base point:

  1. Frame-alignment via ~coordinax.vectors.AbstractVector.to_frame ensures pv["velocity"].frame == pv.point.frame.

Fibre vectors are not chart-aligned on construction; each fibre retains the chart it was supplied with. Chart conversion is handled lazily: ~coordinax.vectors.Coordinate.cconvert pushes each fibre forward using the Jacobian at the base point expressed in the fibre’s current chart.

Coordinate conversion (chart change) is handled automatically: the base converts as a point map, and each fibre vector converts via the Jacobian pushforward at the base.

Parameters:
  • point (Any) – Base point. Must be an instance of ~coordinax.vectors.Point.

  • **fields (Any) – Named fibre vectors anchored at point. Must have TangentGeometry representation (i.e. ~coordinax.vectors.Tangent instances). Shapes must be broadcastable with point.

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> point = cx.Point.from_([1.0, 0.0, 0.0], "m")
>>> vel = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel)
>>> pv = cx.Coordinate(point=point, velocity=vel)
>>> pv.point.chart
Cart3D(M=Rn(3))

Convert to spherical — point converts as a point map, velocity via Jacobian:

>>> pv_sph = pv.cconvert(cxc.sph3d)
>>> pv_sph.point.chart
Spherical3D(M=Rn(3))
>>> pv_sph["velocity"].chart
Spherical3D(M=Rn(3))
point: Point

Base point of the bundle. Must be a Point instance.

property data: Any

Component data of the base point.

property chart: AbstractChart

Chart of the base point.

property rep: Representation

Representation of the base point (always PointGeometry).

property manifold: AbstractManifold

Manifold of the base point.

property frame: AbstractReferenceFrame

Reference frame of the bundle — always equal to point.frame.

keys()

Return field names (excluding base point).

Return type:

KeysView[str]

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> base = cx.Point.from_([1.0, 0.0, 0.0], "m")
>>> vel = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel)
>>> pv = cx.Coordinate(point=base, velocity=vel)
>>> list(pv.keys())
['velocity']
values()

Return field vectors (excluding base point).

Return type:

ValuesView[Tangent]

items()

Return (name, vector) pairs for fields (excluding base point).

Return type:

ItemsView[str, Tangent]

cconvert(to_chart: AbstractChart, /, *, field_charts: Mapping[str, AbstractChart] | None = None, usys: AbstractUnitSystem | None = None)

Convert the bundle to a new coordinate chart.

Algorithm:

  1. Convert base as a point map: new_point = cconvert(point, to_chart).

  2. For each field vector, apply the tangent pushforward at point via cconvert(vec, field_to_chart, at=point).

Parameters:
Return type:

Coordinate

Examples

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> import coordinax.representations as cxr
>>> point = cx.Point.from_([1.0, 0.0, 0.0], "m")
>>> vel = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel)
>>> pv = cx.Coordinate(point=point, velocity=vel)
>>> sph = pv.cconvert(cxc.sph3d)
>>> sph.point.chart
Spherical3D(M=Rn(3))
>>> sph["velocity"].chart
Spherical3D(M=Rn(3))
property shape: tuple[int, ...]

Broadcast shape of base point and all field vectors.

Examples

>>> import coordinax as cx
>>> pv = cx.Coordinate(point=cx.Point.from_([1.0, 2.0, 3.0], "m"))
>>> pv.shape
()
property M: AbstractManifold

The manifold of the vector, from the chart.

astype(dtype: Any, /, **kwargs: Any)

Cast the vector to a new dtype.

Examples

>>> import quaxed.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx

We can cast a vector to a new dtype:

>>> vec = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> print(vec.astype(jnp.float32))
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
>>> print(jnp.astype(vec, jnp.float32))
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
Parameters:
Return type:

Self

copy()

Return a copy of the vector.

Return type:

Self

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m")
>>> print(vec.copy())
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
property dtype: Any
flatten()

Flatten the vector.

Return type:

Self

classmethod from_(*args, **kwargs)

Create a vector-like object from arguments.

from_(cls: type[Point], obj: Point, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from another point.

>>> import coordinax as cx
>>> vec1 = cx.Point.from_([1, 2, 3], "m")
>>> vec2 = cx.Point.from_(vec1)
>>> print(vec2)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
from_(cls: type[Point], obj: Any, chart: AbstractChart, rep: Representation, /) → Point
Parameters:
Return type:

AbstractVector

Construct a vector from an object, and chart and rep info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, chart: AbstractChart, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, and chart info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = {"x": u.Q([1, 2], "m"), "y": u.Q([3, 4], "m"), "z": u.Q([5, 6], "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 3 5]
     [2 4 6]]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, rep: Representation, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, and rep info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = {"x": u.Q([1, 2], "m"), "y": u.Q([3, 4], "m"), "z": u.Q([5, 6], "m")}
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 3 5]
     [2 4 6]]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, /) → Any
Parameters:
Return type:

AbstractVector

Construct a point from an object.

Note that this is a pretty limited constructor since it often lacks the necessary information to do a proper construction.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> vec = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, /) → Any
Parameters:
Return type:

AbstractVector

Construct a cartesian vector from an array and unit.

The ArrayLike[Any, (*#batch, N), "..."] is expected to have the components as the last dimension.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "meter")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "meter")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, /) → Any
Parameters:
Return type:

AbstractVector

Construct a vector from an array, unit, and chart.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m", cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "m", cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, /) → Any
Parameters:
Return type:

AbstractVector

Construct a vector from an array, unit, chart, and rep.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m", cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "m", cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Point, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from another point, replacing its frame.

Every constructor above also accepts a trailing frame, which is attached to the constructed point:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.frames as cxf
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
>>> for args in [(cx.Point.from_(d),), (d,), (d, cxc.cart3d),
...              (d, cxc.cart3d, cxr.point), ([1, 2, 3], "km")]:
...     print(cx.Point.from_(*args, cxf.alice).frame)
Alice()
Alice()
Alice()
Alice()
Alice()

An existing frame is replaced, not merged:

>>> p_alice = cx.Point.from_(d, cxf.alice)
>>> cx.Point.from_(p_alice, cxf.noframe).frame == cxf.noframe
True
from_(cls: type[Point], obj: Any, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from any object, with a frame.

from_(cls: type[Point], obj: Any, chart: AbstractChart, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object and chart, with a frame.

from_(cls: type[Point], obj: Any, chart: AbstractChart, rep: Representation, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, chart, and representation, with a frame.

from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an array and unit, with a frame.

from_(cls: type[Tangent], obj: Tangent, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from another Tangent (identity / fast path).

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel,
... )
>>> v2 = cx.Tangent.from_(v)
>>> v2 is v
True
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, basis, and semantic.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d, cxr.coord_basis, cxr.vel)
>>> v.chart
Cart3D(M=Rn(3))
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, rep: Representation, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, and a tangent Representation.

Extracts basis and semantic from the representation. Raises TypeError if the representation’s geometry kind is not TangentGeometry.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d, cxr.coord_vel)
>>> v.basis == cxr.coord_basis
True
>>> v.semantic == cxr.vel
True
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data and chart (rep inferred from data).

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: Any, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data alone (chart and rep inferred).

>>> import coordinax as cx
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array and unit (chart inferred).

>>> import coordinax as cx
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s")
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, and chart.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s", cxc.cart3d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, chart, and Representation.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_vel)
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, chart, Representation, and frame.

from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from array, unit, chart, basis, and semantic.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_(
...     [1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_basis, cxr.vel
... )
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: AbstractQuantity, unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from a Quantity, unit, chart, basis, and semantic.

The Quantity is converted to the given unit before construction.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     u.Q([1.0, 2.0, 3.0], "m/s"), "m/s", cxc.cart3d, cxr.coord_basis, cxr.vel
... )
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: Tangent, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from another Tangent, replacing its frame.

Every constructor above also accepts a trailing frame, which is attached to the constructed tangent:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.frames as cxf
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> for args in [(cx.Tangent.from_(d),), (d,), (d, cxc.cart3d),
...              (d, cxc.cart3d, cxr.coord_basis, cxr.vel),
...              ([1.0, 2.0, 3.0], "m/s"),
...              ([1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_vel)]:
...     print(cx.Tangent.from_(*args, cxf.alice).frame)
Alice()
Alice()
Alice()
Alice()
Alice()
Alice()
from_(cls: type[Tangent], obj: Any, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data with a frame (chart and rep inferred).

from_(cls: type[Tangent], obj: Any, chart: AbstractChart, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data and chart, with a frame.

from_(cls: type[Tangent], obj: Any, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, basis, and semantic, with a frame.

from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array and unit, with a frame.

from_(cls: type[Coordinate], pv: Coordinate, /) → Coordinate
Parameters:
Return type:

AbstractVector

Identity: return the same Coordinate unchanged.

>>> import coordinax as cx
>>> pv = cx.Coordinate(point=cx.Point.from_([1.0, 2.0, 3.0], "m"))
>>> cx.Coordinate.from_(pv) is pv
True
from_(cls: type[Coordinate], p: Point, /) → Coordinate
Parameters:
Return type:

AbstractVector

Wrap a single Point as a point-only bundle (no field vectors).

>>> import coordinax as cx
>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> pv = cx.Coordinate.from_(p)
>>> pv.point is p
True
from_(cls: type[Coordinate], data: Mapping[str, Any], /, *, point: Point | None = None) → Coordinate
Parameters:
Return type:

AbstractVector

Create a Coordinate from a mapping of named objects.

The mapping may contain a "point" key for the base; the explicit point keyword argument takes precedence if both are supplied.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> vel = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel)
>>> pv = cx.Coordinate.from_({"point": p, "velocity": vel})
>>> pv.point is p
True
from_(cls: type[Point], obj: CartesianRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy CartesianRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import CartesianRepresentation
>>> vec = CartesianRepresentation(1, 2, 3, unit="km")
>>> cxv.Point.from_(vec)
Point({'x': Q(1., 'km'), 'y': Q(2., 'km'), 'z': Q(3., 'km')}, chart=Cart3D(M=Rn(3)))
from_(cls: type[Point], obj: CylindricalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy CylindricalRepresentation.

>>> import astropy.units as apyu
>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import CylindricalRepresentation
>>> vec = CylindricalRepresentation(rho=1 * apyu.km, phi=90 * apyu.deg,
...                                 z=3 * apyu.km)
>>> cxv.Point.from_(vec)
Point(
  {'rho': Q(1., 'km'), 'phi': Q(90., 'deg'), 'z': Q(3., 'km')},
  chart=Cylindrical3D(M=Rn(3))
)
from_(cls: type[Point], obj: PhysicsSphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy PhysicsSphericalRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import PhysicsSphericalRepresentation
>>> import astropy.units as apyu
>>> vec = PhysicsSphericalRepresentation(
...     r=1 * apyu.kpc, theta=45 * apyu.deg, phi=90 * apyu.deg)
>>> cxv.Point.from_(vec)
Point(
  {'r': Q(1., 'kpc'), 'theta': Q(45., 'deg'), 'phi': Q(90., 'deg')},
  chart=Spherical3D(M=Rn(3))
)
from_(cls: type[Point], obj: SphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy SphericalRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import SphericalRepresentation
>>> import astropy.units as apyu
>>> vec = SphericalRepresentation(
...     lon=90 * apyu.deg, lat=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3))
)
from_(cls: type[Point], obj: UnitSphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy UnitSphericalRepresentation.

A direction with no distance, so the point lives on the two-sphere rather than in R^3.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import UnitSphericalRepresentation
>>> import astropy.units as apyu
>>> vec = UnitSphericalRepresentation(lon=90 * apyu.deg, lat=45 * apyu.deg)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg')},
  chart=LonLatSphericalTwoSphere(M=Sn(2))
)
from_(cls: type[Point], obj: RadialRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy RadialRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import RadialRepresentation
>>> import astropy.units as apyu
>>> cxv.Point.from_(RadialRepresentation(distance=1 * apyu.kpc))
Point({'r': Q(1., 'kpc')}, chart=Radial1D(M=Rn(1)))
from_(cls: type[Point], obj: BaseCoordinateFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy frame with data.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.ICRS(ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3)), frame=ICRS()
)
>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc
... )
>>> cxv.Point.from_(vec)
Point(
    {'x': Q(1., 'kpc'), 'y': Q(2., 'kpc'), 'z': Q(3., 'kpc')},
    chart=Cart3D(M=Rn(3)), frame=Galactocentric(...)
)

A ~coordinax.vectors.Point is a position, so any velocity the frame carries is not part of the result. ~coordinax.vectors.Tangent.from_ takes the other half:

>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)
from_(cls: type[Tangent], obj: BaseCoordinateFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from the velocity of an Astropy frame.

The position half is ~coordinax.vectors.Point.from_; this is the other half, so a frame carrying both splits into the two coordinax objects.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)

Angular rates come across too, when the frame states them as plain d_lon rather than astropy’s cos(lat)-scaled convention:

>>> vec = apyc.ICRS(
...     ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc,
...     pm_ra=3 * apyu.mas / apyu.yr, pm_dec=2 * apyu.mas / apyu.yr,
...     radial_velocity=10 * apyu.km / apyu.s,
...     differential_type=apyc.SphericalDifferential,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'lon': Q(3., 'mas / yr'), 'lat': Q(2., 'mas / yr'),
   'distance': Q(10., 'km / s')},
  chart=LonLatSpherical3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)

A frame without velocities says so rather than handing back a zero:

>>> try:
...     cxv.Tangent.from_(apyc.ICRS(ra=1 * apyu.deg, dec=2 * apyu.deg))
... except ValueError as e:
...     print(e)
ICRS carries no velocity; there is nothing to convert to a Tangent.
from_(cls: type[Tangent], obj: SkyCoord, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from the velocity of an Astropy SkyCoord.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv

A ~astropy.coordinates.SkyCoord defaults to the cos(lat)-scaled proper motion convention, which coordinax has no chart for, so the usual astronomical spelling is refused rather than silently reinterpreted:

>>> sc = apyc.SkyCoord(
...     ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc,
...     pm_ra_cosdec=3 * apyu.mas / apyu.yr, pm_dec=2 * apyu.mas / apyu.yr,
...     radial_velocity=10 * apyu.km / apyu.s,
... )
>>> try:
...     cxv.Tangent.from_(sc)
... except ValueError as e:
...     print(e)
astropy's SphericalCosLatDifferential is a rate convention ...

Cartesian velocities carry across unchanged:

>>> sc = apyc.SkyCoord(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
...     representation_type="cartesian", differential_type="cartesian",
... )
>>> cxv.Tangent.from_(sc)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)
from_(cls: type[Point], obj: SkyCoord, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy SkyCoord.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.SkyCoord(ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3)), frame=ICRS()
)
>>> vec = vec.transform_to(apyc.Galactocentric())
>>> cxv.Point.from_(vec)
Point(
  {'x': Q(-9.08123957, 'kpc'), 'y': Q(0.21365468, 'kpc'), 'z': Q(0.2056243, 'kpc')},
  chart=Cart3D(M=Rn(3)),
  frame=Galactocentric(
    galcen=Point(
      {
        'lon': Q(266.4051, 'deg'),
        'lat': Q(-28.936175, 'deg'),
        'distance': Q(8.122, 'kpc')
      },
      chart=LonLatSpherical3D(M=Rn(3)),
      frame=ICRS()
    ),
    roll=Angle(0., 'deg'),
    z_sun=Q(20.8, 'pc'),
    galcen_v_sun=Tangent(
      {'x': Q(12.9, 'km / s'), 'y': Q(245.6, 'km / s'), 'z': Q(7.78, 'km / s')},
      chart=Cart3D(M=Rn(3)),
      basis=coord_basis,
      semantic=vel
    )
  )
)
Parameters:
Return type:

AbstractVector

classmethod is_like(obj: Any, /)

Check if the object is a AbstractVector object.

Examples

>>> import coordinax.vectors as cxv
>>> vec = cxv.Point.from_([1, 2, 3], "m")
>>> cxv.AbstractVector.is_like(vec)
True
>>> cxv.AbstractVector.is_like(42)
False
Parameters:

obj (Any)

Return type:

TypeIs[Self]

property ndim: int
ravel()

Return a flattened vector.

Return type:

Self

reshape(*shape: int)

Return a reshaped vector.

Parameters:

shape (int)

Return type:

Self

round(decimals: int = 0)

Return a rounded vector.

Parameters:

decimals (int)

Return type:

Self

property size: int
to_cartesian()

Return the vector in a Cartesian chart.

This just forwards to coordinax.cartesian_chart and cconvert.

Return type:

AbstractVector

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m").cconvert(cx.sph3d)
>>> print(vec)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
>>> print(vec.to_cartesian())
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
to_device(device: None | Device = None)

Move the vector to a new device.

Parameters:

device (None | Device)

Return type:

Self

to_frame(toframe: AbstractReferenceFrame, /, t: Quantity | None = None)

Transform the vector to a specified reference frame.

Parameters:
Returns:

New vector with the data transformed into toframe and frame=toframe.

Return type:

AbstractVector

Notes

This is a convenience: it rebuilds the frame-transition operator on every call, which is pure Python and dominates the cost. On a hot path or under jit, build the operator once outside with coordinax.frames.frame_transition and pass it into the jitted function that applies it – see that function’s notes.

Examples

>>> import coordinax as cx
>>> import coordinax.frames as cxf
>>> p = cx.Point.from_([1, 2, 3], "kpc", cxf.alice)
>>> p.to_frame(cxf.alice) is p
True
uconvert(*args, **kwargs)

Convert the vector to the given units.

This just forwards to unxt.uconvert, reversing the order of the arguments to match the unxt API.

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "km")
>>> print(vec.uconvert({"length": "km"}))
<Point: chart=Cart3D (x, y, z) [km]
    [1 2 3]>
uconvert(self, usys: AbstractUnitSystem, /) → Any
Parameters:
Return type:

Any

Convert the vector to the given units.

Parameters:
  • usys – The units to convert to according to the physical type of the components. This is passed to [unxt.unitsystem][].

  • args (Any)

  • kwargs (Any)

Return type:

Any

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> usys = u.unitsystem("m", "s", "kg", "rad")
>>> vec = cx.Point.from_([1, 2, 3], "km")
>>> print(vec.uconvert(usys))
<Point: chart=Cart3D (x, y, z) [m]
    [1000. 2000. 3000.]>
>>> print(vec.uconvert("galactic"))
<Point: chart=Cart3D (x, y, z) [kpc]
    [3.241e-17 6.482e-17 9.722e-17]>
Parameters:
Return type:

Any

final class coordinax.Tangent(data: dict[str, V], chart: ChartT, basis: BasisT, semantic: SemanticT, frame: Any = NoFrame())

Bases: AstropyRepresentationAPIMixin, NumpyInvertMixin[Any], LaxLenMixin, AbstractVector[ChartT, TangentGeometry, BasisT, SemanticT, V], Generic[ChartT, BasisT, SemanticT, V]

A tangent-geometry vector with explicit basis and semantic kind.

A Tangent stores four pieces of information:

  • data: a mapping from component name to scalar-like value (typically unxt.Quantity),

  • chart: a chart object describing the coordinate system and component schema,

  • basis: an ~coordinax.representations.AbstractLinearBasis specifying the basis in which tangent components are expressed (e.g. ~coordinax.representations.CoordinateBasis or ~coordinax.representations.PhysicalBasis), and

  • semantic: an ~coordinax.representations.AbstractTangentSemanticKind giving the physical interpretation of the tangent vector (e.g. ~coordinax.representations.Velocity, ~coordinax.representations.Displacement).

The representation is computed from these, always with ~coordinax.representations.TangentGeometry as the geometry kind:

\[\mathrm{rep} = ( \mathrm{TangentGeometry},\, \mathrm{basis},\, \mathrm{semantic} ).\]

This is contrast to ~coordinax.vectors.Point, which stores a fixed ~coordinax.representations.PointGeometry-flavoured rep and a concrete location on the manifold.

Parameters:
  • data (dict[str, TypeVar(V, bound= HasShape)]) – Mapping from chart component name to scalar value.

  • chart (TypeVar(ChartT, bound= AbstractChart[Any, Any, Any])) – A chart instance (e.g. cxc.cart3d) that defines the coordinate system.

  • basis (TypeVar(BasisT, bound= AbstractLinearBasis)) – The linear basis in which the tangent components are expressed.

  • semantic (TypeVar(SemanticT, bound= AbstractTangentSemanticKind)) – The semantic kind of the tangent vector (velocity, displacement, etc.).

  • frame (Any) – The reference frame. Defaults to cxf.noframe.

Examples

Construct a coordinate-basis velocity in Cartesian 3D:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel,
... )
>>> v.rep == cxr.coord_vel
True
data: dict[str, V]

The data for each component.

chart: ChartT

The chart of the vector, e.g. cxc.cart3d.

basis: BasisT

The linear basis for tangent components.

semantic: SemanticT

The semantic kind of the tangent vector.

property M: AbstractManifold

The manifold of the vector, from the chart.

astype(dtype: Any, /, **kwargs: Any)

Cast the vector to a new dtype.

Examples

>>> import quaxed.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx

We can cast a vector to a new dtype:

>>> vec = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> print(vec.astype(jnp.float32))
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
>>> print(jnp.astype(vec, jnp.float32))
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
Parameters:
Return type:

Self

cconvert(*args, **kwargs)

Represent the vector as another type.

This forwards to coordinax.representations.cconvert.

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> print(vec.cconvert(cx.sph3d))
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
Parameters:
Return type:

Any

copy()

Return a copy of the vector.

Return type:

Self

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m")
>>> print(vec.copy())
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
property dtype: Any
flatten()

Flatten the vector.

Return type:

Self

frame: AbstractReferenceFrame = NoFrame()

The reference frame. Defaults to cxf.noframe.

classmethod from_(*args, **kwargs)

Create a vector-like object from arguments.

from_(cls: type[Point], obj: Point, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from another point.

>>> import coordinax as cx
>>> vec1 = cx.Point.from_([1, 2, 3], "m")
>>> vec2 = cx.Point.from_(vec1)
>>> print(vec2)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
from_(cls: type[Point], obj: Any, chart: AbstractChart, rep: Representation, /) → Point
Parameters:
Return type:

AbstractVector

Construct a vector from an object, and chart and rep info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, chart: AbstractChart, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, and chart info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = {"x": u.Q([1, 2], "m"), "y": u.Q([3, 4], "m"), "z": u.Q([5, 6], "m")}
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 3 5]
     [2 4 6]]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, rep: Representation, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, and rep info.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = {"x": u.Q([1, 2], "m"), "y": u.Q([3, 4], "m"), "z": u.Q([5, 6], "m")}
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 3 5]
     [2 4 6]]>
>>> xs = u.Q(jnp.array([[1, 2, 3], [4, 5, 6]]), "m")
>>> vec = cx.Point.from_(xs, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Any, /) → Any
Parameters:
Return type:

AbstractVector

Construct a point from an object.

Note that this is a pretty limited constructor since it often lacks the necessary information to do a proper construction.

>>> import jax.numpy as jnp
>>> import unxt as u
>>> import coordinax as cx
>>> xs = {"x": u.Q(1, "m"), "y": u.Q(2, "m"), "z": u.Q(3, "m")}
>>> vec = cx.Point.from_(xs)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> vec = cx.Point.from_(u.Q([1, 2, 3], "m"))
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, /) → Any
Parameters:
Return type:

AbstractVector

Construct a cartesian vector from an array and unit.

The ArrayLike[Any, (*#batch, N), "..."] is expected to have the components as the last dimension.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "meter")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "meter")
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, /) → Any
Parameters:
Return type:

AbstractVector

Construct a vector from an array, unit, and chart.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m", cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "m", cx.cart3d)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, /) → Any
Parameters:
Return type:

AbstractVector

Construct a vector from an array, unit, chart, and rep.

>>> import jax.numpy as jnp
>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m", cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [1 2 3]>
>>> xs = jnp.array([[1, 2, 3], [4, 5, 6]])
>>> vec = cx.Point.from_(xs, "m", cx.cart3d, cx.point)
>>> print(vec)
<Point: chart=Cart3D (x, y, z) [m]
    [[1 2 3]
     [4 5 6]]>
from_(cls: type[Point], obj: Point, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from another point, replacing its frame.

Every constructor above also accepts a trailing frame, which is attached to the constructed point:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.frames as cxf
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
>>> for args in [(cx.Point.from_(d),), (d,), (d, cxc.cart3d),
...              (d, cxc.cart3d, cxr.point), ([1, 2, 3], "km")]:
...     print(cx.Point.from_(*args, cxf.alice).frame)
Alice()
Alice()
Alice()
Alice()
Alice()

An existing frame is replaced, not merged:

>>> p_alice = cx.Point.from_(d, cxf.alice)
>>> cx.Point.from_(p_alice, cxf.noframe).frame == cxf.noframe
True
from_(cls: type[Point], obj: Any, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from any object, with a frame.

from_(cls: type[Point], obj: Any, chart: AbstractChart, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object and chart, with a frame.

from_(cls: type[Point], obj: Any, chart: AbstractChart, rep: Representation, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an object, chart, and representation, with a frame.

from_(cls: type[Point], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, frame: AbstractReferenceFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct a point from an array and unit, with a frame.

from_(cls: type[Tangent], obj: Tangent, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from another Tangent (identity / fast path).

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")},
...     cxc.cart3d, cxr.coord_basis, cxr.vel,
... )
>>> v2 = cx.Tangent.from_(v)
>>> v2 is v
True
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, basis, and semantic.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d, cxr.coord_basis, cxr.vel)
>>> v.chart
Cart3D(M=Rn(3))
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, rep: Representation, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, and a tangent Representation.

Extracts basis and semantic from the representation. Raises TypeError if the representation’s geometry kind is not TangentGeometry.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d, cxr.coord_vel)
>>> v.basis == cxr.coord_basis
True
>>> v.semantic == cxr.vel
True
from_(cls: type[Tangent], obj: Any, chart: AbstractChart, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data and chart (rep inferred from data).

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d, cxc.cart3d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: Any, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data alone (chart and rep inferred).

>>> import coordinax as cx
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> v = cx.Tangent.from_(d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array and unit (chart inferred).

>>> import coordinax as cx
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s")
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, and chart.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s", cxc.cart3d)
>>> isinstance(v, cx.Tangent)
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, chart, and Representation.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_([1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_vel)
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, rep: Representation, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array, unit, chart, Representation, and frame.

from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from array, unit, chart, basis, and semantic.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> v = cx.Tangent.from_(
...     [1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_basis, cxr.vel
... )
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: AbstractQuantity, unit: UnitBase | FunctionUnitBase | str, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from a Quantity, unit, chart, basis, and semantic.

The Quantity is converted to the given unit before construction.

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import unxt as u
>>> v = cx.Tangent.from_(
...     u.Q([1.0, 2.0, 3.0], "m/s"), "m/s", cxc.cart3d, cxr.coord_basis, cxr.vel
... )
>>> v.basis == cxr.coord_basis
True
from_(cls: type[Tangent], obj: Tangent, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from another Tangent, replacing its frame.

Every constructor above also accepts a trailing frame, which is attached to the constructed tangent:

>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> import coordinax.frames as cxf
>>> import unxt as u
>>> d = {"x": u.Q(1.0, "m/s"), "y": u.Q(2.0, "m/s"), "z": u.Q(3.0, "m/s")}
>>> for args in [(cx.Tangent.from_(d),), (d,), (d, cxc.cart3d),
...              (d, cxc.cart3d, cxr.coord_basis, cxr.vel),
...              ([1.0, 2.0, 3.0], "m/s"),
...              ([1.0, 2.0, 3.0], "m/s", cxc.cart3d, cxr.coord_vel)]:
...     print(cx.Tangent.from_(*args, cxf.alice).frame)
Alice()
Alice()
Alice()
Alice()
Alice()
Alice()
from_(cls: type[Tangent], obj: Any, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data with a frame (chart and rep inferred).

from_(cls: type[Tangent], obj: Any, chart: AbstractChart, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data and chart, with a frame.

from_(cls: type[Tangent], obj: Any, chart: AbstractChart, basis: AbstractLinearBasis, semantic: AbstractTangentSemanticKind, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from data, chart, basis, and semantic, with a frame.

from_(cls: type[Tangent], obj: ArrayLike | list[Any], unit: UnitBase | FunctionUnitBase | str, frame: AbstractReferenceFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from an array and unit, with a frame.

from_(cls: type[Coordinate], pv: Coordinate, /) → Coordinate
Parameters:
Return type:

AbstractVector

Identity: return the same Coordinate unchanged.

>>> import coordinax as cx
>>> pv = cx.Coordinate(point=cx.Point.from_([1.0, 2.0, 3.0], "m"))
>>> cx.Coordinate.from_(pv) is pv
True
from_(cls: type[Coordinate], p: Point, /) → Coordinate
Parameters:
Return type:

AbstractVector

Wrap a single Point as a point-only bundle (no field vectors).

>>> import coordinax as cx
>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> pv = cx.Coordinate.from_(p)
>>> pv.point is p
True
from_(cls: type[Coordinate], data: Mapping[str, Any], /, *, point: Point | None = None) → Coordinate
Parameters:
Return type:

AbstractVector

Create a Coordinate from a mapping of named objects.

The mapping may contain a "point" key for the base; the explicit point keyword argument takes precedence if both are supplied.

>>> import unxt as u
>>> import coordinax as cx
>>> import coordinax.charts as cxc
>>> import coordinax.representations as cxr
>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> vel = cx.Tangent.from_(
...     {"x": u.Q(1.0, "m/s"), "y": u.Q(0.0, "m/s"), "z": u.Q(0.0, "m/s")},
...     cxc.cart3d, cxr.coord_vel)
>>> pv = cx.Coordinate.from_({"point": p, "velocity": vel})
>>> pv.point is p
True
from_(cls: type[Point], obj: CartesianRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy CartesianRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import CartesianRepresentation
>>> vec = CartesianRepresentation(1, 2, 3, unit="km")
>>> cxv.Point.from_(vec)
Point({'x': Q(1., 'km'), 'y': Q(2., 'km'), 'z': Q(3., 'km')}, chart=Cart3D(M=Rn(3)))
from_(cls: type[Point], obj: CylindricalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy CylindricalRepresentation.

>>> import astropy.units as apyu
>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import CylindricalRepresentation
>>> vec = CylindricalRepresentation(rho=1 * apyu.km, phi=90 * apyu.deg,
...                                 z=3 * apyu.km)
>>> cxv.Point.from_(vec)
Point(
  {'rho': Q(1., 'km'), 'phi': Q(90., 'deg'), 'z': Q(3., 'km')},
  chart=Cylindrical3D(M=Rn(3))
)
from_(cls: type[Point], obj: PhysicsSphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy PhysicsSphericalRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import PhysicsSphericalRepresentation
>>> import astropy.units as apyu
>>> vec = PhysicsSphericalRepresentation(
...     r=1 * apyu.kpc, theta=45 * apyu.deg, phi=90 * apyu.deg)
>>> cxv.Point.from_(vec)
Point(
  {'r': Q(1., 'kpc'), 'theta': Q(45., 'deg'), 'phi': Q(90., 'deg')},
  chart=Spherical3D(M=Rn(3))
)
from_(cls: type[Point], obj: SphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy SphericalRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import SphericalRepresentation
>>> import astropy.units as apyu
>>> vec = SphericalRepresentation(
...     lon=90 * apyu.deg, lat=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3))
)
from_(cls: type[Point], obj: UnitSphericalRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy UnitSphericalRepresentation.

A direction with no distance, so the point lives on the two-sphere rather than in R^3.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import UnitSphericalRepresentation
>>> import astropy.units as apyu
>>> vec = UnitSphericalRepresentation(lon=90 * apyu.deg, lat=45 * apyu.deg)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg')},
  chart=LonLatSphericalTwoSphere(M=Sn(2))
)
from_(cls: type[Point], obj: RadialRepresentation, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy RadialRepresentation.

>>> import coordinax.vectors as cxv
>>> from astropy.coordinates import RadialRepresentation
>>> import astropy.units as apyu
>>> cxv.Point.from_(RadialRepresentation(distance=1 * apyu.kpc))
Point({'r': Q(1., 'kpc')}, chart=Radial1D(M=Rn(1)))
from_(cls: type[Point], obj: BaseCoordinateFrame, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy frame with data.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.ICRS(ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3)), frame=ICRS()
)
>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc
... )
>>> cxv.Point.from_(vec)
Point(
    {'x': Q(1., 'kpc'), 'y': Q(2., 'kpc'), 'z': Q(3., 'kpc')},
    chart=Cart3D(M=Rn(3)), frame=Galactocentric(...)
)

A ~coordinax.vectors.Point is a position, so any velocity the frame carries is not part of the result. ~coordinax.vectors.Tangent.from_ takes the other half:

>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)
from_(cls: type[Tangent], obj: BaseCoordinateFrame, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from the velocity of an Astropy frame.

The position half is ~coordinax.vectors.Point.from_; this is the other half, so a frame carrying both splits into the two coordinax objects.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.Galactocentric(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)

Angular rates come across too, when the frame states them as plain d_lon rather than astropy’s cos(lat)-scaled convention:

>>> vec = apyc.ICRS(
...     ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc,
...     pm_ra=3 * apyu.mas / apyu.yr, pm_dec=2 * apyu.mas / apyu.yr,
...     radial_velocity=10 * apyu.km / apyu.s,
...     differential_type=apyc.SphericalDifferential,
... )
>>> cxv.Tangent.from_(vec)
Tangent(
  {'lon': Q(3., 'mas / yr'), 'lat': Q(2., 'mas / yr'),
   'distance': Q(10., 'km / s')},
  chart=LonLatSpherical3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)

A frame without velocities says so rather than handing back a zero:

>>> try:
...     cxv.Tangent.from_(apyc.ICRS(ra=1 * apyu.deg, dec=2 * apyu.deg))
... except ValueError as e:
...     print(e)
ICRS carries no velocity; there is nothing to convert to a Tangent.
from_(cls: type[Tangent], obj: SkyCoord, /) → Tangent
Parameters:
Return type:

AbstractVector

Construct a Tangent from the velocity of an Astropy SkyCoord.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv

A ~astropy.coordinates.SkyCoord defaults to the cos(lat)-scaled proper motion convention, which coordinax has no chart for, so the usual astronomical spelling is refused rather than silently reinterpreted:

>>> sc = apyc.SkyCoord(
...     ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc,
...     pm_ra_cosdec=3 * apyu.mas / apyu.yr, pm_dec=2 * apyu.mas / apyu.yr,
...     radial_velocity=10 * apyu.km / apyu.s,
... )
>>> try:
...     cxv.Tangent.from_(sc)
... except ValueError as e:
...     print(e)
astropy's SphericalCosLatDifferential is a rate convention ...

Cartesian velocities carry across unchanged:

>>> sc = apyc.SkyCoord(
...     x=1 * apyu.kpc, y=2 * apyu.kpc, z=3 * apyu.kpc,
...     v_x=4 * apyu.km / apyu.s, v_y=5 * apyu.km / apyu.s,
...     v_z=6 * apyu.km / apyu.s,
...     representation_type="cartesian", differential_type="cartesian",
... )
>>> cxv.Tangent.from_(sc)
Tangent(
  {'x': Q(4., 'km / s'), 'y': Q(5., 'km / s'), 'z': Q(6., 'km / s')},
  chart=Cart3D(M=Rn(3)),
  basis=coord_basis,
  semantic=vel
)
from_(cls: type[Point], obj: SkyCoord, /) → Point
Parameters:
Return type:

AbstractVector

Construct Point from Astropy SkyCoord.

>>> import astropy.units as apyu
>>> import astropy.coordinates as apyc
>>> import coordinax.vectors as cxv
>>> vec = apyc.SkyCoord(ra=90 * apyu.deg, dec=45 * apyu.deg, distance=1 * apyu.kpc)
>>> cxv.Point.from_(vec)
Point(
  {'lon': Q(90., 'deg'), 'lat': Q(45., 'deg'), 'distance': Q(1., 'kpc')},
  chart=LonLatSpherical3D(M=Rn(3)), frame=ICRS()
)
>>> vec = vec.transform_to(apyc.Galactocentric())
>>> cxv.Point.from_(vec)
Point(
  {'x': Q(-9.08123957, 'kpc'), 'y': Q(0.21365468, 'kpc'), 'z': Q(0.2056243, 'kpc')},
  chart=Cart3D(M=Rn(3)),
  frame=Galactocentric(
    galcen=Point(
      {
        'lon': Q(266.4051, 'deg'),
        'lat': Q(-28.936175, 'deg'),
        'distance': Q(8.122, 'kpc')
      },
      chart=LonLatSpherical3D(M=Rn(3)),
      frame=ICRS()
    ),
    roll=Angle(0., 'deg'),
    z_sun=Q(20.8, 'pc'),
    galcen_v_sun=Tangent(
      {'x': Q(12.9, 'km / s'), 'y': Q(245.6, 'km / s'), 'z': Q(7.78, 'km / s')},
      chart=Cart3D(M=Rn(3)),
      basis=coord_basis,
      semantic=vel
    )
  )
)
Parameters:
Return type:

AbstractVector

classmethod is_like(obj: Any, /)

Check if the object is a AbstractVector object.

Examples

>>> import coordinax.vectors as cxv
>>> vec = cxv.Point.from_([1, 2, 3], "m")
>>> cxv.AbstractVector.is_like(vec)
True
>>> cxv.AbstractVector.is_like(42)
False
Parameters:

obj (Any)

Return type:

TypeIs[Self]

property ndim: int
ravel()

Return a flattened vector.

Return type:

Self

represent_as(target: Any, *args: Any, **kwargs: Any)

Represent the vector as another type.

This just forwards to coordinax.cconvert.

Parameters:
  • target (Any) – The representation type to convert to, e.g. cxc.sph3d.

  • *args (Any) – Extra arguments. These are passed to coordinax.cconvert and might be used, depending on the dispatched method. E.g. for transforming a velocity or acceleration vector, generally the first argument is the position vector at which the differential is defined. In general this is a required argument, though it is not for Cartesian-to-Cartesian transforms – see https://en.wikipedia.org/wiki/Tensors_in_curvilinear_coordinates for more information.

  • **kwargs (Any) – Extra arguments. These are passed to coordinax.cconvert and might be used, depending on the dispatched method. E.g. for transforming a velocity or acceleration vector, generally the first argument is the position vector at which the differential is defined. In general this is a required argument, though it is not for Cartesian-to-Cartesian transforms – see https://en.wikipedia.org/wiki/Tensors_in_curvilinear_coordinates for more information.

Return type:

Any

Examples

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

Transforming a Position:

>>> q_cart = cx.Point.from_([1, 2, 3], "m")
>>> q_sph = q_cart.represent_as(cxc.sph3d)
>>> print(q_sph)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
reshape(*shape: int)

Return a reshaped vector.

Parameters:

shape (int)

Return type:

Self

round(decimals: int = 0)

Return a rounded vector.

Parameters:

decimals (int)

Return type:

Self

property shape: tuple[int, ...]

Return the batch shape of the vector.

property size: int
to_cartesian()

Return the vector in a Cartesian chart.

This just forwards to coordinax.cartesian_chart and cconvert.

Return type:

AbstractVector

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "m").cconvert(cx.sph3d)
>>> print(vec)
<Point: chart=Spherical3D (r[m], theta[rad], phi[rad])
    [3.742 0.641 1.107]>
>>> print(vec.to_cartesian())
<Point: chart=Cart3D (x, y, z) [m]
    [1. 2. 3.]>
to_device(device: None | Device = None)

Move the vector to a new device.

Parameters:

device (None | Device)

Return type:

Self

to_frame(toframe: AbstractReferenceFrame, /, t: Quantity | None = None)

Transform the vector to a specified reference frame.

Parameters:
Returns:

New vector with the data transformed into toframe and frame=toframe.

Return type:

AbstractVector

Notes

This is a convenience: it rebuilds the frame-transition operator on every call, which is pure Python and dominates the cost. On a hot path or under jit, build the operator once outside with coordinax.frames.frame_transition and pass it into the jitted function that applies it – see that function’s notes.

Examples

>>> import coordinax as cx
>>> import coordinax.frames as cxf
>>> p = cx.Point.from_([1, 2, 3], "kpc", cxf.alice)
>>> p.to_frame(cxf.alice) is p
True
uconvert(*args, **kwargs)

Convert the vector to the given units.

This just forwards to unxt.uconvert, reversing the order of the arguments to match the unxt API.

Examples

>>> import coordinax as cx
>>> vec = cx.Point.from_([1, 2, 3], "km")
>>> print(vec.uconvert({"length": "km"}))
<Point: chart=Cart3D (x, y, z) [km]
    [1 2 3]>
uconvert(self, usys: AbstractUnitSystem, /) → Any
Parameters:
Return type:

Any

Convert the vector to the given units.

Parameters:
  • usys – The units to convert to according to the physical type of the components. This is passed to [unxt.unitsystem][].

  • args (Any)

  • kwargs (Any)

Return type:

Any

Examples

>>> import unxt as u
>>> import coordinax as cx
>>> usys = u.unitsystem("m", "s", "kg", "rad")
>>> vec = cx.Point.from_([1, 2, 3], "km")
>>> print(vec.uconvert(usys))
<Point: chart=Cart3D (x, y, z) [m]
    [1000. 2000. 3000.]>
>>> print(vec.uconvert("galactic"))
<Point: chart=Cart3D (x, y, z) [kpc]
    [3.241e-17 6.482e-17 9.722e-17]>
Parameters:
Return type:

Any

property rep: Representation

The representation, computed from basis and semantic.

final class coordinax.ToUnitsOptions(*values)

Bases: Enum

Options for the units argument of uconvert.

This enum provides named conversion behaviors that are accepted by Point.uconvert.

Examples

Point.uconvert with consistent:

>>> import unxt as u
>>> import coordinax as cx
>>> vec = cx.Point.from_({"x": u.Q(1, "m"), "y": u.Q(2, "km")}, cx.cart2d)
>>> print(vec.uconvert(cx.ToUnitsOptions.consistent))
<Point: chart=Cart2D (x, y) [m]
    [1.e+00 2.e+03]>
consistent = 'consistent'

Convert to consistent units.

coordinax.equivalent(a, b, /)

Whether two unit systems describe the same physical structure.

Two unit systems are equivalent when they share the same set of base dimensions and, for each dimension, their units are interconvertible. Unlike ==, the specific units need not be identical – only convertible – so systems that measure the same dimensions in different (but compatible) units are equivalent.

Examples

>>> from unxt.unitsystems import galactic, solarsystem, si, equivalent

galactic (kpc, Myr, solMass, rad) and solarsystem (AU, yr, solMass, rad) have the same base dimensions and interconvertible units:

>>> equivalent(galactic, solarsystem)
True
>>> equivalent(galactic, galactic)
True

A system with different base dimensions is not equivalent:

>>> equivalent(galactic, si)
False
coordinax.equivalent(a: AbstractQuantity, b: AbstractQuantity, /) → Any
Parameters:
Return type:

bool

Whether two quantities are physically equal, accounting for units.

This is the unit-aware counterpart to == (which is unit-blind for StaticValue-backed quantities – see AbstractQuantity.__eq__). The result mirrors ==’s shape: a scalar bool for StaticValue-backed operands, and an element-wise dimensionless Quantity of booleans for array-backed ones. Quantities with incompatible dimensions are never equivalent (and this never raises).

Examples

>>> import numpy as np
>>> import unxt as u
>>> from unxt.quantity import StaticValue

Physically-equal static quantities in different units are equivalent, even though unit-blind == reports False:

>>> a = u.Q(StaticValue(np.array([1.0, 2.0])), "m")
>>> b = u.Q(StaticValue(np.array([0.001, 0.002])), "km")
>>> a == b
False
>>> u.equivalent(a, b)
True
>>> a.is_equivalent(b)
True

Array-backed quantities compare element-wise (unit-aware):

>>> u.equivalent(u.Q([1.0, 2.0], "m"), u.Q([0.001, 0.009], "km"))
Quantity(Array([ True, False], dtype=bool), unit='')

Incompatible dimensions are never equivalent:

>>> u.equivalent(u.Q(1.0, "m"), u.Q(1.0, "s"))
False
coordinax.equivalent(a: AbstractVector, b: AbstractVector, /, *, rtol: float = 1e-05, atol: float = 1e-08) → Any
Parameters:
Return type:

bool

Whether two vectors denote the same geometric point.

Unlike == – which is strict (equal only for matching chart, frame, and data) – equivalent is invariant to the chart and to the component units: it compares the two vectors as points in a common Cartesian chart. It remains frame-strict, since coordinates in different frames describe different physical points. Because chart transitions are trigonometric and square-root heavy, the comparison is tolerance-based (rtol, atol); atol is measured in the Cartesian component units of the first operand (or in raw component units for unitless vectors).

Examples

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

The same point in Cartesian and spherical charts is not == (the charts differ) but is equivalent:

>>> p = cx.Point.from_([1.0, 2.0, 3.0], "m")
>>> sph = p.cconvert(cxc.sph3d)
>>> bool(p == sph)
False
>>> bool(cx.equivalent(p, sph))
True

Equivalence is also invariant to the component units:

>>> q = cx.Point.from_([1.0, 2.0, 3.0], "km")
>>> mm = cx.Point.from_([1e6, 2e6, 3e6], "mm")
>>> bool(cx.equivalent(q, mm))
True

Distinct points are not equivalent:

>>> bool(cx.equivalent(p, cx.Point.from_([1.0, 2.0, 4.0], "m")))
False
Parameters:
Return type:

bool

coordinax.geodesic_distance(*args, **kwargs)

Distance between two points on a manifold.

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

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

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

Any

Geodesic distance between two points, on the chart’s manifold.

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

A 3-4-5 triangle in flat space:

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

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

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

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

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

Any

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

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

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

Any

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

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

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

Any

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

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

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

Any

Return the great-circle distance on the unit hypersphere.

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

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

A quarter turn along the equator is pi / 2:

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

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

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

Any

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

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

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

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

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

Any

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

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

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

Any

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

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

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

Any

Geodesic distance with the metric stated explicitly.

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

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

Any

Packed operands in a Cartesian chart: measure without unpacking.

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

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

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

Any

Distance between two points, via the manifold norm.

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

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

Examples

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

A 3-4-5 right triangle:

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

Chart- and unit-invariant – the same points expressed differently give the same distance:

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

The distance lives on the points’ manifold, so 2-D points give a 2-D distance:

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

Any