MTH3007b Final Exam Cheat Sheet
1. Errors and Order
Local truncation error (LTE): error introduced in a single step due to truncating the Taylor series. For Euler: .
Global truncation error (GTE): accumulated error over the whole integration. Since there are steps, GTE . For Euler: .
Order of a method: the power of in the GTE. Euler is 1st order; midpoint/Ralston/trapezoid are 2nd order; RK4 is 4th order.
| Method | LTE | GTE (order) |
|---|---|---|
| Explicit / Implicit Euler | — 1st | |
| Midpoint, Ralston, Implicit Trapezoid | — 2nd | |
| RK4 | — 4th | |
| Monte Carlo | — | — “half order” |
2. ODE Solvers — Scalar
For , .
| Method | Update formula | Stable? |
|---|---|---|
| Explicit Euler | Conditional | |
| Implicit Euler | Unconditional | |
| Midpoint | Conditional | |
| Ralston | ; ; | Conditional |
| Implicit Trapezoid | Unconditional | |
| RK4 | See formula below | Conditional |
RK4 in full:
Closed forms for
These are derived by solving the implicit equations algebraically for .
Heun’s method vs implicit trapezoid
The implicit trapezoid uses the true on the right-hand side and is implicit and symmetric.
Heun’s method (explicit trapezoid) is different - it uses a forward-Euler predictor to evaluate the derivative at the next step:
Heun’s method is 2nd order but is not symmetric. The implicit trapezoid method is symmetric (it is the same under the exchange , ).
3. Stability of ODEs and Methods
ODE stability
An ODE is stable if small perturbations to initial conditions remain bounded for all . For : stable when (solution decays), unstable when (solution grows).
Amplification factor
For the test equation (), each method produces . The method is stable when .
| Method | Stable region | |
|---|---|---|
| Explicit Euler | ; for : | |
| Implicit Euler | All (unconditional) | |
| Implicit Trapezoid | All (unconditional) | |
| Explicit RK methods | (more complex) | Conditional only |
Richardson method (symmetric, but unstable)
This is 2nd order in , but is unconditionally unstable for ODEs like - for any the numerical solution diverges. Not useful in practice.
4. Convergence, Consistency, and Lax
Convergence: the numerical solution approaches the exact solution as .
Consistency: the exact solution satisfies the numerical scheme in the limit .
Lax Equivalence Theorem: for a well-posed linear IVP/BVP and a consistent discretisation,
Both explicit and implicit Euler are consistent, so they converge for sufficiently small .
5. Systems of ODEs
Introduce state vector and vector field . Apply any scalar method componentwise.
Predator-prey (Lotka-Volterra), lecture parameters:
with , , , , , , , .
= prey, = predator. Solutions are periodic orbits. Large breaks the orbit (explicit Euler becomes unstable).
import numpy as np
prey_growth_rate = 1.2
predator_prey_interaction_prey = 0.6
predator_death_rate = 0.8
predator_prey_interaction_predator = 0.3
def system_derivative(time: float, state: np.ndarray) -> np.ndarray:
prey, predator = state
dprey = prey_growth_rate * prey - predator_prey_interaction_prey * prey * predator
dpredator = (-predator_death_rate * predator
+ predator_prey_interaction_predator * prey * predator)
return np.array([dprey, dpredator])
number_of_steps = int(30.0 / 0.01)
state = np.array([2.0, 1.0])
for step_index in range(number_of_steps):
state = state + 0.01 * system_derivative(0.0, state)Reducing a 2nd-order ODE
For , set , :
Then solve as a 2-component system with any ODE method.
def second_order_system(time: float, state: np.ndarray) -> np.ndarray:
position, velocity = state
acceleration = forcing_function(time, position, velocity)
return np.array([velocity, acceleration])6. Numerical Integration via ODE
To compute , define by:
Apply any ODE solver; gives the integral. Order of the integral estimate matches the order of the ODE solver used.
def integrand_as_ode(time: float, current_value: float) -> float:
return f(time) # f is the function to integrate
time_values, solution_values = explicit_euler_method(
integrand_as_ode, initial_value=0.0, time_start=t_start,
time_end=t_end, time_step=delta_t
)
integral_estimate = solution_values[-1]7. PDE Schemes (Heat Equation)
Heat equation:
Define (the diffusion number / Fourier number).
| Scheme | Update | GTE | Stable? |
|---|---|---|---|
| FTCS (explicit) | Conditional: | ||
| BTCS (implicit) | Unconditional | ||
| Richardson | Unconditionally unstable |
FTCS stability condition:
For fine grids (small ), this forces very small - the key disadvantage of FTCS.
8. Boundary Conditions
Dirichlet
Fix at boundary nodes directly. In the BTCS matrix:
coefficient_matrix[0, 0] = 1.0 # left boundary row: u_0 = u_left
coefficient_matrix[-1, -1] = 1.0 # right boundary row: u_N = u_right
right_hand_side[0] = left_temperature
right_hand_side[-1] = right_temperatureNeumann ( at , insulated end)
Use the ghost/imaginary point method: . This modifies the last interior row of the BTCS matrix from to . For FTCS at the last interior node:
9. FTCS and BTCS Code
FTCS loop
stability_parameter = diffusivity * time_step / spatial_step ** 2 # must be <= 0.5
for time_index in range(number_of_time_steps - 1):
u_next = u_profile.copy()
for node_index in range(1, number_of_spatial_nodes - 1):
u_next[node_index] = (
u_profile[node_index]
+ stability_parameter * (
u_profile[node_index + 1]
- 2 * u_profile[node_index]
+ u_profile[node_index - 1]
)
)
u_profile = u_nextBTCS matrix setup and solve (lecture §42.5 — use inv + matmul)
import numpy as np
diffusion_number = diffusivity * time_step / spatial_step ** 2
coefficient_matrix = np.zeros((number_of_spatial_nodes, number_of_spatial_nodes))
coefficient_matrix[0, 0] = 1.0
coefficient_matrix[-1, -1] = 1.0
for node_index in range(1, number_of_spatial_nodes - 1):
coefficient_matrix[node_index, node_index - 1] = -diffusion_number
coefficient_matrix[node_index, node_index] = 1 + 2 * diffusion_number
coefficient_matrix[node_index, node_index + 1] = -diffusion_number
inverse_matrix = np.linalg.inv(coefficient_matrix) # invert once, reuse each step
# Each time step:
right_hand_side = u_profile.copy()
right_hand_side[0] = left_temperature
right_hand_side[-1] = right_temperature
u_profile = np.matmul(inverse_matrix, right_hand_side)10. Gaussian Elimination
Solves from the augmented matrix via in-place triangularisation then back substitution. Lecture §42.4:
import numpy as np
def gaussian_elimination(augmented_matrix: np.ndarray) -> np.ndarray:
"""Solve Ax = b via Gaussian elimination on the augmented matrix [A|b].
Args:
augmented_matrix: Shape (n, n+1). Last column is b. Modified in place.
Returns:
Solution vector x of length n.
"""
number_of_rows = augmented_matrix.shape[0]
number_of_cols = augmented_matrix.shape[1]
# Forward elimination (triangularisation)
for pivot_row in range(number_of_rows - 1):
for target_row in range(pivot_row + 1, number_of_rows):
coefficient = augmented_matrix[target_row, pivot_row] / augmented_matrix[pivot_row, pivot_row]
for col_index in range(pivot_row, number_of_cols):
augmented_matrix[target_row, col_index] -= augmented_matrix[pivot_row, col_index] * coefficient
# Back substitution
solution = np.zeros(number_of_rows)
for row_index in range(number_of_rows - 1, -1, -1):
solution[row_index] = augmented_matrix[row_index, number_of_cols - 1]
for col_index in range(row_index + 1, number_of_cols - 1):
solution[row_index] -= augmented_matrix[row_index, col_index] * solution[col_index]
solution[row_index] /= augmented_matrix[row_index, row_index]
return solutionThomas algorithm: for tridiagonal systems specifically, an algorithm exists (versus for general Gaussian elimination). The lecture mentions it but does not require its implementation - use numpy.linalg.inv + matmul per §42.5.
11. 2D Laplace and Liebmann’s Method
2D Laplace equation:
Finite difference (uniform ):
Liebmann’s method (two-array, lecture §46.4): compute all from , then replace. Convergence tolerance .
convergence_tolerance = 1e-4
for iteration_index in range(maximum_iterations):
u_new = u_grid.copy()
for row_index in range(1, number_of_rows - 1):
for col_index in range(1, number_of_cols - 1):
u_new[row_index, col_index] = (
u_grid[row_index + 1, col_index]
+ u_grid[row_index - 1, col_index]
+ u_grid[row_index, col_index + 1]
+ u_grid[row_index, col_index - 1]
) / 4.0
maximum_change = np.amax(np.abs(u_new - u_grid))
u_grid = u_new.copy()
if maximum_change < convergence_tolerance:
breakConvergence is guaranteed for Dirichlet BCs by the maximum principle for elliptic equations.
12. Monte Carlo Integration
One-sigma error estimate (lecture §32.4):
Order: , or equivalently . Much worse than deterministic methods for 1D, but the order is dimension-independent - the key advantage for high-dimensional integrals.
import numpy as np
def monte_carlo_integrate(
integrand, lower_bound: float, upper_bound: float, number_of_samples: int
) -> tuple[float, float]:
interval_length = upper_bound - lower_bound
sample_points = np.random.uniform(lower_bound, upper_bound, number_of_samples)
function_values = integrand(sample_points)
mean_f = np.mean(function_values)
mean_f_squared = np.mean(function_values ** 2)
estimate = interval_length * mean_f
one_sigma_error = interval_length * np.sqrt(
(mean_f_squared - mean_f ** 2) / number_of_samples
)
return estimate, one_sigma_error13. Stochastic Methods
Wiener process (Brownian motion)
Discrete update (exact):
Properties: , .
Ornstein-Uhlenbeck process (lecture §38.6.1)
Mean-reverting: fluctuates around with restoring force . No drift parameter in the lecture version.
General Langevin SDE (Euler-Maruyama)
For :
import numpy as np
def wiener_and_ou_step(
current_velocity: float,
current_wiener: float,
time_step: float,
mean_reversion_rate: float,
rng: np.random.Generator,
) -> tuple[float, float]:
noise = rng.standard_normal()
next_wiener = current_wiener + np.sqrt(time_step) * noise
next_velocity = (1 - mean_reversion_rate * time_step) * current_velocity + np.sqrt(time_step) * noise
return next_velocity, next_wienerGaussian random numbers
Generated from uniform random numbers via the Box-Muller transform or np.random.normal() / rng.standard_normal(). A Gaussian with mean and variance : use where .
14. First-Passage Time (Numerical)
Run the OU (or other) simulation. At each step, check if (threshold). Record when the threshold is first crossed. Average over walkers:
Choose so that for two significant figures.
import numpy as np
def simulate_first_passage(
initial_velocity: float,
threshold: float,
mean_reversion_rate: float,
time_step: float,
maximum_time: float,
rng: np.random.Generator,
) -> float:
current_velocity = initial_velocity
elapsed_time = 0.0
while elapsed_time < maximum_time:
if current_velocity >= threshold:
return elapsed_time
current_velocity = (
(1 - mean_reversion_rate * time_step) * current_velocity
+ np.sqrt(time_step) * rng.standard_normal()
)
elapsed_time += time_step
return maximum_time # threshold not reached: return max_time as a sentinel
number_of_walkers = 1000
rng = np.random.default_rng(42)
passage_times = np.array([
simulate_first_passage(0.0, 1.0, 0.5, 0.01, 100.0, rng)
for _ in range(number_of_walkers)
])
mean_passage_time = np.mean(passage_times)
standard_error = np.std(passage_times) / np.sqrt(number_of_walkers)15. RK2 Coefficient Derivation
All 2nd-order RK methods have the form where and . Matching Taylor series to order gives three equations in four unknowns:
One parameter is free, giving infinitely many 2nd-order methods. The two covered in the lecture:
| Method | |||
|---|---|---|---|
| Midpoint | |||
| Ralston |
Ralston’s choice of minimises the local truncation error bound.
16. Dirac Delta as Initial Condition
For the ink-diffusion problem: .
In a finite-difference scheme, set the height of the peak so the discrete area equals 1:
All other nodes start at zero. The sifting property verifies area preservation.
u_profile = np.zeros(number_of_spatial_nodes)
middle_node = number_of_spatial_nodes // 2
u_profile[middle_node] = 1.0 / spatial_step # area = (1/dx)*dx = 117. Poisson Equation (1D)
Poisson: . Finite difference: .
Rearranged to matrix form : interior rows carry the stencil (scaled by ); boundary rows enforce Dirichlet conditions. Solved with Gaussian elimination or np.linalg.inv + matmul.
Laplace (1D) is the special case — covered in semester A.
18. Round-off Errors and Number Types
Two distinct error types in any numerical method:
- Truncation error — from cutting the Taylor series. Reducible by decreasing .
- Round-off error — from finite floating-point precision. Accumulates as (more steps). The two effects compete: there is an optimal .
Float precision: 32-bit ~7 decimal digits; 64-bit ~16 decimal digits. Always use 64-bit.
Critical int pitfall: int(tmax / dt) can be off-by-one because 1.0 / 0.01 may evaluate to 99.9999.... Always use:
number_of_steps = int(round(tmax / dt)) # correct
Nx = int(np.round(xmax / dx) + 1) # correct for spatial grid
number_of_steps = int(tmax / dt) # WRONG: may be off by oneArray alias pitfall: B = A does NOT copy — it is an alias. Modifying B also changes A. Use B = A.copy() or B = 1.0 * A.
19. Random Walk and Wiener Process Properties
Discrete random walk: steps of with equal probability. After steps: , .
Wiener process — three defining properties:
- for all
- Non-overlapping increments are independent
Consequently: , .
Connection to diffusion: the pdf of satisfies the diffusion equation with and a delta-function IC. A histogram of many Brownian walkers is identical to the FTCS solution.
Gaussian RNG in Python:
rng = np.random.default_rng(seed)
noise_single = rng.standard_normal() # one N(0,1) draw
noise_array = rng.standard_normal(N) # N draws
# If Z ~ N(0,1) then k*Z ~ N(0, k^2) -- scale standard deviation by k20. Quick Formula Index
| Quantity | Formula |
|---|---|
| FTCS stability bound | |
| Explicit Euler stability | for |
| MC error | |
| MC order | |
| Liebmann tolerance | |
| OU update | |
| Wiener update | |
| BTCS GTE | |
| Implicit trapezoid closed form | |
| Implicit Euler closed form |