Sparse arrays and sparse solvers

SciPy · Practical · 12 min

Graphs, text features and discretised PDEs produce matrices far too big to store densely.

A sparse matrix stores only its non-zero entries. A 100,000 x 100,000 matrix with 5 non-zeros per row needs about 6 MB sparse versus 80 GB dense.

Formats suit different jobs:

  • COO (coo_array): (row, col, value) triplets, easy to build; duplicates are summed on conversion.
  • CSR (csr_array): fast row slicing and matrix-vector products; the default for computation.
  • CSC: fast column operations, preferred by some solvers.
  • LIL / DOK: efficient for incremental construction, then convert with .tocsr().

Build in COO (or with sparse.diags_array, sparse.eye_array, sparse.random_array), then convert to CSR for computing. SciPy now recommends the array classes (csr_array) over the older matrix classes (csr_matrix): with arrays, * is element-wise and @ is matrix multiplication, as in NumPy.

Linear algebra lives in scipy.sparse.linalg:

  • spsolve(A, b): direct solve,
  • iterative solvers cg (symmetric positive definite), gmres, bicgstab, minres for very large systems,
  • eigsh/eigs: a few eigenvalues, svds: a truncated SVD.

Avoid .toarray() on large matrices, and avoid changing the sparsity pattern of a CSR matrix element by element, which is slow and triggers a SparseEfficiencyWarning.

Example

import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve, cg

n = 100_000
# tridiagonal matrix of a 1-D Laplacian: 2 on the diagonal, -1 beside it
A = sparse.diags_array([-1.0, 2.0, -1.0], offsets=[-1, 0, 1], shape=(n, n)).tocsr()
print(A.nnz, A.format)                      # 299998 csr
print(round(A.data.nbytes / 1e6, 1), "MB")  # 2.4 MB (dense would be 80,000 MB)

b = np.ones(n)
x = spsolve(A, b)
print(round(x[n // 2], 1))                  # 1250024999.6 (exact: 1250025000; ill-conditioned)

rows, cols, vals = [0, 1, 1], [0, 0, 2], [5.0, 3.0, 4.0]
M = sparse.coo_array((vals, (rows, cols)), shape=(2, 3)).tocsr()
print(M.toarray())
# [[5. 0. 0.]
#  [3. 0. 4.]]

Remember

  • Build in COO/LIL, compute in CSR/CSC
  • Prefer csr_array over csr_matrix: @ is matmul, * element-wise
  • Use spsolve or iterative solvers from scipy.sparse.linalg

Watch out for

  • Calling .toarray() on a huge sparse matrix and running out of memory.

Check yourself

  1. With scipy.sparse array classes such as csr_array, what does `A * B` compute?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scipy.org/doc/scipy/tutorial/sparse.html