LU Decomposition and Partial Pivoting

Published on 2026-11-04·v1.0

Learning Objectives

  • Read LU decomposition as Gaussian elimination with its multipliers recorded, and state when A=LUA=LU exists.
  • Compute PA=LUPA=LU by hand with partial pivoting, then solve by forward and back substitution and reuse the factors for a new right-hand side.
  • Count the exact number of floating-point operations in the factorization and in each solve.
  • Show in float64 how elimination without pivoting destroys the answer on a well-conditioned matrix, and measure forward error, backward error, and growth.
  • Separate the partial-pivoting pivot from the simplex pivot, and name the cases where partial pivoting is unnecessary or insufficient.

Context & Motivation

gaussian-elimination solved a system by row operations, with a swap only when a needed pivot was zero. Two things change on a computer. First, that lesson threw its multipliers away, so a second right-hand side would repeat all the work, although the matrix is the same. Keeping the multipliers fixes this: the work on AA is done once and each new bb is cheap. Second, in floating point the swap rule is not enough. A pivot that is tiny rather than zero passes the rule and ruins the result, even when the problem itself is well conditioned, in the sense of conditioning-and-numerical-stability.

Core Theory

Elimination records its own inverse

Step kk subtracts mik=aik/akkm_{ik}=a_{ik}/a_{kk} times row kk from each row i>ki>k. In matrix form that is MkAM_kA with Mk=I−mkekTM_k=I-\mathbf m_k\mathbf e_k^T, where mk\mathbf m_k holds the multipliers below position kk. After n−1n-1 steps Mn−1⋯M1A=UM_{n-1}\cdots M_1A=U is upper triangular. Each inverse just flips a sign, Mk−1=I+mkekTM_k^{-1}=I+\mathbf m_k\mathbf e_k^T, and in the order they appear the products do not interact:

A=M1−1⋯Mn−1−1⏟L U,L=I+∑k=1n−1mkekT.A=\underbrace{M_1^{-1}\cdots M_{n-1}^{-1}}_{L}\,U,\qquad L=I+\sum_{k=1}^{n-1}\mathbf m_k\mathbf e_k^T.

So LL is unit lower triangular with the multipliers sitting in place. Elimination needs no second matrix: multipliers overwrite the zeros they create, which is the format scipy.linalg.lu_factor returns.

Existence. A square AA has A=LUA=LU with LL unit lower triangular and UU nonsingular upper triangular if and only if every leading principal submatrix of AA is nonsingular. Nonsingularity of AA alone is not enough: (0110)\begin{pmatrix}0&1\\1&0\end{pmatrix} is nonsingular and has no such factorization, since u11=0u_{11}=0 would contradict l21u11=1l_{21}u_{11}=1.

Why a small pivot does damage

Rounding errors in a factorization scale with the entries of LL and UU, not with those of AA. A small pivot makes a large multiplier, which scales a row up, and adding that to a smaller row rounds the smaller row away. Two measurements watch this: the growth factor ρ=max⁡i,j∣uij∣/max⁡i,j∣aij∣\rho=\max_{i,j}\lvert u_{ij}\rvert/\max_{i,j}\lvert a_{ij}\rvert, and the backward error ∥b−Ax^∥∞/(∥A∥∞∥x^∥∞+∥b∥∞)\lVert b-A\hat x\rVert_\infty\big/\big(\lVert A\rVert_\infty\lVert\hat x\rVert_\infty+\lVert b\rVert_\infty\big), which says how much the data must change for x^\hat x to be an exact solution. The relative forward error is at most about the condition number times the backward error, so a large forward error on a well-conditioned matrix indicts the algorithm.

Partial pivoting and PA = LU

At step kk, search column kk on and below the diagonal for the largest magnitude, swap that row into position kk (whole rows, stored multipliers included), then eliminate. Every multiplier now has ∣mik∣≤1\lvert m_{ik}\rvert\le1. "Partial" means only a column is searched; full pivoting searches the whole remaining block.

Theorem. For every square AA there is a permutation matrix PP with PA=LUPA=LU, where LL is unit lower triangular with ∣lij∣≤1\lvert l_{ij}\rvert\le1 and UU is upper triangular. AA is nonsingular if and only if every ukk≠0u_{kk}\neq0.

To solve Ax=bAx=b, solve Ly=PbLy=Pb forward and Ux=yUx=y backward. Also det⁡A=(−1)s∏kukk\det A=(-1)^s\prod_k u_{kk} for ss swaps.

