Converting Sparse Tensors to Matrices and vice versa

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.

We show how to convert an sptensor to a matrix stored in coordinate format with extra information so that is can be convertered back to an sptensor.

from __future__ import annotations

import numpy as np

import pyttb as ttb

Creating an sptenmat (sparse tensor as sparse matrix) object

A sparse tensor can be converted to a sparse matrix, with row and column indices stored explicitly.

First, we crease a sparse tensor to be converted.

np.random.seed(0)  # Random seed for reproducibility
X = ttb.sptenrand((10, 10, 10, 10), nonzeros=10)
X
sparse tensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
[0, 8, 7, 8] = 0.359507900573786
[1, 6, 1, 9] = 0.43703195379934145
[4, 5, 0, 6] = 0.6976311959272649
[4, 6, 4, 8] = 0.06022547162926983
[5, 4, 2, 7] = 0.6667667154456677
[5, 7, 6, 5] = 0.6706378696181594
[5, 9, 0, 0] = 0.2103825610738409
[6, 6, 9, 6] = 0.1289262976548533
[9, 3, 7, 5] = 0.31542835092418386
[9, 7, 4, 7] = 0.3637107709426226

Similar options as tenmat are available for sptenmat.

A = X.to_sptenmat(np.array([0]))  # Mode-0 matricization
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2, 3 ] (modes of sptensor corresponding to columns)
	[0, 878] = 0.359507900573786
	[1, 916] = 0.43703195379934145
	[4, 605] = 0.6976311959272649
	[4, 846] = 0.06022547162926983
	[5, 9] = 0.2103825610738409
	[5, 567] = 0.6706378696181594
	[5, 724] = 0.6667667154456677
	[6, 696] = 0.1289262976548533
	[9, 573] = 0.31542835092418386
	[9, 747] = 0.3637107709426226
A = X.to_sptenmat(np.array([1, 2]))  # Multiple modes mapped to rows.
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 1, 2 ] (modes of sptensor corresponding to rows)
cdims = [ 0, 3 ] (modes of sptensor corresponding to columns)
	[5, 64] = 0.6976311959272649
	[9, 5] = 0.2103825610738409
	[16, 91] = 0.43703195379934145
	[24, 75] = 0.6667667154456677
	[46, 84] = 0.06022547162926983
	[47, 79] = 0.3637107709426226
	[67, 55] = 0.6706378696181594
	[73, 59] = 0.31542835092418386
	[78, 80] = 0.359507900573786
	[96, 66] = 0.1289262976548533
A = X.to_sptenmat(cdims=np.array([1, 2]))  # Specify column dimensions.
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 0, 3 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2 ] (modes of sptensor corresponding to columns)
	[5, 9] = 0.2103825610738409
	[55, 67] = 0.6706378696181594
	[59, 73] = 0.31542835092418386
	[64, 5] = 0.6976311959272649
	[66, 96] = 0.1289262976548533
	[75, 24] = 0.6667667154456677
	[79, 47] = 0.3637107709426226
	[80, 78] = 0.359507900573786
	[84, 46] = 0.06022547162926983
	[91, 16] = 0.43703195379934145
A = X.to_sptenmat(np.arange(4))  # All modes mapped to rows, i.e., vectorize.
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 0, 1, 2, 3 ] (modes of sptensor corresponding to rows)
cdims = [  ] (modes of sptensor corresponding to columns)
	[95, 0] = 0.2103825610738409
	[5675, 0] = 0.6706378696181594
	[5739, 0] = 0.31542835092418386
	[6054, 0] = 0.6976311959272649
	[6966, 0] = 0.1289262976548533
	[7245, 0] = 0.6667667154456677
	[7479, 0] = 0.3637107709426226
	[8464, 0] = 0.06022547162926983
	[8780, 0] = 0.359507900573786
	[9161, 0] = 0.43703195379934145
A = X.to_sptenmat(np.array([1]))  # By default, columns are ordered as [0, 2, 3]
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 1 ] (modes of sptensor corresponding to rows)
cdims = [ 0, 2, 3 ] (modes of sptensor corresponding to columns)
	[3, 579] = 0.31542835092418386
	[4, 725] = 0.6667667154456677
	[5, 604] = 0.6976311959272649
	[6, 696] = 0.1289262976548533
	[6, 844] = 0.06022547162926983
	[6, 911] = 0.43703195379934145
	[7, 565] = 0.6706378696181594
	[7, 749] = 0.3637107709426226
	[8, 870] = 0.359507900573786
	[9, 5] = 0.2103825610738409
