Curvilinear Grids

Curvilinear grids extend the base grid infrastructure to non-Cartesian coordinate systems. They provide vector calculus operators – gradient, divergence, curl, and Laplacian – that account for the metric factors of the underlying coordinate system. Concrete implementations are supplied for spherical, cylindrical, and polar coordinates.

class numgrids.CurvilinearGrid(*axes, scale_factors: Sequence[Callable[[tuple[ndarray[tuple[Any, ...], dtype[_ScalarT]], ...]], ndarray[tuple[Any, ...], dtype[_ScalarT]]]])[source]

Orthogonal curvilinear grid with auto-generated vector calculus.

The user supplies one scale-factor callable per axis. Each callable has the signature (coords: tuple[NDArray, ...]) -> NDArray, where coords is self.meshed_coords.

From the scale factors \(h_i\) the class derives:

  • gradient

    \[(\nabla f)_i = \frac{1}{h_i}\frac{\partial f}{\partial q_i}\]
  • divergence (where \(J = \prod h_i\))

    \[\nabla\!\cdot\!\mathbf{v} = \frac{1}{J}\sum_i \frac{\partial}{\partial q_i} \!\left(\frac{J}{h_i}\,v_i\right)\]
  • Laplacian

    \[\nabla^2 f = \frac{1}{J}\sum_i \frac{\partial}{\partial q_i} \!\left(\frac{J}{h_i^2}\,\frac{\partial f}{\partial q_i}\right)\]
  • curl (3D only)

    \[(\nabla\!\times\!\mathbf{v})_i = \frac{1}{h_j h_k}\!\left[ \frac{\partial(h_k\,v_k)}{\partial q_j} - \frac{\partial(h_j\,v_j)}{\partial q_k}\right]\]

For 2D grids a scalar curl is provided, equal to the out-of-plane component.

Coordinate singularities (e.g. r = 0) are handled gracefully: non-finite values are replaced by zero.

Parameters:
  • *axes (Axis) – One or more Axis objects.

  • scale_factors (sequence of callables) – One callable per axis. scale_factors[i](meshed_coords) must return an ndarray of shape grid.shape.

Raises:

ValueError – If the number of scale factors does not match the number of axes.

Examples

>>> from numgrids import CurvilinearGrid, create_axis, AxisType
>>> import numpy as np
>>> r_ax = create_axis(AxisType.CHEBYSHEV, 25, 0.1, 2)
>>> phi_ax = create_axis(AxisType.EQUIDISTANT_PERIODIC, 30, 0, 2 * np.pi)
>>> z_ax = create_axis(AxisType.CHEBYSHEV, 25, -1, 1)
>>> grid = CurvilinearGrid(
...     r_ax, phi_ax, z_ax,
...     scale_factors=(
...         lambda c: np.ones_like(c[0]),   # h_r = 1
...         lambda c: c[0],                  # h_phi = r
...         lambda c: np.ones_like(c[0]),   # h_z = 1
...     ),
... )
>>> R, Phi, Z = grid.meshed_coords
>>> lap = grid.laplacian(R ** 2 + Z ** 2)  # should be ~6

Constructor

Parameters:

axes (one or more Axis objects) – One or more axes defining the grid. The order of the axes matters! The first axis passed will have index 0.

gradient(f: ndarray[tuple[Any, ...], dtype[_ScalarT]]) tuple[ndarray[tuple[Any, ...], dtype[_ScalarT]], ...][source]

Gradient of a scalar field.

\[(\nabla f)_i = \frac{1}{h_i}\,\frac{\partial f}{\partial q_i}\]
Parameters:

f (NDArray) – Scalar field of shape grid.shape.

Returns:

Physical gradient components, one per axis.

Return type:

tuple of NDArray

divergence(*v: ndarray[tuple[Any, ...], dtype[_ScalarT]]) ndarray[tuple[Any, ...], dtype[_ScalarT]][source]

Divergence of a vector field.

\[\nabla\!\cdot\!\mathbf{v} = \frac{1}{J}\sum_i \frac{\partial}{\partial q_i} \!\left(\frac{J}{h_i}\,v_i\right)\]
Parameters:

*v (NDArray) – Physical components of the vector field (one per axis).

Returns:

The scalar divergence field.

Return type:

NDArray

Raises:

ValueError – If the number of components does not match the grid dimension.

laplacian(f: ndarray[tuple[Any, ...], dtype[_ScalarT]]) ndarray[tuple[Any, ...], dtype[_ScalarT]][source]

Laplacian of a scalar field.

