Python Lab Reference
Every library, method, and pattern you need for the Numerical Analysis labs.
74 entries across 8 libraries
🔢NumPy · Arrays
Numerical Analysis lives on arrays and matrices. These build the grids, vectors and coefficient matrices every algorithm operates on.
import numpy as npBuilding arrays & grids
np.array→ ndarrayTurn a Python list (or list of lists) into a vector or matrix.
v = np.array([1.0, 2.0, 3.0])
A = np.array([[2., 1.], [1., 3.]]) # a 2×2 matrixnp.linspace→ ndarraynum evenly spaced points including both endpoints — your standard sample grid.
x = np.linspace(0, 1, 101) # 0.00, 0.01, … 1.00np.arange→ ndarrayValues from start up to (but excluding) stop, spaced by step.
k = np.arange(0, 10) # 0..9
h = np.arange(0.1, 1.0, 0.1)np.logspace→ ndarraynum points spaced evenly in log10 — perfect for step-size / error sweeps.
hs = np.logspace(-1, -6, 20) # 1e-1 down to 1e-6np.zeros / np.ones→ ndarrayPre-allocate an array of zeros or ones. shape can be n or (rows, cols).
x = np.zeros(n) # solution vector
A = np.zeros((n, n)) # coefficient matrix to fill innp.zeros_like / ones_like / full_like→ ndarraySame shape & dtype as an existing array — great inside loops that build results.
errs = np.zeros_like(hs) # one slot per step sizenp.eye→ ndarrayThe n×n identity matrix.
I = np.eye(3)np.diag→ ndarrayMake a diagonal matrix from a vector — or extract the diagonal of a matrix.
D = np.diag([1., 2., 3.]) # vector → diagonal matrix
d = np.diag(A) # matrix → its diagonalnp.vander→ ndarrayVandermonde matrix — the design matrix for polynomial fitting/interpolation.
V = np.vander(x, len(x)) # solve V c = y for polynomial coeffsnp.tril / np.triu→ ndarrayLower / upper triangular part of a matrix (rest zeroed) — for LU-style work.
L = np.tril(A, -1) + np.eye(n) # unit lower-triangularnp.vstack→ ndarrayStack arrays as rows into a taller matrix.
M = np.vstack([row1, row2, row3])➗NumPy · Math & Reductions
Elementwise math and the reductions you use to measure error and convergence. Every function here works on a whole array at once — vectorize instead of looping.
import numpy as npElementwise functions
np.abs→ float or ndarrayAbsolute value — the backbone of every error measurement.
err = np.abs(approx - exact)np.exp / np.loge^x and natural log (ln). np.log10 for base 10.
f = np.exp(-x**2)
rate = np.log(err[1:] / err[:-1])np.sqrtSquare root, elementwise.
rms = np.sqrt(np.sum(r**2) / n)np.sin / np.cosTrig functions (radians) — common test integrands and ODE right-hand sides.
y = np.sin(np.pi * x)np.pi · np.eThe constants π and e. No parentheses — they are values, not functions.
area = np.pi * r**2np.roundRound to a number of decimal places (display / comparison).
print(np.round(A, 4))np.clipClamp values into [lo, hi] — guards against out-of-range arguments.
theta = np.arccos(np.clip(c, -1, 1))Reductions & comparisons
np.max / np.min→ floatLargest / smallest element — max is how you report worst-case error.
max_err = np.max(np.abs(u - u_exact))np.argmin / np.argmax→ intINDEX of the smallest / largest element (not the value).
i = np.argmin(errors) # which step size was bestnp.sum→ float or ndarraySum of elements — quadrature rules, norms, residuals.
integral = h * np.sum(f) # basic Riemann/trapezoid corenp.diff→ ndarray (one shorter than input)Consecutive differences — finite-difference derivatives and spacing checks.
slopes = np.diff(y) / np.diff(x)np.allclose / np.isclose→ bool (allclose) / bool array (isclose)Are arrays equal within tolerance? Use this, never == on floats.
assert np.allclose(A @ x, b) # did the solver actually solve it?np.sort / np.searchsortedSort values / find the insertion index in a sorted array (piecewise lookup).
i = np.searchsorted(nodes, x) # which interval x falls innp.interp→ float or ndarrayFast piecewise-linear interpolation at points x from samples (xp, fp).
y = np.interp(0.37, xp, fp)🧮NumPy · Linear Algebra
The np.linalg module powers the linear-systems half of the course: solving Ax=b, conditioning, eigenvalues and least squares.
import numpy as npSolving & inverting
np.linalg.solve→ ndarray (solution x)Solve the linear system A x = b directly. The right way to “apply A⁻¹”.
x = np.linalg.solve(A, b)np.linalg.inv→ ndarrayThe matrix inverse A⁻¹.
Ainv = np.linalg.inv(A)np.linalg.norm→ floatVector or matrix norm — measure residuals and error magnitudes.
residual = np.linalg.norm(A @ x - b)
rel_err = np.linalg.norm(x - x_true) / np.linalg.norm(x_true)Conditioning & spectra
np.linalg.cond→ floatCondition number κ(A) — how much A amplifies input error. The course’s key diagnostic.
print(f"cond = {np.linalg.cond(A):.2e}")np.linalg.eigvals→ ndarray (possibly complex)Eigenvalues of A (values only). Use eig() if you also need eigenvectors.
lam = np.linalg.eigvals(A)
spectral_radius = np.max(np.abs(lam))Factorizations & least squares
np.linalg.lstsq→ (solution, residuals, rank, singular_values)Least-squares solution to an overdetermined system (best fit).
coef, residuals, rank, sv = np.linalg.lstsq(A, b, rcond=None)np.linalg.cholesky→ ndarray (lower triangular)Cholesky factor L (A = L Lᵀ) for symmetric positive-definite matrices.
L = np.linalg.cholesky(A)np.linalg.qr→ (Q, R)QR factorization A = Q R (orthogonal × upper-triangular).
Q, R = np.linalg.qr(A)🎲Floating Point & Random
Numerical Analysis starts with how computers store numbers. These expose machine precision and generate test data.
import numpy as npMachine precision
np.finfo→ floatMachine epsilon — the gap between 1.0 and the next float (≈ 2.22e-16 for float64).
eps = np.finfo(float).eps
print(f"machine epsilon = {eps:.2e}")np.infPositive infinity — sentinel for “no bound yet” or a diverged value.
best = np.inf # then keep the minimum foundRandom test data
np.random.seedFix the random seed so results are reproducible (and match the grader).
np.random.seed(42)np.random.rand→ ndarrayUniform random values in [0, 1) with the given shape.
A = np.random.rand(n, n)np.random.randn→ ndarrayStandard-normal (mean 0, std 1) random values — noise for perturbation tests.
noise = 1e-6 * np.random.randn(n)📈Matplotlib
Every convergence study ends in a plot. The labs use the object-oriented style: make a figure and axes with plt.subplots, then draw on the ax. Log axes reveal convergence order.
import matplotlib.pyplot as pltFigure & axes
plt.subplots→ (Figure, Axes)Create a figure and one (or a grid of) axes. The standard opening line.
fig, ax = plt.subplots(figsize=(7, 4))
fig, (ax1, ax2) = plt.subplots(1, 2) # two side-by-sideplt.tight_layoutAuto-adjust spacing so labels/titles don’t overlap or clip.
plt.tight_layout()plt.savefigWrite the figure to a file — this is how the lab captures your plot.
plt.savefig('convergence.png', dpi=150, bbox_inches='tight')plt.showRender the figure. In the lab runner, pair with savefig so output is captured.
plt.show()Drawing on an axis
ax.plotLine/marker plot. Third arg is a style string; label feeds the legend.
ax.plot(x, y, 'b.-', label='approx')ax.semilogyLinear x, log y — shows exponential decay of error as a straight line.
ax.semilogy(iterations, residuals)ax.loglogLog-log plot — the slope IS the order of accuracy p (err ∝ hᵖ).
ax.loglog(hs, errors, "o-")ax.set_xlabel / set_ylabel / set_titleLabel the axes and title the plot.
ax.set_xlabel('step size h'); ax.set_ylabel('error'); ax.set_title('Convergence')ax.legendShow the legend built from each plot’s label=.
ax.legend()ax.gridAdd grid lines — helpful for reading slopes off log plots.
ax.grid(True, which='both', alpha=0.3)ax.axhlineDraw a horizontal reference line (e.g. a tolerance or exact value).
ax.axhline(eps, color='r', ls='--', label='machine eps')ax.set_ylimFix the y-axis range so tiny errors don’t collapse to nothing.
ax.set_ylim(1e-16, 1)🔬SciPy
Battle-tested implementations to check your own code against. The labs use interpolation, root-finding, integration and LU factorization from SciPy as reference answers.
from scipy.interpolate import CubicSpline
from scipy.optimize import brentq
from scipy.integrate import quad, solve_ivp
from scipy.linalg import lu_factor, lu_solve, hilbertInterpolation
CubicSpline→ a callable spline objectBuild a smooth natural cubic spline through the data points, then call it like a function.
cs = CubicSpline(x, y)
y_fine = cs(x_fine) # evaluate anywhere
d1 = cs(x_fine, 1) # first derivativeRoot finding
brentq→ float (the root)Robustly find a root of f in the bracket [a, b] where f changes sign.
root = brentq(lambda x: x**2 - 2, 0, 2) # → √2Integration
quad→ (value, estimated_error)Adaptive high-accuracy definite integral — the reference “exact” value for quadrature labs.
val, err = quad(lambda x: np.exp(-x**2), 0, 1)solve_ivp→ solution object with .t and .yIntegrate an ODE system dy/dt = fun(t, y) — the reference for your Euler/RK code.
def f(t, y):
return -y # y' = -y
sol = solve_ivp(f, [0, 5], [1.0], t_eval=np.linspace(0, 5, 100))
y = sol.y[0] # solution over timeLU factorization (scipy.linalg)
lu_factor / lu_solve→ lu_factor → (lu, piv); lu_solve → solution xFactor A once, then solve for many right-hand sides cheaply.
lu, piv = lu_factor(A)
x1 = lu_solve((lu, piv), b1)
x2 = lu_solve((lu, piv), b2) # reuse the factorizationhilbert→ ndarrayThe n×n Hilbert matrix — the classic ill-conditioned test case.
H = hilbert(6)
print(np.linalg.cond(H)) # enormous🐍Python Essentials
The plain-Python and math-module pieces that show up across the labs — clean numeric output, functions, loops, and a couple of special helpers.
Formatting numbers
:.6f (fixed decimals)Show a set number of decimal places — for tables of approximations.
print(f"x = {x:.6f}"):.3e (scientific)Scientific notation — the natural format for errors and condition numbers.
print(f"error = {err:.3e}, cond = {kappa:.2e}"):>12 (alignment)Pad to a width for aligned columns in convergence tables.
print(f"{n:>4}{err:>14.3e}{ratio:>10.2f}")Functions, loops & math
defDefine a function. 4-space indented body; remember to return.
def newton(f, df, x0, tol=1e-10):
x = x0
while abs(f(x)) > tol:
x = x - f(x) / df(x)
return xlambdaA one-line anonymous function — handy for passing f to a solver.
root = brentq(lambda x: x**3 - x - 2, 1, 2)for / whileIterate a fixed number of times, or until a tolerance is met.
for k in range(max_iter):
x_new = g(x)
if abs(x_new - x) < tol:
break
x = x_newmath.ceil / math.logScalar math helpers — e.g. estimating how many iterations bisection needs.
n_steps = math.ceil(math.log((b - a) / tol, 2))np.polynomial.legendre.leggauss→ (nodes, weights)Gauss–Legendre quadrature nodes & weights on [-1, 1].
x, w = np.polynomial.legendre.leggauss(5)
# map [-1,1] → [a,b] then integral ≈ np.sum(w * f(xm)) * (b-a)/2📖NA Cookbook
The recurring ideas and traps behind the labs. If a number looks wrong, the explanation is almost always here.
Go-to recipes
Relative vs absolute errorAbsolute error is |approx − exact|; relative divides by |exact| (scale-free).
abs_err = np.abs(approx - exact)
rel_err = abs_err / np.abs(exact)Order of accuracy from a log-log slopeIf error ∝ hᵖ, then log(err) vs log(h) is a line of slope p.
p = np.polyfit(np.log(hs), np.log(errs), 1)[0]
print(f"observed order ≈ {p:.2f}")Error ratio tableHalving h and watching the error ratio reveals the order (ratio ≈ 2ᵖ).
ratio = errs[:-1] / errs[1:] # ~2 ⇒ 1st order, ~4 ⇒ 2nd orderCheck your linear solveAlways verify the residual after solving Ax=b.
assert np.linalg.norm(A @ x - b) < 1e-8Common traps
Never compare floats with ==Rounding makes exact equality almost always False.
# WRONG: if x == 0.3
if np.isclose(x, 0.3): ...Catastrophic cancellationSubtracting two nearly equal numbers destroys significant digits.
# unstable: (1 - np.cos(x)) / x**2 for small x
# stable: 0.5 * (np.sinc(x/np.pi))**2 or a Taylor forminv(A) @ b vs solve(A, b)Forming the inverse is slower and less accurate than solving directly.
# avoid: x = np.linalg.inv(A) @ b
x = np.linalg.solve(A, b)Ill-conditioning (large κ)A big condition number means the answer is untrustworthy no matter the algorithm.
if np.linalg.cond(A) > 1e12:
print("warning: nearly singular")arange endpoint / float stepsnp.arange excludes the stop and can drop the last point due to rounding.
# safer for a known count:
x = np.linspace(a, b, num)Loop instead of vectorizePython loops over arrays are slow and error-prone — operate on whole arrays.
# slow: for i in range(n): y[i] = f(x[i])
y = f(x) # if f uses np.* it already vectorizes