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.

MethodLTEGTE (order)
Explicit / Implicit Euler — 1st
Midpoint, Ralston, Implicit Trapezoid — 2nd
RK4 — 4th
Monte Carlo — “half order”

2. ODE Solvers — Scalar

For , .

MethodUpdate formulaStable?
Explicit EulerConditional
Implicit EulerUnconditional
MidpointConditional
Ralston; ; Conditional
Implicit TrapezoidUnconditional
RK4See formula belowConditional

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 .

MethodStable region
Explicit Euler; for :
Implicit EulerAll (unconditional)
Implicit TrapezoidAll (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).

SchemeUpdateGTEStable?
FTCS (explicit)Conditional:
BTCS (implicit)Unconditional
RichardsonUnconditionally 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_temperature

Neumann ( 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_next

BTCS 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 solution

Thomas 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:
        break

Convergence 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_error

13. 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_wiener

Gaussian 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 = 1

17. 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:

  1. Truncation error — from cutting the Taylor series. Reducible by decreasing .
  2. 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 one

Array 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:

  1. for all
  2. 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 k

20. Quick Formula Index

QuantityFormula
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