Elimination, and the swap

Elimination is a sequence of choices

Gaussian elimination is taught as a procedure with no decisions in it. There is one decision at every step — which row to use — and every stability property the algorithm has comes from making it well.

Gaussian elimination is the first algorithm in the subject and it is normally presented as a recipe: subtract multiples of the first row from the others to clear the first column, repeat on what remains, back-substitute. Followed exactly, it produces the answer, and there is nothing in the description that looks like a choice.

There is one at every step, and it is the only thing standing between the algorithm and failure.

Gaussian elimination on a 4×4, one step at a timeFour copies of the same matrix: as given, and after each of the three elimination steps. The pivot in use is outlined and the entries reduced to zero are greyed.21-13-3-121-212-443-12as givenrows in the order 1 2 3 443-1201.251.252.502.51.5-30-0.5-0.52after step 1pivot 443-1202.51.5-3000.5400-0.21.4after step 2pivot 2.543-1202.51.5-3000.540003after step 3pivot 0.5‖PA − LU‖/‖A‖0largest multiplier0.75row order 4 3 2 1the pivot is chosen
Fig. 1 The same four-by-four matrix, as given and after each of the three elimination steps. The pivot in use is outlined; the entries reduced to zero are greyed; the block still to be worked on is tinted. The badge carries the two numbers that say the factorisation is what it claims: the residual ‖PA − LU‖/‖A‖, and the largest multiplier used.

What is actually being computed

The recipe hides what it produces. Elimination does not merely solve a system; it computes a factorisation, and the factorisation is more useful than the solution.

Every step subtracts a multiple of the pivot row from the rows below it. Record those multiples and they assemble into a unit lower triangular matrix L. What is left at the end is upper triangular, call it U. Then A = LU, or with row interchanges, PA = LU where P is the permutation that describes which rows were swapped.

This matters for three reasons. It separates the O(n³) work from the O(n²) work, so a second right-hand side costs a hundredth of the first. It makes the determinant free — the product of the pivots, with a sign for the swaps. And it makes the algorithm checkable in a way a bare solution is not: ‖PA − LU‖ is a number, and it must be at the level of rounding, and if it is not then something is wrong with the factorisation rather than with the problem.

That last point is the site’s rule and the reason it appears on every elimination figure here. A solution can be wrong for reasons that have nothing to do with the algorithm. A factorisation cannot.

The choice

At step k the algorithm needs a nonzero entry in position (k,k) to divide by. If the entry sitting there is zero, it must swap in a row that has something nonzero in that column, or it cannot proceed.

That much is forced. What is not forced — and is the whole content of the choice — is what to do when the entry is nonzero but small.

Partial pivoting is the answer everyone uses: scan the column from row k downwards, find the entry of largest absolute value, and swap that row into position. It costs one pass over a column per step, which is nothing next to the elimination itself.

What it buys is a bound. The multiplier used to eliminate row i is U[i][k] divided by the pivot, and since the pivot was chosen to be the largest of them, every multiplier is at most 1 in absolute value. That single fact is the entire stability argument for Gaussian elimination, and it is worth seeing on the figure rather than taking on trust — the badge reports the largest multiplier, and it is required to be ≤ 1 on every build.

Without the swap, a multiplier can be anything at all. On the two-by-two in the swap that is not optional it is 10¹⁷, and the subsequent subtraction annihilates the useful part of the row it touches.

Why bounded multipliers are enough — almost

The reasoning is short. Each entry of U is the original entry minus a sum of products of multipliers with earlier entries. If every multiplier is at most 1, no single step can more than double the largest entry present, so after n−1 steps the largest entry of U is at most 2ⁿ⁻¹ times the largest entry of A.

That ratio — max|u| over max|a| — is the growth factor, and the error analysis of Gaussian elimination says the backward error is bounded by roughly n² times the growth factor times the unit roundoff. So bounded multipliers give a bounded growth factor, which gives a bounded backward error, which is backward stability.

The “almost” is that 2ⁿ⁻¹ is a very large bound. At n = 40 it is 5.5·10¹¹, which would swamp double precision entirely. In practice the growth factor of a random matrix at n = 40 is about three. The bound that is never attained is about that gap, about the one matrix that does attain it, and about why an algorithm with a useless worst-case bound is nonetheless the one everybody uses.

