Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

 Open In Colab

The Necessity of Preconditioning

While direct solvers (like LU\mathbf{L}\mathbf{U} decomposition) are incredibly robust, iterative solvers are highly sensitive to the condition number of the matrix. For large, complex physics problems, iterative solvers often converge at a glacial pace—or fail to converge at all.

The strategy of preconditioning is to modify the original linear system into an algebraically equivalent system that behaves better.

Given a system Ax=b\mathbf{A}\mathbf{x}=\mathbf{b}, we multiply by a preconditioner matrix P−1\mathbf{P}^{-1} (where P−1≈A−1\mathbf{P}^{-1} \approx \mathbf{A}^{-1}). This pushes the condition number of the resulting system closer to 1.

Matrix Application Strategies

There are three ways to apply the preconditioner P−1\mathbf{P}^{-1}:

1. Left Preconditioning (Most Common):

P−1Ax=P−1b\mathbf{P}^{-1} \mathbf{A} \mathbf{x} = \mathbf{P}^{-1} \mathbf{b}

2. Right Preconditioning:

AP−1y=bwherePx=y\mathbf{A} \mathbf{P}^{-1} \mathbf{y} = \mathbf{b} \quad \text{where} \quad \mathbf{P} \mathbf{x} = \mathbf{y}

3. Two-Sided Preconditioning:

QAP−1y=QbwherePx=y\mathbf{Q} \mathbf{A} \mathbf{P}^{-1} \mathbf{y} = \mathbf{Q} \mathbf{b} \quad \text{where} \quad \mathbf{P} \mathbf{x} = \mathbf{y}

(Note: Two-sided preconditioning is often required to preserve the symmetry of A\mathbf{A}, a critical property for solvers like Conjugate Gradient).

The Ideal Preconditioner

The theoretically “perfect” preconditioner is P=A\mathbf{P} = \mathbf{A}, which trivially solves the system in one step. However, finding A−1\mathbf{A}^{-1} explicitly defeats the entire purpose of an iterative solver!

Furthermore, the exact inverse of a sparse matrix is almost always completely dense. Therefore, we never calculate or store P−1\mathbf{P}^{-1} explicitly in memory. Instead, we store P\mathbf{P} (or its factors) and provide the algorithm with a method for applying P−1\mathbf{P}^{-1} to a vector.

A high-quality preconditioner requires balancing:

  • Speed: How fast it can be computed and applied.

  • Memory: The degree of fill-in (sparsity) compared to A\mathbf{A}.

  • Parallelizability: How well it scales across compute clusters.

Let’s test several common preconditioners against a physical system!

The Test Problem: The Poisson Equation

As a working example, let’s discretize a standard Poisson heat-transfer equation on a 2D unit square grid (discussed later in differential equations).

1. Jacobi (Diagonal) Preconditioning

The simplest preconditioner is the Jacobi preconditioner. It simply extracts the main diagonal of A\mathbf{A}. Because it is purely diagonal, its inverse is trivially computed by taking the reciprocal of the diagonal elements, meaning it requires almost zero memory or compute overhead!

Let’s apply it via scipy.sparse.linalg.gmres and track the residual convergence.

The plot shows that the convergence curve barely budges. While cheap to apply, Jacobi is rarely powerful enough for complex PDEs.

2. Incomplete LU (ILU)

We know that a full LU\mathbf{L}\mathbf{U} decomposition solves the system exactly. What if we calculate an approximate L~U~\tilde{\mathbf{L}}\tilde{\mathbf{U}} decomposition that is cheap to calculate and apply, and use them to form a preconditioner P=L~U~≈A\mathbf{P}=\tilde{\mathbf{L}}\tilde{\mathbf{U}} \approx \mathbf{A}.

As we discussed previously, matrix sparsity heavily determines the cost of assembly, storage, and application.

LU fill-in

LU decomposition preserves the bandwidth of the (A)\mathbf(A) matrix, but typically fills in the elements within that band.

Let’s look at the fill-in of the exact L\mathbf{L} and U\mathbf{U} matrices for our Poisson problem:

Incomplete LU decomposition

Let’s use scipy.sparse.linalg.spilu to compute an Incomplete LU (ILU) factorization buy specifying a drop tolerance, a tolerance underneath which elements are never calculated (thus reducing computation load).

Notice that we explicitly set drop_tol=1e-1 to aggressively prune the fill-in. Let’s visually compare the sparsity of our aggressively pruned Incomplete L\mathbf{L} matrix against the Exact L\mathbf{L} matrix!

Applying the preconditioner

Notice that we don’t pass an inverse matrix into GMRES; we pass a LinearOperator function that solves Px=y\mathbf{P}\mathbf{x} = \mathbf{y} based on the ‘ilu’ object defined previously.

The ILU preconditioner achieves good convergence by partially solving the problem ahead of time. Try changing the drop tolarance from before and see the effect.

ILU is a standard workhorse for serial computations, though its sequential forward/backward substitutions make it notoriously difficult to parallelize.

Preconditioners in High Performance Computing

High Performance Computing (HPC) relies on clusters of distributed nodes (thousands of CPUs) connected via a network. To solve massive physical systems, the data must be chunked across the nodes.

Because parallelizing exact direct solvers requires massive amounts of slow inter-node network communication, HPC systems rely heavily on fast iterative solvers combined with highly specialized geometric preconditioners.

Domain Decomposition (Additive Schwarz)

Domain Decomposition is the most intuitive geometric approach. You literally slice the physical CAD geometry into chunks and hand each chunk to a different compute node.

dpw_near_grid.png

A popular approach is the Additive Schwarz method, which creates overlapping boundaries between adjacent subdomains. Each node uses a robust direct solver (like LU\mathbf{L}\mathbf{U}) on its local chunk, and the overlapping regions allow the global iterative solver to rapidly stitch the solutions together across the network.

Multigrid Methods

Multigrid methods are modern, phenomenally powerful algorithms capable of achieving theoretically perfect O(n)\mathcal{O}(n) scaling!

image_20.png

The physical PDE is solved sequentially on a hierarchy of meshes—from incredibly fine grids down to very coarse grids. The high-resolution fine grid captures local details, but struggles to resolve global trends quickly. By projecting the residual down to the coarse grid, the solver can propagate global changes instantly. The coarse grid is often small enough to be solved entirely with a direct LU\mathbf{L}\mathbf{U} solver, and the result is interpolated back up.

Multiphysics Block Preconditioning

Often, physical systems involve tightly coupled equations (e.g., fluid flow interacting with structural mechanics). This produces a massive block matrix system:

[AfluidBcouplingCcouplingDstructure][xfxs]=[bfbs]\begin{bmatrix} \mathbf{A}_{fluid} & \mathbf{B}_{coupling} \\ \mathbf{C}_{coupling} & \mathbf{D}_{structure} \end{bmatrix} \begin{bmatrix} \mathbf{x}_f \\ \mathbf{x}_s \end{bmatrix} = \begin{bmatrix} \mathbf{b}_f \\ \mathbf{b}_s \end{bmatrix}

How do we precondition this block A\mathbf{A} matrix? One powerful approach is using the Block Schur Complement:

[ABCD]−1=[A−1+A−1B(D−CA−1B)−1CA−1−A−1B(D−CA−1B)−1−(D−CA−1B)−1CA−1(D−CA−1B)−1]\begin{bmatrix} \mathbf{A} & \mathbf{B} \\ \mathbf{C} & \mathbf{D} \end{bmatrix}^{-1} = \begin{bmatrix} \mathbf{A}^{-1} + \mathbf{A}^{-1}\mathbf{B}(\mathbf{D} - \mathbf{C}\mathbf{A}^{-1}\mathbf{B})^{-1}\mathbf{C}\mathbf{A}^{-1} & -\mathbf{A}^{-1}\mathbf{B}(\mathbf{D} - \mathbf{C}\mathbf{A}^{-1}\mathbf{B})^{-1} \\ -(\mathbf{D} - \mathbf{C}\mathbf{A}^{-1}\mathbf{B})^{-1}\mathbf{C}\mathbf{A}^{-1} & (\mathbf{D} - \mathbf{C}\mathbf{A}^{-1}\mathbf{B})^{-1} \end{bmatrix}

By mathematically isolating the highly non-linear interaction terms, solvers can apply completely different preconditioners to the fluid block vs the structure block, drastically improving convergence for complex multiphysics simulations!