# Tensors

Tensors are extensions of multidimensional arrays with additional operations defined on them. Here we explain the tensor class, for storing dense tensors, and the basics of creating and working with tensors. The tensor class is best described in the following reference:

## 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.3418
0.4071
0.8515
0.5177
0.1050
```

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.8357
0.7066
0.7186
0.3915
0.4511
```

## 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.5728    0.3852    0.5103
0.1321    0.6371    0.5774
0.9553    0.1920    0.5068
0.6244    0.1059    0.3371
```
```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.5257    0.0709    0.9211
0.3287    0.9804    0.1935
0.2397    0.4832    0.8640
0.2543    0.6887    0.0432
```

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.3450    0.7667    0.4278
0.3065    0.7736    0.3163
0.1382    0.4086    0.6421
0.2251    0.8200    0.3803
ans(:,:,2) =
1.0000    0.6801    0.6244
0.3067    0.3762    0.9555
0.6011    0.7601    0.6332
0.4173    0.1904    0.4562
```
```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.3450    0.7667    0.4278
0.3065    0.7736    0.3163
0.1382    0.4086    0.6421
0.2251    0.8200    0.3803
Y(:,:,2) =
1.0000    0.6801    0.6244
0.3067    0.3762    0.9555
0.6011    0.7601    0.6332
0.4173    0.1904    0.4562
```

## 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.7608    0.5490    0.7244    0.0279
0.3722    0.4983    0.1113    0.9805
0.4261    0.2323    0.8601    0.3572
0.1441    0.2274    0.2960    0.2537
0.7464    0.9448    0.8614    0.8004
X(:,:,2) =
0.3722    0.4194    0.2726    0.8014
0.5129    0.8108    0.6182    0.6885
0.3167    0.1195    0.8916    0.0464
0.9612    0.4022    0.9491    0.2689
0.8488    0.5775    0.7752    0.5743
```

## 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.3450    0.7667    0.4278
0.3065    0.7736    0.3163
0.1382    0.4086    0.6421
0.2251    0.8200    0.3803
ans(:,:,2) =
1.0000    0.6801    0.6244
0.3067    0.3762    0.9555
0.6011    0.7601    0.6332
0.4173    0.1904    0.4562
```

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

```double(Y) %<-- Converts Y to a standard MATLAB array.
```
```ans(:,:,1) =
0.3450    0.7667    0.4278
0.3065    0.7736    0.3163
0.1382    0.4086    0.6421
0.2251    0.8200    0.3803
ans(:,:,2) =
1.0000    0.6801    0.6244
0.3067    0.3762    0.9555
0.6011    0.7601    0.6332
0.4173    0.1904    0.4562
```
```Y.data %<-- Same thing.
```
```ans(:,:,1) =
0.3450    0.7667    0.4278
0.3065    0.7736    0.3163
0.1382    0.4086    0.6421
0.2251    0.8200    0.3803
ans(:,:,2) =
1.0000    0.6801    0.6244
0.3067    0.3762    0.9555
0.6011    0.7601    0.6332
0.4173    0.1904    0.4562
```

## 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.2993
```

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.2993
```

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.2993
0.1279
0.5671
```

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.1216    0.0483
0.1958    0.4250
```

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.2993
0.9616
```
```inds = [1; 24]; X(inds) %<-- Same thing with linear indices.
```
```ans =
0.2993
0.9616
```

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.9403
0.0922
0.4217
0.2784
0.2808
0.3557
```
```X([1:6]','extract') %<-- Same thing *but* result is a vector.
```
```ans =
0.9403
0.0922
0.4217
0.2784
0.2808
0.3557
```

## 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.5306    0.1162    0.5558
0.0012    0.1578    0.9295    0.7267
0.2656    0.7179    0.3484    0.5984
X(:,:,2) =
0.7526    0.4103    0.9115    0.7983
0.0381    0.0838    0.2455    0.4370
0.4041    0.5799    0.4866    0.7407
```
```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.1162    0.5558
1.0000    1.0000    0.9295    0.7267
0.2656    0.7179    0.3484    0.5984
X(:,:,2) =
0.7526    0.4103    0.9115    0.7983
0.0381    0.0838    0.2455    0.4370
0.4041    0.5799    0.4866    0.7407
```
```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.1162    0.5558
1.0000    1.0000    0.9295    0.7267
0.2656    0.7179    0.3484    0.5984
X(:,:,2) =
7.0000    0.4103    0.9115    0.7983
0.0381    0.0838    0.2455    0.4370
0.4041    0.5799    0.4866    0.7407
```
```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.1162    0.5558
1.0000    1.0000    0.9295    0.7267
0.2656    0.7179    0.3484    0.5984
X(:,:,2) =
7.0000    0.4103    0.9115    0.7983
0.0381    0.0838    0.2455    0.4370
0.4041    0.5799    0.4866    0.7407
```

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.1162    0.5558
1.0000    1.0000    0.9295    0.7267
0.2656    0.7179    0.3484    0.5984
X(:,:,2) =
7.0000    0.4103    0.9115    0.7983
0.0381    0.0838    0.2455    0.4370
0.4041    0.5799    0.4866    0.7407
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.1162    0.5558
1.0000    1.0000    0.9295    0.7267
0.2656    0.7179    0.3484    0.5984
X(:,:,2) =
7.0000    0.4103    0.9115    0.7983
0.0381    0.0838    0.2455    0.4370
0.4041    0.5799    0.4866    0.7407
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) =
2     2
0     2
X(:,:,2) =
1     1
2     0
```
```[S,V] = find(X) %<-- Find all the nonzero subscripts and values.
```
```S =
1     1     1
1     2     1
2     2     1
1     1     2
2     1     2
1     2     2
V =
2
2
2
1
2
1
```
```S = find(X >= 2) %<-- Find subscripts of values >= 2.
```
```S =
1     1     1
1     2     1
2     2     1
2     1     2
```
```V = X(S) %<-- Extract the corresponding values from X.
```
```V =
2
2
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 =
3.2798
```

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

## 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) =
1     1     1
1     3     1
ans(:,:,2) =
3     2     1
1     3     2
```
```tenfun(@max,A,B) %<-- Max of A and B, elementwise.
```
```ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
2     1     0
1     2     2
ans(:,:,2) =
2     1     1
0     2     1
```
```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     1     0
4     2     2
C(:,:,2) =
3     4     0
0     4     0
ans is a tensor of size 2 x 3 x 2
ans(:,:,1) =
2     1     0
1     2     2
ans(:,:,2) =
2     1     0
0     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
```