This pivot is not the simplex pivot

the-simplex-method-tableau-and-pivoting also says "pivot". The word is shared because each one divides by an entry to clear a column; the jobs are different.

Partial pivoting in LU Simplex pivot
Purpose limit rounding error move to an adjacent vertex
Entry chosen by largest magnitude in the column entering rule on the objective row, then the ratio test
Operation swap rows, eliminate below only Gauss-Jordan: clear the column everywhere, objective row included
Effect on the answer none: the same xx, by a route with less rounding changes the current solution, on purpose
In exact arithmetic needed only for zero pivots is the algorithm

The simplex rule looks at feasibility and the objective, not at entry size, so its pivot can be small. The partial-pivoting rule looks only at size.

Counting the work

Count one flop for each ++, −-, ×\times, ÷\div. Step kk does n−kn-k divisions and (n−k)2(n-k)^2 multiply-subtract pairs, so

factorization: ∑k=1n−1[(n−k)+2(n−k)2]=23n3−12n2−16n.\text{factorization: }\sum_{k=1}^{n-1}\big[(n-k)+2(n-k)^2\big]=\tfrac23n^3-\tfrac12n^2-\tfrac16n.

Forward substitution with unit diagonal costs n2−nn^2-n. Back substitution costs n2n^2, which includes nn divisions. Each solve is therefore 2n2−n2n^2-n flops. Pivot search adds n(n−1)/2n(n-1)/2 comparisons and the swaps, none of which are flops. With kk right-hand sides the total is 23n3−12n2−16n+k(2n2−n)\tfrac23n^3-\tfrac12n^2-\tfrac16n+k(2n^2-n).

Worked Examples

Example 1: the same system, now factored

Use the system of gaussian-elimination:

A=(111231112),b=(6118).A=\begin{pmatrix}1&1&1\\2&3&1\\1&1&2\end{pmatrix},\qquad b=\begin{pmatrix}6\\11\\8\end{pmatrix}.

That lesson's row operations used multipliers 22 and 11, and the third row needed nothing in column 2. Keeping them gives, with no swaps,

L=(100210101),U=(11101−1001).L=\begin{pmatrix}1&0&0\\2&1&0\\1&0&1\end{pmatrix},\quad U=\begin{pmatrix}1&1&1\\0&1&-1\\0&0&1\end{pmatrix}.

With partial pivoting, the largest entry of column 1 is the 22, so rows 1 and 2 swap. The multipliers are 12,12\tfrac12,\tfrac12, leaving rows (0,−12,12)(0,-\tfrac12,\tfrac12) and (0,−12,32)(0,-\tfrac12,\tfrac32). Column 2 is a tie, so no swap, the multiplier is 11, and

P=(010100001),L=(10012101211),U=(2310−1212001).P=\begin{pmatrix}0&1&0\\1&0&0\\0&0&1\end{pmatrix},\quad L=\begin{pmatrix}1&0&0\\\tfrac12&1&0\\\tfrac12&1&1\end{pmatrix},\quad U=\begin{pmatrix}2&3&1\\0&-\tfrac12&\tfrac12\\0&0&1\end{pmatrix}.

Now Pb=(11,6,8)Pb=(11,6,8), forward substitution gives y=(11,12,2)y=(11,\tfrac12,2), and back substitution gives x=(3,1,2)x=(3,1,2), the answer of that lesson. A new b=(1,0,0)b=(1,0,0) needs no elimination: Pb=(0,1,0)Pb=(0,1,0), y=(0,1,−1)y=(0,1,-1), x=(5,−3,−1)x=(5,-3,-1), for 15 flops. Also det⁡A=(−1)1⋅2⋅(−12)⋅1=1\det A=(-1)^1\cdot2\cdot(-\tfrac12)\cdot1=1. scipy.linalg.lu returns the same LL and UU, with its PP defined by A=PLUA=PLU, so its PP is the transpose of this one. numpy.linalg.solve gives the same two solutions.

Example 2: ordinary entries, a pivot made of noise

A=(3120.30.10.7121),x=(111),b=Ax=(61.14).A=\begin{pmatrix}3&1&2\\0.3&0.1&0.7\\1&2&1\end{pmatrix},\qquad x=\begin{pmatrix}1\\1\\1\end{pmatrix},\qquad b=Ax=\begin{pmatrix}6\\1.1\\4\end{pmatrix}.

