Solving Linear Systems
Solve Ax=b using np.linalg.solve and compare to the naive inverse-multiplication approach for numerical stability.
Solving Linear Systems is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Linear System?
A system of linear equations can be written in matrix form as Ax = b, where A is the coefficient matrix, x is the vector of unknowns, and b is the right-hand side vector. For example, two equations with two unknowns can be expressed as a 2×2 matrix equation. Solving the system means finding x such that A multiplied by x equals b. NumPy's np.linalg.solve(A, b) handles this efficiently and accurately.
import numpy as np
# System: 2x + y = 5
# x + 3y = 10
A = np.array([[2.0, 1.0],
[1.0, 3.0]])
b = np.array([5.0, 10.0])
x = np.linalg.solve(A, b)
print('Solution x:', x)
print('Verify A @ x == b:', np.allclose(A @ x, b))Why Not Use the Inverse?
Mathematically, the solution to Ax = b is x = A⁻¹b. But computing np.linalg.inv(A) @ b is slower and less accurate than calling np.linalg.solve(A, b) directly. solve() uses LU decomposition internally, which requires fewer operations and accumulates less floating-point error. The rule of thumb: if you are solving for x in Ax=b, always use solve, never inv.
import numpy as np
A = np.random.rand(100, 100)
b = np.random.rand(100)
# Avoid this pattern
x_inv = np.linalg.inv(A) @ b
# Prefer this
x_solve = np.linalg.solve(A, b)
print('Max difference:', np.max(np.abs(x_inv - x_solve)))
# solve is faster AND more accurateRequirements for solve()
np.linalg.solve(A, b) requires that A is square and non-singular. If A is singular (determinant is zero), the function raises np.linalg.LinAlgError: Singular matrix. A is singular when rows are linearly dependent — for example, one equation is just a multiple of another. Always check that your system is well-posed before calling solve. If A is not square, use np.linalg.lstsq() instead.
import numpy as np
# Well-posed system
A = np.array([[1.0, 2.0], [3.0, 4.0]])
b = np.array([5.0, 6.0])
print('Solution:', np.linalg.solve(A, b))
# Singular system (row 2 = 3 * row 1)
A_singular = np.array([[1.0, 2.0], [3.0, 6.0]])
try:
np.linalg.solve(A_singular, b)
except np.linalg.LinAlgError as e:
print('Error:', e)LU Decomposition Under the Hood
np.linalg.solve internally applies LU decomposition: it factors A into a lower-triangular matrix L and an upper-triangular matrix U such that A = LU. Solving then becomes two simpler triangular solves (forward and back substitution), which are far more efficient than Gaussian elimination repeated from scratch. This is the same algorithm used by MATLAB, Fortran LAPACK, and virtually every scientific computing library.
import numpy as np
from scipy import linalg
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])
# NumPy solve (uses LAPACK dgesv under the hood)
x = np.linalg.solve(A, b)
print('Solution:', x)
print('Check:', np.allclose(A @ x, b))Solving Multiple Right-Hand Sides
np.linalg.solve(A, B) can handle a matrix B with multiple columns, solving for each column simultaneously. If B has shape (n, k), the result X has shape (n, k), where each column of X is the solution for the corresponding column of B. This is useful in physics simulations and machine learning where you need to solve the same coefficient matrix with many different right-hand sides.
import numpy as np
A = np.array([[3.0, 1.0],
[1.0, 2.0]])
# Three right-hand side vectors at once
B = np.array([[9.0, 3.0, 6.0],
[8.0, 4.0, 2.0]])
X = np.linalg.solve(A, B)
print('Solutions X shape:', X.shape) # (2, 3)
print('Verify A @ X == B:', np.allclose(A @ X, B))Least Squares with np.linalg.lstsq()
When the system is over-determined (more equations than unknowns), no exact solution exists. np.linalg.lstsq(A, b) finds the x that minimises the sum of squared residuals ||Ax - b||² — this is exactly what linear regression computes. It returns the solution x, residuals, rank, and singular values. The rcond=None argument suppresses a deprecation warning and sets the cutoff for treating singular values as zero.
import numpy as np
# Over-determined: 4 equations, 2 unknowns
A = np.array([[1.0, 1.0],
[1.0, 2.0],
[1.0, 3.0],
[1.0, 4.0]])
b = np.array([2.0, 2.5, 3.5, 4.5])
x, residuals, rank, sv = np.linalg.lstsq(A, b, rcond=None)
print('Least-squares solution:', x)
print('Residuals:', residuals)
print('Rank:', rank)Applying solve() in Linear Regression
The normal equations of ordinary least squares are A.T @ A @ x = A.T @ b, which is a square system solvable with np.linalg.solve. This gives the same result as lstsq when A.T @ A is well-conditioned. In practice, lstsq is preferred because it uses SVD and is stable even when columns are nearly collinear, but the normal equations help you understand the math behind regression.
import numpy as np
np.random.seed(0)
X = np.column_stack([np.ones(50), np.random.rand(50) * 10])
y = 3.0 + 2.0 * X[:, 1] + np.random.randn(50)
# Solve normal equations
coeffs = np.linalg.solve(X.T @ X, X.T @ y)
print('Intercept:', round(coeffs[0], 3))
print('Slope:', round(coeffs[1], 3))Checking the Condition Number
Before solving, it is good practice to check the condition number with np.linalg.cond(A). A condition number close to 1 means the system is well-conditioned and the solution is reliable. A very large condition number (e.g., 1e12) means the matrix is nearly singular: small changes in b produce huge changes in x, and the solution may be numerically unreliable. In that case, consider regularisation (ridge regression) or using a more robust solver.
import numpy as np
# Well-conditioned
A1 = np.array([[2.0, 1.0], [1.0, 3.0]])
print('Condition number (well):', np.linalg.cond(A1))
# Ill-conditioned (nearly linearly dependent rows)
A2 = np.array([[1.0, 1.0], [1.0, 1.0001]])
print('Condition number (ill):', np.linalg.cond(A2))Sparse Systems: When Not to Use linalg
For very large systems where A has mostly zero entries (a sparse matrix), storing the full matrix wastes memory and using dense np.linalg.solve is slow. SciPy provides scipy.sparse and scipy.sparse.linalg.spsolve that exploit sparsity for huge systems like finite element models or network problems. Understanding when your matrix is sparse is an important performance consideration in scientific computing.
import numpy as np
# Dense solve is fine for small-to-medium systems
A = np.random.rand(200, 200)
b = np.random.rand(200)
x = np.linalg.solve(A, b)
print('Dense solve OK, solution shape:', x.shape)
# For large sparse systems, use scipy.sparse.linalg.spsolve instead
# from scipy.sparse.linalg import spsolve
# x = spsolve(A_sparse, b)Interpreting the Solution Vector
Once you have the solution vector x from np.linalg.solve(A, b), always verify it by computing A @ x and checking it against b using np.allclose(A @ x, b). In floating-point arithmetic, you rarely get exact equality — allclose checks within a small tolerance. The residual ||Ax - b|| should be near machine epsilon times the scale of the problem for a well-conditioned system.
import numpy as np
A = np.array([[4.0, 3.0, 2.0],
[1.0, 5.0, 3.0],
[2.0, 1.0, 6.0]])
b = np.array([10.0, 20.0, 15.0])
x = np.linalg.solve(A, b)
print('Solution:', np.round(x, 4))
# Residual check
residual = np.linalg.norm(A @ x - b)
print('Residual ||Ax - b||:', residual)
print('Valid:', np.allclose(A @ x, b))Solving in Machine Learning Pipelines
Linear solvers appear throughout machine learning: linear regression solves the normal equations, Gaussian processes solve kernel matrix systems, and Kalman filters solve state-update equations. By understanding np.linalg.solve and lstsq, you can implement these algorithms from scratch and debug them when results are unexpected. NumPy's linalg module gives you the same numerical tools used in production scientific libraries.
import numpy as np
# Ridge regression: (X.T @ X + lambda * I) @ w = X.T @ y
np.random.seed(42)
X = np.random.rand(100, 5)
y = np.random.rand(100)
lambda_reg = 0.1
n_features = X.shape[1]
A = X.T @ X + lambda_reg * np.eye(n_features)
bvec = X.T @ y
w = np.linalg.solve(A, bvec)
print('Ridge regression weights:', np.round(w, 4))Quick Check
Test your understanding of Data Analysis concepts from this lesson.
Lesson Recap
In this lesson you learned: np.linalg.solve(A, b) efficiently solves square linear systems using LU decomposition, np.linalg.lstsq() finds the least-squares solution for over-determined systems, and the condition number measures numerical stability — large values warn of near-singular matrices. Next up we explore eigenvalues and the SVD, which underpin PCA and many ML algorithms.
Frequently asked questions
Is the “Solving Linear Systems” lesson free?
Yes — the full text of “Solving Linear Systems” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.
What will I learn in “Solving Linear Systems”?
Solve Ax=b using np.linalg.solve and compare to the naive inverse-multiplication approach for numerical stability. You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Pandas & NumPy Academy?
No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Solving Linear Systems” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Pandas & NumPy Academy lesson?
Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.