Home / LA 101 / Module 9 / Lesson 3
Stories Mode

Sparse Matrices

Most large real-world matrices are overwhelmingly zeros. Sparse storage formats — CSR, CSC, COO — exploit this structure to reduce memory from O(n²) to O(nnz), enabling computations on graphs, finite-element meshes, and networks that would be impossible with dense representations.

~20 min read M9 · L3 Intermediate

What Is a Sparse Matrix?

A matrix is called sparse when the vast majority of its entries are zero. The threshold is informal — there is no universal cutoff — but in practice, a matrix is sparse when exploiting its zero structure yields meaningful computational savings. The fraction of nonzero entries is the density: density = nnz / (m × n), where nnz is the number of nonzeros. Matrices with density below 1% (and often far lower) arise naturally across science and engineering.

Consider a social network with n = 10⁶ users. The adjacency matrix is n × n = 10¹² entries. Storing it densely requires 8 TB (at 8 bytes per double). Yet the average user follows ~300 others, so nnz ≈ 3 × 10⁸ — just 0.03% of all entries are nonzero. Sparse storage reduces memory to ~2.4 GB: a factor of 3000 improvement. Similar structure appears in finite-element discretizations (each node connects only to neighboring nodes), Markov chains (transitions only to reachable states), and differential operators (each equation involves only nearby grid points).

Three canonical examples generate sparse matrices in engineering: (1) Graph Laplacians — for a graph with n nodes and m edges, L = D − A has nnz = n + 2m, typically O(n) for sparse graphs; (2) Finite-element stiffness matrices — discretizing a 2D PDE on an n-node mesh gives a matrix with nnz ≈ 5n to 7n depending on element type; (3) Markov transition matrices — each column sums to 1 and has nnz equal to the out-degree of the corresponding state.

Storage Formats

The three most common formats each make different trade-offs between memory, construction ease, and operation efficiency.

COO — Coordinate Format

The simplest sparse format stores three arrays: row[k], col[k], and val[k], each of length nnz. Entry k represents the nonzero A[row[k], col[k]] = val[k]. COO makes no assumptions about ordering — entries can appear in any sequence. This makes COO ideal for matrix construction: assembling a finite-element stiffness matrix naturally produces (row, col, val) triples that can be accumulated into COO and later converted to a compressed format. COO requires 3 × nnz storage (plus potential duplicates during assembly). Matrix-vector multiplication is straightforward but not cache-friendly because row access is random.

CSR — Compressed Sparse Row

CSR (also called CRS) is the standard format for arithmetic operations on sparse matrices. It uses three arrays:

Row i has nonzeros at columns col_ind[row_ptr[i]], …, col_ind[row_ptr[i+1]−1] with values val[row_ptr[i]], …, val[row_ptr[i+1]−1]. Memory: (m+1) integers for row_ptr plus 2×nnz for val and col_ind — roughly (2 nnz + m) words total versus m × n for dense.

CSR Sparse Matrix-Vector Product
y_i = \sum_{j=\text{row\_ptr}[i]}^{\text{row\_ptr}[i+1]-1} \text{val}[j] \cdot x[\text{col\_ind}[j]], \quad i = 0, \ldots, m-1
For each row i, the inner loop runs over only the nnz_i nonzeros in that row. Total cost: O(nnz) multiplications and additions. Cache behavior is excellent for the val and col_ind arrays (sequential access), though random access into x at col_ind[j] can cause cache misses for large matrices.

CSR is the workhorse format for iterative solvers because SpMV (sparse matrix-vector product) dominates the cost of methods like conjugate gradient and GMRES. However, CSR makes column access expensive — finding all nonzeros in a given column requires scanning the entire matrix. This motivates the companion format CSC.

CSC — Compressed Sparse Column

CSC is the column-oriented counterpart of CSR: three arrays val, row_ind, and col_ptr, where col_ptr[j] points to the start of column j in val and row_ind. CSC is the native format in MATLAB (which stores all matrices column-major) and in many direct solvers, because Cholesky and LU factorization naturally process columns. For computing A^T v efficiently, CSC format reduces to a CSR SpMV. LAPACK's sparse extensions and SuiteSparse use CSC internally.

BSR and Other Block Formats

When the sparsity pattern has a block structure — e.g., finite-element matrices with 3×3 or 6×6 dense blocks at each nonzero position — Block Sparse Row (BSR) stores the blocks contiguously. This improves SIMD utilization because each block fits into vector registers. Block sizes of 4×4 or 8×8 can achieve 4–8× speedups over scalar CSR on modern CPUs. The ELLPACK format, popular on GPUs, stores a fixed number of nonzeros per row in a regular array for coalesced memory access.

Sparse Matrix Operations

SpMV: The Critical Primitive

Sparse matrix-vector multiplication (y ← Ax) is the single most important sparse operation. It drives iterative solvers, graph algorithms (PageRank, label propagation), and neural network inference for sparse weight matrices. The arithmetic intensity of SpMV is very low — roughly 2 nnz flops on 3 nnz + n words of memory — making it memory-bandwidth bound. For a sparse matrix with nnz = 10⁷ and n = 10⁶ on a modern CPU with 50 GB/s memory bandwidth, SpMV takes ~1.6 ms; with a GPU at 900 GB/s, ~0.09 ms. Efficient SpMV is the subject of decades of research: reordering rows and columns (e.g., reverse Cuthill-McKee) improves cache locality; register blocking and SIMD intrinsics improve arithmetic utilization.

Sparse Matrix-Matrix Multiplication (SpGEMM)

Computing C = A × B when both A and B are sparse is fundamentally harder than SpMV because the sparsity pattern of C is not known in advance and C may be much denser than A or B. SpGEMM is the core primitive in shortest-path algorithms (Bellman-Ford as matrix powers), triangle counting, and graph neural network aggregation. The key challenge is fill-in: even if A and B have O(n) nonzeros, C can have O(n²) nonzeros in the worst case. Practical SpGEMM algorithms use hash maps or sorted merges to accumulate entries efficiently; libraries like SuiteSparse:GraphBLAS provide high-performance implementations.

Reordering for Reduced Fill-In

When factoring a sparse matrix A = LU or A = LL^T, new nonzeros — called fill-in — appear in L and U at positions that are zero in A. A poor ordering can cause catastrophic fill-in, turning a sparse matrix with nnz = O(n) into a dense triangular factor with nnz = O(n²). The minimum degree algorithm greedily eliminates the variable with fewest connections at each step, reducing fill-in substantially. The nested dissection ordering, based on graph separators, achieves provably optimal fill-in for planar graphs: O(n log n) in 2D and O(n^{4/3}) in 3D, versus O(n^{3/2}) and O(n²) for naive orderings. Modern sparse direct solvers (CHOLMOD, UMFPACK, PARDISO) apply nested dissection automatically before factorization.

Fill-In Bounds by Problem Dimension
\text{nnz}(L) = \begin{cases} O(n \log n) & \text{2D, nested dissection} \\ O(n^{4/3}) & \text{3D, nested dissection} \\ O(n^{3/2}) & \text{2D, naive ordering} \\ O(n^2) & \text{3D, naive ordering} \end{cases}
For a 2D PDE on an n-node grid with optimal nested-dissection ordering, the Cholesky factor L has O(n log n) nonzeros and the factorization costs O(n^{3/2}) flops. In 3D, nnz(L) = O(n^{4/3}) and the factorization costs O(n²) flops — still far less than the O(n³) cost for dense matrices but large enough to motivate iterative methods for n > 10⁵.

Applications

Graphs and Network Analysis

Every undirected graph with n nodes and m edges has a symmetric adjacency matrix A ∈ {0,1}^{n×n} with nnz = 2m. For sparse graphs (m = O(n)), A has O(n) nonzeros. The graph Laplacian L = D − A (where D is the diagonal degree matrix) is similarly sparse. Sparse linear algebra underpins graph algorithms:

Finite Element Methods (FEM)

FEM discretizes partial differential equations on unstructured meshes. Each element (triangle, tetrahedron, etc.) contributes a small dense element stiffness matrix that is assembled into the global sparse stiffness matrix K. The resulting K is symmetric positive definite for many physical problems (heat diffusion, structural mechanics, electromagnetics). Sparsity arises because the basis functions have local support — a basis function associated with node i is nonzero only within the elements sharing node i. For a 2D mesh with n nodes and O(n) triangular elements (each with 3 nodes), K has nnz ≈ 7n on average. The assembly process naturally produces COO triples that are summed and converted to CSR for the iterative solve.

Markov Chains and Stochastic Matrices

