differential_geometry.dense_utils.dense_contract_with_metric#

differential_geometry.dense_utils.dense_contract_with_metric(tensor_field: ndarray, metric_field: ndarray, index: int, rank: int, out: ndarray | None = None, **kwargs) ndarray[source]#

Contract a tensor index with the provided metric tensor.

This function contracts one of the tensor indices of the input tensor field with the supplied metric. If the metric is diagonal (1D or (…, N)), an optimized elementwise contraction is used.

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).

  • metric_field (numpy.ndarray) –

    Metric tensor. Must be either:

    • Full matrix of shape (..., N, N), or

    • Diagonal-only array of shape (..., N).

  • index (int) – Index (among the trailing rank tensor indices) to contract.

  • rank (int) – The number of trailing axes in tensor_field that represent tensor indices.

  • 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 low-level routines (e.g., optimize=True for numpy.einsum()).

Returns:

A new tensor field with the specified index contracted, replacing that axis with the contracted result. The shape of the output reflects broadcasting between tensor_field and metric_field.

Return type:

numpy.ndarray

Raises:

ValueError – If shapes are incompatible or inputs are invalid.

Examples

Contract a rank-1 tensor field with a full metric (identity):

>>> import numpy as np
>>> T = np.random.rand(5, 5, 3)  # shape: (grid_x, grid_y, vector_index)
>>> g = np.eye(3)[np.newaxis, np.newaxis, :, :] * np.ones((5, 5, 1, 1))  # shape: (5, 5, 3, 3)
>>> result = dense_contract_with_metric(T, g, index=0, rank=1)
>>> result.shape
(5, 5, 3)

Contract with a diagonal metric:

>>> g_diag = np.array([1.0, 2.0, 3.0])[np.newaxis, np.newaxis, :] * np.ones((5, 5, 1))  # shape: (5, 5, 3)
>>> result = dense_contract_with_metric(T, g_diag, index=0, rank=1)
>>> result.shape
(5, 5, 3)

Use broadcasting between mismatched shapes:

>>> T = np.random.rand(5, 1, 3)                 # shape: (5, 3)
>>> g = np.ones((5, 7, 3, 3))                # shape: (5, 7, 3, 3)
>>> result = dense_contract_with_metric(T, g, index=0, rank=1)
>>> result.shape
(5, 7, 3)
>>> g_diag = np.ones((5, 7, 3))              # diagonal version
>>> result = dense_contract_with_metric(T, g_diag, index=0, rank=1)
>>> result.shape
(5, 7, 3)

Notes

Internally, this function dispatches to one of two lower-level routines:

  • _dense_contract_index_with_metric(): used for full metric tensors of shape (..., N, N). This uses a batched contraction implemented via np.einsum with automatic broadcasting.

  • _dense_contract_index_with_diagonal_metric(): used for diagonal metrics with shape (..., N). This uses elementwise multiplication and is significantly faster and lighter on memory.

These helper functions follow NumPy-style broadcasting and allow tensor fields and metric tensors to differ in shape along non-contracted dimensions, as long as they are broadcast-compatible. No shape validation is performed beyond what NumPy itself requires.

See also

numpy.einsum

Batched Einstein summation (used internally).

numpy.multiply

Elementwise multiplication (used for diagonal metric contraction).