The Runge-Kutta methods

Compared with all previous discussed methods for solving a first order DE $y'(t)=f(t,y(t))$, the Runge-Kutta (RK) methods can achieve higher accuracies, i.e., the order of error term for is higher than all previous discussed methods, due to the much improved approximation of the slope of the secant $y'(t+ch)=(y(t+h)-y(t))/h,\; (0<c<1)$ between the two end points $y(t)$ and $y(t+h)$.

The general iteration of the RK methods takes the following form:

$\displaystyle y(t+h)=y(t)+\Delta y_t=y(t)+h y'(t+ch)
\approx y(t)+h \sum_{i=1}^s b_i k_i$ (226)

where the slope of the secant $y'(t+ch)$ is approximated as the average of $s$ estimations $k_1,\cdots,k_s$ of the slope weighted respectively by $b_1,\cdots,b_s$:

$\displaystyle y'(t+ch)\approx \sum_{i=1}^s b_i k_i$ (227)

These $s$ estimated slopes are obtained recursively, i.e., the ith estimate $k_i$ is based on all previous estimates $k_1,\cdots,k_{i-1}$:

$\displaystyle k_i=f\left(t+c_ih,\;y+h\;\sum_{j=1}^{i-1} a_{ij} k_j \right),
\;\;\;\;\;\;(i=1,\cdots,s)$ (228)

where $0\le c_i\le 1,\;c_1=0$, $b_1=1$ and $k_1=f(t+c_1 h,\,y)=f(t,\,y)$ is the slope of the tangent at $y(t)$.

This iteration can be expressed in terms of $t_n=t$, $t_{n+1}=t_n+h$, $y_n=y(t_n)$, and $f(t_n,y_n)=f_n$:

$\displaystyle y_{n+1}=y_n+h \sum_{i=1}^s b_i k_i$ (229)

where
$\displaystyle k_1$ $\displaystyle =$ $\displaystyle f(t_n,\;y_n)=f_n$  
$\displaystyle k_2$ $\displaystyle =$ $\displaystyle f\left(t_n+c_2h,\;y_n+h (a_{21}k_1)\right)$  
$\displaystyle k_3$ $\displaystyle =$ $\displaystyle f\left(t_n+c_3h,\;y_n+h (a_{31}k_1+a_{32}k_2)\right)$  
    $\displaystyle \cdots\cdots\cdots\cdots$  
$\displaystyle k_s$ $\displaystyle =$ $\displaystyle f\left(t_n+c_s h,\;y_n+h \left(\sum_{j=1}^{s-1} a_{sj}k_j\right)\right)$ (230)

Specially when $s=1$, we have the forward Euler method:

$\displaystyle y_{n+1}=y_n+h b_1 k_1=y_n+h f_n$ (231)

The problem of iteratively solving the DE $y'(t)=f(t,y(t))$ can also be viewed as an integral problem which can be solved by certain quadrature rules:

$\displaystyle y(t+h)=y(t)+\int_t^{t+h} y'(t)\,dt
=y(t)+\int_t^{t+h} f(t)\,dt\approx y(t)+\sum_{i=1}^s A_i f(t_i)$ (232)

where $f(t_i)$ is the estimated function value $f(t)$ at some point inside the interval $[t,\,t+h]$, and $A_i$ is the corresponding weight.

In general, an s-stage RK method with free parameters $c_2,\cdots,c_s$, $b_1\cdots,b_s$ and $a_{ij},\;
(i=1,\cdots,s,\;j=1,\cdots,i-1)$ can be arranged in the form of a Butcher tableau:

$\displaystyle \begin{tabular}{c\vert ccccc}
$c_1=0$\ &&&&& \\
$c_2$\ & $a_{21}...
...line
$1$\ & $b_1$\ & $b_2$\ & $\cdots$\ & $b_{s-1}$\ & $b_s$\ \\
\end{tabular}$ (233)

The total number of parameters is $(s^2-s)/2+s+(s-1)=(s^2+3s-2)/2$.

