view extra/ode/ode45.m @ 463:a0d3391e59e2 octave-forge

Update from v1.06 to v1.14
author pkienzle
date Mon, 05 Aug 2002 02:43:13 +0000
parents 6b33357c7561
children 673d401bf1da
line wrap: on
line source

function [tout,xout,Nsteps_acc,Nsteps_rej] = ode45(FUN,tspan,x0,pair,ode_fcn_format,tol,trace,count,hmax,N_est_acc_steps)

% Copyright (C) 2001, 2000 Marc Compere
% This file is intended for use with Octave.
% ode45.m is free software; you can redistribute it and/or modify it
% under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2, or (at your option)
% any later version.
%
% ode45.m is distributed in the hope that it will be useful, but
% WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
% General Public License for more details at www.gnu.org/copyleft/gpl.html.
%
% --------------------------------------------------------------------
%
% ode45 (v1.15) integrates a system of ordinary differential equations using
% 4th & 5th order embedded formulas from Dormand & Prince or Fehlberg.
%
% The Fehlberg 4(5) pair is established and works well, however, the
% Dormand-Prince 4(5) pair minimizes the local truncation error in the
% 5th-order estimate which is what is used to step forward (local extrapolation.)
% Generally it produces more accurate results and costs roughly the same
% computationally.  The Dormand-Prince pair is the default.
%
% This is a 4th-order accurate integrator therefore the local error normally
% expected is O(h^5).  However, because this particular implementation
% uses the 5th-order estimate for xout (i.e. local extrapolation) moving
% forward with the 5th-order estimate should yield local error of O(h^6).
%
% The order of the RK method is the order of the local *truncation* error, d,
% which is the principle error term in the portion of the Taylor series
% expansion that gets dropped, or intentionally truncated.  This is different
% from the local error which is the difference between the estimated solution
% and the actual, or true solution.  The local error is used in stepsize
% selection and may be approximated by the difference between two estimates of
% different order, l(h) = x_(O(h+1)) - x_(O(h)).  With this definition, the
% local error will be as large as the error in the lower order method.
% The local truncation error is within the group of terms that gets multipled
% by h when solving for a solution from the general RK method.  Therefore, the
% order-p solution created by the RK method will be roughly accurate to O(h^(p+1))
% since the local truncation error shows up in the solution as h*d, which is
% h times an O(h^(p)) term, or rather O(h^(p+1)).
% Summary:   For an order-p accurate RK method,
%            - the local truncation error is O(h^p)
%            - the local error used for stepsize adjustment and that
%              is actually realized in a solution is O(h^(p+1))
%
% This requires 6 function evaluations per integration step.
%
% Both the Dormand-Prince and Fehlberg 4(5) coefficients are from a tableu in
% U.M. Ascher, L.R. Petzold, Computer Methods for  Ordinary Differential Equations
% and Differential-Agebraic Equations, Society for Industrial and Applied Mathematics
% (SIAM), Philadelphia, 1998
%
% The error estimate formula and slopes are from
% Numerical Methods for Engineers, 2nd Ed., Chappra & Cannle, McGraw-Hill, 1985
%
% Usage:
%         [tout, xout] = ode45(FUN,tspan,x0,pair,ode_fcn_format,tol,trace,count,hmax)
%
% INPUTS:
%    FUN   - String containing name of user-supplied problem description.
%            Call: xprime = fun(t,x) where FUN = 'fun'.
%            t      - Time (scalar).
%            x      - Solution column-vector.
%            xprime - Returned derivative COLUMN-vector; xprime(i) = dx(i)/dt.
%    tspan - [ tstart, tfinal ]
%    x0    - Initial value COLUMN-vector.
%    pair  - flag specifying which integrator coefficients to use:
%               0 --> use Dormand-Prince 4(5) pair (default)
%               1 --> use Fehlberg pair 4(5) pair
%    ode_fcn_format - this specifies if the user-defined ode function is in
%            the form:     xprime = fun(t,x)   (ode_fcn_format=0, default)
%            or:           xprime = fun(x,t)   (ode_fcn_format=1)
%            Matlab's solvers comply with ode_fcn_format=0 while
%            Octave's lsode() and sdirk4() solvers comply with ode_fcn_format=1.
%    tol   - The desired accuracy. (optional, default: tol = 1.e-6).
%    trace - If nonzero, each step is printed. (optional, default: trace = 0).
%    count - if nonzero, variable 'rhs_counter' is initalized, made global
%            and counts the number of state-dot function evaluations
%            'rhs_counter' is incremented in here, not in the state-dot file
%            simply make 'rhs_counter' global in the file that calls ode45
%    hmax  - limit the maximum stepsize to be less than or equal to hmax
%    N_est_acc_steps - an estimate of how many accepted steps there may be;
%                      used to preallocate memory for the [tout,xout] solutions
%
% OUTPUTS:
%    tout  - array of discretized times points (an Nsteps_acc by 1 column-vector).
%    xout  - solution, one solution column-vector per tout-value (an Nsteps_acc by Nstates matrix)
%    Nsteps_acc - total number of accepted integration steps + 1
%    Nsteps_rej - total number of rejected integration steps
%
% The result can be displayed by: plot(tout, xout).
%
% The following relationship should hold after a completed run:
%    rhs_counter == (Nsteps_acc-1+Nsteps_rej)*6+1
%
% Marc Compere
% CompereM@asme.org
% created : 06 October 1999
% modified: 03 July 2001

