function [t,x,error] = RKF45(a,b,x0,N,tol,f)
% 4th order Runge-Kutta-Fehlberg time stepping scheme, with an error
% estimator of 5th order
%
% INPUT
%   a       interval left limit
%   b       interval right limit
%   x0      initial condition
%   N       time step guess
%   tol     numerical tolerance for error
%   f       function handle for the right hand side of the ODE
% OUTPUT
%   R

% Runge-Kutta-Fehlberg 4th/5th order integration parameters
a2 = 1/4; b2 = 1/4; a3 = 3/8; b3 = 3/32; c3 = 9/32; a4 = 12/13;
b4 = 1932/2197; c4 = -7200/2197; d4 = 7296/2197; a5 = 1;
b5 = 439/216; c5 = -8; d5 = 3680/513; e5 = -845/4104; a6 = 1/2;
b6 = -8/27; c6 = 2; d6 = -3544/2565; e6 = 1859/4104;
f6 = -11/40; r1 = 1/360; r3 = -128/4275; r4 = -2197/75240; r5 = 1/50;
r6 = 2/55; n1 = 25/216; n3 = 1408/2565; n4 = 2197/4104; n5 = -1/5;

h = (b-a)/N; % initial time step
hmin = h/4096; % minimum time step
hmax = h*4096; % maximum time step

dim = size(x0,1); % dimension of the problem
x = zeros(dim,N); % solution
t = zeros(N,1); % time
error = zeros(N,1); % error from the 5th order estimator

x(:,1) = x0; % initial condition

t(1) = a; % initial time
error(1) = 0; % initial error
br = b - 0.00001*abs(b); % auxiliar
jj = 1; % counter
while (t(jj)<b)
    if ((t(jj)+h)>b), h = b-t(jj); end % correct the final time
    if (jj+1>N) % correct size of arrays
        NN = size(t,1);
        old_error = error;
        old_t = t;
        old_x = x;
        error = zeros(NN+N,1);
        t     = zeros(NN+N,1);
        x     = zeros(dim,NN+N);
        error(1:NN) = old_error;
        t(1:NN) = old_t;
        x(:,1:NN) = old_x;
    end
    
    % Runge-Kutta-Fehlberg method
    k1 = h*f(t(jj),x(:,jj));
    k2 = h*f(t(jj) + a2*h,x(:,jj) + b2*k1);
    k3 = h*f(t(jj) + a3*h,x(:,jj) + b3*k1 + c3*k2);
    k4 = h*f(t(jj) + a4*h,x(:,jj) + b4*k1 + c4*k2 + d4*k3);
    k5 = h*f(t(jj) + a5*h,x(:,jj) + b5*k1 + c5*k2 + d5*k3 + e5*k4);
    k6 = h*f(t(jj) + a6*h,x(:,jj) + b6*k1 + c6*k2 + d6*k3 + e6*k4 + f6*k5);
    err = abs(r1*k1 + r3*k3 + r4*k4 + r5*k5 + r6*k6);
    err = max(err(:)); % error in sup norm
    xnew = x(:,jj) + n1*k1 + n3*k3 + n4*k4 + n5*k5;
    
    if ((err<tol)||(h<=2*hmin)) % error less than tolerance, or with very small time step
        x(:,jj+1) = xnew; % write x
        error(jj+1) = err; % write error
        if ((t(jj)+h)>br)
            t(jj+1) = b; % correction for last time step
        else 
            t(jj+1) = t(jj) + h; % write time step
        end
        jj = jj+1; % counter ++
    end
    if (err == 0) % if error is zero, we must increase time step
        s = 2; 
    else
        s = 0.84*(tol*h/err)^(0.25); % optimal time step
    end
    if((s<0.75)&&(h>2*hmin)) % smaller time step
        h = h/2;
    end
    if ((s>1.50)&&(2*h<hmax)) % bigger time step
        h = 2*h;
    end
end
t = t(1:jj); x = x(:,1:jj); error = error(1:jj);
end