mercoledì 27 agosto 2014

Dense Output

Dense Output

Specifying specific output times for the solution, should not affect the internal time steps that the solver uses. The basic idea of the Dense Output concept is to provide the solution at a given time \(s \in [t, t+dt]\) with the same order of accuracy as the solutions computed at the internal time points by using suitable interpolation methods.
Up to now only linear interpolation was performed and this significantly lowered the accuracy if a higher order solver was used.
I then implemented a series of interpolation function:

  • linear_interpolation:
    x_out = linear_interpolation (t, x, t_out)
    Given the time span \(t=[t_0, t_1]\) and the function values \(x=[x_0, x_1]\), it returns the linear interpolation value \(x_{out}\) at the point \(t_{out}\).
  • quadratic_interpolation:
    x_out = quadratic_interpolation (t, x, der, t_out)
    Given the time span \(t=[t_0, t_1]\), the function values \(x=[x_0, x_1]\) and the derivative of the function at the point \(x_0\), it returns the quadratic interpolation value \(x_{out}\) at the point \(t_{out}\).
  • hermite_cubic_interpolation:
    x_out = hermite_cubic_interpolation (t, x, der, t_out)
    Given the time span \(t=[t_0, t_1]\), the function values \(x=[x_0, x_1]\) and the derivatives of the function at both points \(x_0\) and \(x_1\), it returns the 3rd order approximation \(x_{out}\) at the point \(t_{out}\) by performing Hermite interpolation.
  • hermite_quartic_interpolation:
    x_out = hermite_quartic_interpolation (t, x, der, t_out)
    Given the time span \(t=[t_0, t_1]\), the function values \(x=[x_0, x_{1/2}, x_1]\) (where \(x_{1/2}\) is the value of the function at the time \(t_0+dt/2\)) and the derivatives of the function at the extremes \(x0\) and \(x1\), it returns the 4th order approximation \(x_{out}\) at the point \(t_{out}\) by performing Hermite interpolation.
  • dorpri_interpolation:
    x_out = dorpri_interpolation (t, x, k, t_out)
    This interpolation method is specific for the Dormand-Prince Runge-Kutta scheme. Given the time span \(t=[t_0, t_1]\), the function value \(x=x_0\) and the vector \(k\) with the function evaluations required in the Dormand-Prince method, it returns the 4th order approximation \(x_{out}\) at the point \(t_{out}\). For more information on the method have a look at Hairer, Noersett, Wanner, Solving Ordinary Differential Equations I: Nonstiff Problems (pag. 191-193).
  • hermite_quintic_interpolation:
    x_out = hermite_quintic_interpolation (t, x, der, t_out)
    Given the time span \(t=[t_0, t_1]\), the function values \(x=[x_0, x_{1/2}, x_1]\) and the derivatives of the function at each point, it returns the 5th order approximation \(x_{out}\) at the point \(t_{out}\) by performing Hermite interpolation.
These methods are then used to perform the Dense Output according to the order of the solver chosen. This is the piece of code in integrate_adaptive.m that performs the interpolation:

% if next tspan value is caught, update counter
if( (z(end) == tspan(counter)) || (abs (z(end) - tspan(counter)) / ...
    (max (abs (z(end)), abs (tspan(counter)))) < 8*eps) )
  counter++;