if nargin < 10, N_est_acc_steps = (tspan(2)-tspan(1))*1e3; end
if nargin <  9, hmax = (tspan(2) - tspan(1))/2.5; end
if nargin <  8, count = 0; end
if nargin <  7, trace = 0; end
if nargin <  6, tol = 1.e-6; end
if nargin <  5, ode_fcn_format = 0; end
if nargin <  4, pair = 0; end

pow = 1/6; % see p.91 in the Ascher & Petzold reference for more infomation.

if (pair==0),
   % Use the Dormand-Prince 4(5) coefficients:
    a_(1,1)=0;
    a_(2,1)=1/5;
    a_(3,1)=3/40; a_(3,2)=9/40;
    a_(4,1)=44/45; a_(4,2)=-56/15; a_(4,3)=32/9;
    a_(5,1)=19372/6561; a_(5,2)=-25360/2187; a_(5,3)=64448/6561; a_(5,4)=-212/729;
    a_(6,1)=9017/3168; a_(6,2)=-355/33; a_(6,3)=46732/5247; a_(6,4)=49/176; a_(6,5)=-5103/18656;
    a_(7,1)=35/384; a_(7,2)=0; a_(7,3)=500/1113; a_(7,4)=125/192; a_(7,5)=-2187/6784; a_(7,6)=11/84;
    % 4th order b-coefficients
    b4_(1,1)=5179/57600; b4_(2,1)=0; b4_(3,1)=7571/16695; b4_(4,1)=393/640; b4_(5,1)=-92097/339200; b4_(6,1)=187/2100; b4_(7,1)=1/40;
    % 5th order b-coefficients
    b5_(1,1)=35/384; b5_(2,1)=0; b5_(3,1)=500/1113; b5_(4,1)=125/192; b5_(5,1)=-2187/6784; b5_(6,1)=11/84; b5_(7,1)=0;
    for i=1:7,
     c_(i)=sum(a_(i,:));
    end
else, % pair==1 so use the Fehlberg 4(5) coefficients:
    a_(1,1)=0;
    a_(2,1)=1/4;
    a_(3,1)=3/32; a_(3,2)=9/32;
    a_(4,1)=1932/2197; a_(4,2)=-7200/2197; a_(4,3)=7296/2197;
    a_(5,1)=439/216; a_(5,2)=-8; a_(5,3)=3680/513; a_(5,4)=-845/4104;
    a_(6,1)=-8/27; a_(6,2)=2; a_(6,3)=-3544/2565; a_(6,4)=1859/4104; a_(6,5)=-11/40;
    % 4th order b-coefficients (guaranteed to be a column vector)
    b4_(1,1)=25/216; b4_(2,1)=0; b4_(3,1)=1408/2565; b4_(4,1)=2197/4104; b4_(5,1)=-1/5;
    % 5th order b-coefficients (also guaranteed to be a column vector)
    b5_(1,1)=16/135; b5_(2,1)=0; b5_(3,1)=6656/12825; b5_(4,1)=28561/56430; b5_(5,1)=-9/50; b5_(6,1)=2/55;
    for i=1:6,
     c_(i)=sum(a_(i,:));
    end
end % if (pair==0)


% Initialization
t0 = tspan(1);
tfinal = tspan(2);
t = t0;
hmin = (tfinal - t)/1e20;
h = (tfinal - t)/100; % initial step size guess
x = x0(:);            % ensure x is a column vector

Nstates = size(x,1);
tout = zeros(N_est_acc_steps,1);       % preallocating memory is not only faster but, for long
xout = zeros(N_est_acc_steps,Nstates); % simulations, prevents disappointing surprises later

Nsteps_rej = 0;
Nsteps_acc = 1;
tout(Nsteps_acc) = t;     % first output time
xout(Nsteps_acc,:) = x.'; % first output solution = IC's

if count==1,
 global rhs_counter
 if ~exist('rhs_counter'),rhs_counter=0; end
end % if count

if trace
 clc, t, h %, x
end

if (pair==0),

   k_ = x*zeros(1,7);  % k_ needs to be initialized as an Nx7 matrix where N=number of rows in x
                       % (just for speed so octave doesn't need to allocate more memory at each stage value)

   % Compute the first stage prior to the main loop.  This is part of the Dormand-Prince pair caveat.
   % Normally, during the main loop the last stage for x(k) is the first stage for computing x(k+1).
   % So, the very first integration step requires 7 function evaluations, then each subsequent step
   % 6 function evaluations because the first stage is simply assigned from the last step's last stage.
   % note: you can see this by the last element in c_ is 1.0, thus t+c_(7)*h = t+h, ergo, the next step.
   if (ode_fcn_format==0), % (default)
      k_(:,1)=feval(FUN,t,x); % first stage
   else, % ode_fcn_format==1
      k_(:,1)=feval(FUN,x,t);
   end % if (ode_fcn_format==1)

   % increment rhs_counter
   if (count==1), rhs_counter = rhs_counter + 1; end

   % The main loop using Dormand-Prince 4(5) pair
   while (t < tfinal) & (h >= hmin),
      if t + h > tfinal, h = tfinal - t; end

      % Compute the slopes by computing the k(:,j+1)'th column based on the previous k(:,1:j) columns
      % notes: k_ needs to end up as an Nxs, a_ is 7x6, which is s by (s-1),
      %        s is the number of intermediate RK stages on [t (t+h)] (Dormand-Prince has s=7 stages)
      if (ode_fcn_format==0), % (default)
         for j = 1:6,
            k_(:,j+1) = feval(FUN, t+c_(j+1)*h, x+h*k_(:,1:j)*a_(j+1,1:j)');
         end
      else, % ode_fcn_format==1
         for j = 1:6,
            k_(:,j+1) = feval(FUN, x+h*k_(:,1:j)*a_(j+1,1:j)', t+c_(j+1)*h);
         end
      end % if (ode_fcn_format==1)

      % increment rhs_counter
      if (count==1), rhs_counter = rhs_counter + 6; end

      % compute the 4th order estimate
      x4=x + h* (k_*b4_); % k_ is Nxs (or Nx7) and b4_ is a 7x1

      % compute the 5th order estimate
      x5=x + h*(k_*b5_); % k_ is Nxs (or Nx7) and b5_ is a 7x1

      % estimate the local truncation error
      gamma1 = x5 - x4;

      % Estimate the error and the acceptable error
      delta = norm(gamma1,'inf');       % actual error
      tau = tol*max(norm(x,'inf'),1.0); % allowable error

      % Update the solution only if the error is acceptable
      if (delta<=tau),

         t = t + h;
         x = x5;    % <-- using the higher order estimate is called 'local extrapolation'
         Nsteps_acc = Nsteps_acc + 1;
         tout(Nsteps_acc) = t;
         xout(Nsteps_acc,:) = x.';

         % Assign the last stage for x(k) as the first stage for computing x(k+1).
         % This is part of the Dormand-Prince pair caveat.
         % k_(:,7) has already been computed, so use it instead of recomputing it
         % again as k_(:,1) during the next step.
         k_(:,1)=k_(:,7);

      else, % unacceptable integration step

         Nsteps_rej = Nsteps_rej + 1;

      end % if (delta<=tau)

      if trace
         home, t, h, Nsteps_acc, Nsteps_rej
      end % if trace

      % Update the step size
      if (delta==0.0),
       delta = 1e-16;
      end % if (delta==0.0)
      h = min(hmax, 0.8*h*(tau/delta)^pow);

   end % while (t < tfinal) & (h >= hmin)

else, % pair==1 --> use RK-Fehlberg 4(5) pair

   k_ = x*zeros(1,6);  % k_ needs to be initialized as an Nx6 matrix where N=number of rows in x
                       % (just for speed so octave doesn't need to allocate more memory at each stage value)
 
   % The main loop using Dormand-Prince 4(5) pair
   while (t < tfinal) & (h >= hmin),
      if t + h > tfinal, h = tfinal - t; end

      % Compute the slopes by computing the k(:,j+1)'th column based on the previous k(:,1:j) columns
      % notes: k_ needs to end up as an Nx6, a_ is 6x5, which is s by (s-1),  (RK-Fehlberg has s=6 stages)
      %        s is the number of intermediate RK stages on [t (t+h)]
      if (ode_fcn_format==0), % (default)
         k_(:,1)=feval(FUN,t,x); % first stage
      else, % ode_fcn_format==1
         k_(:,1)=feval(FUN,x,t);
      end % if (ode_fcn_format==1)

      if (ode_fcn_format==0), % (default)
         for j = 1:5,
            k_(:,j+1) = feval(FUN, t+c_(j+1)*h, x+h*k_(:,1:j)*a_(j+1,1:j)');
         end
      else, % ode_fcn_format==1
         for j = 1:5,
            k_(:,j+1) = feval(FUN, x+h*k_(:,1:j)*a_(j+1,1:j)', t+c_(j+1)*h);
         end
      end % if (ode_fcn_format==1)

      % increment rhs_counter
      if (count==1), rhs_counter = rhs_counter + 6; end

      % compute the 4th order estimate
      x4=x + h* (k_(:,1:5)*b4_); % k_(:,1:5) is an Nx5 and b4_ is a 5x1

      % compute the 5th order estimate
      x5=x + h*(k_*b5_); % k_ is the same as k_(:,1:6) and is an Nx6 and b5_ is a 6x1

      % estimate the local truncation error
      gamma1 = x5 - x4;

      % Estimate the error and the acceptable error
      delta = norm(gamma1,'inf');       % actual error
      tau = tol*max(norm(x,'inf'),1.0); % allowable error

      % Update the solution only if the error is acceptable
      if (delta<=tau),

         t = t + h;
         x = x5;    % <-- using the higher order estimate is called 'local extrapolation'
         Nsteps_acc = Nsteps_acc + 1;
         tout(Nsteps_acc) = t;
         xout(Nsteps_acc,:) = x.';

      else, % unacceptable integration step

         Nsteps_rej = Nsteps_rej + 1;

      end % if (delta<=tau)

      if trace
         home, t, h, Nsteps_acc, Nsteps_rej
      end % if trace

      % Update the step size
      if (delta==0.0),
       delta = 1e-16;
      end % if (delta==0.0)
      h = min(hmax, 0.8*h*(tau/delta)^pow);

   end % while (t < tfinal) & (h >= hmin)

end % if (pair==0),


% if necessary, trim outputs
if (Nsteps_acc<N_est_acc_steps),
   tout = tout(1:Nsteps_acc);
   xout = xout(1:Nsteps_acc,:);
end % if (Nsteps_acc<N_est_acc_steps)


if (t < tfinal),
   disp('Step size grew too small.')
   t, h, Nsteps_acc, Nsteps_rej
end