Growth factor under partial pivoting: the bound, the worst case, and realityGrowth factor against matrix size on a logarithmic vertical axis. The two-to-the-n bound rises as a straight line; Wilkinson's matrix sits exactly on it; random matrices stay near one.0816243240110²10⁴10⁶10⁸10¹⁰10¹²10¹⁴matrix size ngrowth factor max|u| / max|a|the 2ⁿ⁻¹ boundworst of 30 randommedian randomWilkinson's matrix sits on the bound30 Gaussian matrices per sizeat n = 40: bound 5.5·10¹¹, worst 4.8
Fig. 2 The bound, the worst case, and reality. The 2ⁿ⁻¹ line rises steeply; Wilkinson’s matrix sits exactly on it; thirty random matrices at each size stay near three. Eleven orders of magnitude separate the guarantee from the behaviour, and both numbers are measured rather than quoted.

What the picture shows that a description does not

Three things, and they are the reason the figure is a matrix rather than a flowchart.

The zeros are made, not found. In the “after step 1” panel the first column below the pivot is grey, and it is grey because three subtractions put zeros there. The algorithm does not discover structure in the matrix; it destroys structure and records what it destroyed.

The remaining block is a smaller instance of the same problem. After step k the untouched region is an (n−k)×(n−k) matrix, and the next step is elimination applied to it. That recursive structure is what makes the operation count n³/3 and what makes blocked implementations possible.

The entries change size. This is the part that only a picture with numbers on it conveys. The entries of U are not the entries of A rearranged; they are new numbers, and how large they get is the quantity the stability of the whole thing depends on.

The row order, recorded

The permutation is not a detail. The factorisation is of PA, not of A, and forgetting the P produces a solver that returns confident nonsense.

Concretely: the figure’s matrix ends with its rows in the order 4, 1, 3, 2. That order is the record of which row had the largest entry at each step, and it has to be applied to the right-hand side before the forward substitution. In an implementation the permutation is usually stored as a vector of indices rather than a matrix, because a matrix multiplication to permute n numbers is an extravagance.

The check that catches a permutation bug is the site’s rule again: ‖PA − LU‖. Get the permutation wrong and that residual is of order 1 rather than of order 10⁻¹⁶ — not slightly wrong, completely wrong — because L and U are then factors of a different matrix. It is the single most effective one-line test for a hand-written LU, and it is why the badge on these figures carries it rather than carrying the residual of the solve.

Two routes to the determinant

The other quantity elimination produces for free is the determinant, and it is a good example of the site’s habit of computing everything twice.

Route one: the product of the diagonal entries of U, times −1 for each row interchange. This costs nothing beyond the factorisation.

Route two: cofactor expansion, which is the definition. For a four-by-four it is 24 terms of four factors each, it is exact on an integer matrix, and it shares no arithmetic whatever with the elimination.

They must agree, and the assertion is run on every build. What it catches is a sign error in the swap counting, which is otherwise invisible: a determinant of the wrong sign looks entirely plausible, and every other property of the factorisation is unaffected by it. Two routes to a number is the thread, and this is the cheapest instance of it on the site.

Wilkinson's 7×7 matrix and its upper triangular factorThe matrix on the left has ones on the diagonal, minus ones below it and a column of ones at the right. On the right, its U factor, whose last column doubles down the rows to sixty-four.1·····1-11····1-1-11···1-1-1-11··1-1-1-1-11·1-1-1-1-1-111-1-1-1-1-1-11A1·····1·1····2··1···4···1··8····1·16·····132······64U‖PA − LU‖/‖A‖0growth factor64the 2ⁿ⁻¹ bound64Row interchanges performed: 0.Partial pivoting had nothing to choose.every entry is 0, 1 or −1the bound is attained here
Fig. 3 A factorisation where the entries do grow, drawn side by side with the matrix that produced it. Every entry of A is 0, 1 or −1; the last column of U doubles down the rows to 64. Partial pivoting performed no swaps at all, because every pivot was already the largest in its column — the choice was made correctly and the growth happened anyway.

What elimination costs

The operation count is n³/3 multiply-and-subtract pairs for the factorisation, and n² for each solve that follows. Three practical consequences follow, and they shape how the algorithm is used.

Reuse the factorisation. Solving with a second right-hand side costs 1% of the first at n = 100 and 0.01% at n = 10,000. Code that calls a black-box solve(A, b) in a loop over right-hand sides is doing a hundred times more work than it needs to.

Never form the inverse. Computing A⁻¹ costs three times as much as factorising, and then every solve is a matrix–vector product — the same n² as a triangular solve pair, but with worse accuracy, because the explicit inverse has been through more arithmetic. x = inv(A) * b is slower and less accurate than x = A \ b, and it is written constantly.

The n³ is why structure matters. A banded matrix, a sparse one, a symmetric positive definite one — each admits a cheaper factorisation, and for large problems that is the difference between possible and not. This site’s foundation is dense; sparsity and fill-in are in the expansion, and they are where the elimination order becomes a combinatorial question rather than a numerical one.

Why L is unit lower triangular

A detail that looks like a convention and is a consequence.

The diagonal of L is all ones, always, and it is not stored. That is not a normalisation chosen for tidiness — it falls out of what L records. Row i of L holds the multipliers used to eliminate row i against each earlier pivot row, and row i’s relationship to itself is the identity, so the diagonal entry is 1 by construction.

The practical effect is that L and U together fit in exactly the storage the original matrix occupied. U goes in the upper triangle including the diagonal, L’s strictly lower part goes in the lower triangle, and the ones are implied. Every serious implementation factorises in place, and the permutation vector is the only extra storage required.

That in-place structure is also why the picture in the filmstrip is honest about what is happening: the greyed entries below the diagonal are not gone, they are where the multipliers now live. A figure showing them as zeros would be showing the mathematics and hiding the computation.

What symmetry buys

For a symmetric positive definite matrix, the whole apparatus simplifies and one worry disappears entirely.

The factorisation becomes A = LLᵀ — Cholesky — with half the arithmetic, half the storage, and no pivoting at all. That is not a heuristic. Positive definiteness guarantees that the diagonal entry at each step is the largest in its column, so the pivot search would find it anyway, and the growth factor is bounded by 1.

The consequence for the bound that is never attained is worth noting: for this whole class of matrices there is no gap between the worst case and the behaviour, because the worst case is 1. Cholesky is the one dense factorisation in the subject whose stability requires no empirical argument whatsoever.

It also gives a test. Cholesky succeeds if and only if the matrix is positive definite — a negative number under the square root is a definitive answer, not a numerical accident. Attempting the factorisation is the standard way to check definiteness, and it is cheaper than computing eigenvalues.

Jacobi sweeps on the 6×6 Hilbert matrixA plot of the off-diagonal norm against sweep number, falling from about one to ten to the minus seventeen in five sweeps, with the matrix shown at three stages beneath it.01234567810⁻¹⁷10⁻¹⁴10⁻¹¹10⁻⁸10⁻⁵10⁻²10¹sweep‖off-diagonal‖eigenvalues, largest first1.6190.24240.016326.157·10⁻⁴1.257·10⁻⁵1.083·10⁻⁷κ = 1.5·10⁷‖VᵀV − I‖ = 1.9·10⁻¹⁵1.000.500.330.250.200.170.500.330.250.200.170.140.330.250.200.170.140.130.250.200.170.140.130.110.200.170.140.130.110.100.170.140.130.110.100.09as given1.620.050.00-0.040.00-0.000.050.240.000.010.00-0.000.000.000.000.00-0.00-0.00-0.040.010.000.020.000.000.000.00-0.000.000.00·-0.00-0.00-0.000.00·0.00after one sweep1.62······0.24······0.00······0.02······0.00······0.00after 8 sweepseach sweep is n(n−1)/2 rotationsthe trace is conserved
Fig. 4 The symmetric case elsewhere. The Hilbert matrix is symmetric positive definite, so it needs no pivoting and its eigenvalues come out to full accuracy — and it is nonetheless one of the worst conditioned matrices in the subject. Structure and conditioning are independent properties, and having one says nothing about the other.

The order the entries are touched in

One more thing the filmstrip shows that the recipe does not, and it is the reason the algorithm as written above is not the algorithm in a library.

The version drawn here touches the matrix a row at a time, which is how it is derived and how it is taught. A production implementation reorganises the same arithmetic into blocks — factor a panel of columns, then update the remaining submatrix with a single large matrix multiplication — because the arithmetic is identical and the memory traffic is not.

The rounding is not identical, though. Reordering the operations changes which sums are accumulated in which order, and the order they are added in is exactly about what that does. Blocked LU is typically slightly more accurate than the unblocked version, because the inner updates are matrix multiplications performed with a longer accumulation.