Nothing here is small, and κ2(A)=10.3\kappa_2(A)=10.3, so the problem is well conditioned. In decimal the first step makes the (2,2)(2,2) entry exactly 0.1−0.33⋅1=00.1-\tfrac{0.3}{3}\cdot1=0, and a swap would follow. But 0.30.3 and 0.10.1 are not binary fractions. In float64, 0.3/3=0.099999999999999990.3/3=0.09999999999999999, so the entry becomes 1.39×10−171.39\times10^{-17}. That is rounding noise, it is nonzero, and the swap rule of gaussian-elimination never fires. The next multiplier is 1.667/(1.39×10−17)=1.2×10171.667/(1.39\times10^{-17})=1.2\times10^{17}, and u33=0.333−(1.2×1017)(0.5)=−6.0×1016u_{33}=0.333-(1.2\times10^{17})(0.5)=-6.0\times10^{16}, in which the 0.3330.333 has vanished. The results:

text
method x max error backward error growth no pivoting (1.3333, 0, 1.0000) 1.0 0.12 2.0e+16 partial pivoting (1, 1, 1) 2.2e-16 0 1 numpy.linalg.solve (1, 1, 1)

The residual of the unpivoted answer is (0,0,1.67)(0,0,1.67): the first two equations hold and the third was lost. Partial pivoting takes the row with 1.6671.667 as the second pivot, and the noise entry only produces a multiplier of 8×10−188\times10^{-18}.

Example 3: the size of the damage, and a scaling trap

For A=(ε111)A=\begin{pmatrix}\varepsilon&1\\1&1\end{pmatrix} and b=(1,2)b=(1,2), the exact solution is close to (1,1)(1,1) and κ∞(A)≈4\kappa_\infty(A)\approx4 for all these ε\varepsilon:

text
epsilon error, no pivoting error, partial pivoting 1e-08 5.0e-09 2.2e-16 1e-12 2.2e-05 0 1e-20 1.0 (x = (0, 1)) 0

Partial pivoting compares raw sizes, so it depends on scaling. Multiply the first equation of the ε=10−20\varepsilon=10^{-20} case by 102010^{20}: (1102011)\begin{pmatrix}1&10^{20}\\1&1\end{pmatrix} with b=(1020,2)b=(10^{20},2). The solution is the same, but the first column is now a tie, no swap happens, and both a hand-written LU and numpy.linalg.solve return (0,1)(0,1). Dividing each row by its largest entry first recovers (1,1)(1,1). Partial pivoting assumes rows of comparable scale.

The damage also appears without any planted entry. Take rng = numpy.random.default_rng(42), A = rng.uniform(-1, 1, (1000, 1000)), x = rng.uniform(-1, 1, 1000), b = A @ x, for which κ2=4269\kappa_2=4269. With relative forward error in the infinity norm:

text
forward error backward error growth max |L| no pivoting 4.5e-10 7.5e-13 2.9e+04 1.3e+03 partial 1.3e-12 1.3e-15 4.7e+01 1

Here pivoting saves more than two digits, and the implementation agrees with numpy.linalg.solve to 2.2×10−122.2\times10^{-12}.

Example 4: the counts, run

An instrumented loop that increments a counter at every arithmetic operation, on random matrices:

text
n sub mul div factor formula forward back solve 2n^2-n 2 1 1 1 3 3 2 4 6 6 3 5 5 3 13 13 6 9 15 15 10 285 285 45 615 615 90 100 190 190 100 328350 328350 4950 661650 661650 9900 10000 19900 19900 200 2646700 2646700 19900 5313300 5313300 39800 40000 79800 79800

For n=2000n=2000 the factorization costs 5.33×1095.33\times10^9 flops and one solve 8.0×1068.0\times10^6, so one factorization equals about 667 solves. For 100 right-hand sides, factoring once and solving 100 times costs 6.13×1096.13\times10^9 flops, against 5.34×10115.34\times10^{11} for 100 separate eliminations, a factor of 87.

This also settles "just invert AA". Solving for the nn columns of A−1A^{-1} adds n(2n2−n)n(2n^2-n) to the factorization, about 83n3\tfrac83n^3 in this count (less if the zeros of the unit vectors are exploited), and then A−1bA^{-1}b costs 2n2−n2n^2-n, exactly what the two triangular solves cost. It is no faster per right-hand side, and on the random matrix of Example 3 its backward error was 7.7×10−147.7\times10^{-14} against 2.0×10−152.0\times10^{-15} for numpy.linalg.solve, 38 times larger; seeds 0 to 3 gave factors from 22 to 52.

