Sparse Tensors

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.

Creating a sptensor

The sptensor class stores the data in coordinate format. A sparse sptensor can be created by passing in a list of subscripts and values. For example, here we pass in three subscripts and a scalar value. The resulting sparse sptensor has three nonzero entries, and the shape is the size of the largest subscript in each dimension.

import pyttb as ttb
import numpy as np
np.random.seed(0)
subs = np.array([[0, 0, 0], [0, 1, 0], [2, 3, 1]])  # Subscripts of the nonzeros.
vals = np.array([[1], [2], [3]])  # Vals is a column vector; values of the nonzeros.
X = ttb.sptensor.from_aggregator(subs, vals)  # Sparse tensor with 3 nonzeros.
X = ttb.sptensor(subs, vals, (3, 5, 2))  # Or, specify the shape explicitly.
X
sparse tensor of shape (3, 5, 2) with 3 nonzeros and order F
[0, 0, 0] = 1
[0, 1, 0] = 2
[2, 3, 1] = 3

Values corresponding to repeated subscripts are summed.

subs = np.array(
    [[0, 0, 0], [0, 0, 2], [2, 2, 2], [3, 3, 3], [0, 0, 0], [0, 0, 0]]
)  # (1,1,1) is repeated.
vals = np.array([2, 2, 2, 2, 2, 2])[:, None]  # Vals is a column vector.
X = ttb.sptensor.from_aggregator(subs, vals)
X
sparse tensor of shape (4, 4, 4) with 4 nonzeros and order F
[0, 0, 0] = 6
[0, 0, 2] = 2
[2, 2, 2] = 2
[3, 3, 3] = 2

Specifying the accumulation method for the constructor

subs = np.array([[0, 0, 0], [0, 0, 2], [2, 2, 2], [3, 3, 3], [0, 0, 0], [0, 0, 0]])
vals = 2 * np.ones((6, 1))  # A column vector of 2s
shape = (4, 4, 4)
X = ttb.sptensor.from_aggregator(subs, vals, shape, np.max)  # Maximum element.
X
sparse tensor of shape (4, 4, 4) with 4 nonzeros and order F
[0, 0, 0] = 2.0
[0, 0, 2] = 2.0
[2, 2, 2] = 2.0
[3, 3, 3] = 2.0
myfun = myfun = lambda x: np.sum(x) / 3  # Total sum divided by three.
X = ttb.sptensor.from_aggregator(
    subs, vals, shape, myfun
)  # Custom accumulation function.
X
sparse tensor of shape (4, 4, 4) with 4 nonzeros and order F
[0, 0, 0] = 2.0
[0, 0, 2] = 0.6666666666666666
[2, 2, 2] = 0.6666666666666666
[3, 3, 3] = 0.6666666666666666

Creating a one-dimensional sptensor

X = ttb.sptensor.from_aggregator(np.array([[0], [2], [4]]), np.ones((3, 1)))
X
sparse tensor of shape (5,) with 3 nonzeros and order F
[0] = 1.0
[2] = 1.0
[4] = 1.0
np.random.seed(0)
X = ttb.sptenrand((50,), nonzeros=5)
X
sparse tensor of shape (50,) with 5 nonzeros and order F
[19] = 0.7917250380826646
[21] = 0.5288949197529045
[32] = 0.5680445610939323
[44] = 0.925596638292661
[48] = 0.07103605819788694

Creating an all-zero sptensor

X = ttb.sptensor()
X[9, 9, 9] = 0  # Creates an all-zero tensor.
X
empty sparse tensor of shape (10, 10, 10) with order F

Constituent parts of a sptensor

np.random.seed(0)
X = ttb.sptenrand([40, 30, 20], nonzeros=5)  # Create data.
X.subs  # Subscripts of nonzeros.
array([[15, 23, 10],
       [17, 26, 19],
       [21, 12, 12],
       [21, 21, 12],
       [22, 27,  1]])
X.vals  # Corresponding nonzero values.
array([[0.0871293 ],
       [0.0202184 ],
       [0.83261985],
       [0.77815675],
       [0.87001215]])