A Markov chain with n states has a transition matrix P where Pᵢⱼ is the probability of moving from state j to state i (column-stochastic convention). For most real processes, each state transitions to only a few others, making P sparse. Finding the stationary distribution π satisfying Pπ = π (i.e., solving (P − I)π = 0 subject to 1^T π = 1) is a sparse eigenvalue problem. PageRank is exactly this problem for the web graph. Sparse power iteration (π ← Pπ, repeated until convergence) costs O(nnz) per step and typically converges in O(1/(1−|λ₂|)) steps where λ₂ is the second eigenvalue of P.

Sparse Direct vs. Iterative Solvers

The choice between sparse direct and sparse iterative solvers involves trade-offs across robustness, memory, time, and the number of right-hand sides:

Sparse Direct Solvers

Sparse direct solvers (SuiteSparse/UMFPACK, PARDISO, SuperLU, MUMPS) apply a reordering permutation, then compute an exact sparse LU or Cholesky factorization. Once factored, each subsequent right-hand side costs only O(nnz(L)) ≈ O(n log n) in 2D via forward/backward substitution. Sparse direct solvers are:

Sparse Iterative Solvers

Iterative methods (CG, GMRES, BiCGSTAB) access A only through SpMV. They maintain sparsity throughout and can exploit any implicit matrix representation. For well-conditioned problems with good preconditioners, they outperform direct methods even in 2D. Their weaknesses: convergence is not guaranteed for all matrices; choosing and tuning a preconditioner requires expertise; they return approximate solutions (though to arbitrary precision); and for poorly conditioned problems, convergence can be extremely slow.

Solver Complexity Comparison
\text{2D Poisson: }\begin{cases} \text{Direct factor} & O(n^{3/2}) \\ \text{Direct solve} & O(n \log n) \\ \text{PCG + multigrid} & O(n) \end{cases}
For a 2D Poisson problem with n unknowns. Direct: factorization O(n^{3/2}), each solve O(n log n). PCG with multigrid preconditioner: O(n) per iteration × O(1) iterations = O(n) total. For n = 10⁶ in 2D, direct takes ~10⁹ flops; multigrid-PCG takes ~10⁷ — 100× faster, though direct remains competitive for n < 10⁵.

Hybrid Approaches

The sharpest tools combine both paradigms. Incomplete LU (ILU) computes a sparse approximate factorization — keeping only entries above a threshold or within the original sparsity pattern — and uses it as a preconditioner for GMRES or BiCGSTAB. Domain decomposition methods split the domain into subdomains, solve each subdomain with a direct solver, and couple the subdomains iteratively. Schur complement methods eliminate interior degrees of freedom directly and solve the sparse interface system iteratively. In practice, most production sparse solvers (PETSc, Trilinos) implement a layered approach: direct subdomain solves + iterative coupling.


Engineering Implication

In Python, scipy.sparse provides COO, CSR, and CSC formats with automatic conversion. scipy.sparse.linalg.spsolve uses UMFPACK for direct sparse solving; scipy.sparse.linalg.cg / gmres for iterative. In MATLAB, sparse() creates CSC matrices; the backslash operator automatically detects sparsity and selects an appropriate direct solver. For large-scale problems, PyAMG provides algebraic multigrid preconditioners. NetworkX and igraph store graphs as adjacency lists; to use sparse linear algebra (PageRank, Laplacian eigenvectors), convert to scipy.sparse first. FAISS and annoy use sparse representations for approximate nearest-neighbor search. When building a finite-element solver, always assemble in COO format and convert to CSR once before the iterative solve — do not convert during assembly.

Key Takeaways

Sparse matrices have overwhelmingly zero entries; exploiting this reduces memory from O(n²) to O(nnz) and computation from O(n³) to O(nnz) per SpMV. COO format (row, col, val triples) is easiest to assemble; CSR (val, col_ind, row_ptr) is most efficient for SpMV; CSC is natural for column operations and direct factorization. SpMV is the critical primitive for iterative solvers and graph algorithms. Real-world sparse matrices arise in graphs (adjacency/Laplacian), FEM (local support basis functions), and Markov chains (sparse transitions). Fill-in during factorization can destroy sparsity; reordering algorithms (nested dissection, minimum degree) reduce fill-in from O(n²) to O(n log n) in 2D. Sparse direct solvers are robust and optimal for multiple right-hand sides; iterative methods are essential for 3D problems, matrix-free settings, and very large n. Hybrid preconditioned iterative methods — ILU, multigrid, domain decomposition — blend the best of both worlds.