A = X.to_sptenmat(np.array([1]), np.array([3, 0, 2]))  # Specify explicit ordering
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 1 ] (modes of sptensor corresponding to rows)
cdims = [ 3, 0, 2 ] (modes of sptensor corresponding to columns)
	[3, 795] = 0.31542835092418386
	[4, 257] = 0.6667667154456677
	[5, 46] = 0.6976311959272649
	[6, 119] = 0.43703195379934145
	[6, 448] = 0.06022547162926983
	[6, 966] = 0.1289262976548533
	[7, 497] = 0.3637107709426226
	[7, 655] = 0.6706378696181594
	[8, 708] = 0.359507900573786
	[9, 50] = 0.2103825610738409
A = X.to_sptenmat(np.array([1]), cdims_cyclic="fc")  # Forward cyclic column ordering
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 1 ] (modes of sptensor corresponding to rows)
cdims = [ 2, 3, 0 ] (modes of sptensor corresponding to columns)
	[3, 957] = 0.31542835092418386
	[4, 572] = 0.6667667154456677
	[5, 460] = 0.6976311959272649
	[6, 191] = 0.43703195379934145
	[6, 484] = 0.06022547162926983
	[6, 669] = 0.1289262976548533
	[7, 556] = 0.6706378696181594
	[7, 974] = 0.3637107709426226
	[8, 87] = 0.359507900573786
	[9, 500] = 0.2103825610738409
A = X.to_sptenmat(np.array([1]), cdims_cyclic="bc")  # Backward cyclic column ordering
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 1 ] (modes of sptensor corresponding to rows)
cdims = [ 0, 3, 2 ] (modes of sptensor corresponding to columns)
	[3, 759] = 0.31542835092418386
	[4, 275] = 0.6667667154456677
	[5, 64] = 0.6976311959272649
	[6, 191] = 0.43703195379934145
	[6, 484] = 0.06022547162926983
	[6, 966] = 0.1289262976548533
	[7, 479] = 0.3637107709426226
	[7, 655] = 0.6706378696181594
	[8, 780] = 0.359507900573786
	[9, 5] = 0.2103825610738409

Constituent parts of an sptenmat

A.subs  # Subscripts of the nonzeros.
array([[  3, 759],
       [  4, 275],
       [  5,  64],
       [  6, 191],
       [  6, 484],
       [  6, 966],
       [  7, 479],
       [  7, 655],
       [  8, 780],
       [  9,   5]])
A.vals  # Corresponding nonzero values.
array([[0.31542835],
       [0.66676672],
       [0.6976312 ],
       [0.43703195],
       [0.06022547],
       [0.1289263 ],
       [0.36371077],
       [0.67063787],
       [0.3595079 ],
       [0.21038256]])
A.tshape  # Shape of the original tensor.
(10, 10, 10, 10)
A.rdims  # Dimensions that were mapped to the rows.
array([1])
A.cdims  # Dimensions that were mapped to the columns.
array([0, 3, 2])

Creating an sptenmat from its constituent parts

B = ttb.sptenmat(A.subs, A.vals, A.rdims, A.cdims, A.tshape)  # Effectively copies A
B
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 1 ] (modes of sptensor corresponding to rows)
cdims = [ 0, 3, 2 ] (modes of sptensor corresponding to columns)
	[3, 759] = 0.31542835092418386
	[4, 275] = 0.6667667154456677
	[5, 64] = 0.6976311959272649
	[6, 191] = 0.43703195379934145
	[6, 484] = 0.06022547162926983
	[6, 966] = 0.1289262976548533
	[7, 479] = 0.3637107709426226
	[7, 655] = 0.6706378696181594
	[8, 780] = 0.359507900573786
	[9, 5] = 0.2103825610738409

Creating an sptenmat with no nonzeros

A = ttb.sptenmat(rdims=A.rdims, cdims=A.cdims, tshape=A.tshape)  # An empty sptenmat
A
sptenmat corresponding to a sptensor of shape (10, 1000) with 0 nonzeros and order F
rdims = [ 1 ] (modes of sptensor corresponding to rows)
cdims = [ 0, 3, 2 ] (modes of sptensor corresponding to columns)

Creating an empty sptenmat

A = ttb.sptenmat()  # A really empty sptenmat
A
sptenmat corresponding to a sptensor of shape () with 0 nonzeros and order F
rdims = [  ] (modes of sptensor corresponding to rows)
cdims = [  ] (modes of sptensor corresponding to columns)

Use double to convert an sptenmat to a SciPy COO Matrix

