Working With Charts#
This guide focuses only on chart functionality in coordinax.charts.
What A Chart Is#
A chart defines coordinate component names and dimensions.
Chart maps change coordinate representation while preserving the same point.
Charts are static descriptors; coordinate values live in dictionaries.
>>> import coordinax.charts as cxc
>>> cxc.cart3d.components
('x', 'y', 'z')
>>> cxc.sph3d.coord_dimensions
('length', 'angle', 'angle')
Choosing The Right Map#
Use these chart APIs by intent:
pt_map: point coordinate change on the same manifoldpt_map: general point map interfacecartesian_chart: chart selection only (no coordinate data transformation)
For same-manifold chart changes, transition and realization maps agree:
Use pt_map for same-manifold chart transitions and pt_map for the general point map interface.
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> p = {"x": u.Q(1, "km"), "y": u.Q(2, "km"), "z": u.Q(3, "km")}
>>> p_sph = cxc.pt_map(p, cxc.cart3d, cxc.sph3d)
>>> sorted(p_sph)
['phi', 'r', 'theta']
>>> p_sph2 = cxc.pt_map(p, cxc.cart3d, cxc.sph3d)
>>> p_sph2 == p_sph
True
Chart selection is independent of point data:
>>> import coordinax.charts as cxc
>>> cxc.cartesian_chart(cxc.sph3d)
Cart3D(M=Rn(3))
Inferring Charts And Normalizing Inputs#
guess_chart infers a chart from keys or array shape heuristics. cdict normalizes different input forms to component dictionaries.
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> cxc.guess_chart({"x": 1.0, "y": 2.0, "z": 3.0})
Cart3D(M=Rn(3))
>>> cxc.guess_chart(frozenset(("x", "y", "z")))
Cart3D(M=Rn(3))
>>> q = u.Q([1.0, 2.0, 3.0], "m")
>>> cxc.cdict(q)
{'x': Q(1., 'm'), 'y': Q(2., 'm'), 'z': Q(3., 'm')}
guess_chart caveats:
Key-based inference uses component-name sets, so it is not a unique identifier when multiple chart types share names.
Array/quantity shape inference is limited to trailing axis sizes 1, 2, or 3.
Product Charts#
Product-chart transitions are factorwise.
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> st_cart = cxc.CartesianProductChart((cxc.time1d, cxc.cart3d), ("ct", "q"))
>>> st_sph = cxc.CartesianProductChart((cxc.time1d, cxc.sph3d), ("ct", "q"))
>>> p_st = {"ct.t": u.Q(1, "km"), "q.x": u.Q(2, "km"), "q.y": u.Q(0, "km"), "q.z": u.Q(0, "km")}
>>> q_st = cxc.pt_map(p_st, st_cart, st_sph)
>>> sorted(q_st)
['ct.t', 'q.phi', 'q.r', 'q.theta']
>>> prod = cxc.CartesianProductChart((cxc.time1d, cxc.sph3d), ("t", "q"))
>>> prod.components
('t.t', 'q.r', 'q.theta', 'q.phi')
Computing Jacobians#
jac_pt_map returns the coordinate-transformation Jacobian \(J^j{}_i = \partial \phi^j / \partial q^i\) evaluated at a base point, where \(\phi\) is the transition function from from_chart to to_chart.
Direct call — quantity-valued dictionary input#
Passing a component dictionary with unxt.Quantity values returns a QMatrix whose element [j, i] carries the unit output_unit_j / input_unit_i:
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> 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
QMatrix(
[[ 1., 0., 0.],
[-0., -0., -1.],
[ 0., 1., 0.]],
'((, , ), (rad / m, rad / m, rad / m), (rad / m, rad / m, rad / m))'
)
>>> J.shape
(3, 3)
Plain-array input with a unit system#
Pass a plain numeric dict and supply usys to interpret the dimensionless elements:
>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> 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)
Curried form — efficient reuse across many points#
jac_pt_map(None, *args, **kwargs) – or more explicitly, jac_pt_map(from_chart, to_chart, usys=usys) – returns a callable that can be applied to many points without re-building the underlying point-map partial each time. This is the recommended pattern for use inside jax.jit and jax.vmap:
>>> import jax
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> jac_fn = 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")}
>>> J = jac_fn(at)
>>> J.shape
(3, 3)
JIT and vmap compatibility#
The curried form is JIT- and vmap-compatible:
>>> import jax
>>> import jax.numpy as jnp
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> jac_fn = cxc.jac_pt_map(cxc.cart3d, cxc.sph3d, usys=u.unitsystems.si)
>>> jac_jit = jax.jit(jac_fn)
>>> at = {"x": u.Q(1.0, "m"), "y": u.Q(0.0, "m"), "z": u.Q(0.0, "m")}
>>> jac_jit(at).shape
(3, 3)
>>> jac_vmap = jax.vmap(jac_jit)
>>> at_batch = jax.tree.map(lambda x: x[None], at) # Add batch dimension
>>> jac_vmap(at_batch).shape
(1, 3, 3)
Chain rule via Jacobian composition#
The coordinate-change chain rule states that composing two Jacobians gives the Jacobian of the composed map. Use quaxed.numpy.matmul (or coordinax._src.quantity_matrix.qnp.matmul) for unit-aware matrix multiply:
>>> import quaxed.numpy as qnp
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> at_cart = {"x": u.Q(1.0, "m"), "y": u.Q(0.5, "m"), "z": u.Q(2.0, "m")}
>>> J_cs = cxc.jac_pt_map(at_cart, cxc.cart3d, cxc.sph3d)
>>> # Transform point to intermediate chart
>>> at_sph = cxc.pt_map(at_cart, cxc.cart3d, cxc.sph3d)
>>> J_sc = cxc.jac_pt_map(at_sph, cxc.sph3d, cxc.cart3d)
>>> # Chain rule: identity (up to floating-point)
>>> J_composed = qnp.matmul(J_sc, J_cs)
>>> J_composed.shape
(3, 3)
Spacetime Charts#
coordinax.charts provides two 4D spacetime chart types.
Minkowski spacetime (minkowskict) is a flat, fixed-component chart with signature \((-,+,+,+)\):
>>> import coordinax.charts as cxc
>>> cxc.minkowskict.components
('ct', 'x', 'y', 'z')
>>> cxc.minkowskict.cartesian is cxc.minkowskict
True
Galilean spacetime (galileanct) is a parametric product chart time1d × spatial_chart. The default spatial chart is cart3d, giving components (ct, x, y, z):
>>> import coordinax.charts as cxc
>>> cxc.galileanct.components
('ct', 'x', 'y', 'z')
>>> cxc.galileanct.spatial_chart
Cart3D(M=Rn(3))
The spatial factor can be changed at construction time. Chart conversions on the spatial part work factorwise — the ct component is untouched:
>>> import coordinax.charts as cxc
>>> import unxt as u
>>> st_sph = cxc.GalileanCT(cxc.sph3d)
>>> st_sph.components
('ct', 'r', 'theta', 'phi')
>>> p = {"ct": u.Q(0.0, "km"), "r": u.Q(1.0, "km"), "theta": u.Q(1.0, "rad"), "phi": u.Q(0.5, "rad")}
>>> p_cart = cxc.pt_map(p, st_sph, cxc.galileanct)
>>> sorted(p_cart)
['ct', 'x', 'y', 'z']
galileanct.cartesian returns self when the spatial chart is already Cartesian; for a non-Cartesian variant it returns a new GalileanCT with a Cartesian spatial chart:
>>> cxc.galileanct.cartesian is cxc.galileanct
True
>>> cxc.GalileanCT(cxc.sph3d).cartesian == cxc.galileanct
True
Quick Reference#
If you already know the source and target charts:
pt_mapIf you are writing general chart-to-chart point code:
pt_mapIf you need a canonical Cartesian chart object:
cartesian_chartIf your input type varies (dict/quantity/array):
cdictandguess_chart