6.3. First-order methods#
Let \(y_{j}\) denote approximation \(y\) at grid points for \(j = 1,2,3,\ldots,n\) (i.e. \(y_{j} = y(t_{j})\)) and some positive integer values of \(n\), we can use several methods. Consider the following IVP:
In this section, we focus on several first-order methods to approximate the solution of this IVP numerically. Here, it is assumed that the grid points are equally distributed throughout the interval \([a, b]\). Let \(n\) be a positive integer, we have, the following step size
and each grid point can be identified as
6.3.1. (Forward) Euler method#
Assume that \(y\in C^{2}[a,~b]\). Then, using Taylor polynomials and series from Section \ref{Taylor_Polynomials}, for \(j = 0, 1, 2, \ldots,n - 1\), we have,
for some \(\xi_{j} \in(t_{j},~t_{j+1})\). Since \(y(t)\) satisfies the differential equation \eqref{IVP_01},
Deleting the remainder term, Euler’s method generates \(w_{i} \approx y(t_i)\) for \(i = 1,~2,~3,\ldots,~n\). Therefore, Euler’s method approximates the solution of the IVP (6.11) at each grid point starting from \(t_0 = a\) where the solution is known as \(\alpha\). This process is carried forward iteratively until point \(t_{n-1}\).
Let \(w_{i} \approx y(t_i)\) for \(i = 1,~2,~3,\ldots,~n\), then the (Forward) Euler’s method is
import pandas as pd
import numpy as np
def ForwardEuler(f, y0, a, b, h= False, N=False):
'''
Parameters
----------
f : function
DESCRIPTION. the ODE y'=f(t,y(x))
y0 : float
the initial value.
a : float
DESCRIPTION. a is the left side of interval [a, b]
b : float
DESCRIPTION. b is the right side of interval [a, b]
h : float, optional
DESCRIPTION. The default is False. stepsize
N : int, optional
DESCRIPTION. The default is False. number of points.
Returns
-------
Table : dataframe
DESCRIPTION. a summary of the algorithm output
'''
if N:
h = (b-a)/(N)
if h:
N = int((b-a)/h)
t = np.linspace(a, b, N+1)
y = np.zeros(t.shape, dtype=float)
y[0] = y0
# loop
for j in range(N):
y[j+1] = y[j] + h*f(t[j], y[j])
Table = pd.DataFrame({'t':t, 'y':y})
return Table
function [Table] = ForwardEuler(f, y0, a, b, N)
%{
Parameters
----------
f : function
DESCRIPTION. the ODE y'=f(t,y(x))
y0 : float
the initial value.
a : float
DESCRIPTION. a is the left side of interval [a, b]
b : float
DESCRIPTION. b is the right side of interval [a, b]
N : int
DESCRIPTION. Number of points.
Returns
-------
Table : dataframe
DESCRIPTION. a summary of the algorithm output
Example:
f = @(t, y) t.*exp(-t.^2)-2.*t.*y
a = 0
b = 1
N = 10
y0 = 1
%}
h = (b-a)/(N);
t = linspace(a, b, N+1)';
y = zeros(length(t),1);
y(1) = y0;
% loop
for j=1:N
y(j+1) = y(j) + h*f(t(j), y(j));
end
Table = table(t,y);
end
Example: Consider the following IVP,
with exact solution \(\displaystyle y\left(t \right) = \left(1 + \dfrac{t^{2}}{2}\right) e^{- t^{2}}\). Use the forward Euler method with \(n = 10\) for solving this IVP.
Solution:
For \(n = 10\): \([0,~1]\) can be discretized using
as follows,
Observe that, here,
Now using \eqref{eq.06.02}, we get,
Furthermore, using the exact solution, we can calculate the exact values at these discretized points. Let \(y_{j} = y(t_{j})\) for \(0 \leq j \leq 10\) denote the exact values at the discretized points. Using
we have,
Finally, the absolute error can be calculated using \(| w_{i}- y_{i}|\) for \(j = 0,1,2,\ldots,10\). The results can be summarized as follows,
import sys
sys.path.insert(0,'..')
import hd_tools as hd
import numpy as np
import pandas as pd
from hd_IVP_Algorithms import ForwardEuler
# f(t, y(t)):
f = lambda t, y: t*np.exp(-t**(2))-2*t*y
(a, b) = (0, 1)
# the exact solution y(t)
y_exact = lambda t: (1+(t**2)/2)*np.exp(-t**2)
y0 = 1
# Table
Table = ForwardEuler(f = f, y0 = y0, a = a, b = b, N = 10)
Table['Exact'] = y_exact(Table['t'])
Table['Error'] = np.abs(Table['Exact'] - Table['y'])
display(Table[1:].style.set_properties(subset=['Error'], **{'background-color': 'Lavender', 'color': 'Navy',
'border-color': 'DarkGreen'}).format({'Error': "{:.6e}"}))
t | y | Exact | Error | |
---|---|---|---|---|
1 | 0.100000 | 1.000000 | 0.995000 | 4.999917e-03 |
2 | 0.200000 | 0.989900 | 0.980005 | 9.895270e-03 |
3 | 0.300000 | 0.969520 | 0.955058 | 1.446218e-02 |
4 | 0.400000 | 0.938767 | 0.920315 | 1.845169e-02 |
5 | 0.500000 | 0.897751 | 0.876151 | 2.160050e-02 |
6 | 0.600000 | 0.846916 | 0.823258 | 2.365822e-02 |
7 | 0.700000 | 0.787147 | 0.762720 | 2.442705e-02 |
8 | 0.800000 | 0.719830 | 0.696026 | 2.380419e-02 |
9 | 0.900000 | 0.646841 | 0.625026 | 2.181517e-02 |
10 | 1.000000 | 0.570447 | 0.551819 | 1.862748e-02 |
6.3.1.1. Error Bounds for (Forward) Euler’s Method#
The following lemma is Lemma 5.7 from [Burden and Faires, 2005].
Lemma
For all \(x \geq -1\) and any positive \(m\), we have
Proof: Using Taylor’s Theorem from \eqref{Taylor_App} with \(f(x) = e^{x}\), \(x_0 = 0\), and \(n = 1\), we have,
where \(\xi\) is a number between \(x\) and zero. Therefore,
Since \(1 + x \geq 0\), we have
The following lemma is Lemma 5.8 from [Burden and Faires, 2005].
Lemma
Assume that \(\{ a_{i} \}_{i =0}^{k}\) is a sequence satisfying \( a_{0} \geq -t/s\) for some positive values \(s\) and \(t\), and
then,
Proof: Let’s fixed index \(i\) and then if follows from the above lemma that,
On the other hand,
represent a geometric series with ratio \((1 + s)\). Thus,
Therefore,
Now, it follows from using Lemma \ref{lemma_5.7} with \(x = 1 + s\) that,
\end{proof}
The following theorem is Theorem 5.9 from [Burden and Faires, 2005].
Theorem
Assume that domain \(D = \{ (t, y):~a\leq t \leq b,~-\infty<y <\infty\}\) and \(f\in C(D)\). If \(f\) satisfies a Lipschitz condition on \(D\) in the variable \(y\) with constant \(L\) and assume that constant \(M\) exists such that
where \(y(t)\) denotes the unique solution to the IVP (6.11).
Assume that \(w_0\), \(w_1\), \ldots , \(w_n\) are the approximations generated by Euler’s method for some positive integer \(n\). Therefore,
Proof:
In case \hlg{\(i = 0\)}, the result is true since \(y(t_{0}) = w_{0} = \alpha\). From (6.13) that,
for \(i = 0, 1, . . . , n - 1\), and from the equations in (6.14),
Now, let \(y_{i} = y(t_{i})\) and \(y_{i+1} = y(t_{i+1})\), then
Therefore,
As \(f\) satisfies a Lipschits condition in the second variable, \(y\), with constant \(L\), and \(|y''(t)|\leq M\). Therefore,
At this point, we use Lemma \ref{lemma_5.8} and let \(s = hL\), \(t = (h^2\, M)/2\), and \(a_{j} = |y_{j} - w_{j}|\), for reach \(j = 0,1,\ldots, n\), it follows that
As \(|y_{0} - w_{0}| = 0\) and \((i+1)h = t_{i+1} - t_{0} = t_{i+1} - a\). Therefore,
and
Example: Consider the following IVP,
with exact solution
a. Use the forward Euler method with \(h = 0.2\) and solve this IVP.
b. Find bounds for the approximation errors and compare these bounds to the absolute errors.
Solution:
a. Discretizing \([0,~2]\) using \(h = 0.2\):
Note that here \(n = 10\) as \(\displaystyle{ n = \frac{b - a}{h} = \frac{2 - 0}{0.2} = 10}\). On the other hand,
Therefore,
Similarly, the exact values at the discretized points can be calculated using the exact solution at \(t_0 = 0\), \(t_1 = 0.2\), \(t_2 = 0.4\),\ldots, \(t_8 = 1.6\), \(t_9 = 1.8\), \(t_{10} = 2.0\). The results can be summarized as follows,
import numpy as np
import pandas as pd
# f(t, y(t)):
cot = lambda t: 1/np.tan(t)
f = lambda t, y: y - t**2 + 1
(a, b) = (0, 2)
# the exact solution y(t)
y_exact = lambda t: (t + 1)**2 - 0.5* np.exp(t)
y0 = 0.5
# Table
Table = ForwardEuler(f = f, y0 = y0, a = a, b = b, N = 10)
Table['Exact'] = y_exact(Table['t'])
Table['Error'] = abs(Table['Exact'] - Table['y'])
display(Table.style.set_properties(subset=['t'],
**{'background-color': 'PaleGreen', 'color': 'Black',
'border-color': 'DarkGreen'}).format({'Error':'{:.6e}'}))
t | y | Exact | Error | |
---|---|---|---|---|
0 | 0.000000 | 0.500000 | 0.500000 | 0.000000e+00 |
1 | 0.200000 | 0.800000 | 0.829299 | 2.929862e-02 |
2 | 0.400000 | 1.152000 | 1.214088 | 6.208765e-02 |
3 | 0.600000 | 1.550400 | 1.648941 | 9.854060e-02 |
4 | 0.800000 | 1.988480 | 2.127230 | 1.387495e-01 |
5 | 1.000000 | 2.458176 | 2.640859 | 1.826831e-01 |
6 | 1.200000 | 2.949811 | 3.179942 | 2.301303e-01 |
7 | 1.400000 | 3.451773 | 3.732400 | 2.806266e-01 |
8 | 1.600000 | 3.950128 | 4.283484 | 3.333557e-01 |
9 | 1.800000 | 4.428154 | 4.815176 | 3.870225e-01 |
10 | 2.000000 | 4.865785 | 5.305472 | 4.396874e-01 |
b. Note that,
for all values of \(y\). On the other hand,
The maximum \(\left|2-\dfrac{e^t}{2}\right|\) on \([0,~2]\) takes place at \(t = 2\) (why!?), and
Using the inequality in the error bound (6.27) for Euler’s method with \(h = 0.2\), \(L = 1\), and \(M = \dfrac{e^2}{2} - 2\) gives
Therefore,
Now, we can compare the absolute error from above table with these error bounds.
Example: In the previous example, investigate the order of convergence numerically.
Solution:
For measuring error, we use the following measurement tool:
h = [2**(-i) for i in range(3, 12)]
Cols = ['h', 'N', 'Eh']
Table = pd.DataFrame(np.zeros([len(h), len(Cols)], dtype = float), columns=Cols)
Table['h'] = h
Table['N'] = ((b-a)/Table['h']).astype(int)
for n in range(Table.shape[0]):
TB = ForwardEuler(f = f, y0 = y0, a = a, b = b, h = Table['h'][n])
Table.loc[n, 'Eh'] = np.max(np.abs(y_exact(TB['t'])[1:] - TB['y'][1:]))
display(Table.style.set_properties(subset=['h', 'N'],
**{'background-color': 'PaleGreen', 'color': 'Black',
'border-color': 'DarkGreen'}).format({'h':'{:.4e}','Eh':'{:.4e}'}))
h | N | Eh | |
---|---|---|---|
0 | 1.2500e-01 | 16 | 2.9500e-01 |
1 | 6.2500e-02 | 32 | 1.5722e-01 |
2 | 3.1250e-02 | 64 | 8.1306e-02 |
3 | 1.5625e-02 | 128 | 4.1364e-02 |
4 | 7.8125e-03 | 256 | 2.0865e-02 |
5 | 3.9062e-03 | 512 | 1.0479e-02 |
6 | 1.9531e-03 | 1024 | 5.2510e-03 |
7 | 9.7656e-04 | 2048 | 2.6284e-03 |
8 | 4.8828e-04 | 4096 | 1.3150e-03 |
from bokeh.plotting import show
hd.derivative_ConvergenceOrder(vecs = [Table['Eh'].values],
labels = ['Forward Euler method'],
xlabel = r"$$i$$",
ylabel = r"$$\ln \left( E_{h_{i}} / E_{h_{i-1}} \right)$$",
title = 'Order of convergence: %s' % 'Forward Euler method',
legend_orientation = 'horizontal', ylim = [0.9, 1.02])
6.3.2. Backward (Implicit) Euler method#
The backward Euler’s method can be used to approximate the solution of the IVP (6.11). This method utilizes \(f(t_{j+1}, w_{j+1})\) instead of the current point \(f(t_{j}, w_{j})\):
where \(w_{0} = \alpha\) and \(w_{j+1}\) for \(j = 0, 1, 2, \ldots ,n - 1\) are approximated by solving the above equation in each step.
Observe that \(w_{j+1}\) appears on both sides of the equation (6.36) which necessitates the solution of an algebraic equation for the unknown [Atkinson, Han, and Stewart, 2011].
import pandas as pd
import numpy as np
from sympy import symbols, solve
def BackwardEuler(f, y0, a, b, h= False, N=False):
'''
Parameters
----------
f : function
DESCRIPTION. the ODE y'=f(t,y(x))
y0 : float
the initial value.
a : float
DESCRIPTION. a is the left side of interval [a, b]
b : float
DESCRIPTION. b is the right side of interval [a, b]
h : float, optional
DESCRIPTION. The default is False. stepsize
N : int, optional
DESCRIPTION. The default is False. number of points.
Returns
-------
Table : dataframe
DESCRIPTION. a summary of the algorithm output
'''
if N:
h = (b-a)/(N)
if h:
N = int((b-a)/h)
t = np.linspace(a, b, N+1)
y = np.zeros(t.shape, dtype=float)
y[0] = y0
b = symbols('b')
for j in range(N):
y[j+1] = float(solve(y[j] + h*f(t[j+1], b) - b)[0])
Table = pd.DataFrame({'t':t, 'y':y})
return Table
function [Table] = BackwardEuler(f, y0, a, b, N)
%{
Parameters
----------
f : function
DESCRIPTION. the ODE y'=f(t,y(x))
y0 : float
the initial value.
a : float
DESCRIPTION. a is the left side of interval [a, b]
b : float
DESCRIPTION. b is the right side of interval [a, b]
N : int
DESCRIPTION. Number of points.
Returns
-------
Table : dataframe
DESCRIPTION. a summary of the algorithm output
Example:
f = @(t, y) t.*exp(-t.^2)-2.*t.*y
a = 0
b = 1
N = 10
y0 = 1
%}
h = (b-a)/(N);
t = linspace(a, b, N+1)';
y = zeros(length(t),1);
y(1) = y0;
syms b
% loop
for j=1:N
y(j+1) = vpasolve(y(j) + h*f(t(j+1), b) - b);
end
Table = table(t,y);
end
Example: Consider the following IVP,
with exact solution
Use the backward Euler method for solving this IVP. Also, investigate the order of convergence numerically.
Solution: From one of our previous examples, we have,
with exact solution \(\displaystyle{y\left(t \right) = \left(1 + \dfrac{t^{2}}{2}\right) e^{- t^{2}}}\). For \hlg{\(n = 10\)}: \([0,~1]\) can be discretized using
as follows,
and using (6.36) and \(y_{0} = 1\), have
The results can be summarized as follows,
from hd_IVP_Algorithms import BackwardEuler
# f(t, y(t)):
f = lambda t, y: t*np.exp(-t**(2))-2*t*y
(a, b) = (0, 1)
# the eact solution y(t)
y_exact = lambda t: (1+(t**2)/2)*np.exp(-t**2)
#
y0 = 1
# Table
Table = BackwardEuler(f = f, y0 = y0, a = a, b = b, N = 10)
Table['Exact'] = y_exact(Table['t'])
Table['Error'] = np.abs(Table['Exact'] - Table['y'])
display(Table[1:].style.set_properties(subset=['Error'], **{'background-color': 'Lavender', 'color': 'Navy',
'border-color': 'DarkGreen'}).format({'Error': "{:.4e}"}))
t | y | Exact | Error | |
---|---|---|---|---|
1 | 0.100000 | 0.990099 | 0.995000 | 4.9016e-03 |
2 | 0.200000 | 0.970495 | 0.980005 | 9.5107e-03 |
3 | 0.300000 | 0.941427 | 0.955058 | 1.3631e-02 |
4 | 0.400000 | 0.903252 | 0.920315 | 1.7063e-02 |
5 | 0.500000 | 0.856539 | 0.876151 | 1.9612e-02 |
6 | 0.600000 | 0.802142 | 0.823258 | 2.1116e-02 |
7 | 0.700000 | 0.741251 | 0.762720 | 2.1469e-02 |
8 | 0.800000 | 0.675374 | 0.696026 | 2.0652e-02 |
9 | 0.900000 | 0.606281 | 0.625026 | 1.8745e-02 |
10 | 1.000000 | 0.535891 | 0.551819 | 1.5928e-02 |
Moreover, as for the error analysis, define,
then,
h = [2**(-i) for i in range(3, 8)]
Cols = ['h', 'N', 'Eh']
Table = pd.DataFrame(np.zeros([len(h), len(Cols)], dtype = float), columns=Cols)
Table['h'] = h
Table['N'] = ((b-a)/Table['h']).astype(int)
for n in range(Table.shape[0]):
TB = BackwardEuler(f = f, y0 = y0, a = a, b = b, h = Table['h'][n])
Table.loc[n, 'Eh'] = np.max(np.abs(y_exact(TB['t'])[1:] - TB['y'][1:]))
display(Table.style.set_properties(subset=['h', 'N'],
**{'background-color': 'PaleGreen', 'color': 'Black',
'border-color': 'DarkGreen'}).format({'h':'{:.4e}','Eh':'{:.4e}'}))
hd.derivative_ConvergenceOrder(vecs = [Table['Eh'].values],
labels = ['Backward Euler method'],
xlabel = r"$$i$$",
ylabel = r"$$\ln \left( E_{h_{i}} / E_{h_{i-1}} \right)$$",
title = 'Order of convergence: %s' % ' Backward Euler method',
legend_orientation = 'horizontal', ylim = [0.9, 1.1])
h | N | Eh | |
---|---|---|---|
0 | 1.2500e-01 | 8 | 2.6255e-02 |
1 | 6.2500e-02 | 16 | 1.3750e-02 |
2 | 3.1250e-02 | 32 | 7.0121e-03 |
3 | 1.5625e-02 | 64 | 3.5410e-03 |
4 | 7.8125e-03 | 128 | 1.7793e-03 |