Skip to content

PDE Discretization

Sparse matrices from discretizing PDEs.

Mental Model

Discretizing a PDE on a grid turns it into a linear system where each grid point only interacts with its immediate neighbors. The resulting matrix is extremely sparse -- a 1000-point grid produces a matrix with a million entries but only a few thousand nonzeros. Sparse solvers exploit this structure to solve PDE systems that would be impossible with dense methods.

1D Laplacian

```python import numpy as np from scipy import sparse from scipy.sparse import linalg as splinalg

def main(): n = 100 h = 1 / (n + 1)

# Tridiagonal Laplacian
A = sparse.diags([-1, 2, -1], [-1, 0, 1], shape=(n, n)) / h**2

# Solve -u'' = f with u(0) = u(1) = 0
x = np.linspace(h, 1-h, n)
f = np.sin(np.pi * x)

u = splinalg.spsolve(A.tocsr(), f)

print(f"Solution at midpoint: {u[n//2]:.6f}")
print(f"Exact: {np.sin(np.pi * 0.5) / np.pi**2:.6f}")

if name == "main": main() ```

2D Laplacian

```python import numpy as np from scipy import sparse

def main(): n = 10 # Grid points per dimension N = n * n # Total unknowns

# 5-point stencil
diag = 4 * np.ones(N)
off1 = -np.ones(N - 1)
off1[n-1::n] = 0  # No wrap at row boundaries
offn = -np.ones(N - n)

A = sparse.diags([offn, off1, diag, off1, offn], 
                 [-n, -1, 0, 1, n], format='csr')

print(f"2D Laplacian: {A.shape}")
print(f"Nonzeros: {A.nnz}")
print(f"Density: {A.nnz / N**2:.4%}")

if name == "main": main() ```


Exercises

Exercise 1. Construct the 1D Laplacian matrix for \(n = 50\) interior grid points with grid spacing \(h = 1/(n+1)\). Solve the Poisson equation \(-u'' = \pi^2 \sin(\pi x)\) with homogeneous Dirichlet boundary conditions. Compare the numerical solution at the midpoint with the exact solution \(u(x) = \sin(\pi x)\) and print the absolute error.

Solution to Exercise 1
import numpy as np
from scipy import sparse
from scipy.sparse import linalg as splinalg

n = 50
h = 1 / (n + 1)
A = sparse.diags([-1, 2, -1], [-1, 0, 1], shape=(n, n)) / h**2

x = np.linspace(h, 1 - h, n)
f = np.pi**2 * np.sin(np.pi * x)

u = splinalg.spsolve(A.tocsr(), f)

mid = n // 2
exact = np.sin(np.pi * x[mid])
error = abs(u[mid] - exact)
print(f"Numerical at midpoint: {u[mid]:.8f}")
print(f"Exact at midpoint:     {exact:.8f}")
print(f"Absolute error:        {error:.2e}")

Exercise 2. Build the 2D Laplacian using the 5-point stencil for a \(20 \times 20\) interior grid. Print the matrix shape, the number of nonzeros, and the sparsity (percentage of zero entries). Verify that the matrix is symmetric by checking that (A - A.T).nnz == 0.

Solution to Exercise 2
import numpy as np
from scipy import sparse

n = 20
N = n * n

diag = 4 * np.ones(N)
off1 = -np.ones(N - 1)
off1[n - 1::n] = 0  # no wrap at row boundaries
offn = -np.ones(N - n)

A = sparse.diags([offn, off1, diag, off1, offn],
                 [-n, -1, 0, 1, n], format='csr')

print(f"Shape: {A.shape}")
print(f"Nonzeros: {A.nnz}")
sparsity = 1 - A.nnz / (N * N)
print(f"Sparsity: {sparsity:.4%}")
print(f"Symmetric: {(A - A.T).nnz == 0}")

Exercise 3. For the 1D Laplacian with \(n = 200\), compute the 5 smallest eigenvalues using scipy.sparse.linalg.eigsh. Compare them with the exact eigenvalues \(\lambda_k = \frac{4}{h^2}\sin^2\!\bigl(\frac{k\pi h}{2}\bigr)\) for \(k = 1, \ldots, 5\) and print the maximum absolute error.

Solution to Exercise 3
import numpy as np
from scipy import sparse
from scipy.sparse import linalg as splinalg

n = 200
h = 1 / (n + 1)
A = sparse.diags([-1, 2, -1], [-1, 0, 1], shape=(n, n)) / h**2

vals, _ = splinalg.eigsh(A.tocsr(), k=5, which='SM')
vals = np.sort(vals)

exact = np.array([4 / h**2 * np.sin(k * np.pi * h / 2)**2
                  for k in range(1, 6)])
max_error = np.max(np.abs(vals - exact))
print(f"Numerical eigenvalues: {vals}")
print(f"Exact eigenvalues:     {exact}")
print(f"Max absolute error:    {max_error:.2e}")