%# Copyright (C) 2015-2016, Jacopo Corno <jacopo.corno@gmail.com>
%# Copyright (C) 2008-2012, Thomas Treichl <treichl@users.sourceforge.net>
%# OdePkg - A package for solving ordinary differential equations and more
%#
%# This program 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 of the License, or
%# (at your option) any later version.
%#
%# This program 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.
%#
%# You should have received a copy of the GNU General Public License
%# along with this program; If not, see <http://www.gnu.org/licenses/>.

%# -*- texinfo -*-
%# @deftypefn  {} {[@var{t}, @var{y}] =} ode23d (@var{fun}, @var{trange}, @var{init}, @var{lags}, @var{history})
%# @deftypefnx {} {[@var{t}, @var{y}] =} ode23d (@var{fun}, @var{trange}, @var{init}, @var{lags}, @var{history}, @var{ode_opt})
%# @deftypefnx {} {[@var{t}, @var{y}, @var{te}, @var{ye}, @var{ie}] =} ode23d (@dots{})
%# @deftypefnx {} {@var{solution} =} ode23d (@dots{})
%#
%# This function file can be used to solve a set of non--stiff delay
%# differential equations (non--stiff DDEs) with a modified version of the well
%# known explicit Runge--Kutta method of order 3.
%#
%# @var{fun} is a function handle, inline function, or string containing the
%# name of the function that defines the ODE: @code{y' = f(t,y)}.  The function
%# must accept two inputs where the first is time @var{t} and the second is a
%# column vector of unknowns @var{y}.
%#
%# @var{trange} specifies the time interval over which the ODE will be
%# evaluated.  Typically, it is a two-element vector specifying the initial and
%# final times (@code{[tinit, tfinal]}).  If there are more than two elements
%# then the solution will also be evaluated at these intermediate time
%# instances using an interpolation procedure of the same order as the one of
%# the solver.
%#
%# @var{init} contains the initial value for the unknowns.  If it is a row
%# vector then the solution @var{y} will be a matrix in which each column is
%# the solution for the corresponding initial value in @var{init}.
%#
%# @var{lags} is a vector that describes the lags of time.
%#
%# @var{history} is a matrix and describes the history of the DDEs.
%#
%# The optional fourth argument @var{ode_opt} specifies non-default options to
%# the ODE solver. It is a structure generated by @code{odeset}. @code{ode23d}
%# will ignore the following options: "BDF", "InitialSlope", "Jacobian",
%# "JPattern", "MassSingular", "MaxOrder", "MvPattern", "Vectorized".
%#
%# The function typically returns two outputs.  Variable @var{t} is a
%# column vector and contains the times where the solution was computed.  The
%# output @var{y} is a matrix in which each column refers to a different
%# unknown of the problem and each row corresponds to a time in @var{t}.  If
%# @var{trange} specifies intermediate time steps, only those will be returned.
%#
%# The output can also be returned as a structure @var{solution} which
%# has field @var{x} containing the time where the solution was evaluated and
%# field @var{y} containing the solution matrix for the times in @var{x}.
%# Use @code{fieldnames (@var{solution})} to see the other fields and
%# additional information returned.
%#
%# If using the @code{"Events"} option then three additional outputs may
%# be returned.  @var{te} holds the time when an Event function returned a
%# zero.  @var{ye} holds the value of the solution at time @var{te}.  @var{ie}
%# contains an index indicating which Event function was triggered in the case
%# of multiple Event functions.
%#
%# In other words, this function will solve a problem of the form
%# @example
%# dy/dt = fun (t, y(t), y(t-lags(1), y(t-lags(2), @dots{})))
%# y(slot(1)) = init
%# y(slot(1)-lags(1)) = hist(1), y(slot(1)-lags(2)) = hist(2), @dots{}
%# @end example
%#
%# For example:
%# @itemize @minus
%# @item
%# the following code solves an anonymous implementation of a chaotic behavior
%#
%# @example
%# fcao = @@(vt, vy, vz) [2 * vz / (1 + vz^9.65) - vy];
%#
%# vopt = odeset ("NormControl", "on", "RelTol", 1e-3);
%# vsol = ode23d (fcao, [0, 100], 0.5, 2, 0.5, vopt);
%#
%# vlag = interp1 (vsol.x, vsol.y, vsol.x - 2);
%# plot (vsol.y, vlag); legend ("fcao (t,y,z)");
%# @end example
%#
%# @item
%# to solve the following problem with two delayed state variables
%#
%# @example
%# d y1(t)/dt = -y1(t)
%# d y2(t)/dt = -y2(t) + y1(t-5)
%# d y3(t)/dt = -y3(t) + y2(t-10)*y1(t-10)
%# @end example
%#
%# one might do the following
%#
%# @example
%# function f = fun (t, y, yd)
%# f(1) = -y(1);                   %% y1' = -y1(t)
%# f(2) = -y(2) + yd(1,1);         %% y2' = -y2(t) + y1(t-lags(1))
%# f(3) = -y(3) + yd(2,2)*yd(1,2); %% y3' = -y3(t) + y2(t-lags(2))*y1(t-lags(2))
%# end
%# T = [0,20]
%# res = ode23d (@@fun, T, [1;1;1], [5, 10], ones (3,2));
%# @end example
%#
%# @end itemize
%# @seealso{odeset, ode45d, ode78d}
%# @end deftypefn