X = ttb.sptenrand((10, 10, 10, 10), nonzeros=10)  # Create sptensor
A = X.to_sptenmat(np.array([0]))  # Convert to an sptenmat
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 10 nonzeros and order F
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2, 3 ] (modes of sptensor corresponding to columns)
	[0, 256] = 0.31856895245132366
	[2, 261] = 0.13179786240439217
	[2, 431] = 0.6674103799636817
	[4, 112] = 0.7163272041185655
	[5, 194] = 0.18319136200711683
	[5, 950] = 0.2894060929472011
	[6, 311] = 0.5865129348100832
	[7, 120] = 0.020107546187493552
	[8, 80] = 0.8289400292173631
	[9, 694] = 0.004695476192547066
B = A.double()  # Convert to scipy
B
<10x1000 sparse matrix of type '<class 'numpy.float64'>'
	with 10 stored elements in COOrdinate format>

Use full to convert an sptenmat to a tenmat

B = ttb.sptenrand((3, 3, 3), nonzeros=3).to_sptenmat(np.array([0]))
B
sptenmat corresponding to a sptensor of shape (3, 3, 3) with 3 nonzeros and order F
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2 ] (modes of sptensor corresponding to columns)
	[1, 1] = 0.952749011516985
	[2, 3] = 0.44712537861762736
	[2, 6] = 0.8464086724711278
C = B.full()
C
matrix corresponding to a tensor of shape (3, 3, 3) with order F
rindices = [ 0 ] (modes of tensor corresponding to rows)
cindices = [ 1, 2 ] (modes of tensor corresponding to columns)
data[:, :] = 
[[0.         0.         0.         0.         0.         0.
  0.         0.         0.        ]
 [0.         0.95274901 0.         0.         0.         0.
  0.         0.         0.        ]
 [0.         0.         0.         0.44712538 0.         0.
  0.84640867 0.         0.        ]]

Use to_sptensor to convert an sptenmat to an sptensor.

Y = B.to_sptensor()
Y
sparse tensor of shape (3, 3, 3) with 3 nonzeros and order F
[1, 1, 0] = 0.952749011516985
[2, 0, 1] = 0.44712537861762736
[2, 0, 2] = 0.8464086724711278
## Access `shape` and `tshape` for dimensions of an `sptenmat`
print(f"Matrix shape: {A.shape}\nOriginal tensor shape: {A.tshape}")
Matrix shape: (10, 1000)
Original tensor shape: (10, 10, 10, 10)
## Subscripted assignment for an `sptenmat`
A[0:2, 0:2] = 1
A
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 14 nonzeros and order F
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2, 3 ] (modes of sptensor corresponding to columns)
	[0, 0] = 1.0
	[0, 1] = 1.0
	[0, 256] = 0.31856895245132366
	[1, 0] = 1.0
	[1, 1] = 1.0
	[2, 261] = 0.13179786240439217
	[2, 431] = 0.6674103799636817
	[4, 112] = 0.7163272041185655
	[5, 194] = 0.18319136200711683
	[5, 950] = 0.2894060929472011
	[6, 311] = 0.5865129348100832
	[7, 120] = 0.020107546187493552
	[8, 80] = 0.8289400292173631
	[9, 694] = 0.004695476192547066
## Basic operations for `sptenmat`
A.norm()  # Norm of the matrix.
2.4952551873589304
+A  # Positive version of matrix (no change)
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 14 nonzeros and order F
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2, 3 ] (modes of sptensor corresponding to columns)
	[0, 0] = 1.0
	[0, 1] = 1.0
	[0, 256] = 0.31856895245132366
	[1, 0] = 1.0
	[1, 1] = 1.0
	[2, 261] = 0.13179786240439217
	[2, 431] = 0.6674103799636817
	[4, 112] = 0.7163272041185655
	[5, 194] = 0.18319136200711683
	[5, 950] = 0.2894060929472011
	[6, 311] = 0.5865129348100832
	[7, 120] = 0.020107546187493552
	[8, 80] = 0.8289400292173631
	[9, 694] = 0.004695476192547066
-A  # Negative version of matrix
sptenmat corresponding to a sptensor of shape (10, 10, 10, 10) with 14 nonzeros and order F
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2, 3 ] (modes of sptensor corresponding to columns)
	[0, 0] = -1.0
	[0, 1] = -1.0
	[0, 256] = -0.31856895245132366
	[1, 0] = -1.0
	[1, 1] = -1.0
	[2, 261] = -0.13179786240439217
	[2, 431] = -0.6674103799636817
	[4, 112] = -0.7163272041185655
	[5, 194] = -0.18319136200711683
	[5, 950] = -0.2894060929472011
	[6, 311] = -0.5865129348100832
	[7, 120] = -0.020107546187493552
	[8, 80] = -0.8289400292173631
	[9, 694] = -0.004695476192547066