Converting a tensor to a 2D numpy array and vice versa

Copyright 2022 National Technology & Engineering Solutions of Sandia,
LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the
U.S. Government retains certain rights in this software.

We show how to convert a tensor to a 2D numpy array stored with extra information so that it can be converted back to a tensor. Converting to a 2D numpy array requires an ordered mapping of the tensor indices to the rows and the columns of the 2D numpy array.

import pyttb as ttb
import numpy as np

Creating a tenmat (tensor as 2D numpy array) object

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
X
tensor of shape (3, 2, 2, 2) with order F
data[:, :, 0, 0] =
[[1 4]
 [2 5]
 [3 6]]
data[:, :, 1, 0] =
[[ 7 10]
 [ 8 11]
 [ 9 12]]
data[:, :, 0, 1] =
[[13 16]
 [14 17]
 [15 18]]
data[:, :, 1, 1] =
[[19 22]
 [20 23]
 [21 24]]
# Dims [0,1] map to rows, [2,3] to columns.
A = X.to_tenmat(np.array([0, 1]), np.array([2, 3]))
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 0, 1 ] (modes of tensor corresponding to rows)
cindices = [ 2, 3 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  7 13 19]
 [ 2  8 14 20]
 [ 3  9 15 21]
 [ 4 10 16 22]
 [ 5 11 17 23]
 [ 6 12 18 24]]
B = X.to_tenmat(np.array([1, 0]), np.array([2, 3]))  # Order matters!
B
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1, 0 ] (modes of tensor corresponding to rows)
cindices = [ 2, 3 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  7 13 19]
 [ 4 10 16 22]
 [ 2  8 14 20]
 [ 5 11 17 23]
 [ 3  9 15 21]
 [ 6 12 18 24]]
C = X.to_tenmat(np.array([0, 1]), np.array([3, 2]))
C
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 0, 1 ] (modes of tensor corresponding to rows)
cindices = [ 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1 13  7 19]
 [ 2 14  8 20]
 [ 3 15  9 21]
 [ 4 16 10 22]
 [ 5 17 11 23]
 [ 6 18 12 24]]

Creating a tenmat by specifying the dimensions mapped to the rows

If just the row indices are specified, then the columns are arranged in increasing order.

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]))  # np.array([1]) passed to the `rdims` parameter
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 2, 3 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3  7  8  9 13 14 15 19 20 21]
 [ 4  5  6 10 11 12 16 17 18 22 23 24]]

Creating a tenmat by specifying the dimensions mapped to the columns

Likewise, just the columns can be specified if the cdims argument is given. The columns are arranged in increasing order.

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
# Same as A = ttb.tenmat.from_tensor_type(X, np.array([0,3]), np.array([1,2]))
A = X.to_tenmat(cdims=np.array([1, 2]))
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 0, 3 ] (modes of tensor corresponding to rows)
cindices = [ 1, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  4  7 10]
 [ 2  5  8 11]
 [ 3  6  9 12]
 [13 16 19 22]
 [14 17 20 23]
 [15 18 21 24]]

Vectorize via tenmat

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(cdims=np.arange(0, 4))  # Map all the dimensions to the columns
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [  ] (modes of tensor corresponding to rows)
cindices = [ 0, 1, 2, 3 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]]

Alternative ordering for the columns for mode-\(n\) matricization

Mode-\(n\) matricization means that only mode \(n\) is mapped to the rows. Different column orderings are available.

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([2]))  # By default, columns are ordered as [0, 1, 3].
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 2 ] (modes of tensor corresponding to rows)
cindices = [ 0, 1, 3 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3  4  5  6 13 14 15 16 17 18]
 [ 7  8  9 10 11 12 19 20 21 22 23 24]]
X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), np.array([2, 0, 3]))  # Explicit specification.
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 2, 0, 3 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  7  2  8  3  9 13 19 14 20 15 21]
 [ 4 10  5 11  6 12 16 22 17 23 18 24]]
A = X.to_tenmat(np.array([1]), cdims_cyclic="fc")  # Forward cyclic, [2,3,0].
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 2, 3, 0 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  7 13 19  2  8 14 20  3  9 15 21]
 [ 4 10 16 22  5 11 17 23  6 12 18 24]]
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3 13 14 15  7  8  9 19 20 21]
 [ 4  5  6 16 17 18 10 11 12 22 23 24]]

Constituent parts of a tenmat

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A.data  # The 2D numpy array itself.
array([[ 1,  2,  3, 13, 14, 15,  7,  8,  9, 19, 20, 21],
       [ 4,  5,  6, 16, 17, 18, 10, 11, 12, 22, 23, 24]])
