Solving First-Order ODEs#

Teng-Jui Lin

Content adapted from UW AMATH 301, Beginning Scientific Computing, in Spring 2020.

  • Solving first-order ODEs

    • Methods

      • Forward Euler

      • Backward Euler

      • Midpoint method

      • Fourth-order Runge-Kutta (RK4) method

    • Error

  • scipy implementation

Solving first-order ODEs#

Suppose we have an ODE of the form

y˙=f(t,y).

Forward Euler#

Forward Euler uses the forward difference as approximation for the derivative, having formula of

yk+1=yk+f(yk,tk)Δt

Backward Euler#

Backward Euler uses backward difference to approximate the derivative, having implicit formula of

yk+1=yk+f(yk+1,tk+1)Δt

At each step of backward Euler, yk+1 can be found by finding the root of

yk+f(yk+1,tk+1)Δtyk+1=0

where yk is known.

Midpoint method#

Midpoint method uses the slope at the midpoint as approximation for the derivative:

  1. Do a half step of forward Euler using the slope at starting point k1, record the slope at midpoint k2

  2. Use the slope at the midpoint k2 to do a full step of forward Euler to find yk+1

The method follows the steps

k1=f(yk)k2=f(yk+Δt2k1)yk+1=yk+k2Δt

Fourth-order Runge-Kutta (RK4)#

Fourth-order Runge-Kutta method uses a weighted average of slopes at the midpoint and endpoint as approximation for the derivative:

  1. Do a half step of forward Euler using the slope at starting point k1, record the slope at midpoint k2

  2. Use the slope at midpoint k2 to do another half step of forward Euler, record the new slope at midpoint k3

  3. Use the new slope at midpoint k3 to do a full step of forward Euler, record the slope at the endpoint k4

  4. Use a weighted average of all slopes k1,k2,k3,k4 to find yk+1

The method follows the steps

k1=f(yk)k2=f(yk+k1Δt2)k3=f(yk+k2Δt2)k4=f(yk+k3Δt)yk+1=yk+Δt6(k1+2k2+2k3+k4)

Errors of methods#

The order of errors of each method is shown below:

Method

Order

Global Error

Local Error

Explicit/Implicit

Forward Euler

1

O(Δt)

O(Δt2)

Explicit

Backward Euler

1

O(Δt)

O(Δt2)

Implicit

Midpoint method (RK2)

2

O(Δt2)

O(Δt3)

Explicit

Fourth-order Runge-Kutta (RK4)

4

O(Δt4)

O(Δt5)

Explicit

Implementation#

Problem Statement. Consider the ODE

dxdt=5sinx

with the initial condition x(0)=π4. Its analytical solution is

x(t)=2tan1(e5t1+2).

(a) Solve the ODE using backward Euler, forward Euler, midpoint method, RK4 method, and scipy.integrate.solve_ivp() from t[0,1] with Δt=0.05.

(b) Compare the solution of each method with the analytical solution.

import numpy as np
import matplotlib.pyplot as plt
import scipy
from scipy import optimize, integrate
# define the ode and analytic soln
dxdt = lambda t, x : 5*np.sin(x)
x_exact = lambda t : 2*np.arctan(np.exp(5*t) / (1 + np.sqrt(2)))
# define time steps
dt = 0.1
t_initial = 0
t_final = 1
t = np.arange(t_initial, t_final+dt/2, dt)
t_len = len(t)
# forward euler
x_forward = np.zeros(t_len)
x_forward[0] = np.pi/4

for i in range(t_len - 1):
    x_forward[i+1] = x_forward[i] + dxdt(t[i], x_forward[i])*dt
# backward euler
x_backward = np.zeros(t_len)
x_backward[0] = np.pi/4

for i in range(t_len - 1):
    g = lambda y : y - x_backward[i] - dxdt(t[i+1], y)*dt
    x_backward[i+1] = scipy.optimize.root(g, x_backward[i]).x
