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
sptensor
s support plus, minus, times, divide, power, equals, and not-equals operators. sptensor
s can use their operators with another sptensor
or a scalar (with the exception of equalities which only takes sptensor
s). 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