Mercurial > octave
changeset 22593:dba5074bdc79 stable
simplify options management in ode solvers
* scripts/ode/ode23.m : use new input parsing and validation
* scripts/ode/ode45.m : use new input parsing and validation
* scripts/ode/odeget.m : complete overhaul
* scripts/ode/odeset.m : complete overhaul
* scripts/ode/private/integrate_adaptive.m : use new style for option management
author | Carlo de Falco <carlo.defalco@polimi.it> |
---|---|
date | Thu, 06 Oct 2016 06:54:32 +0200 |
parents | ee0df00e12d6 |
children | b8d525710075 |
files | scripts/ode/ode23.m scripts/ode/ode45.m scripts/ode/odeget.m scripts/ode/odeset.m scripts/ode/private/integrate_adaptive.m |
diffstat | 5 files changed, 187 insertions(+), 564 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/ode/ode23.m Wed Oct 05 23:09:31 2016 -0400 +++ b/scripts/ode/ode23.m Thu Oct 06 06:54:32 2016 +0200 @@ -1,3 +1,4 @@ +## Copyright (C) 2016, Francesco Faccio <francesco.faccio@mail.polimi.it> ## Copyright (C) 2014-2016 Jacopo Corno <jacopo.corno@gmail.com> ## Copyright (C) 2013-2016 Roberto Porcu' <roberto.porcu@polimi.it> ## Copyright (C) 2006-2016 Thomas Treichl <treichl@users.sourceforge.net> @@ -45,10 +46,7 @@ ## 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. +## default value of @math{1e-6}. ## ## @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 @@ -106,25 +104,25 @@ print_usage (); endif - order = 3; + order = 3; solver = "ode23"; if (nargin >= 4) if (! isstruct (varargin{1})) ## varargin{1:len} are parameters for fun odeopts = odeset (); - odeopts.funarguments = varargin; + 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)}}; + odeopts = varargin{1}; + funarguments = {varargin{2:length(varargin)}}; else # if (isstruct (varargin{1})) - odeopts = ode_struct_value_check ("ode23", varargin{1}, "ode23"); - odeopts.funarguments = {}; + odeopts = varargin{1}; + funarguments = {}; endif else # nargin == 3 odeopts = odeset (); - odeopts.funarguments = {}; + funarguments = {}; endif if (! isnumeric (trange) || ! isvector (trange)) @@ -132,17 +130,14 @@ "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))) + if (length (trange) < 2) 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)); + direction = sign (trange(2) - trange(1)); endif trange = trange(:); @@ -164,54 +159,33 @@ "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 + + + persistent defaults = []; + persistent classes = []; + persistent attributes = []; + + + [defaults, classes, attributes] = odedefaults (numel (init), trange(1), + trange(end)); - if (isempty (odeopts.RelTol) && ! odeopts.stepsizefixed) - odeopts.RelTol = 1e-3; - elseif (! isempty (odeopts.RelTol) && odeopts.stepsizefixed) - warning ("Octave:invalid-input-arg", - ["ode23: option \"RelTol\" is ignored", ... - " when fixed time stamps are given\n"]); - endif + defaults = rmfield (defaults, {"Jacobian", "JPattern", "Vectorized", ... + "MvPattern", "MassSingular", ... + "InitialSlope", "MaxOrder", "BDF"}); + classes = rmfield (classes, {"Jacobian", "JPattern", "Vectorized", ... + "MvPattern", "MassSingular", ... + "InitialSlope", "MaxOrder", "BDF"}); + attributes = rmfield (attributes, {"Jacobian", "JPattern", "Vectorized", ... + "MvPattern", "MassSingular", ... + "InitialSlope", "MaxOrder", "BDF"}); - if (isempty (odeopts.AbsTol) && ! odeopts.stepsizefixed) - odeopts.AbsTol = 1e-6; - 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 = odemergeopts (odeopts, defaults, classes, attributes, 'ode23'); - odeopts.normcontrol = strcmp (odeopts.NormControl, "on"); + odeopts.funarguments = funarguments; + odeopts.direction = direction; if (! isempty (odeopts.NonNegative)) if (isempty (odeopts.Mass)) @@ -233,47 +207,15 @@ 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")) + if (isempty (odeopts.InitialStep)) odeopts.InitialStep = odeopts.direction * ... starting_stepsize (order, fun, trange(1), init, odeopts.AbsTol, odeopts.RelTol, - odeopts.normcontrol); - elseif (isempty (odeopts.InitialStep)) - odeopts.InitialStep = TimeStepSize; - endif - - if (isempty (odeopts.MaxStep) && ! odeopts.stepsizefixed) - odeopts.MaxStep = abs (trange(end) - trange(1)) / 10; + strcmp (odeopts.NormControl, + "on")); 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; @@ -284,75 +226,38 @@ 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 initialization of the core solver ode23 if (havemasshandle) # Handle only the dynamic mass matrix, - if (massdependence) # constant mass matrices have already + if (! strcmp (odeopts.MStateDependence, "none")) # 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) + else # if ((! strcmp (odeopts.MStateDependence, "none")) == 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 + + solution = integrate_adaptive (@runge_kutta_23, ... + order, fun, trange, init, odeopts); + ## 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 + if (! isempty (odeopts.Events)) # Cleanup event function handling ode_event_handler (odeopts.Events, solution.t(end), ... - solution.x(end,:)', "done", odeopts.funarguments{:}); + 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; # cntloop from 2..end nfailed = solution.cntcycles - nsteps; # cntcycl from 1..end nfevals = 3 * solution.cntcycles + 1; # number of ode evaluations @@ -365,8 +270,6 @@ printf ("Number of failed attempts: %d\n", nfailed); printf ("Number of function calls: %d\n", nfevals); endif - else - havestats = false; endif if (nargout == 2) @@ -376,12 +279,12 @@ 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) + if (! isempty (odeopts.Events)) 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) + if (strcmp (odeopts.Stats, "on")) varargout{1}.stats = struct (); varargout{1}.stats.nsteps = nsteps; varargout{1}.stats.nfailed = nfailed; @@ -394,7 +297,7 @@ varargout = cell (1,5); varargout{1} = solution.t; varargout{2} = solution.x; - if (odeopts.haveeventfunction) + if (! isempty (odeopts.Events)) 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 @@ -489,10 +392,6 @@ %! opt = odeset; %! [t, y] = ode23 (@fpol, [0 2], [2 0], opt, 12, 13, "KL"); %! assert ([t(end), y(end,:)], [2, fref], 1e-2); -%!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); @@ -612,7 +511,4 @@ %! ode23 (@fpol, [0 25], [3 15 1; 3 15 1]); %!error <FUN must be a valid function handle> %! ode23 (1, [0 25], [3 15 1]); -%!error # strange ODEOPT structure -%! opt = struct ("foo", 1); -%! [t, y] = ode23 (@fpol, [0 2], [2 0], opt);
--- a/scripts/ode/ode45.m Wed Oct 05 23:09:31 2016 -0400 +++ b/scripts/ode/ode45.m Thu Oct 06 06:54:32 2016 +0200 @@ -1,3 +1,4 @@ +## Copyright (C) 2016, Francesco Faccio <francesco.faccio@mail.polimi.it> ## Copyright (C) 2014-2016 Jacopo Corno <jacopo.corno@gmail.com> ## Copyright (C) 2013-2016 Roberto Porcu' <roberto.porcu@polimi.it> ## Copyright (C) 2006-2012 Thomas Treichl <treichl@users.sourceforge.net> @@ -43,10 +44,7 @@ ## By default, @code{ode45} 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. +## default value of @math{1e-6}. ## ## @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 @@ -89,25 +87,25 @@ print_usage (); endif - order = 5; # runge_kutta_45_dorpri uses local extrapolation + order = 5; # runge_kutta_45_dorpri uses local extrapolation solver = "ode45"; if (nargin >= 4) if (! isstruct (varargin{1})) ## varargin{1:len} are parameters for fun odeopts = odeset (); - odeopts.funarguments = varargin; + funarguments = varargin; elseif (length (varargin) > 1) - ## varargin{1} is an ODE options structure vopt - odeopts = ode_struct_value_check ("ode45", varargin{1}, "ode45"); - odeopts.funarguments = {varargin{2:length(varargin)}}; + ## varargin{1} is an ODE options structure opt + odeopts = varargin{1}; + funarguments = {varargin{2:length(varargin)}}; else # if (isstruct (varargin{1})) - odeopts = ode_struct_value_check ("ode45", varargin{1}, "ode45"); - odeopts.funarguments = {}; + odeopts = varargin{1}; + funarguments = {}; endif else # nargin == 3 odeopts = odeset (); - odeopts.funarguments = {}; + funarguments = {}; endif if (! isnumeric (trange) || ! isvector (trange)) @@ -115,17 +113,15 @@ "ode45: 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))) + + if (length (trange) < 2) error ("Octave:invalid-input-arg", "ode45: TRANGE must contain at least 2 elements"); elseif (trange(1) == trange(2)) error ("Octave:invalid-input-arg", "ode45: invalid time span, TRANGE(1) == TRANGE(2)"); else - odeopts.direction = sign (trange(2) - trange(1)); + direction = sign (trange(2) - trange(1)); endif trange = trange(:); @@ -147,54 +143,33 @@ "ode45: 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", - ["ode45: 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", - ["ode45: option 'TimeStepSize' has the wrong sign, ", - "but will be corrected automatically\n"]); - TimeStepSize = -TimeStepSize; - endif - else - warning ("Octave:invalid-input-arg", - "ode45: assuming an adaptive integrate function\n"); - integrate_func = "adaptive"; - endif + + persistent defaults = []; + persistent classes = []; + persistent attributes = []; + + + [defaults, classes, attributes] = odedefaults (numel (init), trange(1), + trange(end)); - if (isempty (odeopts.RelTol) && ! odeopts.stepsizefixed) - odeopts.RelTol = 1e-3; - elseif (! isempty (odeopts.RelTol) && odeopts.stepsizefixed) - warning ("Octave:invalid-input-arg", - ["ode45: option 'RelTol' is ignored", ... - " when fixed time stamps are given\n"]); - endif + defaults = odeset (defaults, 'Refine', 4); + defaults = rmfield (defaults, {"Jacobian", "JPattern", "Vectorized", ... + "MvPattern", "MassSingular", ... + "InitialSlope", "MaxOrder", "BDF"}); + classes = rmfield (classes, {"Jacobian", "JPattern", "Vectorized", ... + "MvPattern", "MassSingular", ... + "InitialSlope", "MaxOrder", "BDF"}); + attributes = rmfield (attributes, {"Jacobian", "JPattern", "Vectorized", ... + "MvPattern", "MassSingular", ... + "InitialSlope", "MaxOrder", "BDF"}); - if (isempty (odeopts.AbsTol) && ! odeopts.stepsizefixed) - odeopts.AbsTol = 1e-6; - elseif (! isempty (odeopts.AbsTol) && odeopts.stepsizefixed) - warning ("Octave:invalid-input-arg", - ["ode45: option 'AbsTol' is ignored", ... - " when fixed time stamps are given\n"]); - else - odeopts.AbsTol = odeopts.AbsTol(:); # Create column vector - endif + odeopts = odemergeopts (odeopts, defaults, classes, attributes, 'ode45'); - odeopts.normcontrol = strcmp (odeopts.NormControl, "on"); + odeopts.funarguments = funarguments; + odeopts.direction = direction; if (! isempty (odeopts.NonNegative)) if (isempty (odeopts.Mass)) @@ -216,48 +191,15 @@ 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); - elseif (isempty (odeopts.InitialStep)) - odeopts.InitialStep = TimeStepSize; - endif + if (isempty (odeopts.InitialStep)) + odeopts.InitialStep = odeopts.direction * ... + starting_stepsize (order, fun, trange(1), + init, odeopts.AbsTol, + odeopts.RelTol, + strcmp (odeopts.NormControl, + "on")); + endif - if (isempty (odeopts.MaxStep) && ! odeopts.stepsizefixed) - odeopts.MaxStep = abs (trange(end) - trange(1)) / 10; - 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", - "ode45: option 'Jacobian' is ignored by this solver\n"); - endif - - if (! isempty (odeopts.JPattern)) - warning ("Octave:invalid-input-arg", - "ode45: option 'JPattern' is ignored by this solver\n"); - endif - - if (! isempty (odeopts.Vectorized)) - warning ("Octave:invalid-input-arg", - "ode45: option 'Vectorized' is ignored by this solver\n"); - endif if (! isempty (odeopts.Mass) && isnumeric (odeopts.Mass)) havemasshandle = false; @@ -268,76 +210,38 @@ 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", - "ode45: option 'MvPattern' is ignored by this solver\n"); - endif - - if (! isempty (odeopts.MassSingular)) - warning ("Octave:invalid-input-arg", - "ode45: option 'MassSingular' is ignored by this solver\n"); - endif - - if (! isempty (odeopts.InitialSlope)) - warning ("Octave:invalid-input-arg", - "ode45: option 'InitialSlope' is ignored by this solver\n"); - endif - - if (! isempty (odeopts.MaxOrder)) - warning ("Octave:invalid-input-arg", - "ode45: option 'MaxOrder' is ignored by this solver\n"); - endif - - if (! isempty (odeopts.BDF)) - warning ("Octave:invalid-input-arg", - "ode45: option 'BDF' is ignored by this solver\n"); - endif ## Starting the initialization of the core solver ode45 if (havemasshandle) # Handle only the dynamic mass matrix, - if (massdependence) # constant mass matrices have already + if (! strcmp (odeopts.MStateDependence, "none")) # 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) + else # if ((! strcmp (odeopts.MStateDependence, "none")) == 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_45_dorpri, - order, fun, trange, init, odeopts); - case "n_steps" - solution = integrate_n_steps (@runge_kutta_45_dorpri, - fun, trange(1), init, - TimeStepSize, TimeStepNumber, odeopts); - case "const" - solution = integrate_const (@runge_kutta_45_dorpri, - fun, trange, init, - TimeStepSize, odeopts); - endswitch + + solution = integrate_adaptive (@runge_kutta_45_dorpri, + order, fun, trange, init, odeopts); + ## Postprocessing, do whatever when terminating integration algorithm if (odeopts.haveoutputfunction) # Cleanup plotter - feval (odeopts.OutputFcn, solution.t(end), + 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{:}); + if (! isempty (odeopts.Events)) # 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; # cntloop from 2..end nfailed = solution.cntcycles - nsteps; # cntcycl from 1..end nfevals = 6 * solution.cntcycles + 1; # number of ode evaluations @@ -350,8 +254,6 @@ printf ("Number of failed attempts: %d\n", nfailed); printf ("Number of function calls: %d\n", nfevals); endif - else - havestats = false; endif if (nargout == 2) @@ -361,12 +263,12 @@ 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) + if (! isempty (odeopts.Events)) 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) + if (strcmp (odeopts.Stats, "on")) varargout{1}.stats = struct (); varargout{1}.stats.nsteps = nsteps; varargout{1}.stats.nfailed = nfailed; @@ -379,7 +281,7 @@ varargout = cell (1,5); varargout{1} = solution.t; varargout{2} = solution.x; - if (odeopts.haveeventfunction) + if (! isempty (odeopts.Events)) 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 @@ -480,10 +382,6 @@ %! opt = odeset; %! [t, y] = ode45 (@fpol, [0 2], [2 0], opt, 12, 13, "KL"); %! assert ([t(end), y(end,:)], [2, fref], 1e-2); -%!test # Solve vdp in fixed step sizes -%! opt = odeset("TimeStepSize", 0.1); -%! [t, y] = ode45 (@fpol, [0,2], [2 0], opt); -%! assert (t(:), [0:0.1:2]', 1e-2); %!test # Solve another anonymous function below zero %! vref = [0, 14.77810590694212]; %! [t, y] = ode45 (@(t,y) y, [-2 0], 2); @@ -609,7 +507,4 @@ %! ode45 (@fpol, [0 25], [3 15 1; 3 15 1]); %!error <FUN must be a valid function handle> %! ode45 (1, [0 25], [3 15 1]); -%!error # strange ODEOPT structure -%! opt = struct ("foo", 1); -%! [t, y] = ode45 (@fpol, [0 2], [2 0], opt);
--- a/scripts/ode/odeget.m Wed Oct 05 23:09:31 2016 -0400 +++ b/scripts/ode/odeget.m Thu Oct 06 06:54:32 2016 +0200 @@ -1,3 +1,5 @@ +## Copyright (C) 2016, Carlo de Falco +## Copyright (C) 2016, Francesco Faccio <francesco.faccio@mail.polimi.it> ## Copyright (C) 2013-2016 Roberto Porcu' <roberto.porcu@polimi.it> ## Copyright (C) 2006-2012 Thomas Treichl <treichl@users.sourceforge.net> ## @@ -39,79 +41,19 @@ function val = odeget (ode_opt, field, default = [], opt = "") - ## Shortcut for quickly extracting option - if (strncmp (opt, "fast", 4)) - try - val = ode_opt.(field); - if (strcmp (opt, "fast_not_empty") && isempty (val)) - val = default; - endif - catch - val = default; - end_try_catch - return; - endif - - if (nargin < 1 || nargin > 4) - print_usage (); - endif - - ## Shortcut for empty option structures - if (isempty (ode_opt)) - if (nargin < 3) - val = []; - else + validateattributes (ode_opt, {'struct'}, {'nonempty'}); + validateattributes (field, {'char'}, {'nonempty'}); + + if (! isfield (ode_opt, field)) + error ('Octave:odeget:InvalidPropName', + 'odeget: Unrecognized property name "%s".', field); + else + val = ode_opt.(field); + if (isempty (val)) val = default; endif - return; endif - - if (! isstruct (ode_opt)) - error ("odeget: ODE_OPT must be a valid ODE_STRUCT"); - elseif (! ischar (field)) - error ("odeget: FIELD must be a string"); - endif - - ## Check if the given struct is a valid ODEOPT struct - ode_struct_value_check ("odeget", ode_opt); - - ## Define all the possible ODEOPT fields - persistent options = known_option_names (); - - exactmatch = true; - match = find (strcmpi (field, options)); - if (isempty (match)) - match = find (strncmpi (field, options, length (field))); - exactmatch = false; - endif - - if (isempty (match)) - ## Possibly a custom user-defined option - try - val = ode_opt.(field); - catch - warning ("Octave:invalid-input-arg", - "odeget: no field '%s' exists in ODE_OPT\n", field); - val = default; - end_try_catch - elseif (numel (match) == 1) - if (! exactmatch) - warning ("odeget:NoExactMatching", - "odeget: no exact match for '%s'. Assuming '%s'.\n", - field, options{match}); - endif - val = []; - try - val = ode_opt.(options{match}); - end_try_catch - if (isempty (val)) - val = default; - endif - else - error ("odeget: no exact match for '%s'. Possible fields found: %s.", - field, strjoin (options(match), ", ")); - endif - + endfunction @@ -129,16 +71,14 @@ %!assert (odeget (odeset (), "Stats"), []) %!assert (odeget (odeset (), "Stats", "on"), "on") %!assert (odeget (odeset (), "Mass"), []) -%!assert (odeget (odeset (), "AbsTol", 1e-6, "fast"), []) -%!assert (odeget (odeset (), "AbsTol", 1e-6, "fast_not_empty"), 1e-6) %!assert (odeget (odeset (), "AbsTol", 1e-9), 1e-9) +%!assert (odeget (odeset ("AbsTol", 1e-9), "AbsTol", []), 1e-9) +%!assert (odeget (odeset ('foo', 42), 'foo'), 42) %!error odeget () %!error odeget (1) %!error odeget (1,2,3,4,5) -%!error <ODE_OPT must be a valid ODE_STRUCT> odeget (1, "opt1") -%!error <FIELD must be a string> odeget (struct ("opt1", 1), 1) -%!warning <no field 'foo' exists> odeget (struct ("opt1", 1), "foo"); -%!warning <no exact match for 'Rel'. Assuming 'RelTol'> odeget (struct ("RelTol", 1), "Rel"); -%!error <Possible fields found: InitialSlope, InitialStep> odeget (odeset (), "Initial") +%!error odeget (1, "opt1") +%!error odeget (struct ("opt1", 1), 1) +%!error odeget (struct ("opt1", 1), "foo");
--- a/scripts/ode/odeset.m Wed Oct 05 23:09:31 2016 -0400 +++ b/scripts/ode/odeset.m Thu Oct 06 06:54:32 2016 +0200 @@ -1,3 +1,5 @@ +## Copyright (C) 2016, Carlo de Falco +## Copyright (C) 2016, Francesco Faccio <francesco.faccio@mail.polimi.it> ## Copyright (C) 2013-2016 Roberto Porcu' <roberto.porcu@polimi.it> ## Copyright (C) 2006-2012 Thomas Treichl <treichl@users.sourceforge.net> ## @@ -47,168 +49,59 @@ function odestruct = odeset (varargin) - ## Column vector of all possible ODE options - persistent options = known_option_names (); + persistent p; + + if (isempty (p)) - if (nargin == 0) - ## Special calling syntax to display defaults - if (nargout == 0) - print_options (); - else - odestruct = cell2struct (cell (numel (options), 1), options); - endif - return; + p = inputParser (); + p.addParameter ("AbsTol", []); + p.addParameter ("BDF", []); + p.addParameter ("Events", []); + p.addParameter ("InitialSlope", []); + p.addParameter ("InitialStep", []); + p.addParameter ("Jacobian", []); + p.addParameter ("JConstant", []); + p.addParameter ("JPattern", []); + p.addParameter ("Mass", []); + p.addParameter ("MassConstant", []); + p.addParameter ("MassSingular", []); + p.addParameter ("MaxOrder", []); + p.addParameter ("MaxStep", []); + p.addParameter ("MStateDependence", []); + p.addParameter ("MvPattern", []); + p.addParameter ("NonNegative", []); + p.addParameter ("NormControl", []); + p.addParameter ("OutputFcn", []); + p.addParameter ("OutputSel", []); + p.addParameter ("Refine", []); + p.addParameter ("RelTol", []); + p.addParameter ("Stats", []); + p.addParameter ("Vectorized", []); + p.KeepUnmatched = true; + endif - ## initialize output - odestruct = cell2struct (cell (numel (options), 1), options); - - if (isstruct (varargin{1})) - oldstruct = varargin{1}; - - ## Copy oldstruct values into output odestruct - for [val, name] = oldstruct - - exactmatch = true; - match = find (strcmpi (name, options)); - if (isempty (match)) - match = find (strncmpi (name, options, length (name))); - exactmatch = false; - endif - - if (isempty (match)) - odestruct.(name) = val; - elseif (numel (match) == 1) - if (! exactmatch) - warning ("odeset:NoExactMatching", - "no exact match for '%s'. Assuming '%s'.", - name, options{match}); - endif - odestruct.(options{match}) = val; - else - error ("odeset: no exact match for '%s'. Possible fields found: %s.", - name, strjoin (options(match), ", ")); - endif - - if (nargin == 1) - ## Check if all changes have resulted in a valid ODEOPT struct - ode_struct_value_check ("odeset", odestruct); - return; - endif + if (nargin == 0 && nargout == 0) + print_options (); + else + p.parse (varargin{:}); + odestruct = p.Results; + odestruct_extra = p.Unmatched; - endfor - - ## At this point, odestruct has been initialized with default values, - ## and if oldstruct was present it has overwritten fields in odestruct. - - if (nargin == 2 && isstruct (varargin{2})) - newstruct = varargin{2}; - - ## Update the first struct with the values from the second one - for [val, name] = newstruct - - exactmatch = true; - match = find (strcmpi (name, options)); - if (isempty (match)) - match = find (strncmpi (name, options, length (name))); - exactmatch = false; - endif - - if (isempty (match)) - odestruct.(name) = val; - elseif (numel (match) == 1) - if (! exactmatch) - warning ("odeset:NoExactMatching", - "no exact match for '%s'. Assuming '%s'.", - name, options{match}); - endif - odestruct.(options{match}) = val; - else - error ("odeset: no exact match for '%s'. Possible fields found: %s.", - name, strjoin (options(match), ", ")); - endif - endfor - - ## Check if all changes have resulted in a valid ODEOPT struct - ode_struct_value_check ("odeset", odestruct); - return; - endif + s1 = cellfun (@(x) ifelse (iscell(x), {x}, x), + struct2cell(odestruct), + 'UniformOutput', false); - ## Second argument is not a struct - if (mod (nargin, 2) != 1) - error ("odeset: FIELD/VALUE arguments must occur in pairs"); - endif - if (! all (cellfun ("isclass", varargin(2:2:end), "char"))) - error ("odeset: All FIELD names must be strings"); - endif - - ## Write new field/value pairs into odestruct - for i = 2:2:nargin - name = varargin{i}; - - exactmatch = true; - match = find (strcmpi (name, options)); - if (isempty (match)) - match = find (strncmpi (name, options, length (name))); - exactmatch = false; - endif - - if (isempty (match)) - odestruct.(name) = varargin{i+1}; - elseif (numel (match) == 1) - if (! exactmatch) - warning ("odeset:NoExactMatching", - "no exact match for '%s'. Assuming '%s'.", - name, options{match}); - endif - odestruct.(options{match}) = varargin{i+1}; - else - error ("odeset: no exact match for '%s'. Possible fields found: %s.", - name, strjoin (options(match), ", ")); - endif - endfor - - ## Check if all changes have resulted in a valid ODEOPT struct - ode_struct_value_check ("odeset", odestruct); - - else - ## First input argument was not a struct, must be field/value pairs - if (mod (nargin, 2) != 0) - error ("odeset: FIELD/VALUE arguments must occur in pairs"); - elseif (! all (cellfun ("isclass", varargin(1:2:end), "char"))) - error ("odeset: All FIELD names must be strings"); - endif - - for i = 1:2:nargin - name = varargin{i}; - - exactmatch = true; - match = find (strcmpi (name, options)); - if (isempty (match)) - match = find (strncmpi (name, options, length (name))); - exactmatch = false; - endif - - if (isempty (match)) - odestruct.(name) = varargin{i+1}; - elseif (numel (match) == 1) - if (! exactmatch) - warning ("odeset:NoExactMatching", - "no exact match for '%s'. Assuming '%s'.", - name, options{match}); - endif - odestruct.(options{match}) = varargin{i+1}; - else - error ("odeset: no exact match for '%s'. Possible fields found: %s.", - name, strjoin (options(match), ", ")); - endif - endfor - - ## Check if all changes have resulted in a valid ODEOPT struct - ode_struct_value_check ("odeset", odestruct); - + s2 = cellfun (@(x) ifelse (iscell(x), {x}, x), + struct2cell(odestruct_extra), + 'UniformOutput', false); + + C = [fieldnames(odestruct) s1; + fieldnames(odestruct_extra) s2]; + + odestruct = struct (C'{:}); endif - + endfunction ## function to print all possible options @@ -262,11 +155,7 @@ %! odeoptB = odeset ("AbsTol", 1e-2, "RelTol", 1e-1); %! odeoptC = odeset (odeoptB, "NormControl", "on"); -## All tests that are needed to check if a valid option has been set are -## implemented in ode_struct_value_check.m -## FIXME: xtest currently fails as there are two extra options to control -## fixed step integration options. -%!xtest +%!test %! odeoptA = odeset (); %! assert (isstruct (odeoptA)); %! assert (numfields (odeoptA), 23); @@ -298,10 +187,13 @@ %! end_unwind_protect ## Test input validation -%!error <FIELD/VALUE arguments must occur in pairs> odeset ("opt1") -%!error <FIELD names must be strings> odeset (1, 1) -%!error <FIELD/VALUE arguments must occur in pairs> odeset (odeset (), "opt1") -%!error <FIELD names must be strings> odeset (odeset (), 1, 1) -%!warning <no exact match for 'Rel'. Assuming 'RelTol'> odeset ("Rel", 1); -%!error <Possible fields found: InitialSlope, InitialStep> odeset ("Initial", 1) +%!error <argument 'OPT1' is not a valid parameter> odeset ("opt1") +%!error odeset (1, 1) +%!error <argument 'OPT1' is not a valid parameter> odeset (odeset (), "opt1") +%!error odeset (odeset (), 1, 1) +##FIXME: Add not exact match option +## %!warning <no exact match for 'Rel'. Assuming 'RelTol'> odeset ("Rel", 1); +## %!error <Possible fields found: InitialSlope, InitialStep> odeset ("Initial", 1) + +
--- a/scripts/ode/private/integrate_adaptive.m Wed Oct 05 23:09:31 2016 -0400 +++ b/scripts/ode/private/integrate_adaptive.m Thu Oct 06 06:54:32 2016 +0200 @@ -76,7 +76,7 @@ dt = odeget (options, "InitialStep", [], "fast"); if (isempty (dt)) dt = starting_stepsize (order, func, t, x, options.AbsTol, options.RelTol, - options.normcontrol); + strcmp (options.NormControl, "on")); endif dir = odeget (options, "direction", [], "fast"); @@ -91,7 +91,7 @@ ## Initialize the OutputFcn if (options.haveoutputfunction) - if (options.haveoutputselection) + if (! isempty (options.OutputSel)) solution.retout = x(options.OutputSel,end); else solution.retout = x; @@ -101,7 +101,7 @@ endif ## Initialize the EventFcn - if (options.haveeventfunction) + if (! isempty (options.Events)) ode_event_handler (options.Events, tspan(end), x, "init", options.funarguments{:}); endif @@ -134,7 +134,7 @@ endif err = AbsRel_Norm (x_new, x_old, options.AbsTol, options.RelTol, - options.normcontrol, x_est); + strcmp (options.NormControl, "on"), x_est); ## Accept solution only if err <= 1.0 if (err <= 1) @@ -161,7 +161,7 @@ ## Call Events function only if a valid result has been found. ## Stop integration if eventbreak is true. - if (options.haveeventfunction) + if (! isempty (options.Events)) break_loop = false; for idenseout = 1:numel (t_caught) id = t_caught(idenseout); @@ -192,7 +192,7 @@ approxvals = interp1 ([t_old, t(t_caught), t_new], [x_old, x(:, t_caught), x_new] .', approxtime, 'linear') .'; - if (options.haveoutputselection) + if (! isempty (options.OutputSel)) approxvals = approxvals(options.OutputSel, :); endif for ii = 1:numel (approxtime) @@ -216,7 +216,7 @@ ## Call Events function only if a valid result has been found. ## Stop integration if eventbreak is true. - if (options.haveeventfunction) + if (! isempty (options.Events)) solution.event = ... ode_event_handler (options.Events, t(istep), x(:, istep), [], options.funarguments{:}); @@ -236,7 +236,7 @@ approxvals = interp1 ([t_old, t_new], [x_old, x_new] .', approxtime, 'linear') .'; - if (options.haveoutputselection) + if (! isempty (options.OutputSel)) approxvals = approxvals(options.OutputSel, :); endif for ii = 1:numel (approxtime)