% if there is an element in time vector at which the solution is required
% the program must compute this solution before going on with next steps
elseif( vdirection*z(end) > vdirection*tspan(counter) )
% initializing counter for the following cycle
  i = 2;
  while ( i <= length (z) )

    % if next tspan value is caught, update counter
    if( (counter <= k) && ...
        ( (z(i) == tspan(counter)) || (abs (z(i) - tspan(counter)) / ...
        (max (abs (z(i)), abs (tspan(counter)))) < 8*eps)) )
      counter++;
    endif
    
    % else, loop until there are requested values inside this subinterval
    while((counter <= k) && (vdirection*z(i) > vdirection*tspan(counter)))
      % choose interpolation scheme according to order of the solver
      switch order
      case 1
        u_interp = linear_interpolation ([z(i-1) z(i)], [u(:,i-1) u(:,i)], tspan(counter));
      case 2
        if (~isempty (k_vals))
          der = k_vals(1);
        else
          der = feval (func, z(i-1) , u(:,i-1), options.vfunarguments{:});
        endif
        u_interp = quadratic_interpolation ([z(i-1) z(i)], [u(:,i-1) u(:,i)], der, tspan(counter));
      case 3
        % only ode23 - use k_vals
        u_interp = hermite_cubic_interpolation ([z(i-1) z(i)], [u(:,i-1) u(:,i)], [k_vals(:,1) k_vals(:,end)], tspan(counter));
      case 4
        % if ode45 used without local extrapolation this function doesn't require a new function evaluation.
        u_interp = dorpri_interpolation ([z(i-1) z(i)], [u(:,i-1) u(:,i)], k_vals, tspan(counter));
      case 5
        % ode45 with Dormand-Prince scheme:
        % 4th order approximation of y in t+dt/2 as proposed by Shampine in Lawrence, Shampine, "Some Practical Runge-Kutta Formulas", 1986.
        u_half = u(:,i-1) + 1/2*dt*((6025192743/30085553152)*k_vals(:,1) + (51252292925/65400821598)*k_vals(:,3) - (2691868925/45128329728)*k_vals(:,4) + (187940372067/1594534317056)*k_vals(:,5) - (1776094331/19743644256)*k_vals(:,6) + (11237099/235043384)*k_vals(:,7));
        u_interp = hermite_quartic_interpolation ([z(i-1) z(i)], [u(:,i-1) u_half u(:,i)], [k_vals(:,1) k_vals(:,end)], tspan(counter));

        % it is also possible to do a new function evaluation and the quintic hermite interpolator
        %f_half = feval (func, t+1/2*dt, u_half, options.vfunarguments{:});
        %u_interp = hermite_quintic_interpolation ([z(i-1) z(i)], [u(:,i-1) u_half u(:,i)], [k_vals(:,1) f_half k_vals(:,end)], tspan(counter));
      otherwise
        warning ('high order interpolation not yet implemented: using cubic iterpolation instead');
        der(:,1) = feval (func, z(i-1) , u(:,i-1), options.vfunarguments{:});
        der(:,2) = feval (func, z(i) , u(:,i), options.vfunarguments{:});
        u_interp = hermite_cubic_interpolation ([z(i-1) z(i)], [u(:,i-1) u(:,i)], der, tspan(counter));
      end

      % add the interpolated value of the solution
      u = [u(:,1:i-1), u_interp, u(:,i:end)];
            
      % add the time requested
      z = [z(1:i-1);tspan(counter);z(i:end)];

      % update counters
      counter++;
      i++;
    endwhile

    % if new time requested is not out of this interval
    if ((counter <= k) && (vdirection*z(end) > vdirection*tspan(counter)))
      % update the counter
      i++;
    else
      % else, stop the cycle and go on with the next iteration
      i = length (z) + 1;
    endif

  endwhile

endif

It is important to notice that:

  • The 1st order approximation doesn't require any additional function evaluation.
  • The 2nd order approximation may require the evaluation of the function at the current time. This can be avoided if the stepper already returns that value.
  • The only 3rd order solver implemented is ode23. The 3rd order approximation exploits the Runge-Kutta \(k\) values to avoid further function evaluations.
  • There are no 4th order schemes as yet implemented. However if ones were to use ode45 without local extrapolation then the dorpri_interpolation function can be used to obtain a 4th order approximation without any additional function evaluation. For any other 4th order scheme the hermite_quartic_interpolation function can be used.
  • For the 5th order method ode45, Shampine proposes to obtain a 4th order approximation at the middle point and to use quartic interpolation. It is however possible to directly do quintic interpolation but this require an additional function evaluation without (according to Shampine) a significant improvement.
  • For the higher order solvers (ode78), a suitable interpolator has not yet been implemented.
Finally, since I wrote the interpolation functions in such a way that they are independent of the number of output points requested, a possible improvement would be to compute all the values of the dense output between \(t\) and \(t+dt\) all at once instead of one value at a time.

lunedì 18 agosto 2014

FSAL - new stepper implementation

As stated in the previous post, the implementation of the steppers as it was did not allow the possibility to exploit the FSAL (First Same As Last) property of the Bogacki-Shampine algorithm (ode23) and of the Dormand-Prince algorithm (ode45).
The input and output arguments of the steppers have then be modified. As an example here is the runge_kutta_23 stepper:

function varargout = runge_kutta_23 (f, t, x, dt, varargin)
  options = varargin{1};
  k = zeros (size (x, 1), 4);

  if (nargin == 5) % only the options are passed
    k(:,1) = feval (f, t , x, options.vfunarguments{:});
  elseif (nargin == 6) % both the options and the k values are passed
    k(:,1) = varargin{2}(:,end); % FSAL property
  endif
  k(:,2) = feval (f, t + (1/2)*dt, x + dt*(1/2)*k(:,1), options.vfunarguments{:});
  k(:,3) = feval (f, t + (3/4)*dt, x + dt*(3/4)*k(:,2), options.vfunarguments{:});

  %# computing new time and new values for the unkwnowns
  varargout{1} = t + dt; %t_next
  varargout{2} = x + dt.*((2/9)*k(:,1) + (1/3)*k(:,2) + (4/9)*k(:,3)); % return the 3rd order approximation x_next

  %# if the estimation of the error is required
  if (nargout >= 3)
    %# new solution to be compared with the previous one
    k(:,4) = feval (f, t + dt, varargout{2}, options.vfunarguments{:});
    varargout{3} = x + dt.*((7/24)*k(:,1) + (1/4)*k(:,2) + (1/3)*k(:,3) + (1/8)*k(:,4)); %x_est
    varargout{4} = k;
  endif

