Tensors

Tensors are mathematical objects that give generalizations of vectors and matrices. In the Wolfram System, a tensor is represented as a set of lists, nested to a certain number of levels. The nesting level is the rank of the tensor.

Interpretations of nested lists.

A tensor of rank k is essentially a k-dimensional table of values. To be a true rank k tensor, it must be possible to arrange the elements in the table in a k-dimensional cuboidal array. There can be no holes or protrusions in the cuboid.

The indices that specify a particular element in the tensor correspond to the coordinates in the cuboid. The dimensions of the tensor correspond to the side lengths of the cuboid.

One simple way that a rank k tensor can arise is in giving a table of values for a function of k variables. In physics, the tensors that occur typically have indices which run over the possible directions in space or spacetime. Notice, however, that there is no built-in notion of covariant and contravariant tensor indices in the Wolfram System: you have to set these up explicitly using metric tensors.

Functions for creating and testing the structure of tensors.

Here is a 2×3×2 tensor:

t=Table[i1+i2 i3,{i1,2},{i2,3},{i3,2}]

This is another way to produce the same tensor:

Array[(#1+#2#3)&,{2,3,2}]

(RefLink[MatrixForm, paclet:ref/MatrixForm]) displays the elements of the tensor in a two-dimensional array. You can think of the array as being a 2×3 matrix of column vectors:

MatrixForm[t]

(RefLink[Dimensions, paclet:ref/Dimensions]) gives the dimensions of the tensor:

Dimensions[t]

Here is the 111 element of the tensor:

t[[1,1,1]]

(RefLink[ArrayDepth, paclet:ref/ArrayDepth]) gives the rank of the tensor:

ArrayDepth[t]

The rank of a tensor is equal to the number of indices needed to specify each element. You can pick out subtensors by using a smaller number of indices.

Tensor manipulation operations.

You can think of a rank k tensor as having k "slots" into which you insert indices. Applying (RefLink[Transpose, paclet:ref/Transpose]) is effectively a way of reordering these slots. If you think of the elements of a tensor as forming a k-dimensional cuboid, you can view (RefLink[Transpose, paclet:ref/Transpose]) as effectively rotating (and possibly reflecting) the cuboid.

In the most general case, (RefLink[Transpose, paclet:ref/Transpose]) allows you to specify an arbitrary reordering to apply to the indices of a tensor. The function (RefLink[Transpose, paclet:ref/Transpose])[T,{(Subscript[p, 1]),(Subscript[p, 2]),…,(Subscript[p, k])}] gives you a new tensor T' such that the value of (Subscript[T', (Subscript[i, 1]) (Subscript[i, 2]) … (Subscript[i, k])]) is given by (Subscript[T, (Subscript[i, (Subscript[p, 1])]) (Subscript[i, (Subscript[p, 2])]) … (Subscript[i, (Subscript[p, k])])]).

If you originally had an (Subscript[n, (Subscript[p, 1])])×(Subscript[n, (Subscript[p, 2])])×…×(Subscript[n, (Subscript[p, k])]) tensor, then by applying (RefLink[Transpose, paclet:ref/Transpose]), you will get an  (Subscript[n, 1])×(Subscript[n, 2])×…×(Subscript[n, k]) tensor.

Here is a matrix that you can also think of as a 2×3 tensor:

m={{a,b,c},{ap,bp,cp}}

Applying (RefLink[Transpose, paclet:ref/Transpose]) gives you a 3×2 tensor. (RefLink[Transpose, paclet:ref/Transpose]) effectively interchanges the two "slots" for tensor indices:

mt=Transpose[m]

The element m[[2,3]] in the original tensor becomes the element m[[3,2]] in the transposed tensor:

{m[[2,3]],mt[[3,2]]}

This produces a 2×3×1×2 tensor:

t=Array[a,{2,3,1,2}]

This transposes the first two levels of t:

tt1=Transpose[t]

The result is a 3×2×1×2 tensor:

Dimensions[tt1]

If you have a tensor that contains lists of the same length at different levels, then you can use (RefLink[Transpose, paclet:ref/Transpose]) to effectively collapse different levels.

This collapses all three levels, giving a list of the elements on the "main diagonal":

Transpose[Array[a,{3,3,3}],{1,1,1}]

This collapses only the first two levels:

Transpose[Array[a,{2,2,2}],{1,1}]

You can also use (RefLink[Tr, paclet:ref/Tr]) to extract diagonal elements of a tensor.

This forms the ordinary trace of a rank 3 tensor:

Tr[Array[a,{3,3,3}]]

Here is a generalized trace, with elements combined into a list:

Tr[Array[a,{3,3,3}],List]

This combines diagonal elements only down to level 2:

Tr[Array[a,{3,3,3}],List,2]

Outer products, and their generalizations, are a way of building higher-rank tensors from lower-rank ones. Outer products are also sometimes known as direct, tensor, or Kronecker products.

From a structural point of view, the tensor you get from (RefLink[Outer, paclet:ref/Outer])[f,t,u] has a copy of the structure of u inserted at the "position" of each element in t. The elements in the resulting structure are obtained by combining elements of t and u using the function f.