A.tshape  # Shape of the original tensor.
(3, 2, 2, 2)
A.rindices  # Dimensions that were mapped to the rows.
array([1])
A.cindices  # Dimensions that were mapped to the columns.
array([0, 3, 2])

Creating a tenmat from its constituent parts

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
B = ttb.tenmat(A.data, A.rindices, A.cindices, A.tshape)
B  # Recreates A.
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3 13 14 15  7  8  9 19 20 21]
 [ 4  5  6 16 17 18 10 11 12 22 23 24]]

Creating an empty tenmat

B = ttb.tenmat()  # Empty tenmat.
B
matrix corresponding to a tensor of shape () with order F
rindices = [  ] (modes of tensor corresponding to rows)
cindices = [  ] (modes of tensor corresponding to columns)
data = []

Use double to convert a tenmat to a 2D numpy array

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A.double()  # Converts A to a standard 2D numpy array.
array([[ 1.,  2.,  3., 13., 14., 15.,  7.,  8.,  9., 19., 20., 21.],
       [ 4.,  5.,  6., 16., 17., 18., 10., 11., 12., 22., 23., 24.]])

Use to_tensor to convert a tenmat to a tensor

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
Y = A.to_tensor()
Y
tensor of shape (3, 2, 2, 2) with order F
data[:, :, 0, 0] =
[[1 4]
 [2 5]
 [3 6]]
data[:, :, 1, 0] =
[[ 7 10]
 [ 8 11]
 [ 9 12]]
data[:, :, 0, 1] =
[[13 16]
 [14 17]
 [15 18]]
data[:, :, 1, 1] =
[[19 22]
 [20 23]
 [21 24]]

Use shape and tshape for the dimensions of a tenmat

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A.shape  # 2D numpy array shape.
(2, 12)
A.tshape  # Corresponding tensor shape.
(3, 2, 2, 2)

Subscripted reference for a tenmat

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A[1, 0]  # Returns the (1,0) element of the 2D numpy array
np.int64(4)

Subscripted assignment for a tenmat

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A[0:2, 0:2] = np.ones((2, 2))
A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  1  3 13 14 15  7  8  9 19 20 21]
 [ 1  1  6 16 17 18 10 11 12 22 23 24]]

Using negative indexing for the last array index

A[-1][-1]  # Same as A[1, 11].
np.int64(24)

Basic operations for tenmat

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
A.norm()  # Norm of the 2D numpy array.
70.0
A.ctranspose()  # Also swaps mapped dimensions.
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 0, 3, 2 ] (modes of tensor corresponding to rows)
cindices = [ 1 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  4]
 [ 2  5]
 [ 3  6]
 [13 16]
 [14 17]
 [15 18]
 [ 7 10]
 [ 8 11]
 [ 9 12]
 [19 22]
 [20 23]
 [21 24]]
+A  # Calls uplus.
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3 13 14 15  7  8  9 19 20 21]
 [ 4  5  6 16 17 18 10 11 12 22 23 24]]
-A  # Calls uminus.
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ -1  -2  -3 -13 -14 -15  -7  -8  -9 -19 -20 -21]
 [ -4  -5  -6 -16 -17 -18 -10 -11 -12 -22 -23 -24]]
A + A  # Calls plus
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 2  4  6 26 28 30 14 16 18 38 40 42]
 [ 8 10 12 32 34 36 20 22 24 44 46 48]]
A - A  # Calls minus.
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0]]

Multiplying two tenmats

It is possible to compute the product of two tenmats and have a result that can be converted into a tensor.

X = ttb.tensor(np.arange(1, 25), shape=(3, 2, 2, 2))  # Create a tensor.
A = X.to_tenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic, [0,3,2].
B = A * A.ctranspose()  # Tenmat that is the product of two tenmats.
B
WARNING:root:Selected no copy, but input data isn't F ordered so must copy.
matrix corresponding to a tensor of shape (2, 2) with order F
rindices = [ 0 ] (modes of tensor corresponding to rows)
cindices = [ 1 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[2000 2396]
 [2396 2900]]
B.to_tensor()  # Corresponding tensor.
tensor of shape (2, 2) with order F
data[:, :] =
[[2000 2396]
 [2396 2900]]

Displaying a tenmat

Shows the original tensor dimensions, the modes mapped to rows, the modes mapped to columns, and the 2D numpy array.

A
matrix corresponding to a tensor of shape (3, 2, 2, 2) with order F
rindices = [ 1 ] (modes of tensor corresponding to rows)
cindices = [ 0, 3, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[ 1  2  3 13 14 15  7  8  9 19 20 21]
 [ 4  5  6 16 17 18 10 11 12 22 23 24]]