# Tensors

Tensors are extensions of multidimensional arrays with additional operations defined on them. Here we explain the basics of creating and working with tensors.

## Creating a tensor from an array

The tensor command converts a (multidimensional) array to a tensor object.

```M = ones(4,3,2); %<-- A 4 x 3 x 2 array.
X = tensor(M) %<-- Convert to a tensor object.
```
```X is a tensor of size 4 x 3 x 2
X(:,:,1) =
1     1     1
1     1     1
1     1     1
1     1     1
X(:,:,2) =
1     1     1
1     1     1
1     1     1
1     1     1
```

Optionally, you can specify a different shape for the tensor, so long as the input array has the right number of elements.

```X = tensor(M,[2 3 4]) %<-- M has 24 elements.
```
```X is a tensor of size 2 x 3 x 4
X(:,:,1) =
1     1     1
1     1     1
X(:,:,2) =
1     1     1
1     1     1
X(:,:,3) =
1     1     1
1     1     1
X(:,:,4) =
1     1     1
1     1     1
```

## Creating a one-dimensional tensor

The tensor class explicitly supports order-one tensors as well as trailing singleton dimensions, but the size must be explicit in the constructor. By default, a column array produces a 2-way tensor.

```X = tensor(rand(5,1)) %<-- Creates a 2-way tensor.
```
```X is a tensor of size 5 x 1
X(:,:) =
0.8147
0.9058
0.1270
0.9134
0.6324
```

This is fixed by specifying the size explicitly.

```X = tensor(rand(5,1),5) %<-- Creates a 1-way tensor.
```
```X is a tensor of size 5
X(:) =
0.0975
0.2785
0.5469
0.9575
0.9649
```

## Specifying trailing singleton dimensions in a tensor

Likewise, trailing singleton dimensions must be explictly specified.

```Y = tensor(rand(4,3,1)) %<-- Creates a 2-way tensor.
```
```Y is a tensor of size 4 x 3
Y(:,:) =
0.1576    0.8003    0.7922
0.9706    0.1419    0.9595
0.9572    0.4218    0.6557
0.4854    0.9157    0.0357
```
```Y = tensor(rand(4,3,1),[4 3 1]) %<-- Creates a 3-way tensor.
```
```Y is a tensor of size 4 x 3 x 1
Y(:,:,1) =
0.8491    0.7431    0.7060
0.9340    0.3922    0.0318
0.6787    0.6555    0.2769
0.7577    0.1712    0.0462
```

Unfortunately, the whos command does not report the size of 1D objects correctly (last checked for MATLAB 2006a).

```whos X Y %<-- Doesn't report the right size for X!
```
```  Name      Size             Bytes  Class     Attributes

X         1x1                400  tensor
Y         4x3x1              472  tensor

```

## The constituent parts of a tensor

```X = tenrand([4 3 2]); %<-- Create data.
X.data %<-- The array.
```
```ans(:,:,1) =

0.0971    0.9502    0.7655
0.8235    0.0344    0.7952
0.6948    0.4387    0.1869
0.3171    0.3816    0.4898

ans(:,:,2) =

0.4456    0.2760    0.1190
0.6463    0.6797    0.4984
0.7094    0.6551    0.9597
0.7547    0.1626    0.3404

```
```X.size %<-- The size.
```
```ans =

4     3     2

```

## Creating a tensor from its constituent parts

```Y = tensor(X.data,X.size) %<-- Copies X.
```
```Y is a tensor of size 4 x 3 x 2
Y(:,:,1) =
0.0971    0.9502    0.7655
0.8235    0.0344    0.7952
0.6948    0.4387    0.1869
0.3171    0.3816    0.4898
Y(:,:,2) =
0.4456    0.2760    0.1190
0.6463    0.6797    0.4984
0.7094    0.6551    0.9597
0.7547    0.1626    0.3404
```

## Creating an empty tensor

An empty constructor exists, primarily to support loading previously saved data in MAT-files.

```X = tensor %<-- Creates an empty tensor.
```
```X is a tensor of size [empty tensor]
X = []
```

## Use tenone to create a tensor of all ones

```X = tenones([3 4 2]) %<-- Creates a 3 x 4 x 2 tensor of ones.
```
```X is a tensor of size 3 x 4 x 2
X(:,:,1) =
1     1     1     1
1     1     1     1
1     1     1     1
X(:,:,2) =
1     1     1     1
1     1     1     1
1     1     1     1
```