This gives the "outer f" of two vectors. The result is a matrix:

Outer[f,{a,b},{ap,bp}]

If you take the "outer f" of a length 3 vector with a length 2 vector, you get a 3×2 matrix:

Outer[f,{a,b,c},{ap,bp}]

The result of taking the "outer f" of a 2×2 matrix and a length 3 vector is a 2×2×3 tensor:

Outer[f,{{m11,m12},{m21,m22}},{a,b,c}]

Here are the dimensions of the tensor:

Dimensions[%]

If you take the generalized outer product of an (Subscript[m, 1])×(Subscript[m, 2])×…×(Subscript[m, r]) tensor and an (Subscript[n, 1])×(Subscript[n, 2])×…×(Subscript[n, s]) tensor, you get an (Subscript[m, 1])×…×(Subscript[m, r])×(Subscript[n, 1])×…×(Subscript[n, s]) tensor. If the original tensors have ranks r and s, your result will be a rank r+s tensor.

In terms of indices, the result of applying (RefLink[Outer, paclet:ref/Outer]) to two tensors (Subscript[T, (Subscript[i, 1]) (Subscript[i, 2]) … (Subscript[i, r])]) and (Subscript[U, (Subscript[j, 1]) (Subscript[j, 2]) … (Subscript[j, s])]) is the tensor (Subscript[V, (Subscript[i, 1]) (Subscript[i, 2]) … (Subscript[i, r])(Subscript[j, 1]) (Subscript[j, 2]) … (Subscript[j, s])]) with elements f[(Subscript[T, (Subscript[i, 1]) (Subscript[i, 2]) … (Subscript[i, r])]),(Subscript[U, (Subscript[j, 1]) (Subscript[j, 2]) … (Subscript[j, s])])].

In doing standard tensor calculations, the most common function f to use in (RefLink[Outer, paclet:ref/Outer]) is (RefLink[Times, paclet:ref/Times]), corresponding to the standard outer product.

Particularly in doing combinatorial calculations, however, it is often convenient to take f to be (RefLink[List, paclet:ref/List]). Using (RefLink[Outer, paclet:ref/Outer]), you can then get combinations of all possible elements in one tensor, with all possible elements in the other.

In constructing (RefLink[Outer, paclet:ref/Outer])[f,t,u] you effectively insert a copy of u at every point in t. To form (RefLink[Inner, paclet:ref/Inner])[f,t,u], you effectively combine and collapse the last dimension of t and the first dimension of u. The idea is to take an (Subscript[m, 1])×(Subscript[m, 2])×…×(Subscript[m, r]) tensor and an (Subscript[n, 1])×(Subscript[n, 2])×…×(Subscript[n, s]) tensor, with (Subscript[m, r])=(Subscript[n, 1]), and get an (Subscript[m, 1])×(Subscript[m, 2])×…×(Subscript[m, r-1])×(Subscript[n, 2])×…×(Subscript[n, s]) tensor as the result.

The simplest examples are with vectors. If you apply (RefLink[Inner, paclet:ref/Inner]) to two vectors of equal length, you get a scalar. (RefLink[Inner, paclet:ref/Inner])[f,(Subscript[v, 1]),(Subscript[v, 2]),g] gives a generalization of the usual scalar product, with f playing the role of multiplication, and g playing the role of addition.

This gives a generalization of the standard scalar product of two vectors:

Inner[f,{a,b,c},{ap,bp,cp},g]

This gives a generalization of a matrix product:

Inner[f,{{1,2},{3,4}},{{a,b},{c,d}},g]

Here is a 3×2×2 tensor:

a=Array[1&,{3,2,2}]

Here is a 2×3×1 tensor:

b=Array[2&,{2,3,1}]

This gives a 3×2×3×1 tensor:

a.b

Here are the dimensions of the result:

Dimensions[%]

You can think of (RefLink[Inner, paclet:ref/Inner]) as performing a "contraction" of the last index of one tensor with the first index of another. If you want to perform contractions across other pairs of indices, you can do so by first transposing the appropriate indices into the first or last position, then applying (RefLink[Inner, paclet:ref/Inner]), and then transposing the result back.

In many applications of tensors, you need to insert signs to implement antisymmetry. The function (RefLink[Signature, paclet:ref/Signature])[{(Subscript[i, 1]),(Subscript[i, 2]),…}], which gives the signature of a permutation, is often useful for this purpose.

Treating only certain sublists in tensors as separate elements.

Here every single symbol is treated as a separate element:

Outer[f,{{i,j},{k,l}},{x,y}]

But here only sublists at level 1 are treated as separate elements:

Outer[f,{{i,j},{k,l}},{x,y},1]

Flattening block tensors.

Here is a block matrix (a matrix of matrices that can be viewed as blocks that fit edge to edge within a larger matrix):

TableForm[{{ {{1,2},{4,5}}, {{3},{6}} },{ {{7,8}}, {{9}} }}]

Here is the matrix formed by piecing the blocks together:

TableForm[ArrayFlatten[%]]