X.shape  # The shape.
(40, 30, 20)

Creating a sptensor from its constituent parts

np.random.seed(0)
X = ttb.sptenrand([40, 30, 20], nonzeros=5)  # Create data.
Y = X.copy()
Y
sparse tensor of shape (40, 30, 20) with 5 nonzeros and order F
[15, 23, 10] = 0.08712929970154071
[17, 26, 19] = 0.02021839744032572
[21, 12, 12] = 0.832619845547938
[21, 21, 12] = 0.7781567509498505
[22, 27, 1] = 0.8700121482468192

Creating an empty sptensor

Y = ttb.sptensor()  # Create an empty sptensor.
Y
empty sparse tensor of shape () with order F

Use sptenrand to create a random sptensor

np.random.seed(0)
X = ttb.sptenrand(
    [10, 10, 10], 0.01
)  # Create a tesnor with 1% nonzeros using the 'density' param.
X
sparse tensor of shape (10, 10, 10) with 10 nonzeros and order F
[0, 0, 8] = 0.26455561210462697
[1, 6, 1] = 0.7742336894342167
[3, 7, 5] = 0.45615033221654855
[4, 8, 9] = 0.5684339488686485
[5, 4, 6] = 0.018789800436355142
[5, 7, 6] = 0.6176354970758771
[5, 9, 0] = 0.6120957227224214
[7, 4, 7] = 0.6169339968747569
[7, 8, 9] = 0.9437480785146242
[9, 5, 4] = 0.6818202991034834
np.random.seed(0)
X = ttb.sptenrand(
    [10, 10, 10], nonzeros=10
)  # Create a tensor with 10 nonzeros using the 'nonzeros' param.
X
sparse tensor of shape (10, 10, 10) with 10 nonzeros and order F
[0, 0, 8] = 0.26455561210462697
[1, 6, 1] = 0.7742336894342167
[3, 7, 5] = 0.45615033221654855
[4, 8, 9] = 0.5684339488686485
[5, 4, 6] = 0.018789800436355142
[5, 7, 6] = 0.6176354970758771
[5, 9, 0] = 0.6120957227224214
[7, 4, 7] = 0.6169339968747569
[7, 8, 9] = 0.9437480785146242
[9, 5, 4] = 0.6818202991034834

Use squeeze to remove singleton dimensions from a sptensor

indices = np.array([[0, 0, 0], [1, 0, 0]])
values = np.ones((2, 1))

Y = ttb.sptensor.from_aggregator(indices, values)  # Create a sparse tensor.
Y
sparse tensor of shape (2, 1, 1) with 2 nonzeros and order F
[0, 0, 0] = 1.0
[1, 0, 0] = 1.0
Y.squeeze()  # Remove singleton dimensions.
sparse tensor of shape (2,) with 2 nonzeros and order F
[0] = 1.0
[1] = 1.0

Use squash to remove empty slices from a sptensor

indices = np.array([[0, 0, 0], [2, 2, 2]])
values = np.array([[1], [3]])

Y = ttb.sptensor.from_aggregator(indices, values)  # Create a sparse tensor.
Y
sparse tensor of shape (3, 3, 3) with 2 nonzeros and order F
[0, 0, 0] = 1
[2, 2, 2] = 3
Y.squash()
sparse tensor of shape (2, 2, 2) with 2 nonzeros and order F
[0, 0, 0] = 1
[1, 1, 1] = 3

Use full or to_tensor to convert a sptensor to a (dense) tensor

indices = np.array([[0, 0, 0], [1, 1, 1]])
values = np.array([[1], [1]])
X = ttb.sptensor.from_aggregator(indices, values)  # Create a sparse tensor.
X.full()  # Convert it to a (dense) tensor.
tensor of shape (2, 2, 2) with order F
data[:, :, 0] =
[[1. 0.]
 [0. 0.]]
data[:, :, 1] =
[[0. 0.]
 [0. 1.]]
Y = X.to_tensor()  # Same as above.
Y
tensor of shape (2, 2, 2) with order F
data[:, :, 0] =
[[1. 0.]
 [0. 0.]]
