The lesson on the inverse and the transpose defined and established when it exists. This one is about how it is computed, what that computation costs, and a detail that usually goes unnoticed: the customary check cannot detect that the result is wrong.
The inverse is a set of systems
By definition, is the matrix satisfying . Writing that equality column by column, with the -th column of the identity:
Computing an inverse means solving linear systems sharing the same matrix. This is exactly the situation described in the lesson on Gaussian elimination: one factorisation, right-hand sides.
import numpy as np
A = np.array([[1., 2., 1.],
[4., 4., 5.],
[6., 7., 7.]])
np.allclose(np.linalg.inv(A), np.linalg.solve(A, np.eye(3))) # True
The two expressions agree because they compute the same thing. inv has no privileged method available to it: it factorises and solves against the identity, just as the second line does.
The cost follows. An LU factorisation is operations and each triangular substitution ; with right-hand sides the total comes to about , roughly three times the cost of solving a single system. That is the quantifiable part of the recommendation to use solve.
The Gauss-Jordan construction
The manual procedure consists of forming the augmented matrix and applying row operations until the left block is the identity. The right block then holds .
[ A | I ]
Step 1 / 13
The justification is the same one used in the lesson on particular and general solutions. Each row operation amounts to left-multiplication by an elementary matrix . If the complete sequence transforms into , then
and applying those same operations to the right block, which starts at , produces precisely . The right block is not a bookkeeping device: it accumulates the product of the operations, which is the inverse.
For the example matrix, whose determinant is , the result has integer entries:
np.linalg.inv(A)
# array([[-7., -7., 6.],
# [ 2., 1., -1.],
# [ 4., 5., -4.]])
The check that does not check
The next step looks obvious:
np.allclose(A @ np.linalg.inv(A), np.eye(3)) # True
That check passes always, even when the computed inverse is useless. The reason lies in the distinction between two measures of error.
The residual measures how well the result satisfies the equation. The forward error measures how close it is to the true answer. LAPACK's routines are backward stable: they guarantee a residual on the order of machine epsilon, and they do not bound the forward error, which is limited only by .
The figure uses a family whose exact inverse is known, so both quantities are measurable:
- cond(A) ≈
- 4.0e+6
- ‖A·Â⁻¹ − I‖
- 0
- true error of Â⁻¹
- 8.2e-11
- κ · ε
- 8.9e-10
the A·Â⁻¹ ≈ I check passes in every case
A = [[1, 1], [1, 1 + δ]]. Its exact inverse is known, so the true error is measurable.
The residual stays at machine precision while the error grows like κ·ε: the check cannot detect it.
At the residual is still zero in floating point, while the computed inverse differs from the true one in the fifth significant digit. The check does not separate the two cases because it does not measure what it is assumed to measure.
The correct diagnostic is the condition number, covered in the lesson on the inverse and the transpose. A small residual says the computation was carried out correctly; it says nothing about whether the problem was well posed.
When the inverse is the object
The recommendation to solve rather than invert presupposes that what is wanted is . There are situations in which the entries of the inverse matrix are themselves the object of interest.
The precision matrix of a multivariate Gaussian is the clearest case. Its entries are not a means to a computation: they encode conditional independence. If , variables and are conditionally independent given the rest. That reading is the basis of Gaussian graphical models, and it requires the complete matrix.
The second case is the standard errors of a regression. The covariance matrix of the estimated coefficients is , and its diagonal gives the variance of each coefficient separately.
XtX_inv = np.linalg.inv(X.T @ X)
errors = np.sqrt(sigma2 * np.diag(XtX_inv))
Even in these cases the direct route is rarely the best one. For a symmetric positive definite matrix, the Cholesky factorisation yields the inverse with half the work and better stability, and scipy.linalg.cho_solve obtains it without forming intermediate products. When only the diagonal is needed, as in the standard error computation, computing the full inverse in order to discard entries is a waste that grows with the square of the size.
Exercise. Check that np.linalg.inv(A) and np.linalg.solve(A, np.eye(n)) produce the same result, and time both against np.linalg.solve(A, b) with a single , for . Compare the observed ratio with the against prediction.