Converting Sparse Tensors to Matrices and vice versa

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.

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.

import pyttb as ttb
import numpy as np

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}\n" f"Original 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