.. _coordinates_user:
================================
Coordinate Systems: General Info
================================
PyMetric provides a flexible and extensible framework for defining and working with coordinate systems,
especially curvilinear coordinate systems used in scientific and engineering applications. The coordinate
systems are built on top of a symbolic foundation, allowing for advanced operations in both symbolic and
numerical form.
Coordinate systems in PyMetric are defined in the :py:mod:`coordinates` module and all
inherit from a common base, ensuring a consistent interface across systems. Each coordinate system represents a
curvilinear coordinate space (e.g., spherical, cylindrical, prolate spheroidal) and includes full support for:
1. Coordinate transformations
2. Metric and inverse metric tensor evaluations,
3. Symbolic and numeric differential operators (e.g., gradient, divergence, Laplacian),
4. Custom parameters and extensibility via subclassing.
These systems can be used in both analytical and simulation contexts, making them ideal for finite-difference,
spectral, or tensor calculus applications in custom geometries.
.. note::
The underlying design goal of PyMetric is to ensure that
1. Coordinate systems are easy to use.
2. Coordinate systems are easily extensible to allow custom geometries.
3. Coordinate systems are accurate in computation.
In many cases, we have pursued **as efficient an implementation as possible**; however, efficiency is not
the highest priority in this module. Therefore, coordinate systems and their differential operations are **not
suitable for use in (for example) high resolution time dependent PDEs**, where calls to differential geometry
functions would occur many 1000's of times. Instead, PyMetric is ideal for instances where a PDE needs
to be solved on the order of 1 time in order to perform a necessary task.
.. contents::
:local:
:depth: 2
Overview
--------
Coordinate systems in PyMetric represent curvilinear geometries such as spherical, cylindrical,
and prolate spheroidal spaces. These coordinate systems provide a symbolic and numerical interface
to geometric quantities and operations, including:
- Coordinate transformations
- Metric and inverse metric tensors
- Jacobians and metric densities
- Symbolic and numerical differential operators
- Parameterized geometries
Coordinate systems are very useful in their own right; however, they are most commonly used in PyMetric
as part of the construction of a grid (see :ref:`grids`) or a field (see :ref:`fields`).
Each coordinate system class inherits from a common abstract base and defines:
1. **Axes**: Named coordinate directions (e.g., ``["r", "theta", "phi"]``).
2. **Symbolic infrastructure**: Metric tensors, derivatives, and other expressions are computed symbolically
using `SymPy `__.
3. **Numerical evaluation**: Expressions are compiled into NumPy-compatible functions for high-performance evaluation
on structured grids or unstructured inputs.
4. **Parameter support**: Some systems are parameterized (e.g., ellipsoidal focus distance ``a``),
allowing for flexible instantiation of geometric families.
5. **Differential operators**: Each system defines methods for computing gradients, divergences,
Laplacians, and other tensor calculus expressions in its own basis.
Coordinate systems in PyMetric are suitable for use in:
- Finite difference and finite volume solvers
- Symbolic exploration of curvilinear geometry
- Tensor calculus in custom geometries
- Evaluation of scalar or vector fields in native coordinates
.. hint::
Coordinate systems are **not intended** for high-throughput time-stepping applications
where millions of derivative evaluations are required per second. Instead, they are
optimized for flexibility, clarity, and correctness in symbolic and semi-analytic workflows.
.. important::
All coordinate systems in PyMetric share a consistent interface, and expose symbolic and
numerical methods for working with geometry. This makes it easy to switch between
geometries or extend the framework with custom systems.
Coordinate systems are defined in the :mod:`~coordinates` module, and typically subclass
either:
- :class:`~coordinates.core.OrthogonalCoordinateSystem` (for diagonal metric tensors)
- :class:`~coordinates.core.CurvilinearCoordinateSystem` (for full curvilinear geometries)
Constructing Coordinate Systems
-------------------------------
Coordinate systems in PyMetric are available in the :py:mod:`~coordinates` module. Each class represents
a specific curvilinear coordinate system, such as
- :class:`~coordinates.coordinate_systems.SphericalCoordinateSystem`: Spherical coordinates.
- :class:`~coordinates.coordinate_systems.CartesianCoordinateSystem2D`: 2D cartesian coordinates.
- :class:`~coordinates.coordinate_systems.CylindricalCoordinateSystem`: Cylindrical coordinates.
These classes provide symbolic and numerical support for differential geometry and coordinate
transformations, and can be directly instantiated as needed.
To create a coordinate system, import the desired class and instantiate it:
.. code-block:: python
from pymetric.coordinates import (
SphericalCoordinateSystem,
ProlateSpheroidalCoordinateSystem
)
# Create a standard spherical coordinate system
spherical = SphericalCoordinateSystem()
# Create a prolate spheroidal system with a custom focal length
prolate = ProlateSpheroidalCoordinateSystem(a=1.5)
Coordinate system instances are lightweight and behave like symbolic geometry containers. Once
created, they provide access to axes, symbolic tensors, and geometry-aware operations such as gradient
or Laplacian computations.
.. hint::
PyMetric coordinate systems support both symbolic inspection and NumPy-compatible numerical evaluation.
Required Parameters
^^^^^^^^^^^^^^^^^^^
Some coordinate systems require parameters to define their shape or scaling. For example, the
:py:class:`~coordinates.coordinate_systems.ProlateSpheroidalCoordinateSystem` requires the focal
distance ``a`` as a parameter, which defines the spacing between the foci of the ellipsoids.
If parameters are not provided, default values are used:
.. code-block:: python
cs1 = ProlateSpheroidalCoordinateSystem() # uses a = 1.0 by default
cs2 = ProlateSpheroidalCoordinateSystem(a=2.0) # custom focal parameter
print(cs1.parameters)
{'a': 1.0}
print(cs2.parameters)
{'a': 2.0}
To inspect the current parameters of a coordinate system, use the
:py:attr:`~coordinates.coordinate_systems.CurvilinearCoordinateSystem.parameters` attribute.
Accessing Coordinate System Metadata
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Each coordinate system exposes useful metadata via attributes:
- :py:attr:`~coordinates.core.CurvilinearCoordinateSystem.axes`:
The logical axis names (e.g., ``["r", "theta", "phi"]``).
- :py:attr:`~coordinates.core.CurvilinearCoordinateSystem.ndim`:
The dimensionality of the coordinate system.
- :py:attr:`~coordinates.core.CurvilinearCoordinateSystem.parameters`:
Dictionary of any shape or transformation parameters.
This metadata is used throughout PyMetric to ensure consistency between coordinate systems,
grids, and differential operations.
.. code-block:: python
cs = SphericalCoordinateSystem()
print(cs.axes) # ['r', 'theta', 'phi']
print(cs.ndim) # 3
print(cs.parameters) # {}
.. note::
Some coordinate systems (especially those with nontrivial geometry) may emit logging messages
during initialization. These messages provide information about expression parsing, symbolic
expression caching, or internal warnings.
You can configure or disable this output using the PyMetric logging tools via
:py:mod:`~utilities.logging`.
Converting Between Coordinate Systems
-------------------------------------
PyMetric provides a unified and extensible API for converting coordinates between different coordinate systems.
All conversions are performed using Cartesian space as an intermediate representation:
.. code-block::
native (source) → Cartesian → native (target)
This ensures generality and allows conversion between any pair of coordinate systems with matching dimensionality.
.. important::
Coordinate systems must have the same number of dimensions to be convertible.
Basic Conversion
^^^^^^^^^^^^^^^^
Use the :py:meth:`~coordinates.core.CurvilinearCoordinateSystem.convert_to` method to perform a one-shot conversion
between coordinate systems:
.. code-block:: python
from pymetric.coordinates import SphericalCoordinateSystem, CylindricalCoordinateSystem
sph = SphericalCoordinateSystem()
cyl = CylindricalCoordinateSystem()
# Convert from spherical to cylindrical coordinates
r, theta, phi = 1.0, 3.14 / 2, 0.0
rho, phi_cyl, z = sph.convert_to(cyl, r, theta, phi)
This method returns the native coordinates of the `target` system by first converting to Cartesian and then
to the destination system’s basis.
Creating Reusable Converters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To avoid repeating transformation logic, you can construct a reusable conversion function using
:py:meth:`~coordinates.core.CurvilinearCoordinateSystem.get_conversion_transform`:
.. code-block:: python
transform = sph.get_conversion_transform(cyl)
rho, phi_cyl, z = transform(1.0, 3.14 / 2, 0.0)
This is especially useful when you need to convert many points across different contexts, or
embed conversion logic into higher-level functions.
Conversion to/from Cartesian Space
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Each coordinate system provides direct access to Cartesian conversion:
- :py:meth:`~coordinates.core.CurvilinearCoordinateSystem.to_cartesian` converts from native coordinates to Cartesian.
- :py:meth:`~coordinates.core.CurvilinearCoordinateSystem.from_cartesian` converts from Cartesian to native coordinates.
.. code-block:: python
x, y, z = sph.to_cartesian(r, theta, phi)
r2, theta2, phi2 = sph.from_cartesian(x, y, z)
These methods work with both scalar and array inputs, and are automatically vectorized using NumPy broadcasting.
Symbolic Manipulations
----------------------
Coordinate systems in PyMetric utilize a mixed design in which symbolic (CAS) based manipulations are favored for deriving
analytical quantities in the coordinate system (metrics, Christoffel Symbols, etc.) but then provides numerical access to
these quantities via efficient numpy conversion. The symbolic side of PyMetric coordinate systems is handled by
`SymPy `__.
These symbolic representations form the foundation for both analytical exploration and numerical computations,
allowing you to derive differential operators like gradients or divergences while respecting the geometry
of the coordinate system.
Coordinate System Symbols
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When a coordinate system class is created, its axes and parameters are converted into symbolic attributes which
are stored in the :py:attr:`~coordinates.core.CurvilinearCoordinateSystem.axes_symbols` and
:py:attr:`~coordinates.core.CurvilinearCoordinateSystem.parameter_symbols` attributes respectively.
.. code-block:: python
cs = SphericalCoordinateSystem()
print(cs.axes_symbols)
[r, theta, phi]
These symbols are then fed into the class's methods in order to construct critical symbolic infrastructure
like the metric tensor, the inverse metric, etc.
The Metric Tensor
^^^^^^^^^^^^^^^^^
There are a number of symbolic attributes derived as part of class definition; however, the most important
is the metric tensor. The metric tensor is essential for performing a variety of differential operations and
is therefore present in every class. You can access the symbolic version of the attribute using
:py:attr:`~coordinates.core.CurvilinearCoordinateSystem.metric_tensor_symbol`
.. code-block:: python
cs = SphericalCoordinateSystem()
print(cs.metric_tensor_symbol)
[1, r**2, r**2*sin(theta)**2]
.. note::
Many of the coordinate systems defined in PyMetric are not only curvilinear, but are also
orthogonal. In this case, the metric is **diagonal** and is therefore represented internally as a vector
instead of a tensor. For classes like :py:class:`~coordinates.coordinate_systems.OblateHomoeoidalCoordinateSystem`,
which are fully curvilinear, the output here is a true matrix.
The metric tensor is also available as a **numpy-like** numerical function:
.. code-block:: python
cs = SphericalCoordinateSystem()
cs.metric_tensor(1,np.pi/2,0)
array([1., 1., 1.])
You can call the metric tensor function by simply passing arrays for each coordinate into the function.
Creating / Retrieving Derived Attributes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PyMetric supports derived expressions beyond the metric, such as:
1. Christoffel terms (for custom systems)
2. Coordinate Jacobians
3. System-specific auxiliary expressions
along with a few symbols which are of critical importance internally for differential
geometry operations (like the metric determinant). Regardless of which symbolic attribute
is of interest, it is **always possible** to access the attribute symbolically and numerically.
Attributes which are not implemented by default are called **derived attributes** and a list of
them can be accessed with
.. code-block:: python
cs = OblateHomoeoidalCoordinateSystem(ecc=0.3)
print(cs.list_expressions())
['Lterm', 'Dterm', 'metric_tensor', 'metric_density', 'inverse_metric_tensor']
If you want to retrieve a particular symbolic attribute, you can simply
use the :py:meth:`~coordinates.coordinate_systems.CurvilinearCoordinateSystem.get_expression` method.
.. code-block:: python
cs = OblateHomoeoidalCoordinateSystem(ecc=0.3)
print(cs.get_expression('metric_density'))
sqrt(-xi**4*sin(theta)**2/(0.000729*sin(theta)**6 - 0.0243*sin(theta)**4 ^ 0.27*sin(theta)**2 - 1.0))
cs = OblateHomoeoidalCoordinateSystem(ecc=0.0)
print(cs.get_expression('metric_density'))
sqrt(xi**4*sin(theta)**2)
Accessing Numerical Versions of Symbolic Expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All symbolic expressions can be turned into callable NumPy functions using:
.. code-block:: python
fn = cs.get_numeric_expression("metric_density")
val = fn(r=1.0, theta=np.pi/2, phi=0.0)
This process uses :py:func:`sympy.lambdify` under the hood, and allows fast evaluation over grids or datasets.
Class Level Expressions
^^^^^^^^^^^^^^^^^^^^^^^
Some expressions—like the metric tensor—are computed at the class level and
shared across all instances (symbolically). You can inspect or retrieve these
without instantiating the coordinate system:
.. code-block:: python
from pymetric.coordinates.coordinate_systems import CylindricalCoordinateSystem
g = CylindricalCoordinateSystem.get_class_expression("metric_tensor")
print(g)
This is useful for inspecting or manipulating symbolic expressions analytically
before plugging in parameter values.