## Use tenzeros to create a tensor of all zeros

```X = tenzeros([1 4 2]) %<-- Creates a 1 x 4 x 2 tensor of zeros.
```
```X is a tensor of size 1 x 4 x 2
X(:,:,1) =
0     0     0     0
X(:,:,2) =
0     0     0     0
```

## Use tenrand to create a random tensor

```X = tenrand([5 4 2]) %<-- Creates a random 5 x 4 x 2 tensor.
```
```X is a tensor of size 5 x 4 x 2
X(:,:,1) =
0.5853    0.6991    0.1493    0.2435
0.2238    0.8909    0.2575    0.9293
0.7513    0.9593    0.8407    0.3500
0.2551    0.5472    0.2543    0.1966
0.5060    0.1386    0.8143    0.2511
X(:,:,2) =
0.6160    0.5497    0.3804    0.7792
0.4733    0.9172    0.5678    0.9340
0.3517    0.2858    0.0759    0.1299
0.8308    0.7572    0.0540    0.5688
0.5853    0.7537    0.5308    0.4694
```

## Use squeeze to remove singleton dimensions from a tensor

```squeeze(Y) %<-- Removes singleton dimensions.
```
```ans is a tensor of size 4 x 3 x 2
ans(:,:,1) =
0.0971    0.9502    0.7655
0.8235    0.0344    0.7952
0.6948    0.4387    0.1869
0.3171    0.3816    0.4898
ans(:,:,2) =
0.4456    0.2760    0.1190
0.6463    0.6797    0.4984
0.7094    0.6551    0.9597
0.7547    0.1626    0.3404
```

## Use double to convert a tensor to a (multidimensional) array

```double(Y) %<-- Converts Y to a standard MATLAB array.
```
```ans(:,:,1) =

0.0971    0.9502    0.7655
0.8235    0.0344    0.7952
0.6948    0.4387    0.1869
0.3171    0.3816    0.4898

ans(:,:,2) =

0.4456    0.2760    0.1190
0.6463    0.6797    0.4984
0.7094    0.6551    0.9597
0.7547    0.1626    0.3404

```
```Y.data %<-- Same thing.
```
```ans(:,:,1) =

0.0971    0.9502    0.7655
0.8235    0.0344    0.7952
0.6948    0.4387    0.1869
0.3171    0.3816    0.4898

ans(:,:,2) =

0.4456    0.2760    0.1190
0.6463    0.6797    0.4984
0.7094    0.6551    0.9597
0.7547    0.1626    0.3404

```

## Use ndims and size to get the size of a tensor

```ndims(Y) %<-- Number of dimensions (or ways).
```
```ans =

3

```
```size(Y) %<-- Row vector with the sizes of all dimension.
```
```ans =

4     3     2

```
```size(Y,3) %<-- Size of a single dimension.
```
```ans =

2

```

## Subscripted reference for a tensor

```X = tenrand([3 4 2 1]); %<-- Create a 3 x 4 x 2 x 1 random tensor.
X(1,1,1,1) %<-- Extract a single element.
```
```ans =

0.0119

```

It is possible to extract a subtensor that contains a single element. Observe that singleton dimensions are not dropped unless they are specifically specified, e.g., as above.

```X(1,1,1,:) %<-- Produces a tensor of order 1 and size 1.
```
```ans is a tensor of size 1
ans(:) =
0.0119
```

In general, specified dimensions are dropped from the result. Here we specify the second and third dimension.

```X(:,1,1,:) %<-- Produces a tensor of size 3 x 1.
```
```ans is a tensor of size 3 x 1
ans(:,:) =
0.0119
0.3371
0.1622
```

Moreover, the subtensor is automatically renumbered/resized in the same way that MATLAB works for arrays except that singleton dimensions are handled explicitly.

```X(1:2,[2 4],1,:) %<-- Produces a tensor of size 2 x 2 x 1.
```
```ans is a tensor of size 2 x 2 x 1
ans(:,:,1) =
0.7943    0.6541
0.3112    0.6892
```

It's also possible to extract a list of elements by passing in an array of subscripts or a column array of linear indices.