data[:, :, 1] =
[[0. 0.]
 [0. 1.]]

Use to_sptensor to convert a (dense) tensor to a sptensor

indices = np.array([[0, 0, 0], [1, 1, 1]])
values = np.array([[1], [1]])
X = ttb.sptensor.from_aggregator(indices, values)  # Create a sparse tensor.
Y = X.to_tensor()  # Convert it to a (dense) tensor.
Z = Y.to_sptensor()  # Convert a tensor to a sptensor.
Z
sparse tensor of shape (2, 2, 2) with 2 nonzeros and order F
[0, 0, 0] = 1.0
[1, 1, 1] = 1.0

Use double to convert a sptensor to a (dense) multidimensional array

indices = np.array([[0, 0, 0], [1, 1, 1]])
values = np.array([[1], [1]])
X = ttb.sptensor.from_aggregator(indices, values)  # Create a sparse tensor.
Y = ttb.sptensor.double(X)  # Creates numpy.ndarray
Y
array([[[1., 0.],
        [0., 0.]],

       [[0., 0.],
        [0., 1.]]])

Use find to extract nonzeros from a tensor and then create a sptensor

np.random.seed(0)
X = ttb.tensor(np.random.rand(5, 4, 2))  # Create a tensor.
larger_entries = X > 0.9  # Extract subscipts of values greater than 0.9.
subs, vals = larger_entries.find()  # Extract corresponding subscripts and values.
Y = ttb.sptensor.from_aggregator(subs, vals)  # Create a new sptensor.
Y
sparse tensor of shape (5, 4, 2) with 5 nonzeros and order F
[1, 0, 0] = 1
[1, 2, 1] = 1
[2, 2, 0] = 1
[3, 1, 1] = 1
[4, 3, 0] = 1

Use ndims and shape to get the shape of a sptensor

X = ttb.sptensor(
    np.array([[1, 1, 1], [2, 3, 2], [3, 4, 1], [1, 0, 0]]),
    np.array([[3], [2], [1], [3]]),
    (4, 5, 3),
)
X
sparse tensor of shape (4, 5, 3) with 4 nonzeros and order F
[1, 1, 1] = 3
[2, 3, 2] = 2
[3, 4, 1] = 1
[1, 0, 0] = 3
X.ndims  # Number of dimensions or modes.
3
X.shape  # Shape of X.
(4, 5, 3)
X.shape[2]  # Shape of mode 3 of X.
3

Use nnz to get the number of nonzeroes of a sptensor

X = ttb.sptensor(
    np.array([[1, 1, 1], [2, 3, 2], [3, 4, 1], [1, 0, 0]]),
    np.array([[3], [2], [1], [3]]),
    (4, 5, 3),
)
X.nnz  # Number of nonzeros of X.
4

Subscripted reference for a sptensor

X = ttb.sptensor(
    np.array([[3, 3, 3], [1, 1, 0], [1, 2, 1]]), np.array([[3], [5], [1]]), (4, 4, 4)
)  # Create a sptensor.
X
sparse tensor of shape (4, 4, 4) with 3 nonzeros and order F
[3, 3, 3] = 3
[1, 1, 0] = 5
[1, 2, 1] = 1
X[0, 1, 0]  # Extract the (0,1,0) element, which is zero.
0
X[3, 3, 3]  # Extract the (3,3,3) element, which is non-zero.
3
X[0:2, 1:4, :]  # Extract the 2x3x4 subtensor.
sparse tensor of shape (2, 3, 4) with 2 nonzeros and order F
[1, 0, 0] = 5
[1, 1, 1] = 1
X[1, 1, 1]
0
X[1, 1, 0]
5
X[[0, 5]]  # Same as above but with linear indices.
array([[0],
       [5]])
indices = np.array([[0], [2], [4]])
values = np.array([[1], [1], [1]])
X = ttb.sptensor.from_aggregator(indices, values)
X
sparse tensor of shape (5,) with 3 nonzeros and order F
[0] = 1
[2] = 1
[4] = 1
X[(2,)]
1
X[[2, 4],]  # Returns a subtensor.
sparse tensor of shape (2,) with 2 nonzeros and order F
[0] = 1
[1] = 1