Example 5: where partial pivoting is not enough, and where it is not needed

Not enough. The theorem bounds multipliers, not growth, and ρ≤2n−1\rho\le2^{n-1} can be attained. Take ones on the diagonal, −1-1 below it, and ones in the last column. Every entry below the diagonal ties with the pivot, so no swap happens (ties keep the current row), the last column doubles at each step, and for n=60n=60:

text
kappa_2 = 26.8 growth = 5.8e+17 (2^59) numpy.linalg.solve: forward error 0.89, backward error 0.045 QR factorization: forward error 3.0e-15

So partial pivoting is not backward stable in the worst case. It is the default because such matrices are rare in practice: over the 20 matrices numpy.random.default_rng(s).uniform(-1, 1, (500, 500)) with s=0,…,19s=0,\dots,19 the growth stayed between 25 and 41.

Singular AA. AA with rows (1,2,3)(1,2,3), (4,5,6)(4,5,6), (7,8,9)(7,8,9) is singular and b=(1,2,4)b=(1,2,4) has no solution. Rounding leaves u33=−1.6×10−16u_{33}=-1.6\times10^{-16} instead of 00, so numpy.linalg.solve raises nothing and returns entries of size 6×10156\times10^{15}. scipy.linalg.solve returns the same with an ill-conditioning warning. numpy.linalg.solve does raise LinAlgError on (1224)\begin{pmatrix}1&2\\2&4\end{pmatrix}, where the pivot is exactly 00. A nonzero pivot does not prove nonsingularity.

Not needed. If each diagonal entry strictly dominates its column, it stays the largest, and partial pivoting never swaps. Take rng = numpy.random.default_rng(42), R = rng.uniform(-1, 1, (500, 500)), x = rng.uniform(-1, 1, 500), b = A @ x. With AA equal to RR except that each diagonal entry is replaced by 11 plus the sum of the absolute values of the other entries of its column, there were 0 swaps, growth 1.001.00, and no-pivot backward error 6×10−166\times10^{-16}. The symmetric positive definite A=RTR+10−3IA=R^TR+10^{-3}I, with κ2=5×105\kappa_2=5\times10^5, gave growth 0.950.95 and backward error 8×10−178\times10^{-17} with no pivoting, and such matrices use Cholesky at about half the flops. iterative-methods-jacobi-and-gauss-seidel takes up the sparse case, where factorization has a different problem.

Common Misconceptions & Pitfalls

  • "Pivoting is for zero pivots." In floating point the dangerous pivot is the small one, and in Example 2 a true zero arrived disguised as 10−1710^{-17}.
  • "A bad answer means a bad matrix." Example 2 has κ2=10.3\kappa_2=10.3. The algorithm did the damage.
  • "Partial pivoting makes LU always stable." Example 5 lost every digit at n=60n=60 with κ2=26.8\kappa_2=26.8. It is reliable in practice, not in the worst case.
  • "The simplex pivot is the same pivot." One chooses a vertex and changes the solution. The other only reduces rounding and leaves the solution alone.
  • "A small residual means an accurate solution." It means small backward error. The forward error is that times up to the condition number.
  • "Invert AA, then multiply." It costs more up front, the same per right-hand side, and its backward error was 38 times larger on the matrix of Example 4.
  • "scipy.linalg.lu gives PA=LUPA=LU." It gives A=PLUA=PLU. Check the convention before using its PP.

Summary

LU decomposition is elimination with memory: the multipliers form a unit lower triangular LL, the result is UU, and A=LUA=LU with nonsingular UU exists exactly when all leading principal submatrices are nonsingular. Partial pivoting takes the largest entry of each column, which gives PA=LUPA=LU for every square matrix with ∣lij∣≤1\lvert l_{ij}\rvert\le1. The factorization costs exactly 23n3−12n2−16n\tfrac23n^3-\tfrac12n^2-\tfrac16n flops and each solve 2n2−n2n^2-n, so reusing factors is the main practical gain. Without pivoting, a noise pivot of 10−1710^{-17} destroyed a system with κ2=10.3\kappa_2=10.3, and a random n=1000n=1000 system lost more than two digits. The pivot here reduces rounding and leaves the solution unchanged, unlike the simplex pivot. It depends on row scaling, it can be defeated by growth of 2n−12^{n-1}, and it is unnecessary for diagonally dominant and positive definite matrices.

Documentation Links