# midpoint method
x_midpoint = np.zeros(t_len)
x_midpoint[0] = np.pi/4

for i in range(t_len - 1):
    k1 = dxdt(t[i], x_midpoint[i])
    k2 = dxdt(t[i], x_midpoint[i] + k1*dt/2)
    x_midpoint[i+1] = x_midpoint[i] + k2*dt
# rk4 method
x_rk4 = np.zeros(t_len)
x_rk4[0] = np.pi/4

for i in range(t_len - 1):
    k1 = dxdt(t[i], x_rk4[i])
    k2 = dxdt(t[i], x_rk4[i] + k1*dt/2)
    k3 = dxdt(t[i], x_rk4[i] + k2*dt/2)
    k4 = dxdt(t[i], x_rk4[i] + k3*dt)
    x_rk4[i+1] = x_rk4[i] + (k1 + 2*k2 + 2*k3 + k4)*dt/6
# scipy.integrate.solve_ivp()
x_scipy = np.zeros(t_len)
x_scipy[0] = np.pi/4

x_scipy = scipy.integrate.solve_ivp(dxdt, [t[0], t[-1]], [t[0], x_scipy[0]], t_eval=t).y[1]
# plot settings
%config InlineBackend.figure_format = 'retina'
%matplotlib inline

plt.rcParams.update({
    'font.family': 'Arial',  # Times New Roman, Calibri
    'font.weight': 'normal',
    'mathtext.fontset': 'cm',
    'font.size': 18,
    
    'lines.linewidth': 2,
    
    'axes.linewidth': 2,
    'axes.spines.top': False,
    'axes.spines.right': False,
    'axes.titleweight': 'bold',
    'axes.titlesize': 18,
    'axes.labelweight': 'bold',
    
    'xtick.major.size': 8,
    'xtick.major.width': 2,
    'ytick.major.size': 8,
    'ytick.major.width': 2,
    
    'figure.dpi': 80,
    
    'legend.framealpha': 1, 
    'legend.edgecolor': 'black',
    'legend.fancybox': False,
    'legend.fontsize': 14
})
fig, ax = plt.subplots(figsize=(4, 4))
ax.plot(t, x_exact(t), label='Exact')
ax.plot(t, x_forward, label='Forward Euler')
ax.plot(t, x_backward, label='Backward Euler')
ax.plot(t, x_midpoint, label='Midpoint method')
ax.plot(t, x_rk4, label='RK4')
ax.plot(t, x_scipy, label='scipy')
ax.set_xlabel('$t$')
ax.set_ylabel('$x$')
ax.set_title('$x(t)$')
ax.set_xlim(t_initial, t_final)
ax.set_ylim(0, 3.5)
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1.05))
<matplotlib.legend.Legend at 0x1a9ef5d2248>
../../_images/391e04afe6d3233450f825bc63fbc340dc06d3a63de64942f3151ac63e51252d.png
fig, ax = plt.subplots(figsize=(4, 4))
ax.plot(t, x_exact(t), label='Analytic', color='black')
ax.plot(t, x_forward, label='Forward Euler')
ax.plot(t, x_backward, label='Backward Euler')
ax.plot(t, x_midpoint, label='Midpoint method')
ax.plot(t, x_rk4, '-.', label='RK4')
ax.plot(t, x_scipy, '--', label='scipy')
ax.set_xlabel('$t$')
ax.set_ylabel('$x$')
ax.set_title('$x(t)$')
ax.set_xlim(0.5, 0.75)
ax.set_ylim(2.7, 3.1)
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1.05))
<matplotlib.legend.Legend at 0x1a9f1759648>
../../_images/ec7730caff13d470d409c41648ea925caf944f26a6a907a7fd04b64335bdeb82.png

▲ The figure above shows the accuracy of each method. The RK4 and scipy solution agrees exactly with the analytic solution, having a fourth order error. The midpoint method is second closest to the analytic solution, having a second order error. Forward and backward Euler have the largest error, having a first order error.