Subscripted assignment for a sptensor

X = ttb.sptensor(np.array([[]]), np.array([[]]), (30, 40, 20))
X
empty sparse tensor of shape (30, 40, 20) with order F
X[29, 39, 19] = 7  # Assign a single element.
X
sparse tensor of shape (30, 40, 20) with 1 nonzeros and order F
[29, 39, 19] = 7.0
X[0, 0, 0], X[1, 1, 1] = [1, 1]  # Assign a list of elements.
X
sparse tensor of shape (30, 40, 20) with 3 nonzeros and order F
[29, 39, 19] = 7.0
[0, 0, 0] = 1.0
[1, 1, 1] = 1.0
np.random.seed(0)
Y = ttb.sptenrand((10, 10, 10), nonzeros=10)
X[10:20, 10:20, 10:20] = Y  # Assign a subtensor.
X[30, 40, 20] = 4  # Grows the shape of the sptensor.
X
sparse tensor of shape (31, 41, 21) with 14 nonzeros and order F
[29, 39, 19] = 7.0
[0, 0, 0] = 1.0
[1, 1, 1] = 1.0
[10, 10, 18] = 0.26455561210462697
[11, 16, 11] = 0.7742336894342167
[13, 17, 15] = 0.45615033221654855
[14, 18, 19] = 0.5684339488686485
[15, 14, 16] = 0.018789800436355142
[15, 17, 16] = 0.6176354970758771
[15, 19, 10] = 0.6120957227224214
[17, 14, 17] = 0.6169339968747569
[17, 18, 19] = 0.9437480785146242
[19, 15, 14] = 0.6818202991034834
[30, 40, 20] = 4.0
X[110:120, 110:120, 110:120] = ttb.sptenrand((10, 10, 10), nonzeros=10)  # Grow more.

Using negative indexing for the last array index

X[-10:, -10:, -5:]
sparse tensor of shape (10, 10, 5) with 3 nonzeros and order F
[0, 6, 1] = 0.9764594650133958
[1, 3, 3] = 0.9767610881903371
[3, 4, 1] = 0.2828069625764096

Use elemfun to manipulate the nonzeros of a sptensor

np.random.seed(0)
X = ttb.sptenrand((10, 10, 10), nonzeros=3)
X
sparse tensor of shape (10, 10, 10) with 3 nonzeros and order F
[4, 8, 9] = 0.3834415188257777
[5, 4, 6] = 0.7917250380826646
[5, 7, 6] = 0.5288949197529045
Z = X.elemfun(lambda value: np.sqrt(value))  # Square root of every nonzero.
Z
sparse tensor of shape (10, 10, 10) with 3 nonzeros and order F
[4, 8, 9] = 0.6192265488702642
[5, 4, 6] = 0.8897893223020068
[5, 7, 6] = 0.7272516206602118
Z = X.elemfun(lambda value: value + 1)  # Use a custom function.
Z
sparse tensor of shape (10, 10, 10) with 3 nonzeros and order F
[4, 8, 9] = 1.3834415188257778
[5, 4, 6] = 1.7917250380826646
[5, 7, 6] = 1.5288949197529045
Z = X.ones()  # Change every nonzero to one.
Z
sparse tensor of shape (10, 10, 10) with 3 nonzeros and order F
[4, 8, 9] = 1.0
[5, 4, 6] = 1.0
[5, 7, 6] = 1.0

Basic operations (plus, minus, times, etc.) on a sptensor

sptensors support plus, minus, times, divide, power, equals, and not-equals operators. sptensors can use their operators with another sptensor or a scalar (with the exception of equalities which only takes sptensors). All mathematical operators are elementwise operations.

Addition

X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))
X
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = 2
[1, 1] = 2
Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))
Y
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = 3
[0, 1] = 3
+X  # Calls uplus.
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = 2
[1, 1] = 2
X + 1  # This addition yields dense tensor
tensor of shape (2, 2) with order F
data[:, :] =
[[3. 1.]
 [1. 3.]]
