MATH-300 · Lesson 3 of 5 · 32 min

Solving linear systems

Systems of linear equations are the workhorse of engineering computation. A truss with 200 members, a circuit with 50 nodes, a temperature field on a grid of 10 000 points, a finite element model of a car body with a million unknowns: all of them come down, at some stage, to solving \(A\mathbf{x} = \mathbf{b}\). Even nonlinear problems are solved as a sequence of linear ones (Newton's method in Lesson 2). Most of a simulation's running time is spent here.

MATH-125 solved small systems by row reduction. This chapter looks at the same task from the computer's side: how much work it takes, why pivoting matters, how LU factorization lets one factorization serve many load cases, how conditioning limits accuracy, and how iterative methods handle very large, sparse systems.

Gaussian elimination

Gaussian elimination has two phases. Forward elimination subtracts multiples of each pivot row from the rows below to create zeros under the diagonal, leaving an upper triangular system. Back substitution then solves for the unknowns from the bottom up.

Worked problem

Elimination and back substitution

Solve \(2x + y - z = 8\), \(-3x - y + 2z = -11\), \(-2x + y + 2z = -3\).

Show solution
  1. Pivot 2 in row 1. Multipliers: row 2: −3/2 = −1.5; row 3: −2/2 = −1. Subtracting gives row 2: \(0.5y + 0.5z = 1\) and row 3: \(2y + z = 5\).
  2. Pivot 0.5 in row 2. Multiplier for row 3: 2/0.5 = 4. Row 3 becomes \(-z = 1\).
  3. Back substitution: \(z = -1\); \(0.5y + 0.5(-1) = 1\) gives \(y = 3\); \(2x + 3 - (-1) = 8\) gives \(x = 2\).

Answer: (x, y, z) = (2, 3, −1). Check in an original equation: −3(2) − 3 + 2(−1) = −11 ✓

How much work?

Eliminating below each pivot touches every entry of the remaining submatrix. Adding it up, forward elimination takes about \(\frac{2}{3}n^3\) floating-point operations (flops), and back substitution only about \(n^2\). The cubic growth dominates everything:

  • n = 100: about 670 000 flops, instant.
  • n = 1000: about 6.7 × 10⁸ flops, well under a second on a laptop.
  • n = 10 000: about 6.7 × 10¹¹ flops: seconds to minutes, and 800 MB just to store the matrix.
  • n = 1 000 000 (a modest finite element model): impossible as a dense matrix.

Fortunately, the large matrices of engineering are sparse: each unknown is connected only to its neighbours, so almost all entries are zero. Exploiting sparsity, through banded solvers, sparse direct solvers and iterative methods, is what makes large simulations possible.

Two matrix pictures side by side. On the left a full 12 by 12 matrix with every cell shaded, labelled dense. On the right a 12 by 12 matrix with only the diagonal and its two neighbours shaded, labelled tridiagonal: from a 1D heat or beam model, with 3n entries instead of n squared.
Dense and sparse (tridiagonal) matrices. Engineering models usually produce sparse matrices with a few nonzeros per row.

Pivoting

Elimination divides by each pivot. If a pivot is zero, the method fails outright; if it is very small, it succeeds but amplifies round-off error enormously.

Worked problem

A tiny pivot

Solve \(10^{-20}x_1 + x_2 = 1\), \(x_1 + x_2 = 2\) in double precision, without and with row exchanges. (The true solution is very close to x₁ = x₂ = 1.)

Show solution
  1. Without pivoting: multiplier \(m = 1/10^{-20} = 10^{20}\). Row 2 becomes \((1 - 10^{20})x_2 = 2 - 10^{20}\). In floating point both sides round to \(-10^{20}\), so \(x_2 = 1\).
  2. Back substitution: \(x_1 = (1 - x_2)/10^{-20} = 0\). Completely wrong.
  3. With partial pivoting: swap the rows so the larger entry, 1, is the pivot. Then the multiplier is \(10^{-20}\), row 2 becomes \(x_2 \approx 1\), and \(x_1 = 2 - x_2 = 1\).

Answer: Without pivoting: (0, 1). With pivoting: (1, 1). The same arithmetic, ordered differently, is the difference between garbage and full accuracy

Partial pivoting chooses, at each step, the row with the largest absolute entry in the pivot column and swaps it into the pivot position. This keeps every multiplier at most 1 in size and makes Gaussian elimination stable in practice. Every serious library does it automatically.

LU factorization

Elimination can be recorded as a factorization \(PA = LU\): U is the upper triangular result, L is lower triangular with 1s on its diagonal and the multipliers below it, and P is the permutation matrix of row swaps. Solving \(A\mathbf{x} = \mathbf{b}\) then takes two quick triangular solves:

\[L\mathbf{y} = P\mathbf{b} \quad (\text{forward substitution}), \qquad U\mathbf{x} = \mathbf{y} \quad (\text{back substitution})\]

The expensive \(\frac{2}{3}n^3\) work is all in the factorization; each solve costs only about \(2n^2\). This is why LU matters in practice. A structural model is analysed for dozens of load combinations (dead, live, snow, wind, earthquake…) with the same stiffness matrix A; it is factored once and each load case is a cheap pair of substitutions.

Worked problem

LU with partial pivoting

For the 3 × 3 system above, partial pivoting chooses −3 (row 2) as the first pivot. A library routine returns the factors below. Verify that the multipliers make sense.

Show solution
  1. \[L = \begin{pmatrix} 1 & 0 & 0 \\ 0.667 & 1 & 0 \\ -0.667 & 0.2 & 1 \end{pmatrix}, \qquad U = \begin{pmatrix} -3 & -1 & 2 \\ 0 & 1.667 & 0.667 \\ 0 & 0 & 0.2 \end{pmatrix}\]
  2. Row order after pivoting: original rows 2, 3, 1. First-column multipliers: (−2)/(−3) = 0.667 for row 3 and 2/(−3) = −0.667 for row 1. After that step the second column holds 1.667 (row 3) and 0.333 (row 1), so 1.667 is the pivot and the last multiplier is 0.333/1.667 = 0.2. Every multiplier is at most 1 in magnitude, as pivoting guarantees.
  3. Solving with these factors for b = (8, −11, −3) gives (2, 3, −1) again.

Answer: The factors reproduce the solution; any new right-hand side now costs only two triangular solves

Conditioning: how accurate can the answer be?

Lesson 1 defined the condition number of a function. For a matrix, the condition number \(\kappa(A) = \|A\|\,\|A^{-1}\|\) bounds how much relative errors in the data can grow in the solution:

\[\frac{\|\Delta\mathbf{x}\|}{\|\mathbf{x}\|} \le \kappa(A)\,\frac{\|\Delta\mathbf{b}\|}{\|\mathbf{b}\|}\]

A useful rule of thumb: you can lose up to \(\log_{10}\kappa\) significant digits. With double precision's 16 digits and κ = 10⁴, expect about 12 good digits; with κ = 10¹⁶, possibly none. A nearly singular matrix (rows almost linearly dependent) has a huge condition number.

Two panels. Left: two lines crossing at a steep angle, labelled well-conditioned; shifting one line slightly moves the intersection only a little. Right: two nearly parallel lines, labelled ill-conditioned; a small shift of one line moves the intersection a long way along the lines.
Geometrically, an ill-conditioned 2 × 2 system is two nearly parallel lines: a small change in either line moves the intersection a long way.

Worked problem

Nearly parallel lines

Solve \(x + y = 2\), \(x + 1.0001y = 2.0001\). Then change the second right-hand side to 2.0002.

Show solution
  1. Original: x = 1, y = 1.
  2. Perturbed (a change of 0.005% in b): x = 0, y = 2.
  3. The condition number is about 40 000.

Answer: A 0.005% change in the data changed the answer by 100%. No algorithm can fix this: the problem itself is ill-conditioned, and the remedy is better-posed equations or more accurate data

The Hilbert matrix, with entries \(1/(i + j - 1)\), is the classic ill-conditioned example: κ is about 1.6 × 10⁴ for n = 4, 1.5 × 10¹⁰ for n = 8 and 1.6 × 10¹³ for n = 10. It appears when fitting high-degree polynomials with the normal equations, one reason Lesson 4 warns against that.

Iterative methods for large sparse systems

For very large sparse systems, iterative methods start from a guess and improve it, using only matrix–vector products, which are cheap when most entries are zero. The simplest solve each equation for its diagonal unknown:

  • Jacobi: compute every new \(x_i\) from the previous iterate's values.
  • Gauss–Seidel: use each new value as soon as it is available, which usually roughly doubles the convergence rate.
\[x_i^{(k+1)} = \frac{1}{a_{ii}}\left(b_i - \sum_{j < i} a_{ij}x_j^{(k+1)} - \sum_{j > i} a_{ij}x_j^{(k)}\right) \quad \text{(Gauss–Seidel)}\]

Both are guaranteed to converge if A is strictly diagonally dominant (each diagonal entry larger in magnitude than the sum of the others in its row) and Gauss–Seidel also converges for symmetric positive definite matrices, which is what most physical models produce (stiffness, conductance and diffusion matrices).

Worked problem

Temperatures in a rod

A rod is held at 100 °C at one end and 20 °C at the other. With four equally spaced interior nodes and steady conduction, each interior temperature is the average of its neighbours: \(T_i = (T_{i-1} + T_{i+1})/2\). Solve by Jacobi and by Gauss–Seidel from all zeros, stopping when no value changes by more than 10⁻⁴ °C.

Show solution
  1. Exact solution (linear profile): 84, 68, 52, 36 °C.
  2. Gauss–Seidel, first sweep: T₁ = (100 + 0)/2 = 50; T₂ = (50 + 0)/2 = 25; T₃ = 12.5; T₄ = (12.5 + 20)/2 = 16.25.
  3. Jacobi's first sweep uses only old values: 50, 0, 0, 10.
  4. Jacobi needs 60 sweeps to meet the tolerance; Gauss–Seidel needs 31.

Answer: Both reach 84, 68, 52, 36 °C; Gauss–Seidel in about half the sweeps. Their error reduction factors per sweep are about 0.81 and 0.65

Plot of the maximum error in the rod temperatures against sweep number on a log scale. Both Jacobi and Gauss–Seidel errors fall along straight lines; the Gauss–Seidel line is about twice as steep, reaching 10⁻⁴ in about 30 sweeps against about 60 for Jacobi.
Convergence of Jacobi and Gauss–Seidel for the rod. Both are linear, Gauss–Seidel about twice as fast.

These basic methods slow down badly as grids get finer. Modern practice uses faster relatives: successive over-relaxation (SOR), the conjugate gradient method for symmetric positive definite systems, GMRES for general ones, and preconditioning and multigrid to accelerate them. Together with sparse direct solvers, they are the engines inside finite element, circuit and reservoir simulators.

A special case deserves mention: tridiagonal systems, like the rod, can be solved directly in about 8n operations by the Thomas algorithm, Gaussian elimination that skips all the zeros. Splines (Lesson 4) and implicit ODE and heat-equation solvers use it constantly.

Direct solution, reusable LU factors and condition numbers
import numpy as np
from scipy.linalg import lu_factor, lu_solve

A = np.array([[ 2.0,  1.0, -1.0],
              [-3.0, -1.0,  2.0],
              [-2.0,  1.0,  2.0]])
b = np.array([8.0, -11.0, -3.0])
print("solve:", np.linalg.solve(A, b))

lu, piv = lu_factor(A)              # factor once...
for rhs in ([8, -11, -3], [1, 0, 0]):
    print("LU solve:", lu_solve((lu, piv), rhs))   # ...solve many times
print(f"cond(A) = {np.linalg.cond(A):.1f}")

B = np.array([[1, 1], [1, 1.0001]])
print(np.linalg.solve(B, [2, 2.0001]), np.linalg.solve(B, [2, 2.0002]))
print(f"cond(B) = {np.linalg.cond(B):.0f}")

Output: solve: [2. 3. −1.]; LU solve: [2. 3. −1.] and [4. −2. 5.]; cond(A) = 44.7; [1. 1.] [0. 2.]; cond(B) = 40002.

Common mistakes

  • MistakeComputing the inverse matrix to solve a system.

    InsteadUse LU-based solvers; they are faster and more accurate.

  • MistakeWriting elimination without pivoting.

    InsteadUse partial pivoting, or a library that does it for you.

  • MistakeRefactoring the matrix for every new right-hand side.

    InsteadFactor once with LU, then reuse the factors for each load case.

  • MistakeTrusting all printed digits of a solution.

    InsteadExpect to lose about log₁₀ κ(A) digits; check the condition number.

  • MistakeStoring large sparse matrices as dense arrays.

    InsteadUse sparse storage and sparse or iterative solvers.

  • MistakeApplying Jacobi or Gauss–Seidel to any matrix.

    InsteadThey are guaranteed to converge for diagonally dominant (and, for Gauss–Seidel, symmetric positive definite) matrices; otherwise they may diverge.

Summary and key terms

  • Gaussian elimination costs about (2/3)n³ flops; back substitution about n². Large engineering matrices are sparse, and exploiting that is essential.
  • Partial pivoting (largest pivot in the column) keeps multipliers ≤ 1 and makes elimination stable.
  • PA = LU: factor once, then solve each right-hand side with two triangular solves of about 2n² flops.
  • The condition number κ(A) bounds error amplification; expect to lose about log₁₀ κ digits.
  • Never invert a matrix to solve a system.
  • Jacobi and Gauss–Seidel iterate using cheap sparse products; Gauss–Seidel is about twice as fast, and modern methods (conjugate gradients, multigrid) are much faster still.
Gaussian elimination
Reducing a system to upper triangular form by row operations, then back substituting.
Pivot
The diagonal entry used to eliminate entries below it.
Partial pivoting
Swapping rows to use the largest available pivot in each column.
Back substitution
Solving an upper triangular system from the last equation upward.
LU factorization
Writing PA = LU with L lower and U upper triangular.
Flop
One floating-point operation.
Sparse matrix
A matrix in which most entries are zero.
Tridiagonal matrix
A matrix with nonzeros only on the main diagonal and the two adjacent diagonals.
Condition number
κ(A) = ‖A‖‖A⁻¹‖, the worst-case amplification of relative error.
Diagonally dominant
Each diagonal entry exceeds in magnitude the sum of the other entries in its row.
Jacobi method
An iteration updating all unknowns from the previous iterate.
Gauss–Seidel method
An iteration using each newly computed value immediately.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Solve by elimination: x + 2y = 5, 3x + 4y = 11.

    Show answer

    Row 2 − 3 × row 1: −2y = −4, y = 2; x = 1.

  2. Roughly how many times longer does dense Gaussian elimination take for n = 2000 than for n = 1000?

    Show answer

    (2000/1000)³ = 8 times.

  3. In the system 0.0001x + y = 1, x + y = 2, which row should be the first pivot row with partial pivoting? Why?

    Show answer

    Row 2: its entry 1 in column 1 is larger than 0.0001, giving a multiplier of 0.0001 rather than 10 000.

  4. A model must be solved for 30 load cases with the same 5000 × 5000 matrix. Estimate the flops with LU reuse versus refactoring every time.

    Show answer

    LU once: (2/3)(5000)³ ≈ 8.3 × 10¹⁰, plus 30 × 2(5000)² = 1.5 × 10⁹. Refactoring: 30 × 8.3 × 10¹⁰ = 2.5 × 10¹². About 29 times more work.

  5. Is A = [[4, −1, 0], [−1, 4, −1], [0, −1, 4]] strictly diagonally dominant?

    Show answer

    Yes: 4 > 1, 4 > 2, 4 > 1 in each row.

  6. Do one Gauss–Seidel sweep from (0, 0) for 4x − y = 3, −x + 4y = 3.

    Show answer

    x = 3/4 = 0.75; y = (3 + 0.75)/4 = 0.9375. (The solution is x = y = 1.)

  7. Do one Jacobi sweep from (0, 0) for the same system.

    Show answer

    x = 0.75, y = 0.75.

  8. A system has κ(A) = 10⁹ and is solved in double precision. About how many digits of the solution can be trusted?

    Show answer

    About 16 − 9 = 7.

  9. Explain why x + y = 2, x − y = 0 is well-conditioned.

    Show answer

    The lines are perpendicular; small changes in either move the intersection only slightly (κ = 1).

  10. Why are stiffness matrices of large structures sparse?

    Show answer

    Each joint (degree of freedom) is connected only to the few members and joints next to it, so most entries are zero.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz