Linear Solvers and the Condition Number
Solving a system of linear equations numerically can amplify tiny input errors into huge output errors — how much amplification depends on a single number, the condition number of the matrix.
Prerequisites: Catastrophic Cancellation
A portfolio optimizer solves for optimal weights, where is a covariance matrix. The inputs — estimated covariances — are never known exactly; there's always some noise from finite sample size. For a well-behaved matrix, small noise in the inputs produces small changes in the solution. For an ill-conditioned matrix, the same tiny input noise can blow up into wildly different, useless output weights — and this happens even with perfect floating-point arithmetic, purely because of the matrix's own geometry.
The condition number of a matrix measures how much it can amplify relative error: a solver's output error is roughly bounded by the input error times the condition number. A condition number near 1 means the system is well-behaved; a condition number of means a relative error in your inputs can become a relative error in your answer.
What the condition number measures
For a matrix , the condition number is — informally, the ratio of the matrix's largest to smallest "stretch factor" (for a symmetric matrix, the ratio of its largest to smallest eigenvalue). A matrix that stretches some directions enormously and barely touches others has a huge condition number: information about the barely-touched direction is nearly invisible in , so recovering it by inverting is extremely sensitive to any noise in or itself.
In words: the relative error in your solution is bounded by the condition number times the relative error in your input. A condition number of 100 means input noise gets amplified up to 100x; a condition number of means the solution can be almost entirely noise.
Worked example
Take a nearly-singular 2x2 matrix , with . The exact solution is . Now perturb by a tiny amount, to — a relative change of about . Solving gives — the solution moved by 100%, not 0.005%. This matrix's condition number is roughly 40,000, and the amplification in the example (relative output change divided by relative input change) is consistent with that order of magnitude: a tiny nudge in the input became a completely different answer.
What this means in practice
Ill-conditioned systems show up whenever a quant inverts a covariance matrix built from highly correlated assets (two ETFs tracking nearly the same index), fits a regression with near-collinear features, or calibrates a model where two parameters trade off against each other almost perfectly. np.linalg.cond(A) reports the condition number directly; a common rule of thumb is to be cautious above - and treat results as unreliable well before reaching the machine-precision limit (around for double precision, where the matrix is numerically singular). Standard fixes include ridge regularization (adding a small multiple of the identity before inverting), dropping near-duplicate features, or using a solver method (like SVD-based pseudo-inverse) that degrades more gracefully than direct inversion.
A small residual does not mean is close to the true solution when is ill-conditioned — the solver can report a solution that fits the equations almost perfectly while being far from the answer you'd get with slightly different, equally plausible input data. Checking the condition number is necessary precisely because the usual sanity check (does it satisfy the equations) can mislead you.
Related concepts
Practice in interviews
Further reading
- Trefethen & Bau, Numerical Linear Algebra (lecture 12)
- Higham, Accuracy and Stability of Numerical Algorithms