N-dim array expressions

Array expressions are expressions representing N-dimensional arrays, without evaluating them. These expressions represent in a certain way abstract syntax trees of operations on N-dimensional arrays.

Every N-dimensional array operator has a corresponding array expression object.

Table of correspondences:

Array operator

Array expression operator

tensorproduct

ArrayTensorProduct

tensorcontraction

ArrayContraction

tensordiagonal

ArrayDiagonal

permutedims

PermuteDims

Examples

ArraySymbol objects are the N-dimensional equivalent of MatrixSymbol objects in the matrix module:

>>> from sympy.tensor.array.expressions import ArraySymbol
>>> from sympy.abc import i, j, k
>>> A = ArraySymbol("A", (3, 2, 4))
>>> A.shape
(3, 2, 4)
>>> A[i, j, k]
A[i, j, k]
>>> A.as_explicit()
[[[A[0, 0, 0], A[0, 0, 1], A[0, 0, 2], A[0, 0, 3]],
  [A[0, 1, 0], A[0, 1, 1], A[0, 1, 2], A[0, 1, 3]]],
 [[A[1, 0, 0], A[1, 0, 1], A[1, 0, 2], A[1, 0, 3]],
  [A[1, 1, 0], A[1, 1, 1], A[1, 1, 2], A[1, 1, 3]]],
 [[A[2, 0, 0], A[2, 0, 1], A[2, 0, 2], A[2, 0, 3]],
  [A[2, 1, 0], A[2, 1, 1], A[2, 1, 2], A[2, 1, 3]]]]

Component-explicit arrays can be added inside array expressions:

>>> from sympy import Array
>>> from sympy import tensorproduct
>>> from sympy.tensor.array.expressions import ArrayTensorProduct
>>> a = Array([1, 2, 3])
>>> b = Array([i, j, k])
>>> expr = ArrayTensorProduct(a, b, b)
>>> expr
ArrayTensorProduct([1, 2, 3], [i, j, k], [i, j, k])
>>> expr.as_explicit() == tensorproduct(a, b, b)
True

Constructing array expressions from index-explicit forms

Array expressions are index-implicit. This means they do not use any indices to represent array operations. The function convert_indexed_to_array( ... ) may be used to convert index-explicit expressions to array expressions. It takes as input two parameters: the index-explicit expression and the order of the indices:

>>> from sympy.tensor.array.expressions import convert_indexed_to_array
>>> from sympy import Sum
>>> A = ArraySymbol("A", (3, 3))
>>> B = ArraySymbol("B", (3, 3))
>>> convert_indexed_to_array(A[i, j], [i, j])
A
>>> convert_indexed_to_array(A[i, j], [j, i])
PermuteDims(A, (0 1))
>>> convert_indexed_to_array(A[i, j] + B[j, i], [i, j])
ArrayAdd(A, PermuteDims(B, (0 1)))
>>> convert_indexed_to_array(Sum(A[i, j]*B[j, k], (j, 0, 2)), [i, k])
ArrayContraction(ArrayTensorProduct(A, B), (1, 2))

The diagonal of a matrix in the array expression form:

>>> convert_indexed_to_array(A[i, i], [i])
ArrayDiagonal(A, (0, 1))

The trace of a matrix in the array expression form:

>>> convert_indexed_to_array(Sum(A[i, i], (i, 0, 2)), [i])
ArrayContraction(A, (0, 1))

Compatibility with matrices

Array expressions can be mixed with objects from the matrix module:

>>> from sympy import MatrixSymbol
>>> from sympy.tensor.array.expressions import ArrayContraction
>>> M = MatrixSymbol("M", 3, 3)
>>> N = MatrixSymbol("N", 3, 3)

Express the matrix product in the array expression form:

>>> from sympy.tensor.array.expressions import convert_matrix_to_array
>>> expr = convert_matrix_to_array(M*N)
>>> expr
ArrayContraction(ArrayTensorProduct(M, N), (1, 2))

The expression can be converted back to matrix form:

>>> from sympy.tensor.array.expressions import convert_array_to_matrix
>>> convert_array_to_matrix(expr)
M*N

Add a second contraction on the remaining axes in order to get the trace of \(M \cdot N\):

>>> expr_tr = ArrayContraction(expr, (0, 1))
>>> expr_tr
ArrayContraction(ArrayContraction(ArrayTensorProduct(M, N), (1, 2)), (0, 1))

Flatten the expression by calling .doit() and remove the nested array contraction operations:

>>> expr_tr.doit()
ArrayContraction(ArrayTensorProduct(M, N), (0, 3), (1, 2))

Get the explicit form of the array expression:

>>> expr.as_explicit()
[[M[0, 0]*N[0, 0] + M[0, 1]*N[1, 0] + M[0, 2]*N[2, 0], M[0, 0]*N[0, 1] + M[0, 1]*N[1, 1] + M[0, 2]*N[2, 1], M[0, 0]*N[0, 2] + M[0, 1]*N[1, 2] + M[0, 2]*N[2, 2]],
 [M[1, 0]*N[0, 0] + M[1, 1]*N[1, 0] + M[1, 2]*N[2, 0], M[1, 0]*N[0, 1] + M[1, 1]*N[1, 1] + M[1, 2]*N[2, 1], M[1, 0]*N[0, 2] + M[1, 1]*N[1, 2] + M[1, 2]*N[2, 2]],
 [M[2, 0]*N[0, 0] + M[2, 1]*N[1, 0] + M[2, 2]*N[2, 0], M[2, 0]*N[0, 1] + M[2, 1]*N[1, 1] + M[2, 2]*N[2, 1], M[2, 0]*N[0, 2] + M[2, 1]*N[1, 2] + M[2, 2]*N[2, 2]]]

Express the trace of a matrix:

>>> from sympy import Trace
>>> convert_matrix_to_array(Trace(M))
ArrayContraction(M, (0, 1))
>>> convert_matrix_to_array(Trace(M*N))
ArrayContraction(ArrayTensorProduct(M, N), (0, 3), (1, 2))

Express the transposition of a matrix (will be expressed as a permutation of the axes:

>>> convert_matrix_to_array(M.T)
PermuteDims(M, (0 1))

Compute the derivative array expressions:

>>> from sympy.tensor.array.expressions import array_derive
>>> d = array_derive(M, M)
>>> d
PermuteDims(ArrayTensorProduct(I, I), (3)(1 2))

Verify that the derivative corresponds to the form computed with explicit matrices:

>>> d.as_explicit()
[[[[1, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 1]]]]
>>> Me = M.as_explicit()
>>> Me.diff(Me)
[[[[1, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 1, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 1], [0, 0, 0], [0, 0, 0]]], [[[0, 0, 0], [1, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]]], [[[0, 0, 0], [0, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 1, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 1]]]]
class sympy.tensor.array.expressions.ArrayTensorProduct(*args, **kwargs)[source]

Class to represent the tensor product of array-like objects.

class sympy.tensor.array.expressions.ArrayContraction(
expr,
*contraction_indices,
**kwargs,
)[source]

Contraction operation of array axes.

Explanation

In a 2-dimensional array it returns the trace, this looks like the operation:

\(A_{ij} \Longrightarrow \sum_{i} A_{ii}\)

Examples

>>> from sympy import MatrixSymbol
>>> from sympy.tensor.array.expressions import ArrayContraction, ArrayTensorProduct
>>> M = MatrixSymbol('M', 3, 3)
>>> N = MatrixSymbol('N', 3, 3)
>>> ArrayContraction(M, (0, 1))
ArrayContraction(M, (0, 1))

We can define a matrix multiplication equivalent operation:

>>> expr = ArrayContraction(ArrayTensorProduct(M, N), (1, 2))
>>> expr
ArrayContraction(ArrayTensorProduct(M, N), (1, 2))

Indeed, given two matrices \(M\) and \(N\), the contraction of the second axis of \(M\) with the first of \(N\), here represented as the tuple (1, 2), is equivalent to the matrix multiplication between \(M\) and \(N\).

This can be verified with the proper conversion function:

>>> from sympy.tensor.array.expressions import convert_array_to_matrix
>>> convert_array_to_matrix(expr)
M*N
class sympy.tensor.array.expressions.ArrayDiagonal(expr, *diagonal_indices, **kwargs)[source]

Class to represent the diagonal operator.

Explanation

In a 2-dimensional array it returns the diagonal, this looks like the operation:

\(A_{ij} \Longrightarrow A_{ii}\)

The diagonal over axes 1 and 2 (the second and third) of the tensor product of two 2-dimensional arrays \(A \otimes B\) is

\(\Big[ A_{ab} B_{cd} \Big]_{abcd} \Longrightarrow \Big[ A_{ai} B_{id} \Big]_{adi}\)

In this last example the array expression has been reduced from 4-dimensional to 3-dimensional. Notice that no contraction has occurred, rather there is a new index \(i\) for the diagonal, contraction would have reduced the array to 2 dimensions.

Notice that the diagonalized out dimensions are added as new dimensions at the end of the indices.

class sympy.tensor.array.expressions.PermuteDims(
expr,
permutation=None,
index_order_old=None,
index_order_new=None,
**kwargs,
)[source]

Class to represent permutation of axes of arrays.

Examples

>>> from sympy.tensor.array import permutedims
>>> from sympy import MatrixSymbol
>>> M = MatrixSymbol("M", 3, 3)
>>> cg = permutedims(M, [1, 0])

The object cg represents the transposition of M, as the permutation [1, 0] will act on its indices by switching them:

\(M_{ij} \Rightarrow M_{ji}\)

This is evident when transforming back to matrix form:

>>> from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
>>> convert_array_to_matrix(cg)
M.T
>>> N = MatrixSymbol("N", 3, 2)
>>> cg = permutedims(N, [1, 0])
>>> cg.shape
(2, 3)

There are optional parameters that can be used as alternative to the permutation:

>>> from sympy.tensor.array.expressions import ArraySymbol, PermuteDims
>>> M = ArraySymbol("M", (1, 2, 3, 4, 5))
>>> expr = PermuteDims(M, index_order_old="ijklm", index_order_new="kijml")
>>> expr
PermuteDims(M, (0 2 1)(3 4))
>>> expr.shape
(3, 1, 2, 5, 4)

Permutations of tensor products are simplified in order to achieve a standard form:

>>> from sympy.tensor.array import tensorproduct
>>> M = MatrixSymbol("M", 4, 5)
>>> tp = tensorproduct(M, N)
>>> tp.shape
(4, 5, 3, 2)
>>> perm1 = permutedims(tp, [2, 3, 1, 0])

The args (M, N) have been sorted and the permutation has been simplified, the expression is equivalent:

>>> perm1.expr.args
(N, M)
>>> perm1.shape
(3, 2, 5, 4)
>>> perm1.permutation
(2 3)

The permutation in its array form has been simplified from [2, 3, 1, 0] to [0, 1, 3, 2], as the arguments of the tensor product \(M\) and \(N\) have been switched:

>>> perm1.permutation.array_form
[0, 1, 3, 2]

We can nest a second permutation:

>>> perm2 = permutedims(perm1, [1, 0, 2, 3])
>>> perm2.shape
(2, 3, 5, 4)
>>> perm2.permutation.array_form
[1, 0, 3, 2]