endfunction

And the call within the solver becomes:
[s, y, y_est, k_vals] = stepper (func, z(end), u(:,end), dt, options, k_vals);

where k_vals has to be initialized for the first iteration as f(t, x).
This implementation will reduce the number of function evaluation for each step.

Furthermore, after some tests in MATLAB, the return values for the solution and the estimate in the  runge_kutta_23 and runge_kutta_45 steppers have been swapped to automatically perform local extrapolation. The MATLAB functions are in fact of order 3 and 5 respectively.

martedì 12 agosto 2014

Status of the code: bugfixes and new issues


ODESET and ODEGET


  • odeset and odeget functions have been slightly modified to be compliant with MATLAB. Each MATLAB option is present and all the options are tested. The coding style has been adapted to the GNU-Octave standard.
  • ode_struct_value_check: this function has been introduced by Roberto in addition to odepkg_structue_check. The relation between the two functions has to be clarified: in particular it is necessary to understand if it is really necessary to have two different functions or one is sufficient.


CHANGES TO THE STEPPERS


  • The runge_kutta_78 stepper has been implemented.
  • Two 4th order steppers have been implemented: runge_kutta_45_dopri (Dormand-Prince coefficients) and runge_kutta_45_fehlberg (Fehlberg coefficients).


CHANGES TO THE SOLVERS


  • ode78 solver has been updated to the new structure. It now exploits the runge_kutta_78 stepper.
  • A series of tests has been added to each solver to check all the functionalities and the all options. This has made me possible to detect some bugs that have been corrected. In particular the adaptive timestep evaluation had some issues that lead to the use of too short timesteps. This has been corrected and now the algorithm proposed in [1] is used.
  • Furthermore the current implementation uses linear interpolation to evaluate the solution at the user specified times. This leads to a considerable loss in accuracy and is not consistent with MATLAB (which guarantees the same order of accuracy of the scheme used). In [1] different methods are proposed for the dense output: these will be used as a reference for the implementation of a better solution.
  • In the released version of odepkg some of the solvers perform local extrapolation, that is the higher-order estimate is chosen as the solution. With the new stepper structure, as it is now, this choice effects all the solvers. It have to be decided whether to perform it or not (MATLAB doesn't seem to do it, thus I suggest to avoid it).
  • MATLAB implementation of ode45 uses the Dormand-Prince (DP) coefficients. In the released version of odepkg there exits two solvers: ode45 that uses the Fehlberg coefficients and ode54 that uses the DP coefficients. To be consistent with MATLAB, ode45 now uses the DP method. This makes the runge_kutta_45_fehlberg stepper and the ode54 solver, as it is now, redundant. Either their elimination or a change of the solver might be considered. However one of the advantages of DP coefficients is the possibility to reuse the last function evaluation at a given step as the first evaluation of the subsequent one. This is not easily done with the stepper structure introduced by Roberto.


CHANGES TO THE OPTIONS


  • InitialStep option has been modified to be MATLAB compatible (it must be a positive scalar).
  • RelTol defalut value has been changed to 1e-3 instead of 1e-6 to be MATLAB compatible.
  • MaxStep option has been implemented.
  • NormControl option has been implemented.


TODO

In addition to the general plan, a couple of issues need to be addressed:

  • Clarify the relation between ode_struct_value_check and odepkg_structue_check.
  • Decide if local extrapolation has to be used or not. My opinion (and the current implementation) is to avoid it to be compliant to what MATLAB seems to be doing.
  • Solve the dense output problem in a way that guarantees the consistency with MATLAB.
  • Consider if it's possible to reduce the number of function evaluation for the Dormand-Prince stepper (ode45) and the Bogacki-Shampine stepper (ode23) exploiting the FSAL property (first same as last).
  • Decide if in the future releases of odepkg ode54 has to be removed or maybe changed to become a Fehlberg solver.



[1] E. Hairer, S.P. N{\o}rsett, G. Wanner, Solving Ordinary Differential Equations, 1993, Springer.