5.3. The finite-difference method#
The finite-difference method for solving a boundary value problem replaces the derivatives in the ODE with finite-difference approximations derived from the Taylor series. This results in linear system of algebraic equations that can be solved to give an approximation of the solution to the BVP.
The derivatives of \(y_i\) are approximated using values of neighbouring nodes \(y_{i+1}\), \(y_{i-1}\) etc. using expressions derived by truncating the Taylor series and rearranging to make the derivative term the subject. Some common finite-difference approximations are listed in Table 5.2.
Finite-difference approximation |
Order |
Name |
---|---|---|
\(y' = \dfrac{y_{i+1} - y_i}{h}\) |
first |
forward difference |
\(y' = \dfrac{y_i - y_{i-1}}{h}\) |
first |
backward difference |
\(y' = \dfrac{y_{i+1} - y_{i-1}}{2h}\) |
second |
central difference |
\(y'' = \dfrac{y_{i-1} - 2 y_i + y_{i+1}}{h^2}\) |
second |
symmetric difference |
The solution to a boundary value problem using then finite-difference method is determined by approximating the derivatives in the ODE using finite-differences. Consider the following boundary value problem
Using the symmetric difference to approximate \(y''\) in the ODE we have
Since we know that \(y_0 = \alpha\) and \(y_n = \beta\) then the first and last equations become we have a system of \(n\) equations
which can be written as the matrix equation
5.3.1. The Thomas algorithm#
The coefficient matrix for the linear system that results from the application of the second-order symmetric finite-difference approximation of \(y''\) is tri-diagonal matrix (the only non-zero elements are on the main, lower and upper diagonals). The usual methods for solving linear systems of equations such as Gaussian elimination and LU decomposition could be applied here, however there is a more efficient method that can solve tri-diagonal systems called the Thomas algorithm).
(The Thomas algorithm)
Given a tri-diagonal linear system of equations of the form
the solution can be found by performing a forward sweep on the \(b_i\) and \(d_i\) coefficients
and then calculate the values of \(x_i\) using back substitution
Use the finite-difference method to solve the boundary below using a step length of \(h = 0.2\)
Solution (click to show)
Using a forward difference to approximate \(y'\) and a symmetric difference to approximate \(y''\) we have
which can be simplified to
This gives the following system of linear equations
which we can write the linear system as the matrix equation
If we use a step length of \(h=0.2\) we have
and
Performing the forward sweep of the Thomas algorithm
Performing the back substitution
The solutions to this boundary value problem using the finite-difference method are tabulated below.
\(t_n\) |
\(y_n\) |
Exact |
Error |
---|---|---|---|
0.0 |
0.000000 |
0.000000 |
0.00e+00 |
0.2 |
0.205992 |
0.221296 |
1.53e-02 |
0.4 |
0.473782 |
0.501419 |
2.76e-02 |
0.6 |
0.832209 |
0.865840 |
3.36e-02 |
0.8 |
1.321853 |
1.349412 |
2.76e-02 |
1.0 |
2.000000 |
2.000000 |
0.00e+00 |
5.3.2. Code#
The code below calculates the solution to the boundary value problem in Example 5.4. The coefficient arrays a
, b
and c
along with the constant vector d
are defined for this problem and the tridiagonal_solver()
function is used to solve the linear system. Note that the values in a
, b
, c
and d
are specific to the problem being solved and will need to be changed when solving other boundary value problems.
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['text.usetex'] = True # allows use of LaTeX in labels
# Define tridiagonal solver
def tridiagonal_solver(a, b, c, d):
n = len(b)
for i in range(1, n):
w = a[i] / b[i-1]
b[i] -= w * c[i-1]
d[i] -= w * d[i-1]
x = np.zeros(n)
x[-1] = d[-1] / b[-1]
for i in range(n - 2, -1, -1):
x[i] = (d[i] - c[i] * x[i+1]) / b[i]
return x
# Define exact solution
def exact(t):
return (2 * np.exp((1 - np.sqrt(5)) * (t - 1) / 2) * (np.exp(np.sqrt(5) * t) -1)) / (np.exp(np.sqrt(5)) - 1)
# Define BVP parameters
tspan = [0, 1] # boundaries of the t domain
bvals = [0, 2] # boundary values
h = 0.2 # step length
# Define linear system
n = int((tspan[1] - tspan[0]) / h) + 1
t = np.arange(n) * h
a, b, c, d = [np.zeros(n) for _ in range(4)]
b[::n-1] = 1
d[::n-1] = bvals
for i in range(1, n - 1):
a[i] = 1
b[i] = h - h ** 2 - 2
c[i] = 1 - h
d[i] = 0
# Solve linear system
y = tridiagonal_solver(a, b, c, d)
# Print table of solution values
print("| t | y | Exact | Error |")
print("|:---:|:--------:|:--------:|:--------:|")
for n in range(len(t)):
print(f"| {t[n]:0.1f} | {y[n]:6.6f} | {exact(t[n]):6.6f} | {abs(exact(t[n]) - y[n]):6.2e} |")
# Plot solution
t_exact = np.linspace(0, tspan[1], 200)
y_exact = exact(t_exact)
fig, ax = plt.subplots()
plt.plot(t_exact, y_exact, "k-", label="Exact")
plt.plot(t, y, "bo-", label=f"Finite-difference method")
plt.xlabel("$t$", fontsize=14)
plt.ylabel("$y$", fontsize=14)
plt.legend(fontsize=12)
plt.show()
% Define ODE
f = @(t, y) [y(2), y(1) + y(2)];
% Define exact solution
exact = @(t, y) (2 .* exp((1 - sqrt(5)) .* (t - 1) / 2) .* (exp(sqrt(5) .* t) - 1)) / (exp(sqrt(5)) - 1);
% Solve BVP using finite-difference method
n = floor((tspan(2) - tspan(1)) / h) + 1;
t = (0 : n - 1) * h;
a = zeros(n, 1);
b = zeros(n, 1);
c = zeros(n, 1);
d = zeros(n, 1);
b([1, end]) = 1;
d([1, end]) = bvals;
for i = 2 : n - 1
a(i) = 1;
b(i) = h - h ^ 2 - 2;
c(i) = 1 - h;
d(i) = 0;
end
y = tridiagonal_solver(a, b, c, d);
% Calculate exact solution
t_exact = linspace(tspan(1), tspan(2), 200);
y_exact = exact(t_exact);
% Plot solutions
plot(t_exact, y_exact, "k", LineWidth=2)
hold on
plot(t, y, "b-o", LineWidth=2, MarkerFaceColor="b")
hold off
axis padded
xlabel("$t$", FontSize=14, Interpreter="latex")
ylabel("$y$", FontSize=14, Interpreter="latex")
legend("Exact", "Finite-difference method", Location="northwest")
%% ----------------------------------------------------------------------------
function x = tridiagonal_solver(a, b, c, d)
n = length(b);
for i = 2 : n
w = a(i) / b(i-1);
b(i) = b(i) - w * c(i-1);
d(i) = d(i) - w * d(i-1);
end
x = zeros(n, 1);
x(n) = d(n) / b(n);
for i = n - 1 : -1 : 1
x(i) = (d(i) - c(i) * x(i+1)) / b(i);
end
end
5.3.3. Order vs number of nodes#
The solutions seen in Example 5.4 seem to show that the finite-difference method produces reasonably accurate results for this boundary value problem. One way to improve on the accuracy of our solution is to increase the number of nodes used.
Consider the solution of the following BVP using the finite-difference method
Using forward and symmetric differences to approximate \(y'\) and \(y''\) respectively gives
So the linear system is
The solution of this BVP is shown in Fig. 5.2 for \(h=0.05\) and \(h = 0.005\). Since we used a first-order approximation for \(y'\) the error for this method is \(O(h)\) and we expect the solution using \(h=0.005\) to be more accurate.
To obtain a more accurate solution, instead of increasing the number of nodes we could use the central difference approximation (which is second-order accurate) to approximate \(y'\)
So the linear system for the second-order finite-difference method is
The solution using the second-order finite difference method with \(h=0.05\) has been plotted against the first-order solution using \(h=0.05\) and \(h=0.005\) in Fig. 5.3. The second order solution gives good agreement with the first order solution using 10 times fewer nodes.