This is why a bit-for-bit comparison between two LU implementations is not a meaningful test, and why the checks on this site compare residuals and invariants rather than entries.

What is asserted here

Every claim in this essay is checked when the figure is generated, which means on every build.

The filmstrip’s last frame must be exactly the U that the factorisation routine returns, entry by entry, to 10⁻¹². That sounds pedantic and it is the assertion that catches a figure showing one computation while its caption describes another — the elimination in the generator is written out separately, so that it can record the intermediate states, and nothing but this check keeps the two implementations in agreement.

‖PA − LU‖/‖A‖ must be below 10⁻¹³. The largest multiplier must be at most 1. The determinant from the pivots must equal the determinant from cofactors to 10⁻¹².

And the growth measurement is fed something it must not miss: Wilkinson’s matrix, whose growth factor is exactly 2ⁿ⁻¹. A measurement routine that reported small growth for everything would make the bound that is never attained a meaningless essay, so it is shown the one matrix that breaks it and required to notice.

Where elimination sits in the collection

Elimination is the first algorithm in the subject and the last one to be fully understood, and the three essays of this field take it in that order.

This one is about what the algorithm computes and what the choice at each step is. The swap that is not optional runs the version that makes the choice badly, and measures a backward error of 0.25 on a matrix with κ ≈ 2.6 — a well conditioned problem answered wrongly, which is the one situation where the algorithm is unambiguously to blame. The bound that is never attained is about the quantity the whole stability argument rests on, and about the fact that its bound is eleven orders of magnitude away from its behaviour.

Read together, they are a case study in the distinction the exact answer to a nearby problem sets up. The same algorithm, on the same kind of matrix, is backward stable or not depending on one comparison per step; and when it is stable, everything that remains wrong with the answer belongs to the problem.

Backward and forward error against the condition numberA log–log plot over twelve decades of condition number. The backward error is a flat line at ten to the minus sixteen; the forward error rises in proportion to the condition number.110²10⁴10⁶10⁸10¹⁰10¹²10¹⁴10⁻¹⁷10⁻¹⁴10⁻¹¹10⁻⁸10⁻⁵10⁻²10¹condition number κ(A)relative errorforward errorbackward errorpredicted: κ · u8×8, 20 seeds per κ; dashed is the worstthe problem worsens, not the method
Fig. 5 What a correctly pivoted elimination delivers, across twelve orders of magnitude of difficulty. The backward error does not move — the factorisation is as good on the worst matrix as on the best. The forward error rises with κ, and none of that rise is the elimination’s doing.

The one thing to take away

Anyone writing an LU factorisation by hand should, print ‖PA − LU‖/‖A‖ once and look at it.

It costs two matrix multiplications on a matrix already in memory, it takes five minutes, and it distinguishes a working factorisation from every common way of getting one wrong: a permutation applied in the wrong direction, an off-by-one in the multiplier loop, a forgotten sign, an L whose diagonal was stored when it should have been implied. All of those produce a factorisation that looks entirely normal and reconstructs a different matrix.

That is the reason the rule this site runs on is about residuals rather than about answers. An answer can be wrong for reasons no code change would fix. A factorisation that does not reconstruct its matrix is a bug, every time, and it is one number away from being visible.

The exact solution of a 13×13 Hilbert system beside the computed oneTwo columns of numbers: the exact answer, which is the integers one to thirteen, and the answer double-precision elimination returns, with the number of correct digits beside each.H13 x = b, b formed exactly so that x = (1, 2, …, 13)12345678910111213exact1.00002.00003.00133.97865.18864.993010.46720.048921.2657-2.575319.21478.906013.5113computed6.6 correct digits4.8 correct digits3.4 correct digits2.3 correct digits1.4 correct digit0.8 correct digitno correct digitsno correct digitsno correct digitsno correct digitsno correct digits0.6 correct digit1.4 correct digitbackward error2.2·10⁻¹⁷κ = 1.7·10¹⁸The algorithm solved a neighbouring problem perfectly. That problem's answer is this one.right-hand side built in BigInt rationalsthe truth is known
Fig. 6 And the other half of the habit: where a known answer is available, use it. This system’s exact solution is the integers one to thirteen, built in rational arithmetic. Elimination’s residual here is 10⁻¹⁷ and four components of the answer have no correct digits — two facts that only sit comfortably together once both numbers are on the page.