differential_geometry.dense_utils.dense_adjust_tensor_signature#

differential_geometry.dense_utils.dense_adjust_tensor_signature(tensor_field: ndarray, indices: List[int], tensor_signature: ndarray, metric_field: ndarray | None = None, inverse_metric_field: ndarray | None = None, out: ndarray | None = None, **kwargs) Tuple[ndarray, ndarray][source]#

Adjust multiple indices of a tensor field by raising or lowering them using the metric or inverse metric.

This function modifies the variance (covariant vs. contravariant) of selected tensor indices by contracting them with either the metric tensor or its inverse. The transformation can be performed efficiently for both full (2D) and diagonal (1D) metric representations.

The adjustment is specified through a signature array, where each element corresponds to the current variance of a tensor index:

  • +1: Contravariant (upper index) → lowering uses the metric

  • -1: Covariant (lower index) → raising uses the inverse metric

Only the indices listed in indices will be modified; others are left untouched.

Parameters:
  • tensor_field (numpy.ndarray) –

    The tensor field whose index signature is to be adjusted. The array should have shape (F₁, ..., F_m, I₁, ..., I_r), where:

    • (F₁, ..., F_m) are the field (spatial or grid) dimensions,

    • (I₁, ..., I_r) are the tensor index dimensions, and

    • r is the tensor rank (i.e., the number of tensor indices, inferred from tensor_signature).

  • indices (list of int) – List of indices (from 0 to rank - 1) specifying which tensor slots to modify. These refer to the positions within the tensor portion of the array (i.e., the last rank axes). Indices may appear in any order. If the same index appears multiple times, the corresponding variance transformations will be applied sequentially, potentially resulting in no net change.

  • tensor_signature (numpy.ndarray) – A 1D array of integers of shape (rank,) specifying the current variance of each tensor index. Each entry must be either +1 (indicating a contravariant index) or -1 (indicating a covariant index). The order of entries corresponds to the last rank axes of tensor_field, and defines how each tensor slot is currently positioned with respect to the coordinate basis.

  • metric_field (numpy.ndarray, optional) –

    The metric tensor used to lower contravariant indices. This can be either:

    • A full metric of shape (…, N, N), where N is the size of each tensor slot.

    • A diagonal metric of shape (…, N), representing only the diagonal components.

    Must be broadcast-compatible with the grid shape of tensor_field.

  • inverse_metric_field (numpy.ndarray, optional) –

    The inverse metric tensor used to raise covariant indices. This can be either:

    • A full inverse metric of shape (…, N, N), or

    • A diagonal inverse metric of shape (…, N).

    Must match the metric type (diagonal vs full) and be broadcast-compatible with tensor_field.

  • out (numpy.ndarray, optional) – Optional output array to store the result. If provided, must have the same shape and dtype as the expected output, and will be used for in-place storage.

  • **kwargs

    Additional keyword arguments forwarded to the underlying einsum or broadcasting routines. These may include:

    • out : Optional output array to hold the result. If provided, the operation may be performed in-place.

    • Array creation keywords such as dtype or order when a new output array is allocated (e.g., via np.empty).

    • Einsum-specific options such as optimize=True for path optimization.

    The accepted keywords depend on whether a diagonal or full metric is used, as different implementations (np.einsum vs broadcasting and np.empty) are internally dispatched.

Returns:

The resulting tensor field with modified index variances. Shape is identical to the input.

Return type:

numpy.ndarray

Raises:

ValueError – If input shapes are inconsistent or if necessary metrics are missing.

See also

raise_index, lower_index

Examples

Consider a tensor defined in spherical coordinates \((r,\theta,\phi)\) with a rank-2 structure. The metric tensor in these coordinates is diagonal:

\[g_{ij} = \text{diag}(1, r^2, r^2 \sin^2 \theta)\]

The inverse metric is:

\[g^{ij} = \text{diag}(1, 1/r^2, 1/(r^2 \sin^2 \theta))\]

We can lower or raise indices of a tensor as follows:

>>> import numpy as np
>>> from pymetric.differential_geometry.dense_utils import dense_adjust_tensor_signature
>>>
>>> # Coordinate values
>>> r = 2.0
>>> theta = np.pi / 4
>>> sin2 = np.sin(theta) ** 2
>>>
>>> # Define a rank-2 tensor with a single nonzero component T^{theta,phi}
>>> T = np.zeros((3, 3))
>>> T[1, 2] = 1.0
>>>
>>> # Diagonal metric and inverse metric
>>> g = np.array([1.0, r**2, r**2 * sin2])
>>> g_inv = np.array([1.0, 1/r**2, 1/(r**2 * sin2)])
>>>
>>> # Lower both indices (contravariant → covariant)
>>> dense_adjust_tensor_signature(
...     tensor_field=T,
...     indices=[0, 1],
...     tensor_signature=np.array([+1, +1]),
...     metric_field=g
... )
(array([[0., 0., 0.],
       [0., 0., 8.],
       [0., 0., 0.]]), array([-1, -1]))
>>> # Raise both indices back (covariant → contravariant)
>>> T_lowered = T
>>> dense_adjust_tensor_signature(
...     tensor_field=T_lowered,
...     indices=[0, 1],
...     tensor_signature=np.array([-1, -1]),
...     inverse_metric_field=g_inv
... )
(array([[0.   , 0.   , 0.   ],
       [0.   , 0.   , 0.125],
       [0.   , 0.   , 0.   ]]), array([1, 1]))