function varargout = ode23d (fun, trange, init, lags, history, varargin)

  if (nargin < 5)
    print_usage ();
  end

  order = 3;
  solver = "ode23d";

  if (nargin >= 6)
    if (! isstruct (varargin{1}))
      %# varargin{1:len} are parameters for fun
      odeopts = odeset ();
      funarguments = varargin;
    elseif (length (varargin) > 1)
      %# varargin{1} is an ODE options structure opt
      odeopts = varargin{1};
      funarguments = {varargin{2:length(varargin)}};
    else  # if (isstruct (varargin{1}))
      odeopts = varargin{1};
      funarguments = {};
    end
  else  # nargin == 3
    odeopts = odeset ();
    funarguments = {};
  end

  if (! isnumeric (trange) || ! isvector (trange))
    error ("Octave:invalid-input-arg",
           "ode23d: TRANGE must be a numeric vector");
  end

  if (length (trange) < 2)
    error ("Octave:invalid-input-arg",
           "ode23d: TRANGE must contain at least 2 elements");
  elseif (trange(2) == trange(1))
    error ("Octave:invalid-input-arg",
           "ode23d: invalid time span, TRANGE(1) == TRANGE(2)");
  else
    direction = sign (trange(2) - trange(1));
  end
  trange = trange(:)'; %# Create a row vector

  if (! isnumeric (init) || ! isvector (init))
    error ("Octave:invalid-input-arg",
           "ode23d: INIT must be a numeric vector");
  end
  init = init(:)'; %# Create a row vector

  if (ischar (fun))
    try
      fun = str2func (fun);
    catch
      warning (lasterr);
    end
  end
  if (! isa (fun, "function_handle"))
    error ("Octave:invalid-input-arg",
           "ode23d: FUN must be a valid function handle");
  end

  if (! isnumeric (lags) || ! isvector (lags))
    error ("Octave:invalid-input-arg",
      "ode23d: LAGS must be a valid numerical value");
  end
  lags = lags(:)'; %# Create a row vector

  if ! (isnumeric (history) || isa (history, "function_handle"))
    error ("Octave:invalid-input-arg",
      "ode23d: HISTORY must either be numeric or a function handle");
  end

  %# Start preprocessing, have a look which options are set in odeopts,
  %# check if an invalid or unused option is set
  [defaults, classes, attributes] = odedefaults (numel (init),
                                                 trange(1), trange(end));

  persistent ode23d_ignore_options = ...
    {"BDF", "InitialSlope", "Jacobian", "JPattern",
     "MassSingular", "MaxOrder", "MvPattern", "Vectorized"};

  defaults   = rmfield (defaults, ode23d_ignore_options);
  classes    = rmfield (classes, ode23d_ignore_options);
  attributes = rmfield (attributes, ode23d_ignore_options);

  odeopts = odemergeopts ("ode23d", odeopts, defaults, classes, attributes);

  odeopts.funarguments = funarguments;
  odeopts.direction    = direction;

  if (! isempty (odeopts.NonNegative))
    if (isempty (odeopts.Mass))
      odeopts.havenonnegative = true;
    else
      odeopts.havenonnegative = false;
      warning ("Octave:invalid-input-arg",
               ["ode23d: option \"NonNegative\" is ignored", ...
                " when mass matrix is set\n"]);
    end
  else
    odeopts.havenonnegative = false;
  end

  if (isempty (odeopts.OutputFcn) && nargout == 0)
    odeopts.OutputFcn = @odeplot;
    odeopts.haveoutputfunction = true;
  else
    odeopts.haveoutputfunction = ! isempty (odeopts.OutputFcn);
  end

  if (isempty (odeopts.InitialStep))
    if isempty (odeopts.Refine)
      odeopts.InitialStep = odeopts.MaxStep;
    else
      odeopts.InitialStep = odeopts.MaxStep / odeopts.Refine;
    end
  end
  stepsize = odeopts.InitialStep;

  if (! isempty (odeopts.Mass) && isnumeric (odeopts.Mass))
    havemasshandle = false;
    mass = odeopts.Mass;    # constant mass
  elseif (isa (odeopts.Mass, "function_handle"))
    havemasshandle = true;  # mass defined by a function handle
  else  # no mass matrix - creating a diag-matrix of ones for mass
    havemasshandle = false; # mass = diag (ones (length (init), 1), 0);
  end

  %# Starting the initialisation of the core solver ode23d
  timestamp  = trange(1,1);      %# timestamp = start time
  timestop   = trange(1,end);    %# stop time = last value
  minstepsize = (timestop - timestamp) / (1/eps);

  retvaltime = timestamp; %# first timestamp output
  if (! isempty (odeopts.OutputSel)) %# first solution output
    retvalresult = init(odeopts.OutputSel);
  else retvalresult = init;
  end

  %# Initialize the History
  if (isnumeric (history))
    hmat = history;
    havehistnumeric = true;
  else %# it must be a function handle
    for cnt = 1:length (lags);
      hmat(:,cnt) = feval (history, (trange(1)-lags(cnt)), funarguments{:});
    end
    havehistnumeric = false;
  end

  %# Initialize DDE variables for history calculation
  saveddetime = [timestamp - lags, timestamp]';
  saveddeinput = [hmat, init']';
  savedderesult = [hmat, init']';

  %# Initialize the EventFcn
  if (! isempty (odeopts.Events))
    ode_event_handler (odeopts.Events, timestamp,
                         {retvalresult', hmat}, "init",
                         funarguments{:});
  end

  pow = 1/3;            %# 20071016, reported by Luis Randez
  a = [  0, 0, 0;       %# The Runge-Kutta-Fehlberg 2(3) coefficients
        1/2, 0, 0;       %# Coefficients proved on 20060827
         -1, 2, 0];      %# See p.91 in Ascher & Petzold
  b2 = [0; 1; 0];       %# 2nd and 3rd order
  b3 = [1/6; 2/3; 1/6]; %# b-coefficients
  c = sum (a, 2);

  %# The solver main loop - stop if the endpoint has been reached
  cntloop = 2; cntcycles = 1; vu = init; vk = vu' * zeros(1,3);
  cntiter = 0; unhandledtermination = true;
  while ((timestamp < timestop && stepsize >= minstepsize))

    %# Hit the endpoint of the time slot exactely
    if ((timestamp + stepsize) > timestop)
      stepsize = timestop - timestamp; end

    %# Estimate the three results when using this solver
    for j = 1:3
      thetime  = timestamp + c(j,1) * stepsize;
      theinput = vu' + stepsize * vk(:,1:j-1) * a(j,1:j-1)';
      %# Claculate the history values (or get them from an external
      %# function) that are needed for the next step of solving
      if (havehistnumeric)
        for cnt = 1:length (lags)
          %# Direct implementation of a 'quadrature cubic Hermite interpolation'
          %# found at the Faculty for Mathematics of the University of Stuttgart
          %# http://mo.mathematik.uni-stuttgart.de/inhalt/aussage/aussage1269
          vnumb = find (thetime - lags(cnt) >= saveddetime);
          velem = min (vnumb(end), length (saveddetime) - 1);
          vstep = saveddetime(velem+1) - saveddetime(velem);
          vdiff = (thetime - lags(cnt) - saveddetime(velem)) / vstep;
          vsubs = 1 - vdiff;
          %# Calculation of the coefficients for the interpolation algorithm
          vua = (1 + 2 * vdiff) * vsubs^2;
          vub = (3 - 2 * vdiff) * vdiff^2;
          vva = vstep * vdiff * vsubs^2;
          vvb = -vstep * vsubs * vdiff^2;
          hmat(:,cnt) = vua * saveddeinput(velem,:)' + ...
              vub * saveddeinput(velem+1,:)' + ...
              vva * savedderesult(velem,:)' + ...
              vvb * savedderesult(velem+1,:)';
        end
      else %# the history must be a function handle
        for cnt = 1:length (lags)
          hmat(:,cnt) = feval ...
            (history, thetime - lags(cnt), funarguments{:});
        end
      end

      if (havemasshandle)   %# Handle only the dynamic mass matrix,
        if (! strcmp (odeopts.MStateDependence, "none")) %# constant mass matrices have already
          mass = feval ...  %# been set before (if any)
            (odeopts.Mass, thetime, theinput, funarguments{:});
        else                 %# if (massdependence == false)
          mass = feval ...  %# then we only have the time argument
            (odeopts.Mass, thetime, funarguments{:});
        end
        vk(:,j) = mass \ feval ...
          (fun, thetime, theinput, hmat, funarguments{:});
      else
        vk(:,j) = feval ...
          (fun, thetime, theinput, hmat, funarguments{:});
      end
    end

    %# Compute the 2nd and the 3rd order estimation
    y2 = vu' + stepsize * (vk * b2);
    y3 = vu' + stepsize * (vk * b3);
    if (odeopts.havenonnegative)
      vu(odeopts.NonNegative) = abs (vu(odeopts.NonNegative));
      y2(odeopts.NonNegative) = abs (y2(odeopts.NonNegative));
      y3(odeopts.NonNegative) = abs (y3(odeopts.NonNegative));
    end
    vSaveVUForRefine = vu;

    %# Calculate the absolute local truncation error and the acceptable error
    if (! strcmp (odeopts.NormControl, "on"))
      vdelta = y3 - y2;
      vtau = max (odeopts.RelTol * vu', odeopts.AbsTol);
    else
      vdelta = norm (y3 - y2, Inf);
      vtau = max (odeopts.RelTol * max (norm (vu', Inf), 1.0), ...
                  odeopts.AbsTol);
    end

    %# If the error is acceptable then update the vretval variables
    if (all (vdelta <= vtau))
      timestamp = timestamp + stepsize;
      vu = y3'; %# MC2001: the higher order estimation as "local extrapolation"
      retvaltime(cntloop,:) = timestamp;
      if (! isempty (odeopts.OutputSel))
        retvalresult(cntloop,:) = vu(odeopts.OutputSel);
      else
        retvalresult(cntloop,:) = vu;
      end
      cntloop = cntloop + 1; cntiter = 0;

      %# Update DDE values for next history calculation
      saveddetime(end+1) = timestamp;
      saveddeinput(end+1,:) = theinput';
      savedderesult(end+1,:) = vu;

      if (! isempty(odeopts.Refine))         %# Do interpolation
        for cnt = 0:odeopts.Refine-1 %# Approximation between told and t
          vapproxtime = (cnt + 1) * stepsize / (odeopts.Refine + 1);
          vapproxvals = vSaveVUForRefine' + vapproxtime * (vk * b3);
          if (! isempty (odeopts.OutputSel))
            vapproxvals = vapproxvals(odeopts.OutputSel);
          end
          if (odeopts.haveoutputfunction)
            feval (odeopts.OutputFcn, (timestamp - stepsize) + vapproxtime, ...
              vapproxvals, [], funarguments{:});
          end
        end
      end
      %# Call plot only if a valid result has been found, therefore this
      %# code fragment has moved here. Stop integration if plot function
      %# returns false
      if (odeopts.haveoutputfunction)
        vpltret = feval (odeopts.OutputFcn, timestamp, ...
          retvalresult(cntloop-1,:)', [], funarguments{:});
        if (vpltret), unhandledtermination = false; break; end
      end

      %# Call event only if a valid result has been found, therefore this
      %# code fragment has moved here. Stop integration if veventbreak is
      %# true
      if (! isempty (odeopts.Events))
        event = ode_event_handler (odeopts.Events, timestamp,
                                    {vu(:), hmat}, [], funarguments{:});
        if (! isempty (event{1}) && event{1} == 1)
          retvaltime(cntloop-1,:) = event{3}(end,:);
          retvalresult(cntloop-1,:) = event{4}(end,:);
          unhandledtermination = false; break;
        end
      end
    end %# If the error is acceptable ...

    %# Update the step size for the next integration step
    %# vdelta may be 0 or even negative - could be an iteration problem
    vdelta = max (vdelta, eps);
    stepsize = min (odeopts.MaxStep, ...
                    min (0.8 * stepsize * (vtau ./ vdelta) .^ pow));

    %# Update counters that count the number of iteration cycles
    cntcycles = cntcycles + 1; %# Needed for postprocessing
    cntiter = cntiter + 1;     %# Needed to find iteration problems

    %# Stop solving because the last 1000 steps no successful valid
    %# value has been found
    if (cntiter >= 5000)
      error (['Solving has not been successful. The iterative', ...
        ' integration loop exited at time t = %f before endpoint at', ...
        ' tend = %f was reached. This happened because the iterative', ...
        ' integration loop does not find a valid solution at this time', ...
        ' stamp. Try to reduce the value of "InitialStep" and/or', ...
        ' "MaxStep" with the command "odeset".\n'], timestamp, timestop);
    end

  end %# The main loop

  %# Check if integration of the ode has been successful
  if (timestamp < timestop)
    if (unhandledtermination == true)
      error (['Solving has not been successful. The iterative', ...
        ' integration loop exited at time t = %f', ...
        ' before endpoint at tend = %f was reached. This may', ...
        ' happen if the stepsize grows smaller than defined in', ...
        ' minstepsize. Try to reduce the value of "InitialStep" and/or', ...
        ' "MaxStep" with the command "odeset".\n'], timestamp, timestop);
    else
      warning ('OdePkg:HideWarning', ...
        ['Solver has been stopped by a call of "break" in', ...
         ' the main iteration loop at time t = %f before endpoint at', ...
         ' tend = %f was reached. This may happen because the @odeplot', ...
         ' function returned "true" or the @event function returned "true".'], ...
         timestamp, timestop);
    end
  end

  %# Postprocessing, do whatever when terminating integration algorithm
  if (odeopts.haveoutputfunction) %# Cleanup plotter
    feval (odeopts.OutputFcn, timestamp, ...
      retvalresult(cntloop-1,:)', 'done', funarguments{:});
  end
  if (! isempty (odeopts.Events))  %# Cleanup event function handling
    ode_event_handler (odeopts.Events, timestamp,
                       {retvalresult(cntloop-1,:), hmat}, "done",
                        funarguments{:});
  end

  %# Print additional information if option Stats is set
  if (strcmp (odeopts.Stats, 'on'))
    havestats = true;
    nsteps    = cntloop-2;                    %# cntloop from 2..end
    nfailed   = (cntcycles-1)-(cntloop-2)+1; %# vcntcycl from 1..end
    nfevals   = 3*(cntcycles-1);              %# number of ode evaluations
    ndecomps  = 0;                             %# number of LU decompositions
    npds      = 0;                             %# number of partial derivatives
    nlinsols  = 0;                             %# no. of solutions of linear systems
    %# Print cost statistics if no output argument is given
    if (nargout == 0)
      msg = fprintf (1, 'Number of successful steps: %d', nsteps);
      msg = fprintf (1, 'Number of failed attempts:  %d', nfailed);
      msg = fprintf (1, 'Number of function calls:   %d', nfevals);
    end
  else havestats = false;
  end

  if (nargout == 1)                  %# Sort output variables, depends on nargout
    varargout{1}.x = retvaltime.';   %# Time stamps are saved in field x
    varargout{1}.y = retvalresult.'; %# Results are saved in field y
    varargout{1}.solver = 'ode23d';  %# Solver name is saved in field solver
    if (! isempty (odeopts.Events))
      varargout{1}.ie = event{2};  %# Index info which event occurred
      varargout{1}.xe = event{3};  %# Time info when an event occurred
      varargout{1}.ye = event{4};  %# Results when an event occurred
    end
    if (havestats)
      varargout{1}.stats = struct;
      varargout{1}.stats.nsteps   = nsteps;
      varargout{1}.stats.nfailed  = nfailed;
      varargout{1}.stats.nfevals  = nfevals;
      varargout{1}.stats.npds     = npds;
      varargout{1}.stats.ndecomps = ndecomps;
      varargout{1}.stats.nlinsols = nlinsols;
    end
  elseif (nargout == 2)
    varargout{1} = retvaltime;     %# Time stamps are first output argument
    varargout{2} = retvalresult;   %# Results are second output argument
  elseif (nargout == 5)
    varargout{1} = retvaltime;     %# Same as (nargout == 2)
    varargout{2} = retvalresult;   %# Same as (nargout == 2)
    varargout{3} = [];              %# LabMat doesn't accept lines like
    varargout{4} = [];              %# varargout{3} = varargout{4} = [];
    varargout{5} = [];
    if (! isempty (odeopts.Events))
      varargout{3} = event{3};     %# Time info when an event occurred
      varargout{4} = event{4};     %# Results when an event occurred
      varargout{5} = event{2};     %# Index info which event occurred
    end
  %# else nothing will be returned, varargout{1} undefined
  end

end

%! # We are using a "pseudo-DDE" implementation for all tests that
%! # are done for this function. We also define an Events and a
%! # pseudo-Mass implementation. For further tests we also define a
%! # reference solution (computed at high accuracy) and an OutputFcn.
%!function [vyd] = fexp (vt, vy, vz, varargin)
%!  vyd(1,1) = exp (- vt) - vz(1); %# The DDEs that are
%!  vyd(2,1) = vy(1) - vz(2);      %# used for all examples
%!function [vval, vtrm, vdir] = feve (vt, vy, vz, varargin)
%!  vval = fexp (vt, vy, vz); %# We use the derivatives
%!  vtrm = zeros (2,1);       %# don't stop solving here
%!  vdir = ones (2,1);        %# in positive direction
%!function [vval, vtrm, vdir] = fevn (vt, vy, vz, varargin)
%!  vval = fexp (vt, vy, vz); %# We use the derivatives
%!  vtrm = ones (2,1);        %# stop solving here
%!  vdir = ones (2,1);        %# in positive direction
%!function [vmas] = fmas (vt, vy, vz, varargin)
%!  vmas =  [1, 0; 0, 1];     %# Dummy mass matrix for tests
%!function [vmas] = fmsa (vt, vy, vz, varargin)
%!  vmas = sparse ([1, 0; 0, 1]); %# A dummy sparse matrix
%!function [vref] = fref ()       %# The reference solution
%!  vref = [0.12194462133618, 0.01652432423938];
%!function [vout] = fout (vt, vy, vflag, varargin)
%!  if (regexp (char (vflag), 'init') == 1)
%!    if (any (size (vt) ~= [2, 1])) error ('"fout" step "init"'); end
%!  elseif (isempty (vflag))
%!    if (any (size (vt) ~= [1, 1])) error ('"fout" step "calc"'); end
%!    vout = false;
%!  elseif (regexp (char (vflag), 'done') == 1)
%!    if (any (size (vt) ~= [1, 1])) error ('"fout" step "done"'); end
%!  else
%!    error ("\"fout\" invalid vflag");
%!  end
%!
%!error %# input argument number one
%!  B = ode23d (1, [0 5], [1; 0], 1, [1; 0]);
%!error %# input argument number two
%!  B = ode23d (@fexp, 1, [1; 0], 1, [1; 0]);
%!error %# input argument number three
%!  B = ode23d (@fexp, [0 5], 1, 1, [1; 0]);
%!error %# input argument number four
%!  B = ode23d (@fexp, [0 5], [1; 0], [1; 1], [1; 0]);
%!error %# input argument number five
%!  B = ode23d (@fexp, [0 5], [1; 0], 1, 1);
%!test %# one output argument
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0]);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!  assert (isfield (vsol, 'solver'));
%!  assert (vsol.solver, 'ode23d');
%!test %# two output arguments
%!  [vt, vy] = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0]);
%!  assert ([vt(end), vy(end,:)], [5, fref], 1e-1);
%!test %# five output arguments and no Events
%!  [vt, vy, vxe, vye, vie] = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0]);
%!  assert ([vt(end), vy(end,:)], [5, fref], 1e-1);
%!  assert ([vie, vxe, vye], []);
%!test %# anonymous function instead of real function
%!  faym = @(vt, vy, vz) [exp(-vt) - vz(1); vy(1) - vz(2)];
%!  vsol = ode23d (faym, [0 5], [1; 0], 1, [1; 0]);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# extra input arguments passed trhough
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], 'KL');
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# empty OdePkg structure *but* extra input arguments
%!  vopt = odeset;
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt, 12, 13, 'KL');
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# AbsTol option
%!  vopt = odeset ('AbsTol', 1e-5);
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# AbsTol and RelTol option
%!  vopt = odeset ('AbsTol', 1e-7, 'RelTol', 1e-7);
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# RelTol and NormControl option
%!  vopt = odeset ('RelTol', 1e-7, 'NormControl', 'on');
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], .5e-1);
%!test %# NonNegative for second component
%!  vopt = odeset ('NonNegative', 1);
%!  vsol = ode23d (@fexp, [0 2.5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [2.5; 0.001; 0.237], 1e-1);
%!test %# Details of OutputSel and Refine can't be tested
%!  vopt = odeset ('OutputFcn', @fout, 'OutputSel', 1, 'Refine', 5);
%!  vsol = ode23d (@fexp, [0 2.5], [1; 0], 1, [1; 0], vopt);
%!test %# Stats must add further elements in vsol
%!  vopt = odeset ('Stats', 'on');
%!  vsol = ode23d (@fexp, [0 2.5], [1; 0], 1, [1; 0], vopt);
%!  assert (isfield (vsol, 'stats'));
%!  assert (isfield (vsol.stats, 'nsteps'));
%!test %# InitialStep option
%!  vopt = odeset ('InitialStep', 1e-8);
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# MaxStep option
%!  vopt = odeset ('MaxStep', 1e-2);
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# Events option add further elements in vsol
%!  vopt = odeset ('Events', @feve);
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert (isfield (vsol, 'ie'));
%!  assert (vsol.ie, [1; 1]);
%!  assert (isfield (vsol, 'xe'));
%!  assert (isfield (vsol, 'ye'));
%!test %# Events option, now stop integration
%!  warning ('off', 'OdePkg:HideWarning');
%!  vopt = odeset ('Events', @fevn, 'NormControl', 'on');
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.ie, vsol.xe, vsol.ye], ...
%!    [1.0000, 2.9219, -0.2127, -0.2671], 1e-1);
%!test %# Events option, five output arguments
%!  vopt = odeset ('Events', @fevn, 'NormControl', 'on');
%!  [vt, vy, vxe, vye, vie] = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vie, vxe, vye], ...
%!    [1.0000, 2.9219, -0.2127, -0.2671], 1e-1);
%!
%! %# test for Jacobian option is missing
%! %# test for Jacobian (being a sparse matrix) is missing
%! %# test for JPattern option is missing
%! %# test for Vectorized option is missing
%! %# test for NewtonTol option is missing
%! %# test for MaxNewtonIterations option is missing
%!
%!test %# Mass option as function
%!  vopt = odeset ('Mass', eye (2,2));
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# Mass option as matrix
%!  vopt = odeset ('Mass', eye (2,2));
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# Mass option as sparse matrix
%!  vopt = odeset ('Mass', sparse (eye (2,2)));
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# Mass option as function and sparse matrix
%!  vopt = odeset ('Mass', @fmsa);
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# Mass option as function and MStateDependence
%!  vopt = odeset ('Mass', @fmas, 'MStateDependence', 'strong');
%!  vsol = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vsol.x(end); vsol.y(:,end)], [5; fref'], 1e-1);
%!test %# Set BDF option to something else than default
%!  vopt = odeset ('BDF', 'on');
%!  [vt, vy] = ode23d (@fexp, [0 5], [1; 0], 1, [1; 0], vopt);
%!  assert ([vt(end), vy(end,:)], [5, fref], 0.5);
%!
%! %# test for MvPattern option is missing
%! %# test for InitialSlope option is missing
%! %# test for MaxOrder option is missing
%!

