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

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