Sparse Tensors
Copyright 2025 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.
from __future__ import annotations
import numpy as np
import pyttb as ttb
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