X + Y  # This addition yields sparse tensor
sparse tensor of shape (2, 2) with 3 nonzeros and order F
[0, 0] = 5
[0, 1] = 3
[1, 1] = 2
X += 2
X
tensor of shape (2, 2) with order F
data[:, :] =
[[4. 2.]
 [2. 4.]]

Subtraction

X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))
Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))
-X  # Calls uminus.
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = -2
[1, 1] = -2
X - Y  # Calls minus.
sparse tensor of shape (2, 2) with 3 nonzeros and order F
[0, 0] = -1
[0, 1] = -3
[1, 1] = 2

Multiplication

X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))
Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))
X * Y  # Calls times.
sparse tensor of shape (2, 2) with 1 nonzeros and order F
[0, 0] = 6
X * 5  # Calls mtimes.
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = 10
[1, 1] = 10

Division

X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))
Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))
X / 2  # Calls rdivide.
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = 1.0
[1, 1] = 1.0
X / Y  # Divide by Zero
sparse tensor of shape (2, 2) with 4 nonzeros and order F
[0, 0] = 0.6666666666666666
[1, 0] = nan
[1, 1] = 0.0
[1, 0] = nan
X /= 4
print(X)
sparse tensor of shape (2, 2) with 2 nonzeros and order F
[0, 0] = 0.5
[1, 1] = 0.5

Equality

X = ttb.sptensor(np.array([[0, 0], [1, 1]]), np.array([[2], [2]]), (2, 2))
Y = ttb.sptensor(np.array([[0, 0], [0, 1]]), np.array([[3], [3]]), (2, 2))
(X == Y)
sparse tensor of shape (2, 2) with 1 nonzeros and order F
[1, 0] = 1
X.isequal(Y)
False
X != Y
sparse tensor of shape (2, 2) with 3 nonzeros and order F
[1, 1] = 1
[0, 1] = 1
[0, 0] = 1

Use permute to reorder the modes of a sptensor

np.random.seed(0)
X = ttb.sptenrand((30, 40, 20, 1), nonzeros=5)  # Create data.
X
sparse tensor of shape (30, 40, 20, 1) with 5 nonzeros and order F
[0, 33, 15, 0] = 0.978618342232764
[12, 25, 8, 0] = 0.7991585642167236
[16, 28, 12, 0] = 0.46147936225293185
[17, 37, 1, 0] = 0.7805291762864555
[28, 15, 15, 0] = 0.11827442586893322
X.permute(np.array([3, 2, 1, 0]))  # Reorder the modes.
sparse tensor of shape (1, 20, 40, 30) with 5 nonzeros and order F
[0, 15, 33, 0] = 0.978618342232764
[0, 8, 25, 12] = 0.7991585642167236
[0, 12, 28, 16] = 0.46147936225293185
[0, 1, 37, 17] = 0.7805291762864555
[0, 15, 15, 28] = 0.11827442586893322

permute works correctly for a 1-dimensional sptensor

np.random.seed(0)
X = ttb.sptenrand((40,), nonzeros=4)  # Create data.
X
sparse tensor of shape (40,) with 4 nonzeros and order F
[16] = 0.9636627605010293
[17] = 0.3834415188257777
[25] = 0.7917250380826646
[35] = 0.5288949197529045
X.permute(np.array([0]))
sparse tensor of shape (40,) with 4 nonzeros and order F
[16] = 0.9636627605010293
[17] = 0.3834415188257777
[25] = 0.7917250380826646
[35] = 0.5288949197529045

Displaying a sptensor

print(X)
sparse tensor of shape (40,) with 4 nonzeros and order F
[16] = 0.9636627605010293
[17] = 0.3834415188257777
[25] = 0.7917250380826646
[35] = 0.5288949197529045
X  # In the python interface
sparse tensor of shape (40,) with 4 nonzeros and order F
[16] = 0.9636627605010293
[17] = 0.3834415188257777
[25] = 0.7917250380826646
[35] = 0.5288949197529045