MTH3007b Weekly Problems 1
Original Documents: Problem Sheet / Provided Solutions
Vibes: …
Used Techniques:
- …
1.1. Key Definitions in Numerical Methods
Question
What is meant by each of the following terms?
- Local truncation error
- Global truncation error
- Order of an algorithm
- Finite difference method
Local truncation error (LTE) is the error introduced in a single step of a numerical method, assuming the previous value is exact. It measures how well the discrete update rule approximates the true differential equation over one step. For a method with step size , LTE is typically expressed as a power of .
Global truncation error (GTE) is the accumulated error over all steps from to . It accounts for the propagation of each local error through the integration. Since there are steps, GTE is typically one order lower in than the LTE.
Order of an algorithm describes how the global truncation error scales with the step size . A method is said to be -th order if
so halving reduces the error by a factor of .
finite difference method is a technique for approximating derivatives by replacing them with algebraic difference quotients on a discrete grid. For example, the forward difference approximation to at is
where is the grid spacing. Substituting such approximations into a differential equation converts it into an algebraic recurrence relation that can be solved on a computer.
1.2. Implicit vs. Explicit Relations
Question
What is the difference between an implicit and an explicit relation?
An explicit relation expresses the unknown quantity directly in terms of known quantities. In the context of numerical ODE solvers, the update for depends only on values already computed (e.g. , ):
This can be evaluated directly with no further solving required.
An implicit relation involves the unknown on both sides, so the equation must be solved (algebraically or iteratively) to find . For example, the backward (implicit) Euler method gives:
Since appears evaluated at , this is an implicit equation for . For linear ODEs this can often be rearranged algebraically; for nonlinear ODEs a root-finding method (e.g. Newton-Raphson) is needed. Implicit methods are generally more stable than explicit ones, especially for stiff equations.
1.3. Explicit Euler Implementation
Question
Implement the explicit (forward) Euler method for the ODE with , , , . Run with and and report in each case.
The Explicit Euler method advances the solution by:
where .
import numpy as np
import matplotlib.pyplot as plt
forcing_coefficient = 1.0
decay_rate = 22.0
def g(t: float, y: float) -> float:
return forcing_coefficient * t - decay_rate * y
time_start = 0.0
time_end = 1.0
initial_value = 1.0
for time_step in [0.01, 0.1]:
number_of_steps = int(round((time_end - time_start) / time_step))
t_values = np.zeros(number_of_steps + 1)
y_values = np.zeros(number_of_steps + 1)
t_values[0] = time_start
y_values[0] = initial_value
for step_index in range(number_of_steps):
t_values[step_index + 1] = t_values[step_index] + time_step
y_values[step_index + 1] = (
y_values[step_index] + time_step * g(t_values[step_index], y_values[step_index])
)
print(f"dt={time_step}: y(t_end) = {y_values[number_of_steps]:.7f}")Results from the PDF:
| (Explicit Euler) | |
|---|---|
| 0.01 | 0.04338843 |
| 0.1 | 6.2479177 |
The large result is wildly wrong because makes this ODE stiff: the stability condition for explicit Euler requires , giving . At the method is unstable.
1.4. Implicit Euler Implementation
Question
Implement the implicit (backward) Euler method for the same ODE with the same parameters. Run with and .
The Implicit Euler method (backward Euler) uses:
For this becomes:
Rearranging algebraically:
import numpy as np
forcing_coefficient = 1.0
decay_rate = 22.0
time_start = 0.0
time_end = 1.0
initial_value = 1.0
for time_step in [0.01, 0.1]:
number_of_steps = int(round((time_end - time_start) / time_step))
t_values = np.zeros(number_of_steps + 1)
y_values = np.zeros(number_of_steps + 1)
t_values[0] = time_start
y_values[0] = initial_value
for step_index in range(number_of_steps):
t_values[step_index + 1] = t_values[step_index] + time_step
y_values[step_index + 1] = (
y_values[step_index] + time_step * forcing_coefficient * t_values[step_index + 1]
) / (1.0 + decay_rate * time_step)
print(f"dt={time_step}: y(t_end) = {y_values[number_of_steps]:.7f}")Results from the PDF:
| (Implicit Euler) | |
|---|---|
| 0.01 | 0.04338843 |
| 0.1 | 0.04339733 |
Note that implicit Euler remains stable for large : the amplification factor for implicit Euler is , which has magnitude less than 1 for all . This is unconditional stability.
1.5. Analytical Solution and Error Comparison
Question
The analytical solution to , is … wait, re-read:
Compare the errors of explicit and implicit Euler for both step sizes.
The exact analytical solution is:
This can be verified by substituting into .
import numpy as np
forcing_coefficient = 1.0
decay_rate = 22.0
initial_value = 1.0
time_end = 1.0
def y_exact(t: float) -> float:
return (
np.exp(-decay_rate * t) * (initial_value + forcing_coefficient / decay_rate ** 2)
+ forcing_coefficient * t / decay_rate
- forcing_coefficient / decay_rate ** 2
)
y_true = y_exact(time_end)
print(f"Exact y(1) = {y_true:.7f}")
numerical_results = {
"Explicit Euler dt=0.01": 0.04338843,
"Explicit Euler dt=0.1": 6.2479177,
"Implicit Euler dt=0.01": 0.04338843,
"Implicit Euler dt=0.1": 0.04339733,
}
for label, y_numerical in numerical_results.items():
print(f"{label}: error = {abs(y_numerical - y_true):.6e}")Key observations:
- Both methods give nearly the same answer at , because at small step sizes their errors are dominated by the truncation error (both are first-order methods).
- Explicit Euler explodes at due to numerical instability - the ODE is stiff and .
- Implicit Euler remains accurate even at , demonstrating unconditional stability. The small remaining error is just first-order truncation error.