```subs = [1,1,1,1; 3,4,2,1]; X(subs) %<-- Extract 2 values by subscript.
```
```ans =

0.0119
0.9619

```
```inds = [1; 24]; X(inds) %<-- Same thing with linear indices.
```
```ans =

0.0119
0.9619

```

The difference between extracting a subtensor and a list of linear indices is ambiguous for 1-dimensional tensors. We can specify 'extract' as a second argument whenever we are using a list of subscripts.

```X = tenrand(10); %<-- Create a random tensor.
X([1:6]') %<-- Extract a subtensor.
```
```ans is a tensor of size 6
ans(:) =
0.0046
0.7749
0.8173
0.8687
0.0844
0.3998
```
```X([1:6]','extract') %<-- Same thing *but* result is a vector.
```
```ans =

0.0046
0.7749
0.8173
0.8687
0.0844
0.3998

```

## Subscripted assignment for a tensor

We can assign a single element, an entire subtensor, or a list of values for a tensor.

```X = tenrand([3,4,2]); %<-- Create some data.
X(1,1,1) = 0 %<-- Replaces the (1,1,1) element.
```
```X is a tensor of size 3 x 4 x 2
X(:,:,1) =
0    0.1361    0.5499    0.6221
0.2638    0.8693    0.1450    0.3510
0.1455    0.5797    0.8530    0.5132
X(:,:,2) =
0.4018    0.1233    0.4173    0.9448
0.0760    0.1839    0.0497    0.4909
0.2399    0.2400    0.9027    0.4893
```
```X(1:2,1:2,1) = ones(2,2) %<-- Replaces a 2 x 2 subtensor.
```
```X is a tensor of size 3 x 4 x 2
X(:,:,1) =
1.0000    1.0000    0.5499    0.6221
1.0000    1.0000    0.1450    0.3510
0.1455    0.5797    0.8530    0.5132
X(:,:,2) =
0.4018    0.1233    0.4173    0.9448
0.0760    0.1839    0.0497    0.4909
0.2399    0.2400    0.9027    0.4893
```
```X([1 1 1;1 1 2]) = [5;7] %<-- Replaces the (1,1,1) and (1,1,2) elements.
```
```X is a tensor of size 3 x 4 x 2
X(:,:,1) =
5.0000    1.0000    0.5499    0.6221
1.0000    1.0000    0.1450    0.3510
0.1455    0.5797    0.8530    0.5132
X(:,:,2) =
7.0000    0.1233    0.4173    0.9448
0.0760    0.1839    0.0497    0.4909
0.2399    0.2400    0.9027    0.4893
```
```X([1;13]) = [5;7] %<-- Same as above using linear indices.
```
```X is a tensor of size 3 x 4 x 2
X(:,:,1) =
5.0000    1.0000    0.5499    0.6221
1.0000    1.0000    0.1450    0.3510
0.1455    0.5797    0.8530    0.5132
X(:,:,2) =
7.0000    0.1233    0.4173    0.9448
0.0760    0.1839    0.0497    0.4909
0.2399    0.2400    0.9027    0.4893
```

It is possible to grow the tensor automatically by assigning elements outside the original range of the tensor.

```X(1,1,3) = 1 %<-- Grows the size of the tensor.
```
```X is a tensor of size 3 x 4 x 3
X(:,:,1) =
5.0000    1.0000    0.5499    0.6221
1.0000    1.0000    0.1450    0.3510
0.1455    0.5797    0.8530    0.5132
X(:,:,2) =
7.0000    0.1233    0.4173    0.9448
0.0760    0.1839    0.0497    0.4909
0.2399    0.2400    0.9027    0.4893
X(:,:,3) =
1     0     0     0
0     0     0     0
0     0     0     0
```

## Using end for the last array index.

```X(end,end,end)  %<-- Same as X(3,4,3).
```
```ans =

0

```
```X(1,1,1:end-1)  %<-- Same as X(1,1,1:2).
```
```ans is a tensor of size 2
ans(:) =
5
7
```

It is also possible to use end to index past the end of an array.

```X(1,1,end+1) = 5 %<-- Same as X(1,1,4).
```
```X is a tensor of size 3 x 4 x 4
X(:,:,1) =
5.0000    1.0000    0.5499    0.6221
1.0000    1.0000    0.1450    0.3510
0.1455    0.5797    0.8530    0.5132
X(:,:,2) =
7.0000    0.1233    0.4173    0.9448
0.0760    0.1839    0.0497    0.4909
0.2399    0.2400    0.9027    0.4893
X(:,:,3) =
1     0     0     0
0     0     0     0
0     0     0     0
X(:,:,4) =
5     0     0     0
0     0     0     0
0     0     0     0
```

