Prerequisites: Matrix multiplication (ch154), identity matrix (ch156) You will learn:
What makes a matrix invertible
How to compute the inverse (via cofactors, Gauss-Jordan, numpy)
When NOT to compute the inverse (numerical stability)
The pseudoinverse for non-square matrices
Environment: Python 3.x, numpy
# --- Matrix Inverse: Definition, Computation, Pitfalls ---
import numpy as np
def inverse_gauss_jordan(A):
"""
Compute matrix inverse via Gauss-Jordan elimination.
Augments [A | I] and row-reduces to [I | A⁻¹].
Args:
A: square 2D numpy array, shape (n,n)
Returns:
A_inv: 2D numpy array, shape (n,n)
Raises:
ValueError if A is singular.
"""
n = A.shape[0]
assert A.shape == (n, n), "Must be square"
# Augmented matrix [A | I]
aug = np.hstack([A.astype(float).copy(), np.eye(n)])
for col in range(n):
# Find pivot
pivot_row = col + np.argmax(np.abs(aug[col:, col]))
if abs(aug[pivot_row, col]) < 1e-12:
raise ValueError("Matrix is singular or nearly singular")
aug[[col, pivot_row]] = aug[[pivot_row, col]] # swap rows
# Normalize pivot row
aug[col] /= aug[col, col]
# Eliminate in all other rows
for row in range(n):
if row != col:
aug[row] -= aug[row, col] * aug[col]
return aug[:, n:] # right half is A⁻¹
# Test
np.random.seed(42)
A = np.array([[2., 1., 0.],
[1., 3., 1.],
[0., 1., 2.]])
A_inv_scratch = inverse_gauss_jordan(A)
A_inv_numpy = np.linalg.inv(A)
print("Inverse via Gauss-Jordan:")
print(np.round(A_inv_scratch, 6))
print("\nMax diff from numpy: ", np.max(np.abs(A_inv_scratch - A_inv_numpy)))
print("\nVerify AA⁻¹ = I:", np.allclose(A @ A_inv_scratch, np.eye(3)))
# When NOT to invert: solve instead
b = np.array([1., 2., 3.])
# Bad: x = A⁻¹ @ b (computes full inverse, then multiplies)
x_inv = np.linalg.inv(A) @ b
# Good: x = np.linalg.solve(A, b) (uses LU decomposition, faster and more stable)
x_solve = np.linalg.solve(A, b)
print(f"\nSolve via inv: {x_inv}")
print(f"Solve via solve: {x_solve}")
print(f"Equal: {np.allclose(x_inv, x_solve)}")
print("\nRule: Never compute A⁻¹ explicitly if you only need A⁻¹b.")Inverse via Gauss-Jordan:
[[ 0.625 -0.25 0.125]
[-0.25 0.5 -0.25 ]
[ 0.125 -0.25 0.625]]
Max diff from numpy: 0.0
Verify AA⁻¹ = I: True
Solve via inv: [0.5 0. 1.5]
Solve via solve: [5.0000000e-01 8.8817842e-17 1.5000000e+00]
Equal: True
Rule: Never compute A⁻¹ explicitly if you only need A⁻¹b.
4. Mathematical Formulation¶
A⁻¹ exists iff A is square AND det(A) ≠ 0 (equivalently, full rank).
AA⁻¹ = A⁻¹A = I
Properties:
(A⁻¹)⁻¹ = A
(AB)⁻¹ = B⁻¹A⁻¹ [reversal, same as transpose]
(Aᵀ)⁻¹ = (A⁻¹)ᵀ
det(A⁻¹) = 1/det(A)
For 2×2: A = [[a,b],[c,d]]
A⁻¹ = (1/(ad-bc)) * [[d,-b],[-c,a]]
Pseudoinverse (Moore-Penrose) A⁺:
For any A (not necessarily square):
A⁺ = Vᵀ Σ⁺ Uᵀ (via SVD — see ch173)
When A is full rank: A⁺ = (AᵀA)⁻¹Aᵀ (left inverse)# --- Pseudoinverse for non-square systems ---
import numpy as np
# Overdetermined system: more equations than unknowns
# A is (5,3) — 5 equations, 3 unknowns
A = np.array([[1,0,0],[0,1,0],[0,0,1],[1,1,0],[0,1,1]], dtype=float)
b = np.array([1., 2., 3., 3.5, 4.5]) # inconsistent — no exact solution
# Pseudoinverse gives least-squares solution
A_pinv = np.linalg.pinv(A)
x_lstsq = A_pinv @ b
print(f"A shape: {A.shape}, A⁺ shape: {A_pinv.shape}")
print(f"Least-squares solution: {x_lstsq.round(4)}")
print(f"Residual ||Ax - b||: {np.linalg.norm(A @ x_lstsq - b):.4f}")
# Underdetermined: fewer equations than unknowns (infinitely many solutions)
# pinv gives minimum-norm solution
A_under = np.array([[1,1,0],[0,1,1]], dtype=float) # 2 eq, 3 unknowns
b_under = np.array([3., 2.])
x_min_norm = np.linalg.pinv(A_under) @ b_under
print(f"\nUnderdetermined min-norm solution: {x_min_norm.round(4)}")
print(f"||x||: {np.linalg.norm(x_min_norm):.4f}")A shape: (5, 3), A⁺ shape: (3, 5)
Least-squares solution: [1.25 2. 2.75]
Residual ||Ax - b||: 0.5000
Underdetermined min-norm solution: [1.3333 1.6667 0.3333]
||x||: 2.1602
7. Exercises¶
Easy 1. Compute by hand the inverse of [[2,1],[1,1]] using the 2×2 formula. Verify with NumPy.
Easy 2. Why does [[1,2],[2,4]] have no inverse? What is its rank?
Medium 1. Implement solve_without_inv(A, b) that solves Ax=b using np.linalg.solve and compare its accuracy and speed to np.linalg.inv(A) @ b for a 500×500 system.
Medium 2. For what values of k is [[1,k],[k,4]] invertible? Find the condition algebraically, then verify by testing k from -3 to 3.
Hard. Implement inverse_2x2(A) using cofactors, then implement inverse_nxn(A) using the general cofactor expansion. Compare numerical accuracy to Gauss-Jordan for ill-conditioned matrices.
9. Chapter Summary & Connections¶
A⁻¹ satisfies
AA⁻¹ = I. Exists iff det(A) ≠ 0 (equivalently, A is full rank).(AB)⁻¹ = B⁻¹A⁻¹— reversal, same pattern as transpose.To solve Ax=b, use
np.linalg.solve, not explicit inversion — faster and more numerically stable.The pseudoinverse A⁺ generalizes inverse to non-square matrices (fully explained in ch173 — SVD).
Forward connections:
In ch159 (Determinants Computation), we formalize the condition det(A)≠0 for invertibility.
In ch161 (Gaussian Elimination), the Gauss-Jordan procedure here is generalized to solve Ax=b directly.