changeset 20901:afe9c529760d

2015 Code Sprint: move ode23 and runge_kutta_23 from odepkg to core * scripts/ode/ode23.m: new file * scripts/ode/private/runge_kutta_23.m: new file * scripts/ode/module.mk: list new files * doc/interpreter/diffeq.txi: mention ode23 among available solvers * scripts/help/__unimplemented__.m: remove ode23 from list of unimplemented functions
author Stefan Miereis <stefan.miereis@gmx.de>
date Tue, 15 Dec 2015 13:59:17 +0100
parents 4d14b0a22b29
children 73cf3434e8c9
files doc/interpreter/diffeq.txi scripts/help/__unimplemented__.m scripts/ode/module.mk scripts/ode/ode23.m scripts/ode/private/runge_kutta_23.m
diffstat 5 files changed, 735 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/doc/interpreter/diffeq.txi	Tue Dec 15 15:10:47 2015 +0100
+++ b/doc/interpreter/diffeq.txi	Tue Dec 15 13:59:17 2015 +0100
@@ -143,12 +143,19 @@
     method.  This is a fourth--order accurate integrator therefore the local
     error normally expected is @math{O(h^5)}.  This solver requires six
     function evaluations per integration step.
+    @item @code{ode23} Integrates a system of non--stiff ordinary differential equations
+    (non-stiff ODEs and DAEs) using second order @nospell{Bogacki-Shampine}
+    method. This is a second-order accurate integrator therefore the local
+    error normally expected is @math{O(h^3)}. This solver requires three
+    function evaluations per integration step.
   @end itemize
 @end itemize
 
 
 @DOCSTRING(ode45)
 
+@DOCSTRING(ode23)
+
 @DOCSTRING(odeset)
 
 @DOCSTRING(odeget)
--- a/scripts/help/__unimplemented__.m	Tue Dec 15 15:10:47 2015 +0100
+++ b/scripts/help/__unimplemented__.m	Tue Dec 15 13:59:17 2015 +0100
@@ -82,7 +82,7 @@
       txt = ["matlabrc is not implemented.  ", ...
              'Octave uses the file ".octaverc" instead.'];
 