## Use find for subscripts of nonzero elements of a tensor

The find function returns a list of nonzero subscripts for a tensor. Note that differs from the standard version, which returns linear indices.

```X = tensor(floor(3*rand(2,2,2))) %<-- Generate some data.
```
```X is a tensor of size 2 x 2 x 2
X(:,:,1) =
1     1
2     0
X(:,:,2) =
2     0
1     1
```
```[S,V] = find(X) %<-- Find all the nonzero subscripts and values.
```
```S =

1     1     1
2     1     1
1     2     1
1     1     2
2     1     2
2     2     2

V =

1
2
1
2
1
1

```
```S = find(X >= 2) %<-- Find subscripts of values >= 2.
```
```S =

2     1     1
1     1     2

```
```V = X(S) %<-- Extract the corresponding values from X.
```
```V =

2
2

```

## Computing the Frobenius norm of a tensor

norm computes the Frobenius norm of a tensor. This corresponds to the Euclidean norm of the vectorized tensor.

```T = tensor(randn(2,3,3));
norm(T)
```
```ans =

4.9219

```

## Using reshape to rearrange elements in a tensor

reshape reshapes a tensor into a given size array. The total number of elements in the tensor cannot change.

```X = tensor(randi(10,3,2,3));
reshape(X,[3,3,2]);
```

## Basic operations (plus, minus, and, or, etc.) on a tensor

The tensor object supports many basic operations, illustrated here.

```A = tensor(floor(3*rand(2,3,2)))
B = tensor(floor(3*rand(2,3,2)))
```
```A is a tensor of size 2 x 3 x 2
A(:,:,1) =
1     2     1
1     1     2
A(:,:,2) =
2     1     0
1     1     0
B is a tensor of size 2 x 3 x 2
B(:,:,1) =
1     2     0
0     0     0
B(:,:,2) =
0     0     1
1     2     0
```
```A & B %<-- Calls and.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1   1   0
0   0   0
ans(:,:,2) =
0   0   0
1   1   0
```
```A | B %<-- Calls or.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1   1   1
1   1   1
ans(:,:,2) =
1   1   1
1   1   0
```
```xor(A,B) %<-- Calls xor.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0   0   1
1   1   1
ans(:,:,2) =
1   1   1
0   0   0
```
```A==B %<-- Calls eq.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1   1   0
0   0   0
ans(:,:,2) =
0   0   0
1   0   1
```
```A~=B %<-- Calls neq.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0   0   1
1   1   1
ans(:,:,2) =
1   1   1
0   1   0
```
```A>B %<-- Calls gt.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0   0   1
1   1   1
ans(:,:,2) =
1   1   0
0   0   0
```
```A>=B %<-- Calls ge.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1   1   1
1   1   1
ans(:,:,2) =
1   1   0
1   0   1
```
```A<B %<-- Calls lt.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0   0   0
0   0   0
ans(:,:,2) =
0   0   1
0   1   0
```
```A<=B %<-- Calls le.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1   1   0
0   0   0
ans(:,:,2) =
0   0   1
1   1   1
```
```~A %<-- Calls not.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0   0   0
0   0   0
ans(:,:,2) =
0   0   1
0   0   1
```
```+A %<-- Calls uplus.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     2     1
1     1     2
ans(:,:,2) =
2     1     0
1     1     0
```
```-A %<-- Calls uminus.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
-1    -2    -1
-1    -1    -2
ans(:,:,2) =
-2    -1     0
-1    -1     0
```
```A+B %<-- Calls plus.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
2     4     1
1     1     2
ans(:,:,2) =
2     1     1
2     3     0
```
```A-B %<-- Calls minus.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0     0     1
1     1     2
ans(:,:,2) =
2     1    -1
0    -1     0
```
```A.*B %<-- Calls times.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     4     0
0     0     0
ans(:,:,2) =
0     0     0
1     2     0
```
```5*A %<-- Calls mtimes.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
5    10     5
5     5    10
ans(:,:,2) =
10     5     0
5     5     0
```
```A.^B %<-- Calls power.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     4     1
1     1     1
ans(:,:,2) =
1     1     0
1     1     1
```
```A.^2 %<-- Calls power.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     4     1
1     1     4
ans(:,:,2) =
4     1     0
1     1     0
```
```A.\B %<-- Calls ldivide.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     1     0
0     0     0
ans(:,:,2) =
0     0   Inf
1     2   NaN
```
```A./2 %<-- Calls rdivide.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
0.5000    1.0000    0.5000
0.5000    0.5000    1.0000
ans(:,:,2) =
1.0000    0.5000         0
0.5000    0.5000         0
```
```A./B %<-- Calls rdivide (but beware divides by zero!)
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     1   Inf
Inf   Inf   Inf
ans(:,:,2) =
Inf       Inf         0
1.0000    0.5000       NaN
```