We need to find the specific values for these parameters so that the error term of an s-stage RK method can achieve the highest possible order in terms of the step size $h$. This is done by matching the coefficients of different orders of the $h$ terms in the RK iteration with those of the corresponding Taylor expansion of the function:

$\displaystyle y(t+h)=y(t)+hy'(t)+\frac{h^2}{2}y''(t)+\frac{h^3}{3!}y'''(t)
+\frac{h^4}{4!}y^{(4)}(t)+O(h^5)$ (234)

where the derivatives of $y(t)$ can be found as:
$\displaystyle y'(t)$ $\displaystyle =$ $\displaystyle \frac{dy}{dt}=f(t,y(t))=f$  
$\displaystyle y''(t)$ $\displaystyle =$ $\displaystyle \frac{d^2y}{dt^2}=\frac{d}{dt}f(t,y)
=\frac{\partial f}{\partial t}
+\frac{\partial f}{\partial y}\frac{dy}{dt}
=f_t+f_yf$  
$\displaystyle y'''(t)$ $\displaystyle =$ $\displaystyle \frac{d^3y}{dt^3}=\frac{d}{dt}(f_t+f_yf)
=\frac{d}{dt}f_t+\frac{d}{dt}(f_yf)
=\frac{d}{dt}f_t+f\frac{df_y}{dt}+f_y\frac{df}{dt}$  
  $\displaystyle =$ $\displaystyle (f_{tt}+f_{ty}f) +f(f_{ty}+f_{yy}f)+f_y(f_t+f_yf)
=f_{tt}+2f_{ty}f +f_{yy}f^2+f_yf_t+f_y^2f$  
$\displaystyle y^{(4)}$ $\displaystyle =$ $\displaystyle \frac{d^4y}{dt^4}
=\frac{d}{dt}(f_{tt}+2f_{ty}f+f_{yy}f^2+f_yf_t+f_y^2f)$  
  $\displaystyle =$ $\displaystyle (f_{ttt}+f_{tty}f)
+(2f_{tty}f+2f_{tyy}f^2+2f_{ty}f_t+2f_{ty}f_yf)$  
  $\displaystyle +$ $\displaystyle (f_{tyy}f^2+f_{yyy}f^3+2f_{yy}f_tf+2f_{yy}f_yf^2)
+(f_{ty}f_t+f_{yy}f_tf+f_yf_{tt}+f_yf_{ty}f)$  
  $\displaystyle +$ $\displaystyle (2f_yf_{ty}f+2f_yf_{yy}f^2+f_y^2f_t+f_y^3f)$  
  $\displaystyle =$ $\displaystyle y(t)+h f+\frac{h^2}{2!}(f_t+f_yf)
+\frac{h^3}{3!}(f_{tt}+2f_{ty}f...
...h^3}{3!}f_y(f_t+f_yf)
+\frac{h^4}{4!}(f_{ttt}+3f_{tty}f+3f_{tyy}f^2+f_{yyy}f^3)$  
    $\displaystyle +\frac{h^4}{24}f_y(f_{tt}+2f_{ty}f+f_{yy}f^2)+\frac{h^4}{8}(f_t+f_yf)(f_{ty}+f_{yy}f)
+\frac{h^4}{24}(f_t+f_yf)f_y^2+O(h^5)$ (235)

where

$\displaystyle f_t=\frac{\partial f}{\partial t},\;\;
f_y=\frac{\partial f}{\par...
...tial t\partial y},\;\;\;
f_{yy}=\frac{\partial^2 f}{\partial y^2},\cdots \cdots$ (236)

Substituting these into the Taylor expansion of $y(t+h)$ in Eq. (234), we get different approximations of the function with truncation errors of progressively higher orders:
$\displaystyle y(t+h)$ $\displaystyle =$ $\displaystyle y(t)+hf+O(h^2)$ (237)
  $\displaystyle =$ $\displaystyle y(t)+h f+\frac{h^2}{2!}(f_t+f_yf)+O(h^3)$ (238)
  $\displaystyle =$ $\displaystyle y(t)+h f+\frac{h^2}{2!}(f_t+f_yf)
+\frac{h^3}{3!}(f_{tt}+2f_{ty}f +f_{yy}f^2+f_yf_t+f_y^2f)+O(h^4)$ (239)
  $\displaystyle =$ $\displaystyle y(t)+h f+\frac{h^2}{2!}(f_t+f_yf)
+\frac{h^3}{3!}(f_{tt}+2f_{ty}f...
...yy}f^2+f_yf_t+f_y^2f)
+\frac{h^4}{4!}(f_{ttt}+3f_{tty}f+3f_{tyy}f^2+f_{yyy}f^3)$  
    $\displaystyle +\frac{h^4}{24}f_y(f_{tt}+2f_{ty}f+f_{yy}f^2)+\frac{h^4}{8}(f_t+f_yf)(f_{ty}+f_{yy}f)
+\frac{h^4}{24}(f_t+f_yf)f_y^2+O(h^5)$ (240)
    $\displaystyle \cdots\cdots\cdots\cdots$  

These Taylor expansions are then compared with the iterations $y(t+h)=y(t)+h \sum_{i=1}^s b_i k_i$ of RK methods of different number of stages $(s=1,\cdots,4)$ to determine the parameters required to achieve the highest possible order for the error term.

From the above we see that up to $s=4$, the order of accuracy is the same as the number of stages of the RK method, e.g., for RK4, the order of its accuracy is the same as its stage $s=4$ (with a 5th order error term $O(h^5)$) . However, this is not the case for $s>4$, as shown in the table below:

$\displaystyle \begin{tabular}{l\vert ccccccc}\hline
Order of accuracy & 1 & 2 &...
...hline
Number of stages & 1 & 2 & 3 & 4 & 6 & 7 & 9 .... \\ \hline
\end{tabular}$ (260)

The Matlab code for RK4 is shown below as a function which takes three arguments, the step size $h$, time moment $t$ and $y(t)$, and generates the approximation of the next value $y(t+h)=y(t)+h \hat{y'}(t+ch)$ with $\hat{y'}(t+ch)$ estimated by one of the several versions of the RK4 method, based on another function that calculate $y'(t)=f(t,y(t))$:

function y = RungeKutta4(h, t, y, k)
    k1 = f(t, y);
    switch k
        case 1
            k2 = f(t+h/2, y + h*k1/2);    
            k3 = f(t+h/2, y + h*k2/2);
            k4 = f(t+h, y + h*k3);
            y  = y + h*(k1 + 2*k2 + 2*k3 + k4)/6;
        case 2
            k2 = f(t+h/3, y + h*k1/3);
            k3 = f(t+2*h/3, y -h*k1/3 +h*k2);
            k4 = f(t+h, y + h*k1 -h*k2 +h*k3);
            y  = y + h*(k1 + 3*k2 + 3*k3 + k4)/8;
        case 3
            k2 = f(t+2*h/3, y + 2*h*k1/3);
            k3 = f(t+h/3, y + h*k1/12 +h*k2/4);
            k4 = f(t+h, y - 5*h*k1/4 +h*k2/4 +2*h*k3);
            y  = y + h*(k1 + 3*k2 + 3*k3 + k4)/8;
        case 4
             k2 = f(t+h/2, y + h*k1/2);
             k3 = f(t+h/2, y + h*k1/6 +h*k2/3);
             k4 = f(t+h, y -h*k2/2 +3*h*k3/2);
             y  = y + h*(k1 + k2 + 3*k3 + k4)/6;
        case 5
            k2 = f(t+h/2, y + h*k1/2);
            k3 = f(t+h/2, y - h*k1/2 +h*k2);
            k4 = f(t+h, y + h*k2/2 + h*k3/2);
            y  = y + h*(k1 + 3*k2 + k3 + k4)/6;
    end    
end

Example 1: Consider a simple equation $y'(t)=f(t,y(t))=\lambda y(t)$ with a closed form solution $y(t)=y(0)e^{\lambda t}$.

We realize that the expressions inside the brackets of the iterations of the RK methods are simply the first few terms of the Taylor expansion of $e^{h\lambda}$, i.e., the iterations above are the approximations of the closed form solution $y(t+h)=y(t) e^{h\lambda}$ with truncation errors of different orders.

The RK4 method considered above is in scalar form, however, it can be easily generalized to the following vector form:

$\displaystyle {\bf k}_1$ $\displaystyle =$ $\displaystyle {\bf f}(t_n,\,{\bf y}_n)$  
$\displaystyle {\bf k}_2$ $\displaystyle =$ $\displaystyle {\bf f}(t_n+h/2,\,{\bf y}_n+h\,{\bf k}_1/2)$  
$\displaystyle {\bf k}_3$ $\displaystyle =$ $\displaystyle {\bf f}(t_n+h/2,\,{\bf y}_n+h\,{\bf k}_2/2)$  
$\displaystyle {\bf k}_4$ $\displaystyle =$ $\displaystyle {\bf f}(t_n+h,\,{\bf y}_n+h\,{\bf k}_3)$  
$\displaystyle {\bf y}_{n+1}$ $\displaystyle =$ $\displaystyle {\bf y}(t_n+h)
={\bf y}_n+h\,({\bf k}_1+2{\bf k}_2+2{\bf k}_3+{\bf k}_4)/6$ (274)

At each step, all ${\bf k}_1$, ${\bf k}_2$, ${\bf k}_3$, and ${\bf k}_4$ in vector form are computed based on ${\bf y}_n$ found in previous step, and then all components of ${\bf y}_{n+1}$ are obtained by the last equation above.

Exanple 2:

Previously we considered solving the second order LCCODE analytically. We now resolve this equation but numerically by the RK4 method. Specifically, consider a spring-damper-mass mechanical system:

$\displaystyle m y''(t)+c y'(t)+k y(t)=x(t)$ (275)

with initial condition $y(0)=y'(0)=0$ and $x(0)=1$. First, we convert the DE into the canonic form

$\displaystyle y''(t)+\frac{c}{m} y'(t)+\frac{k}{m} y(t)
=y''(t)+2\zeta\omega_n y'(t)+\omega_n^2 y(t)=\frac{1}{m}x(t)$ (276)

where

$\displaystyle \omega_n=\sqrt{\frac{k}{m}},\;\;\;\;\;\;
\zeta=\frac{c}{2\sqrt{mk}}$ (277)

This 2nd-order LCCODE can also be solved numerically. We let

$\displaystyle \left\{ \begin{array}{l}
y'_1=y'=y_2 \\
y'_2=y''=-c/m\;y_2-k/m\;y_1+x/m
\end{array}\right.$ (278)

and then solve it iteratively to get $y_n=y(t_n)=y(t_0+nh)$ in the interval $[0,\;50]$, by each of the six methods: forward and backward Euler's methods with $O(h^2)$, trapezoidal method with $O(h^2)$, RK2 with $O(h^3)$, RK3 with $O(h^3)$, and RK4 with $O(h^5)$.

Here we consider four different sets of parameters:

$\displaystyle \begin{tabular}{c\vert c\vert c\vert\vert c\vert c\vert c\vert c\...
...
1 & 1001 & 1000 & -1000 & -1 & 31.6228 & 15.8272 & 1000\\ \hline
\end{tabular}$    

In the first case $s_1$ and $s_2$ are a complex conjugate pair, the system is underdamped. But in all other cases, both $s_1$ and $s_2$ are real, the system is overdamped, its solution contains two exponentially decaying terms $c_1 e^{s_1t}$ and $c_2 e^{s_2t}$, one decaying more quickly than the other.

The numerical solutions are plotted together with the closed-form solution as the ground truth as show below.

Summarizing the above, we can make the following observations:

The Matlab code for the RK methods is listed below:

function RungeKutta
    global M C K
    M=10; C=1; K=10;
    
    figure(1)
%    M=1; C=101; K=100;   %stiff!
%    M=1; C=1001; K=1000;   %stiff!
    t0=0;                       % initial time
    y_int=[1; 1];               % initial values
    tfinal = 50;
    fprintf('Initial and final time:    %.2f to %.2f\n',t0,tfinal);
%    nsteps=input('Number of steps for numerical integral: ');

    
    for l=1:3
        switch l
            case 1
                nsteps=40;
            case 2
                nsteps=60;
            case 3
                nsteps=80;
        end
        tout=zeros(nsteps,1);
        h = (tfinal - t0)/ nsteps;       % step size
        
        y1=ones(nsteps,1);
        y2=ones(nsteps,1);
        y3=ones(nsteps,1);
        y4=ones(nsteps,1);
        y5=ones(nsteps,1);

        fprintf('\n# of steps: %4d\th=%0.3f\n',nsteps,h); 
        for k=0:4
            t = t0;                         % set the variable t.
            y = y_int;                      % initial conditions            
            for j= 1 : nsteps               % go through the steps.
                switch k
                    case 0        
                        y = EulerForward(h, t, y);
                        y1(j)=y(1);          % save only the first component of y.
                    case 1
                        %y=modifiedEuler(h, t, y);
                        y=EulerBackward(h, t, y);
                        y2(j)=y(1);
                    case 2
                        y=RungeKutta2(h, t, y);
                        y3(j)=y(1);
                    case 3
                        y=RungeKutta3(h, t, y);
                        y4(j)=y(1);
                    case 4
                        y=RungeKutta4(h, t, y, 3);
                        y5(j)=y(1);
                end
                t = t + h;      
                tout(j) = t;
            end
        end
        y0=SecondOrder(y_int,tout);

        subaxis(5,3,l, 'Spacing', 0.04, 'Padding', 0, 'Margin', 0.05);
        plot(tout,y0,tout,y1)
        xlim([t0,tfinal])        
%        ylim([-10 100])
        legend('ground truth','EulerForward')        
        fprintf('\tEulerForward:\terror=%e\n',norm((y0-y1)./y0)/nsteps)
        
        str = sprintf('h=%.2f',h);
        title(str);
        subaxis(5,3,l+3, 'Spacing', 0.04, 'Padding', 0, 'Margin', 0.05);
        plot(tout,y0,tout,y2)
        xlim([t0,tfinal])
%        ylim([-10 100])
        legend('ground truth','EulerBackward')
        fprintf('\tEulerBackward:\terror=%e\n',norm((y0-y2)./y0)/nsteps)
        
        subaxis(5,3,l+6, 'Spacing', 0.04, 'Padding', 0, 'Margin', 0.05);
        plot(tout,y0,tout,y3)
        xlim([t0,tfinal])
%        ylim([-10 100])
        legend('ground truth','RK2')
        fprintf('\tRK2:\terror=%e\n',norm((y0-y3)./y0)/nsteps)
        
        subaxis(5,3,l+9, 'Spacing', 0.04, 'Padding', 0, 'Margin', 0.05);
        plot(tout,y0,tout,y4)
        xlim([t0,tfinal])
%        ylim([-10 100])
        legend('ground truth','RK3')
        fprintf('\tRK3:\terror=%e\n',norm((y0-y4)./y0)/nsteps)
        
        subaxis(5,3,l+12, 'Spacing', 0.04, 'Padding', 0, 'Margin', 0.05);
        plot(tout,y0,tout,y5)
        xlim([t0,tfinal])
%        ylim([-10 100])
        legend('ground truth','RK4')
        fprintf('\tRK4:\terror=%e\n',norm((y0-y5)./y0)/nsteps)

    end
end

function e=rr(x,y)    
    e=0;    
    for i=1:length(x)
        e=e+abs(x(i)-y(i))/abs(y(i));        
    end
%     e=sqrt(e)
%     norm(x-y)
%     pause
end

function y = SecondOrder(y_int,t)
    global M C K
    zt=C/2/sqrt(M*K);
    wn=sqrt(K/M);
    s=sqrt(zt^2-1);
    s1=(-zt-s)*wn;
    s2=(-zt+s)*wn;
    c1=(s2*y_int(1)-y_int(2))/(s2-s1);
    c2=(s1*y_int(1)-y_int(2))/(s1-s2);
    y=c1*exp(s1*t)+c2*exp(s2*t);    % homogeneous solution
    y=y+1/(M*wn^2);                 % particular solution
end


function dy = f(t, y)    % 2nd order linear constant-coefficient DE
    global M C K
    dy = zeros(2,1);
    dy(1) = y(2);
    dy(2) = (-C*y(2)-K*y(1))/M;     % homogeneous solution
    dy(2) = dy(2)+1/M;              % particular solution
end


function y = EulerForward(h, t, y)
    y = y + h* f(t, y);
end

function y1 =  EulerBackward(h, t0, y0)    
    global M C K
    Jf=zeros(2);
    Jf(1,1)=0;
    Jf(1,2)=1;
    Jf(2,1)=-K/M;
    Jf(2,2)=-C/M;
    y1=y0;
    f(t0,y1);
    F=y0+h*f(t0,y1)-y1;
    norm(F);
    i=0;
    while norm(F)>0.0001  
        Jy=h*Jf;
        Jy(1,1)=Jy(1,1)-1;
        Jy(2,2)=Jy(2,2)-1;
        y1=y1-inv(Jy)*F;
        F=y0+h*f(t0,y1)-y1;
        norm(F);
    end
end


function y = modifiedEuler(h, t, y)
    k1 = f(t, y);
    k2 = f(t+h, y + h*k1);
    y = y + h*(k1 + k2)/2; 
end

function y = RungeKutta2(h, t, y)
    k1 = f(t, y);
    k2 = f(t+h/2, y + h*k1/2);
    y = y + h*k2;
end

function y = RungeKutta3(h, t, y)
     k1 = f(t, y);
     k2 = f(t+h/2, y + h*k1/2);
     k3 = f(t+h, y -h*k1 + 2*h*k2);
     y = y + h*(k1 + 4*k2 + k3)/6;
%    k1 = f(t0, y0);
%    k2 = f(t0+h/3, y0 + h*k1/3);
%    k3 = f(t0+2*h/3, y0 +2*h*k2/3);
%    y1 = y0 + h*(k1 + 3*k3)/4;
end

function y1 = RungeKutta2a(h, t0, y0)
    a=0.5;
    k1 = f(t0, y0);
    k2 = f(t0+a*h, y0 + a*h*k1);
    y1 = y0 + h*((1-1/2/a)*k1+ k2/2/a)/2;
end


function y = RungeKutta4(h, t, y, k)
    k1 = f(t, y);
    switch k
        case 1
            k2 = f(t+h/2, y + h*k1/2);    
            k3 = f(t+h/2, y + h*k2/2);
            k4 = f(t+h, y + h*k3);
            y  = y + h*(k1 + 2*k2 + 2*k3 + k4)/6;
        case 2
            k2 = f(t+h/3, y + h*k1/3);
            k3 = f(t+2*h/3, y -h*k1/3 +h*k2);
            k4 = f(t+h, y + h*k1 -h*k2 +h*k3);
            y  = y + h*(k1 + 3*k2 + 3*k3 + k4)/8;
        case 3
            k2 = f(t+2*h/3, y + 2*h*k1/3);
            k3 = f(t+h/3, y + h*k1/12 +h*k2/4);
            k4 = f(t+h, y - 5*h*k1/4 +h*k2/4 +2*h*k3);
            y  = y + h*(k1 + 3*k2 + 3*k3 + k4)/8;
        case 4
             k2 = f(t+h/2, y + h*k1/2);
             k3 = f(t+h/2, y + h*k1/6 +h*k2/3);
             k4 = f(t+h, y -h*k2/2 +3*h*k3/2);
             y  = y + h*(k1 + k2 + 3*k3 + k4)/6;
        case 5
            k2 = f(t+h/2, y + h*k1/2);
            k3 = f(t+h/2, y - h*k1/2 +h*k2);
            k4 = f(t+h, y + h*k2/2 + h*k3/2);
            y  = y + h*(k1 + 3*k2 + k3 + k4)/6;
    end    
end