\[\nabla^2 f = \frac{1}{J}\sum_i \frac{\partial}{\partial q_i} \!\left(\frac{J}{h_i^2}\,\frac{\partial f}{\partial q_i}\right)\]
Parameters:

f (NDArray) – Scalar field of shape grid.shape.

Returns:

The Laplacian, same shape as f.

Return type:

NDArray

curl(*v: ndarray[tuple[Any, ...], dtype[_ScalarT]]) tuple[ndarray[tuple[Any, ...], dtype[_ScalarT]], ...] | ndarray[tuple[Any, ...], dtype[_ScalarT]][source]

Curl of a vector field.

3D grids — returns a tuple of three arrays (one per component):

\[(\nabla\!\times\!\mathbf{v})_i = \frac{1}{h_j h_k}\!\left[ \frac{\partial(h_k\,v_k)}{\partial q_j} - \frac{\partial(h_j\,v_j)}{\partial q_k}\right]\]

where \((i, j, k)\) is a cyclic permutation of \((0, 1, 2)\).

2D grids — returns a scalar array (the out-of-plane component):

\[(\nabla\!\times\!\mathbf{v})_z = \frac{1}{h_0 h_1}\!\left[ \frac{\partial(h_1\,v_1)}{\partial q_0} - \frac{\partial(h_0\,v_0)}{\partial q_1}\right]\]
Parameters:

*v (NDArray) – Physical components of the vector field.

Returns:

The curl components.

Return type:

tuple of NDArray (3D) or NDArray (2D)

Raises:

ValueError – If the grid dimension is not 2 or 3, or if the number of components is wrong.

class numgrids.SphericalGrid(raxis: Axis, theta_axis: Axis, phi_axis: Axis)[source]

A spherical grid in spherical coordinates (r, theta, phi).

Provides built-in vector calculus operators:

  • laplacian() — scalar Laplacian

  • gradient() — gradient of a scalar field

  • divergence() — divergence of a vector field

  • curl() — curl of a vector field

Coordinate singularities at r = 0 and theta = 0, pi are handled gracefully: non-finite values are replaced by zero.

Constructor

Parameters:
  • raxis (Axis) – The radial axis.

  • theta_axis (Axis) – The polar axis (theta).

  • phi_axis (Axis) – The azimuthal axis (phi). Must be periodic.

class numgrids.CylindricalGrid(raxis: Axis, phi_axis: Axis, zaxis: Axis)[source]

A grid in cylindrical coordinates (r, phi, z).

Provides built-in vector calculus operators:

  • laplacian() — scalar Laplacian

  • gradient() — gradient of a scalar field

  • divergence() — divergence of a vector field

  • curl() — curl of a vector field

Coordinate singularities at r = 0 are handled gracefully.

Examples

>>> from numgrids import *
>>> import numpy as np
>>> grid = CylindricalGrid(
...     create_axis(AxisType.CHEBYSHEV, 20, 0.1, 2),
...     create_axis(AxisType.EQUIDISTANT_PERIODIC, 30, 0, 2 * np.pi),
...     create_axis(AxisType.CHEBYSHEV, 20, -1, 1),
... )
>>> R, Phi, Z = grid.meshed_coords
>>> f = R ** 2 + Z ** 2
>>> lap_f = grid.laplacian(f)  # should be ~6

Constructor

Parameters:
  • raxis (Axis) – The radial axis.

  • phi_axis (Axis) – The azimuthal axis (phi). Should typically be periodic.

  • zaxis (Axis) – The axial (z) axis.

class numgrids.PolarGrid(raxis: Axis, phi_axis: Axis)[source]

A 2D grid in polar coordinates (r, phi).

Provides built-in vector calculus operators:

  • laplacian() — scalar Laplacian

  • gradient() — gradient of a scalar field

  • divergence() — divergence of a 2D vector field

  • curl() — curl (returns the scalar z-component)

Coordinate singularities at r = 0 are handled gracefully.

Examples

>>> from numgrids import *
>>> import numpy as np
>>> grid = PolarGrid(
...     create_axis(AxisType.CHEBYSHEV, 30, 0.1, 1),
...     create_axis(AxisType.EQUIDISTANT_PERIODIC, 40, 0, 2 * np.pi),
... )
>>> R, Phi = grid.meshed_coords
>>> f = R ** 2 * np.cos(Phi)
>>> lap_f = grid.laplacian(f)

Constructor

Parameters:
  • raxis (Axis) – The radial axis.

  • phi_axis (Axis) – The azimuthal axis (phi). Should typically be periodic.