-    case {"ode113", "ode15i", "ode15s", "ode23", "ode23s", "ode23t", "ode23tb"}
+    case {"ode113", "ode15i", "ode15s", "ode23s", "ode23t", "ode23tb"}
       txt = ["Octave provides lsode and ode45 for solving differential equations. ", ...
              "For more information try @code{help lsode}, @code{help ode45}.  ", ...
              "Matlab-compatible ODE functions are provided by the odepkg ", ...
@@ -758,7 +758,6 @@
   "ode113",
   "ode15i",
   "ode15s",
-  "ode23",
   "ode23s",
   "ode23t",
   "ode23tb",
--- a/scripts/ode/module.mk	Tue Dec 15 15:10:47 2015 +0100
+++ b/scripts/ode/module.mk	Tue Dec 15 13:59:17 2015 +0100
@@ -12,13 +12,15 @@
   scripts/ode/private/ode_struct_value_check.m \
   scripts/ode/private/runge_kutta_45_dorpri.m \
   scripts/ode/private/runge_kutta_interpolate.m \
-  scripts/ode/private/starting_stepsize.m
+  scripts/ode/private/starting_stepsize.m \
+  scripts/ode/private/runge_kutta_23.m
 
 scripts_ode_FCN_FILES = \
   scripts/ode/ode45.m \
   scripts/ode/odeset.m \
   scripts/ode/odeget.m \
-  scripts/ode/odeplot.m
+  scripts/ode/odeplot.m \
+  scripts/ode/ode23.m
 
 scripts_odedir = $(fcnfiledir)/ode
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/ode/ode23.m	Tue Dec 15 13:59:17 2015 +0100
@@ -0,0 +1,614 @@
+## Copyright (C) 2014-2015, Jacopo Corno <jacopo.corno@gmail.com>
+## Copyright (C) 2013-2015, Roberto Porcu' <roberto.porcu@polimi.it>
+## Copyright (C) 2006-2015, Thomas Treichl <treichl@users.sourceforge.net>
+##
+## This file is part of Octave.
+##
+## Octave 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 3 of the License, or (at
+## your option) any later version.
+##
+## Octave 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 Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {[@var{t}, @var{y}] =} ode23 (@var{fun}, @var{trange}, @var{init})
+## @deftypefnx {Function File} {[@var{t}, @var{y}] =} ode23 (@var{fun}, @var{trange}, @var{init}, @var{ode_opt})
+## @deftypefnx {Function File} {[@var{t}, @var{y}] =} ode23 (@dots{}, @var{par1}, @var{par2}, @dots{})
+## @deftypefnx {Function File} {[@var{t}, @var{y}, @var{te}, @var{ye}, @var{ie}] =} ode23 (@dots{})
+## @deftypefnx {Function File} {@var{solution} =} ode23 (@dots{})
+##
+## Solve a set of non-stiff Ordinary Differential Equations (non-stiff ODEs)
+## with the well known explicit Bogacki-Shampine method of order 3. For the
+## definition of this method see
+## @url{http://en.wikipedia.org/wiki/List_of_Runge%E2%80%93Kutta_methods}.
+##
+## @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 unless the integrate function specified is
+## @command{integrate_n_steps}.
+##
+## By default, @code{ode23} uses an adaptive timestep with the
+## @code{integrate_adaptive} algorithm.  The tolerance for the timestep
+## computation may be changed by using the option @qcode{"Tau"}, that has a
+## default value of @math{1e-6}.  If the ODE option @qcode{"TimeStepSize"} is
+## not empty, then the stepper called will be @code{integrate_const}.  If, in
+## addition, the option @qcode{"TimeStepNumber"} is also specified then the
+## integrate function @code{integrate_n_steps} will be used.
+##
+## @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}.
+##
+## The optional fourth argument @var{ode_opt} specifies non-default options to
+## the ODE solver.  It is a structure generated by @code{odeset}.
+##
+## The function typically returns two outputs.  Variable @var{t} is a
+## column vector and contains the times where the solution was found.  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}.
+##
+## 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 @qcode{"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.
+##
+## This function can be called with two output arguments: @var{t} and @var{y}.
+## Variable @var{t} is a column vector and contains the time stamps, instead
+## @var{y} is a matrix in which each column refers to a different unknown of the
+## problem and the rows number is the same of @var{t} rows number so that each
+## row of @var{y} contains the values of all unknowns at the time value
+## contained in the corresponding row in @var{t}.
+##
+## Example: Solve the Van der Pol equation
+##
+## @example
+## @group
+## fvdp = @@(@var{t},@var{y}) [@var{y}(2); (1 - @var{y}(1)^2) * @var{y}(2) - @var{y}(1)];
+## [@var{t},@var{y}] = ode23 (fvdp, [0 20], [2 0]);
+## @end group
+## @end example
+## @seealso{odeset, odeget}
+## @end deftypefn
+
+## ChangeLog:
+##   20010703 the function file "ode23.m" was written by Marc Compere
+##     under the GPL for the use with this software. This function has been
+##     taken as a base for the following implementation.
+##   20060810, Thomas Treichl
+##     This function was adapted to the new syntax that is used by the
+##     new OdePkg for Octave and is compatible to Matlab's ode23.
+
+function varargout = ode23 (fun, trange, init, varargin)
+
+  if (nargin < 3)
+    print_usage ();
+  endif
+
+  order = 3;
+  solver = "ode23";
+  
+  if (nargin >= 4)
+    if (! isstruct (varargin{1}))
+      ## varargin{1:len} are parameters for fun
+      odeopts = odeset ();
+      odeopts.funarguments = varargin;
+    elseif (length (varargin) > 1)
+      ## varargin{1} is an ODE options structure opt
+      odeopts = ode_struct_value_check ("ode23", varargin{1}, "ode23");
+      odeopts.funarguments = {varargin{2:length(varargin)}};
+    else  # if (isstruct (varargin{1}))
+      odeopts = ode_struct_value_check ("ode23", varargin{1}, "ode23");
+      odeopts.funarguments = {};
+    endif
+  else  # nargin == 3
+    odeopts = odeset ();
+    odeopts.funarguments = {};
+  endif
+
+  if (! isvector (trange) || ! isnumeric (trange))
+    error ("Octave:invalid-input-arg",
+           "ode23: TRANGE must be a numeric vector");
+  endif
+
+  TimeStepNumber = odeget (odeopts, "TimeStepNumber", [], "fast");
+  TimeStepSize = odeget (odeopts, "TimeStepSize", [], "fast");
+  if (length (trange) < 2
+      && (isempty (TimeStepSize) || isempty (TimeStepNumber)))
+    error ("Octave:invalid-input-arg",
+           "ode23: TRANGE must contain at least 2 elements");
+  elseif (trange(2) == trange(1))
+    error ("Octave:invalid-input-arg",
+           "ode23: invalid time span, TRANGE(1) == TRANGE(2)");
+  else
+    odeopts.direction = sign (trange(2) - trange(1));
+  endif
+  trange = trange(:);
+
+  if (! isvector (init) || ! isnumeric (init))
+    error ("Octave:invalid-input-arg",
+           "ode23: INIT must be a numeric vector");
+  endif
+  init = init(:);
+
+  if (ischar (fun))
+    try
+      fun = str2func (fun);
+    catch
+      warning (lasterr);
+    end_try_catch
+  endif
+  if (! isa (fun, "function_handle"))
+    error ("Octave:invalid-input-arg",
+           "ode23: FUN must be a valid function handle");
+  endif
+
+  ## Start preprocessing, have a look which options are set in odeopts,
+  ## check if an invalid or unused option is set
+  if (isempty (TimeStepNumber) && isempty (TimeStepSize))
+    integrate_func = "adaptive";
+    odeopts.stepsizefixed = false;
+  elseif (! isempty (TimeStepNumber) && ! isempty (TimeStepSize))
+    integrate_func = "n_steps";
+    odeopts.stepsizefixed = true;
+    if (sign (TimeStepSize) != odeopts.direction)
+      warning ("Octave:invalid-input-arg",
+               ["ode23: option \"TimeStepSize\" has the wrong sign, ", ...
+                "but will be corrected automatically\n"]);
+      TimeStepSize = -TimeStepSize;
+    endif
+  elseif (isempty (TimeStepNumber) && ! isempty (TimeStepSize))
+    integrate_func = "const";
+    odeopts.stepsizefixed = true;
+    if (sign (TimeStepSize) != odeopts.direction)
+      warning ("Octave:invalid-input-arg",
+               ["ode23: option \"TimeStepSize\" has the wrong sign, ",
+                "but will be corrected automatically\n"]);
+      TimeStepSize = -TimeStepSize;
+    endif
+  else
+    warning ("Octave:invalid-input-arg",
+             "ode23: assuming an adaptive integrate function\n");
+    integrate_func = "adaptive";
+  endif
+
+  if (isempty (odeopts.RelTol) && ! odeopts.stepsizefixed)
+    odeopts.RelTol = 1e-3;
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"RelTol\" not set, new value %f will be used\n",
+             odeopts.RelTol);
+  elseif (! isempty (odeopts.RelTol) && odeopts.stepsizefixed)
+    warning ("Octave:invalid-input-arg",
+             ["ode23: option \"RelTol\" is ignored", ...
+              " when fixed time stamps are given\n"]);
+  endif
+
+  if (isempty (odeopts.AbsTol) && ! odeopts.stepsizefixed)
+    odeopts.AbsTol = 1e-6;
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"AbsTol\" not set, new value %f will be used\n",
+             odeopts.AbsTol);
+  elseif (! isempty (odeopts.AbsTol) && odeopts.stepsizefixed)
+    warning ("Octave:invalid-input-arg",
+             ["ode23: option \"AbsTol\" is ignored", ...
+              " when fixed time stamps are given\n"]);
+  else
+    odeopts.AbsTol = odeopts.AbsTol(:);  # Create column vector
+  endif
+
+  odeopts.normcontrol = strcmp (odeopts.NormControl, "on");
+
+  if (! isempty (odeopts.NonNegative))
+    if (isempty (odeopts.Mass))
+      odeopts.havenonnegative = true;
+    else
+      odeopts.havenonnegative = false;
+      warning ("Octave:invalid-input-arg",
+               ["ode23: option \"NonNegative\" is ignored", ...
+                " when mass matrix is set\n"]);
+    endif
+  else 
+    odeopts.havenonnegative = false;
+  endif
+
+  if (isempty (odeopts.OutputFcn) && nargout == 0)
+    odeopts.OutputFcn = @odeplot;
+    odeopts.haveoutputfunction = true;
+  else
+    odeopts.haveoutputfunction = ! isempty (odeopts.OutputFcn);
+  endif
+
+  odeopts.haveoutputselection = ! isempty (odeopts.OutputSel);
+
+  if (odeopts.Refine > 0)
+    odeopts.haverefine = true;
+  else 
+    odeopts.haverefine = false;
+  endif
+
+  if (isempty (odeopts.InitialStep) && strcmp (integrate_func, "adaptive"))
+    odeopts.InitialStep = odeopts.direction* ...
+      starting_stepsize (order, fun, trange(1), init, odeopts.AbsTol,
+                         odeopts.RelTol, odeopts.normcontrol);
+    warning ("Octave:invalid-input-arg",
+             ["ode23: option \"InitialStep\" not set,", ...
+              " estimated value %f will be used\n"],
+             odeopts.InitialStep);
+  elseif (isempty (odeopts.InitialStep))
+    odeopts.InitialStep = TimeStepSize;
+  endif
+
+  if (isempty (odeopts.MaxStep) && ! odeopts.stepsizefixed)
+    odeopts.MaxStep = abs (trange(end) - trange(1)) / 10;
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"MaxStep\" not set, new value %f will be used\n",
+             odeopts.MaxStep);
+  endif
+
+  odeopts.haveeventfunction = ! isempty (odeopts.Events);
+
+  ## The options 'Jacobian', 'JPattern' and 'Vectorized' will be ignored
+  ## by this solver because this solver uses an explicit Runge-Kutta method
+  ## and therefore no Jacobian calculation is necessary
+  if (! isempty (odeopts.Jacobian))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"Jacobian\" is ignored by this solver\n");
+  endif
+
+  if (! isempty (odeopts.JPattern))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"JPattern\" is ignored by this solver\n");
+  endif
+
+  if (! isempty (odeopts.Vectorized))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"Vectorized\" is ignored by this solver\n");
+  endif
+
+  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);
+  endif
+
+  massdependence = ! strcmp (odeopts.MStateDependence, "none");
+
+  ## Other options that are not used by this solver.
+  if (! isempty (odeopts.MvPattern))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"MvPattern\" is ignored by this solver\n");
+  endif
+
+  if (! isempty (odeopts.MassSingular))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"MassSingular\" is ignored by this solver\n");
+  endif
+
+  if (! isempty (odeopts.InitialSlope))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"InitialSlope\" is ignored by this solver\n");
+  endif
+
+  if (! isempty (odeopts.MaxOrder))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"MaxOrder\" is ignored by this solver\n");
+  endif
+
+  if (! isempty (odeopts.BDF))
+    warning ("Octave:invalid-input-arg",
+             "ode23: option \"BDF\" is ignored by this solver\n");
+  endif
+
+  ## Starting the initialisation of the core solver ode23
+  
+  if (havemasshandle)   # Handle only the dynamic mass matrix,
+    if (massdependence) # constant mass matrices have already
+      mass = @(t,x) odeopts.Mass (t, x, odeopts.funarguments{:});
+      fun = @(t,x) mass (t, x, odeopts.funarguments{:}) ...
+        \ fun (t, x, odeopts.funarguments{:});
+    else                 # if (massdependence == false)
+      mass = @(t) odeopts.Mass (t, odeopts.funarguments{:});
+      fun = @(t,x) mass (t, odeopts.funarguments{:}) ...
+        \ fun (t, x, odeopts.funarguments{:});
+    endif
+  endif
+
+  switch (integrate_func)
+    case "adaptive"
+      solution = integrate_adaptive (@runge_kutta_23, ...
+                                     order, fun, trange, init, odeopts);
+    case "n_steps"
+      solution = integrate_n_steps (@runge_kutta_23, ...
+                                    fun, trange(1), init, ...
+                                    TimeStepSize, TimeStepNumber, odeopts);
+    case "const"
+      solution = integrate_const (@runge_kutta_23, ...
+                                  fun, trange, init, ...
+                                  TimeStepSize, odeopts);
+  endswitch
+
+  ## Postprocessing, do whatever when terminating integration algorithm
+  if (odeopts.haveoutputfunction)  # Cleanup plotter
+    feval (odeopts.OutputFcn, solution.t(end), ...
+           solution.x(end,:)', "done", odeopts.funarguments{:});
+  endif
+  if (odeopts.haveeventfunction)   # Cleanup event function handling
+    ode_event_handler (odeopts.Events, solution.t(end), ...
+                         solution.x(end,:)', "done", odeopts.funarguments{:});
+  endif
+
+  ## Print additional information if option Stats is set
+  if (strcmp (odeopts.Stats, "on"))
+    havestats = true;
+    nsteps    = solution.cntloop-2;              # cntloop from 2..end
+    nfailed   = (solution.cntcycles-1)-nsteps+1; # cntcycl from 1..end
+    nfevals   = 4 * (solution.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)
+      printf ("Number of successful steps: %d\n", nsteps);
+      printf ("Number of failed attempts:  %d\n", nfailed);
+      printf ("Number of function calls:   %d\n", nfevals);
+    endif
+  else
+    havestats = false;
+  endif
+
+  if (nargout == 2)
+    varargout{1} = solution.t;      # Time stamps are first output argument
+    varargout{2} = solution.x;      # Results are second output argument
+  elseif (nargout == 1)
+    varargout{1}.x = solution.t;    # Time stamps are saved in field x
+    varargout{1}.y = solution.x;    # Results are saved in field y
+    varargout{1}.solver = solver;   # Solver name is saved in field solver
+    if (odeopts.haveeventfunction)
+      varargout{1}.ie = solution.event{2};  # Index info which event occurred
+      varargout{1}.xe = solution.event{3};  # Time info when an event occurred
+      varargout{1}.ye = solution.event{4};  # Results when an event occurred
+    endif
+    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;
+    endif
+  elseif (nargout == 5)
+    varargout = cell (1,5);
+    varargout{1} = solution.t;
+    varargout{2} = solution.x;
+    if (odeopts.haveeventfunction)
+      varargout{3} = solution.event{3};  # Time info when an event occurred
+      varargout{4} = solution.event{4};  # Results when an event occurred
+      varargout{5} = solution.event{2};  # Index info which event occurred
+    endif
+  endif
+
+endfunction
+
+
+## We are using the "Van der Pol" implementation for all tests that are done
+## for this function.
+## For further tests we also define a reference solution (computed at high
+## accuracy)
+%!function ydot = fpol (t, y)  # The Van der Pol
+%! ydot = [y(2); (1 - y(1)^2) * y(2) - y(1)];
+%!endfunction
+%!function ref = fref ()         # The computed reference sol
+%! ref = [0.32331666704577, -1.83297456798624];
+%!endfunction
+%!function jac = fjac (t, y, varargin) # its Jacobian
+%! jac = [0, 1; -1 - 2 * y(1) * y(2), 1 - y(1)^2];
+%!endfunction
+%!function jac = fjcc (t, y, varargin) # sparse type
+%! jac = sparse ([0, 1; -1 - 2 * y(1) * y(2), 1 - y(1)^2])
+%!endfunction
+%!function [val, trm, dir] = feve (t, y, varargin)
+%! val = fpol (t, y, varargin);    # We use the derivatives
+%! trm = zeros (2,1);              # that's why component 2
+%! dir = ones (2,1);               # seems to not be exact
+%!endfunction
+%!function [val, trm, dir] = fevn (t, y, varargin)
+%! val = fpol (t, y, varargin);    # We use the derivatives
+%! trm = ones (2,1);               # that's why component 2
+%! dir = ones (2,1);               # seems to not be exact
+%!endfunction
+%!function mas = fmas (t, y, varargin)
+%! mas = [1, 0; 0, 1];             # Dummy mass matrix for tests
+%!endfunction
+%!function mas = fmsa (t, y, varargin)
+%! mas = sparse ([1, 0; 0, 1]);    # A sparse dummy matrix
+%!endfunction
+%!function out = fout (t, y, flag, varargin)
+%! if (regexp (char (flag), "init") == 1)
+%!   if (any (size (t) != [2, 1])) error ("\"fout\" step \"init\""); endif
+%! elseif (isempty (flag))
+%!   if (any (size (t) != [1, 1])) error ("\"fout\" step \"calc\""); endif
+%!   out = false;
+%! elseif (regexp (char (flag), "done") == 1)
+%!   if (any (size (t) != [1, 1])) error ("\"fout\" step \"done\""); endif
+%! else
+%!   error ("\"fout\" invalid flag");
+%! endif
+%!endfunction
+%!
+%! ## Turn off output of warning messages for all tests,
+%! ## turn them on again after the last test is called
+%!test
+%! warning ("off", "Octave:invalid-input-arg", "local");
+%!error ## ouput argument
+%!  B = ode23 (1, [0 25], [3 15 1]);
+%!error ## input argument number one
+%!  [t, y] = ode23 (1, [0 25], [3 15 1]);
+%!error ## input argument number two
+%!  [t, y] = ode23 (@fpol, 1, [3 15 1]);
+%!test ## two output arguments
+%!  [t, y] = ode23 (@fpol, [0 2], [2 0]);
+%!  assert ([t(end), y(end,:)], [2, fref], 1e-3);
+%!test ## anonymous function instead of real function
+%!  fvdb = @(t,y) [y(2); (1 - y(1)^2) * y(2) - y(1)];
+%!  [t, y] = ode23 (fvdb, [0 2], [2 0]);
+%!  assert ([t(end), y(end,:)], [2, fref], 1e-3);
+%!test ## extra input arguments passed through
+%!  [t, y] = ode23 (@fpol, [0 2], [2 0], 12, 13, "KL");
+%!  assert ([t(end), y(end,:)], [2, fref], 1e-3);
+%!test ## empty OdePkg structure *but* extra input arguments
+%!  opt = odeset;
+%!  [t, y] = ode23 (@fpol, [0 2], [2 0], opt, 12, 13, "KL");
+%!  assert ([t(end), y(end,:)], [2, fref], 1e-2);
+%!error ## strange OdePkg structure
+%!  opt = struct ("foo", 1);
+%!  [t, y] = ode23 (@fpol, [0 2], [2 0], opt);
+%!test ## Solve vdp in fixed step sizes
+%!  opt = odeset("TimeStepSize",0.1);
+%!  [t, y] = ode23 (@fpol, [0,2], [2 0], opt);
+%!  assert (t(:), [0:0.1:2]', 1e-3);
+%!test ## Solve another anonymous function below zero
+%!  ref = [0, 14.77810590694212];
+%!  [t, y] = ode23 (@(t,y) y, [-2 0], 2);
+%!  assert ([t(end), y(end,:)], ref, 1e-2);
+%!test ## InitialStep option
+%!  opt = odeset ("InitialStep", 1e-8);
+%!  [t, y] = ode23 (@fpol, [0 0.2], [2 0], opt);
+%!  assert ([t(2)-t(1)], [1e-8], 1e-9);
+%!test ## MaxStep option
+%!  opt = odeset ("MaxStep", 1e-3);
+%!  sol = ode23 (@fpol, [0 0.2], [2 0], opt);
+%!  assert ([sol.x(5)-sol.x(4)], [1e-3], 1e-4);
+%!test ## Solve in backward direction starting at t=0
+%!  ref = [-1.205364552835178, 0.951542399860817];
+%!  sol = ode23 (@fpol, [0 -2], [2 0]);
+%!  assert ([sol.x(end), sol.y(end,:)], [-2, ref], 5e-3);
+%!test ## Solve in backward direction starting at t=2
+%!  ref = [-1.205364552835178, 0.951542399860817];
+%!  sol = ode23 (@fpol, [2 0 -2], fref);
+%!  assert ([sol.x(end), sol.y(end,:)], [-2, ref], 2e-2);
+%!test ## Solve another anonymous function in backward direction
+%!  ref = [-1, 0.367879437558975];
+%!  sol = ode23 (@(t,y) y, [0 -1], 1);
+%!  assert ([sol.x(end), sol.y(end,:)], ref, 1e-2);
+%!test ## Solve another anonymous function below zero
+%!  ref = [0, 14.77810590694212];
+%!  sol = ode23 (@(t,y) y, [-2 0], 2);
+%!  assert ([sol.x(end), sol.y(end,:)], ref, 1e-2);
+%!test ## Solve in backward direction starting at t=0 with MaxStep option
+%!  ref = [-1.205364552835178, 0.951542399860817];
+%!  opt = odeset ("MaxStep", 1e-3);
+%!  sol = ode23 (@fpol, [0 -2], [2 0], opt);
+%!  assert ([abs(sol.x(8)-sol.x(7))], [1e-3], 1e-3);
+%!  assert ([sol.x(end), sol.y(end,:)], [-2, ref], 1e-3);
+%!test ## AbsTol option
+%!  opt = odeset ("AbsTol", 1e-5);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## AbsTol and RelTol option
+%!  opt = odeset ("AbsTol", 1e-8, "RelTol", 1e-8);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## RelTol and NormControl option -- higher accuracy
+%!  opt = odeset ("RelTol", 1e-8, "NormControl", "on");
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-4);
+%!test ## Keeps initial values while integrating
+%!  opt = odeset ("NonNegative", 2);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, 2, 0], 1e-1);
+%!test ## Details of OutputSel and Refine can't be tested
+%!  opt = odeset ("OutputFcn", @fout, "OutputSel", 1, "Refine", 5);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!test ## Stats must add further elements in sol
+%!  opt = odeset ("Stats", "on");
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert (isfield (sol, "stats"));
+%!  assert (isfield (sol.stats, "nsteps"));
+%!test ## Events option add further elements in sol
+%!  opt = odeset ("Events", @feve);
+%!  sol = ode23 (@fpol, [0 10], [2 0], opt);
+%!  assert (isfield (sol, "ie"));
+%!  assert (sol.ie(1), 2);
+%!  assert (isfield (sol, "xe"));
+%!  assert (isfield (sol, "ye"));
+%!test ## Events option, now stop integration
+%!  opt = odeset ("Events", @fevn, "NormControl", "on");
+%!  sol = ode23 (@fpol, [0 10], [2 0], opt);
+%!  assert ([sol.ie, sol.xe, sol.ye], ...
+%!    [2.0, 2.496110, -0.830550, -2.677589], .5e-1);
+%!test ## Events option, five output arguments
+%!  opt = odeset ("Events", @fevn, "NormControl", "on");
+%!  [t, y, vxe, ye, vie] = ode23 (@fpol, [0 10], [2 0], opt);
+%!  assert ([vie, vxe, ye], ...
+%!    [2.0, 2.496110, -0.830550, -2.677589], 1e-1);
+%!test ## Jacobian option
+%!  opt = odeset ("Jacobian", @fjac);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## Jacobian option and sparse return value
+%!  opt = odeset ("Jacobian", @fjcc);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!
+%! ## test for JPattern option is missing
+%! ## test for Vectorized option is missing
+%!
+%!test ## Mass option as function
+%!  opt = odeset ("Mass", @fmas);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## Mass option as matrix
+%!  opt = odeset ("Mass", eye (2,2));
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## Mass option as sparse matrix
+%!  opt = odeset ("Mass", sparse (eye (2,2)));
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## Mass option as function and sparse matrix
+%!  opt = odeset ("Mass", @fmsa);
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## Mass option as function and MStateDependence
+%!  opt = odeset ("Mass", @fmas, "MStateDependence", "strong");
+%!  sol = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([sol.x(end), sol.y(end,:)], [2, fref], 1e-3);
+%!test ## Set BDF option to something else than default
+%!  opt = odeset ("BDF", "on");
+%!  [t, y] = ode23 (@fpol, [0 2], [2 0], opt);
+%!  assert ([t(end), y(end,:)], [2, fref], 1e-3);
+%!
+%! ## test for MvPattern option is missing
+%! ## test for InitialSlope option is missing
+%! ## test for MaxOrder option is missing
+%!
+%!  warning ("on", "Octave:InvalidArgument");
+
+## Local Variables: ***
+## mode: octave ***
+## End: ***
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/ode/private/runge_kutta_23.m	Tue Dec 15 13:59:17 2015 +0100
@@ -0,0 +1,109 @@
+## Copyright (C) 2013-2015, Jacopo Corno <jacopo.corno@gmail.com>
+## Copyright (C) 2013-2015, Roberto Porcu' <roberto.porcu@polimi.it>
+##
+## This file is part of Octave.
+##
+## Octave 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 3 of the License, or (at
+## your option) any later version.
+##
+## Octave 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 Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {[@var{t_next}, @var{x_next}] =} runge_kutta_23 (@var{@fun}, @var{t}, @var{x}, @var{dt})
+## @deftypefnx {Function File} {[@var{t_next}, @var{x_next}] =} runge_kutta_23 (@var{@fun}, @var{t}, @var{x}, @var{dt}, @var{options})
+## @deftypefnx {Function File} {[@var{t_next}, @var{x_next}] =} runge_kutta_23 (@var{@fun}, @var{t}, @var{x}, @var{dt}, @var{options}, @var{k_vals_in})
+## @deftypefnx {Function File} {[@var{t_next}, @var{x_next}] =} runge_kutta_23 (@var{@fun}, @var{t}, @var{x}, @var{dt}, @var{options}, @var{k_vals_in}, @var{t_next})
+## @deftypefnx {Function File} {[@var{t_next}, @var{x_next}, @var{x_est}] =} runge_kutta_23 (@dots{})
+## @deftypefnx {Function File} {[@var{t_next}, @var{x_next}, @var{x_est}, @var{k_vals_out}] =} runge_kutta_23 (@dots{})
+##
+## This function can be used to integrate a system of ODEs with a given initial
+## condition @var{x} from @var{t} to @var{t+dt}, with the Bogacki-Shampine
+## method of third order. For the definition of this method see
+## @url{http://en.wikipedia.org/wiki/List_of_Runge%E2%80%93Kutta_methods}.
+##
+## @var{@fun} is a function handle that defines the ODE: @code{y' = f(tau,y)}.
+## The function must accept two inputs where the first is time @var{tau} and the
+## second is a column vector of unknowns @var{y}.
+##
+## @var{t} is the first extreme of integration interval.
+##
+## @var{x} is the initial condition of the system..
+##
+## @var{dt} is the timestep, that is the length of the integration interval.
+##
+## The optional fourth argument @var{options} specifies options for the ODE
+## solver. It is a structure generated by @code{odeset}. In particular it
+## contains the field @var{funarguments} with the optional arguments to be used
+## in the evaluation of @var{fun}.
+##
+## The optional fifth argument @var{k_vals_in} contains the Runge-Kutta
+## evaluations of the previous step to use in a FSAL scheme.
+##
+## The optional sixth argument @var{t_next} (@code{t_next = t + dt}) specifies
+## the end of the integration interval. The output @var{x_next} s the higher
+## order computed solution at time @var{t_next} (local extrapolation is
+## performed).
+##
+## Optionally the functions can also return @var{x_est}, a lower order solution
+## for the estimation of the error, and @var{k_vals_out}, a matrix containing
+## the Runge-Kutta evaluations to use in a FSAL scheme or for dense output.
+##
+## @seealso{runge_kutta_45_dorpri}
+## @end deftypefn
+
+function [t_next, x_next, x_est, k] = runge_kutta_23 (f, t, x, dt, options = [],
+                                                      k_vals = [],
+                                                      t_next = t + dt)
+
+  if (! isempty (options))  # extra arguments for function evaluator
+    args = options.funarguments;
+  else
+    args = {};
+  endif
+  
+  if (! isempty (k_vals))    # k values from previous step are passed
+    k(:,1) = k_vals(:,end);  # FSAL property
+  else
+    k(:,1) = feval (f, t, x, args{:});
+  endif
+
+  k(:,2) = feval (f, t + (1/2)*dt, x + dt*(1/2)*k(:,1), args{:});
+  k(:,3) = feval (f, t + (3/4)*dt, x + dt*(3/4)*k(:,2), args{:});
+
+  ## compute new time and new values for the unkwnowns
+  ## t_next = t + dt;
+  x_next = x + dt.*((2/9)*k(:,1) + (1/3)*k(:,2) + (4/9)*k(:,3)); # 3rd order approximation
+
+  ## 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_next, x_next, args{:});
+    x_est = x + dt.*((7/24)*k(:,1) + (1/4)*k(:,2) ...
+                     + (1/3)*k(:,3) + (1/8)*k(:,4));
+  endif
+
+endfunction
+
+
+%! # We are using the "Van der Pol" implementation.
+%!function [ydot] = fpol (t, y) ## The Van der Pol
+%!  ydot = [y(2); (1 - y(1)^2) * y(2) - y(1)];
+%!endfunction
+%!
+%!shared opts
+%!  opts = odeset;
+%!  opts.funarguments = {};
+%!
+%!test
+%!  [t, y] = runge_kutta_23 (@fpol, 0, [2;0], 0.05, opts);
+%!test
+%!  [t, y, x] = runge_kutta_23 (@fpol, 0, [2;0], 0.1, opts);