Conditioning and numerical stability
Distinguish the conditioning of a problem from the stability of an algorithm, and bound error with the condition number.
A numerical answer can be wrong for two reasons: the problem itself amplifies input perturbations (it is ill-conditioned), or the algorithm introduces excess rounding error (it is unstable). These are independent. A well-conditioned problem solved by an unstable algorithm gives a wrong answer; an ill-conditioned problem solved by the perfect algorithm still gives a wrong answer. Knowing which is which tells you whether to change the algorithm or change the problem.
Conditioning: a property of the problem
The condition number of a problem measures how much the output changes when the input is perturbed. For a function , the relative condition number is
It is the worst-case ratio of relative output change to relative input change[1]. If the input is known to floating-point precision , the output is unavoidably uncertain by about , regardless of how the computation is performed.
The condition number of a linear system
For with invertible, perturbing by perturbs by . The condition number of solving the system is
and in the 2-norm equals the ratio of largest to smallest singular value:
Derivation. From , , so . From , . Dividing,
identifying as the amplification factor.
A matrix with loses 12 digits of precision: even the perfect solver, with double-precision input, returns only 4 correct digits.
Stability: a property of the algorithm
An algorithm is backward stable if the computed answer is the exact answer to a slightly perturbed problem: with [2]. Backward stability is the strongest practical guarantee, because combined with conditioning it yields the forward error bound
The total error is the product of problem conditioning and algorithm stability. A stable algorithm cannot improve a bad condition number; an unstable algorithm contaminates even a perfectly conditioned problem.
Two routes for least squares
The normal equations have condition number : squaring the conditioning of the original problem. The QR or SVD routes (from the A1 least-squares lesson) keep the conditioning at rather than [3]. Both routes solve the same problem; their stability differs.
Check it on arrays
import numpy as np
rng = np.random.default_rng(0)
eps = np.finfo(float).eps
# Build a matrix with controlled condition number via the SVD.
def make_A(n, kappa):
U = np.linalg.qr(rng.normal(size=(n, n)))[0]
V = np.linalg.qr(rng.normal(size=(n, n)))[0]
s = np.geomspace(1.0, 1.0 / kappa, n)
return U @ np.diag(s) @ V.T
n = 50
for kappa in [1e2, 1e6, 1e12]:
A = make_A(n, kappa)
x_true = rng.normal(size=n)
b = A @ x_true
x_hat = np.linalg.solve(A, b)
err = np.linalg.norm(x_hat - x_true) / np.linalg.norm(x_true)
bound = kappa * eps
print(f"kappa={kappa:>.0e}: rel error {err:.2e}, bound kappa*eps {bound:.2e}")
# Squaring the conditioning: solve via normal equations vs QR
A = make_A(n, 1e6)
b = A @ rng.normal(size=n)
x_normal = np.linalg.solve(A.T @ A, A.T @ b) # uses kappa^2
Q, R = np.linalg.qr(A); x_qr = np.linalg.solve(R, Q.T @ b) # uses kappa
truth = np.linalg.solve(A, b)
print("normal-eq error:", np.linalg.norm(x_normal - truth))
print("QR-route error: ", np.linalg.norm(x_qr - truth))The relative error grows with the condition number as predicted, and the normal- equations route loses precision the QR route preserves.
Exercises
Exercise 1. A matrix has and . Compute , and state how many correct digits a backward-stable solver of returns when the input is given to double precision ().
Solution
Step 1. The 2-norm condition number is the ratio of extreme singular values:
Step 2. A backward-stable solver achieves the forward-error bound .
Step 3. A relative error near leaves about correct digits. Of the digits double precision supplies, the condition number consumes , matching the rule that each factor of in costs one digit.
Exercise 2. Show that for the normal equations , the condition number satisfies . Use the SVD .
Solution
Step 1. Write with orthogonal and diagonal holding the singular values . Then
using . This is the eigendecomposition of , so its singular values are the diagonal entries .
Step 2. The extreme singular values of are therefore and , giving
Step 3. Forming squares the conditioning, so a problem with becomes : the normal equations can lose twice as many digits as a route that keeps , even though both solve the same least-squares problem.
Exercise 3 (code). Build a matrix with controlled via its SVD, solve for a known , and confirm the relative error stays under the bound .
Solution
import numpy as np
rng = np.random.default_rng(0)
eps = np.finfo(float).eps
n, kappa = 40, 1e8
# Controlled condition number: singular values geomspaced from 1 to 1/kappa.
U = np.linalg.qr(rng.normal(size=(n, n)))[0]
V = np.linalg.qr(rng.normal(size=(n, n)))[0]
s = np.geomspace(1.0, 1.0 / kappa, n)
A = U @ np.diag(s) @ V.T
assert np.isclose(np.linalg.cond(A), kappa, rtol=1e-3)
x_true = rng.normal(size=n)
b = A @ x_true
x_hat = np.linalg.solve(A, b)
rel_err = np.linalg.norm(x_hat - x_true) / np.linalg.norm(x_true)
assert rel_err <= kappa * eps # forward-error bound holds
print("rel error:", rel_err, " bound kappa*eps:", kappa * eps)The relative error lands below :
the backward-stable solve is as accurate as the conditioning of the problem allows,
no better and no worse.
The next lesson zooms in on the most common source of stability loss: subtracting near-equal numbers.
References
- [1]Trefethen, L. N., & Bau, D. (1997). Numerical Linear Algebra. SIAM. Conditioning, stability, and the condition number (Lectures 12-15).↩
- [2]Higham, N. J. (2002). Accuracy and Stability of Numerical Algorithms (2nd ed.). SIAM. The definitive treatment of backward stability and forward error (doi:10.1137/1.9780898718027).↩
- [3]Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations (4th ed.). Johns Hopkins University Press. Normal equations versus QR for least squares.↩