determin.ant
03-systems / 3.3

Module 3: Systems of Equations

LU Decomposition

Where you'll see this: almost every serious linear algebra library (LAPACK, NumPy, MATLAB) solves systems using LU, not raw Gaussian elimination. When you call np.linalg.solve, it's doing LU decomposition under the hood. It's also the fastest way to compute determinants and inverses for large matrices.

Gaussian elimination is an algorithm. LU decomposition records that algorithm as a matrix factorization — so you can reuse the work.

The idea

Every invertible matrix A can be written as:

A=LUA = LU

where L is lower triangular with 1s on the diagonal, and U is upper triangular. These are exactly the two triangles produced by Gaussian elimination.

Where L and U come from

When you do Gaussian elimination on a 2×2 matrix:

[abcd][ab0dcab]\begin{bmatrix} a & b \\ c & d \end{bmatrix} \to \begin{bmatrix} a & b \\ 0 & d - \frac{c}{a}b \end{bmatrix}

The multiplier m=c/am = c/a is the number you used to eliminate c. It becomes the off-diagonal entry of L:

L=[10m1],U=[ab0dmb]L = \begin{bmatrix} 1 & 0 \\ m & 1 \end{bmatrix}, \quad U = \begin{bmatrix} a & b \\ 0 & d - mb \end{bmatrix}

Why it matters

To solve Ax=bA\mathbf{x} = \mathbf{b}, substitute A = LU:

LUx=bLy=b,Ux=yLU\mathbf{x} = \mathbf{b} \quad \Rightarrow \quad L\mathbf{y} = \mathbf{b}, \quad U\mathbf{x} = \mathbf{y}

Each sub-problem (forward substitution for Ly = b, back substitution for Ux = y) is trivial because the matrices are triangular. And if you need to solve with multiple right-hand sides b₁, b₂, …, you only factorize A once.

LU = "do elimination once, solve many times." The factorization stores all the work; each new solve is cheap.

Pivoting

If the top-left entry is zero (or near-zero), the multiplier blows up. The fix is partial pivoting: swap rows to put the largest entry in the pivot position first. In practice, A = PLU where P is a permutation matrix recording the swaps.

Try it

Edit the matrix or pick a preset. The L and U factors update live. Notice that L always has 1s on the diagonal and zeros above, while U has zeros below. Their product reconstructs A exactly.

In SciPy:
from scipy.linalg import lu
import numpy as np

A = np.array([[2., 1.], [4., 3.]])
P, L, U = lu(A)

print(L)  # lower triangular
print(U)  # upper triangular
print(np.allclose(P @ L @ U, A))  # True

# Efficient: solve multiple systems with same A
import scipy.linalg
lu_factor = scipy.linalg.lu_factor(A)
x1 = scipy.linalg.lu_solve(lu_factor, [1, 2])
x2 = scipy.linalg.lu_solve(lu_factor, [3, 4])
Gaussian EliminationSolution Types
A
A = L · U
L(lower, 1s on diag)
1021
×
U(upper triangular)
2101

Multiplier: L[2,1] = 2 (how much row 1 was subtracted from row 2)

det(A) = det(L)·det(U) = 1 · 2·1 = 2