## Using tenfun for elementwise operations on one or more tensors

The function tenfun applies a specified function to a number of tensors. This can be used for any function that is not predefined for tensors.

```tenfun(@(x)(x+1),A) %<-- Increment every element of A by one.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
2     3     2
2     2     3
ans(:,:,2) =
3     2     1
2     2     1
```
```tenfun(@max,A,B) %<-- Max of A and B, elementwise.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     2     1
1     1     2
ans(:,:,2) =
2     1     1
1     2     0
```
```C = tensor(floor(5*rand(2,3,2))) %<-- Create another tensor.
tenfun(@median,A,B,C) %<-- Elementwise means for A, B, and C.
```
```C is a tensor of size 2 x 3 x 2
C(:,:,1) =
4     2     1
4     0     2
C(:,:,2) =
2     3     1
1     3     0
ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
1     2     1
1     0     2
ans(:,:,2) =
2     1     1
1     2     0
```

## Use permute to reorder the modes of a tensor

```X = tensor(1:24,[3 4 2]) %<-- Create a tensor.
```
```X is a tensor of size 3 x 4 x 2
X(:,:,1) =
1     4     7    10
2     5     8    11
3     6     9    12
X(:,:,2) =
13    16    19    22
14    17    20    23
15    18    21    24
```
```permute(X,[3 2 1]) %<-- Reverse the modes.
```
```ans is a tensor of size 2 x 4 x 3
ans(:,:,1) =
1     4     7    10
13    16    19    22
ans(:,:,2) =
2     5     8    11
14    17    20    23
ans(:,:,3) =
3     6     9    12
15    18    21    24
```

Permuting a 1-dimensional tensor works correctly.

```X = tensor(1:4,4); %<-- Create a 1-way tensor.
permute(X,1) %<-- Call permute with *only* one dimension.
```
```ans is a tensor of size 4
ans(:) =
1
2
3
4
```

## Symmetrizing and checking for symmetry in a tensor

A tensor can be symmetrized in a collection of modes with the command symmetrize. The new, symmetric tensor is formed by averaging over all elements in the tensor which are required to be equal.

```W = tensor(rand(4,4,4));
Y=symmetrize(X);
```

A second argument can also be passed to symmetrize which specifies an array of modes with respect to which the tensor should be symmetrized.

```X = tensor(rand(3,2,3));
Z = symmetrize(X,[1,3]);
```

Additionally, one can check for symmetry in tensors with the issymmetric function. Similar to symmetrize, a collection of modes can be passed as a second argument.

```issymmetric(Y)
issymmetric(Z,[1,3])
```
```ans =

logical

1

ans =

logical

1

```

## Displaying a tensor

The function disp can be used to display a tensor and correctly displays very small and large elements.

```X = tensor(1:24,[3 4 2]); %<-- Create a 3 x 4 x 2 tensor.
X(:,:,1) = X(:,:,1) * 1e15; %<-- Make the first slice very large.
X(:,:,2) = X(:,:,2) * 1e-15; %<-- Make the second slice very small.
disp(X)
```
```ans is a tensor of size 3 x 4 x 2
ans(:,:,1) =
1.0e+16 *
0.1000    0.4000    0.7000    1.0000
0.2000    0.5000    0.8000    1.1000
0.3000    0.6000    0.9000    1.2000
ans(:,:,2) =
1.0e-13 *
0.1300    0.1600    0.1900    0.2200
0.1400    0.1700    0.2000    0.2300
0.1500    0.1800    0.2100    0.2400
```