changeset 28045:13dba3c069f8

Update input validation for odeXXX.m functions. * ode15i.m, ode15s.m, ode23.m, ode45.m: Use name of function in error messages directly rather than interpolating variable "solver" in to a character matrix. Use double quotes around field values such as "Jacobian" rather than single quotes. Use intermediate variables to make the code more readable. Fix incorrect validation and tighten the acceptance tests for Jacobian and Mass fields. Improve error messages from generic "invalid argument" to more specifically state what is wrong with the input.
author Rik <rik@octave.org>
date Wed, 05 Feb 2020 09:14:08 -0800
parents ace8dc642e4d
children 5664362da646
files scripts/ode/ode15i.m scripts/ode/ode15s.m scripts/ode/ode23.m scripts/ode/ode45.m
diffstat 4 files changed, 97 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/ode/ode15i.m	Tue Feb 04 16:29:37 2020 -0800
+++ b/scripts/ode/ode15i.m	Wed Feb 05 09:14:08 2020 -0800
@@ -119,7 +119,7 @@
     if (ischar (options.Jacobian))
       if (! exist (options.Jacobian))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.Jacobian "' not found"]);
+               ['ode15i: "Jacobian" function "' options.Jacobian '" not found']);
       endif
       options.Jacobian = str2func (options.Jacobian);
     endif
@@ -129,13 +129,13 @@
     if (ischar (options.OutputFcn))
       if (! exist (options.OutputFcn))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.OutputFcn "' not found"]);
+               ['ode15i: "OutputFcn" function "' options.OutputFcn '" not found']);
       endif
       options.OutputFcn = str2func (options.OutputFcn);
     endif
     if (! is_function_handle (options.OutputFcn))
       error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'OutputFcn'"]);
+             'ode15i: "OutputFcn" must be a valid function handle');
     endif
   endif
 
@@ -143,13 +143,13 @@
     if (ischar (options.Events))
       if (! exist (options.Events))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.Events "' not found"]);
+               ['ode15i: "Events" function "' options.Events '" not found']);
       endif
       options.Events = str2func (options.Events);
     endif
     if (! is_function_handle (options.Events))
       error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'Events'"]);
+             'ode15i: "Events" must be a valid function handle');
     endif
   endif
 
@@ -178,45 +178,45 @@
     options.havejac = true;
     if (iscell (options.Jacobian))
       if (numel (options.Jacobian) == 2)
-        if (issparse (options.Jacobian{1}) && issparse (options.Jacobian{2}))
-          options.havejacsparse = true;  # Jac is sparse cell
+        J1 = options.Jacobian{1};
+        J2 = options.Jacobian{2};
+        if (   ! issquare (J1) || ! issquare (J2)
+            || rows (J1) != n || rows (J2) != n  
+            || ! isnumeric (J1) || ! isnumeric (J2)
+            || ! isreal (J1) || ! isreal (J2))
+          error ("Octave:invalid-input-arg",
+                 'ode15i: "Jacobian" matrices must be real square matrices');
         endif
-
-        if (any (size (options.Jacobian{1}) != [n n])
-            || any (size (options.Jacobian{2}) != [n n])
-            || ! isnumeric (options.Jacobian{1})
-            || ! isnumeric (options.Jacobian{2})
-            || ! isreal (options.Jacobian{1})
-            || ! isreal (options.Jacobian{2}))
-          error ("Octave:invalid-input-arg",
-                 [solver ": invalid value assigned to field 'Jacobian'"]);
+        if (issparse (J1) && issparse (J2))
+          options.havejacsparse = true;  # Jac is sparse cell
         endif
       else
         error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Jacobian'"]);
+               'ode15i: invalid value assigned to field "Jacobian"');
       endif
 
     elseif (is_function_handle (options.Jacobian))
       options.havejacfun = true;
       if (nargin (options.Jacobian) == 3)
-        [A, B] = options.Jacobian (trange(1), y0, yp0);
-        if (issparse (A) && issparse (B))
-          options.havejacsparse = true;  # Jac is sparse fun
-        endif
+        [J1, J2] = options.Jacobian (trange(1), y0, yp0);
 
-        if (any (size (A) != [n n]) || any (size (B) != [n n])
-            || ! isnumeric (A) || ! isnumeric (B) || ! isreal (A)
-            || ! isreal (B))
+        if (   ! issquare (J1) || rows (J1) != n
+            || ! isnumeric (J1) || ! isreal (J1)
+            || ! issquare (J2) || rows (J2) != n
+            || ! isnumeric (J2) || ! isreal (J2))
           error ("Octave:invalid-input-arg",
-                 [solver ": invalid value assigned to field 'Jacobian'"]);
+                 'ode15i: "Jacobian" function must evaluate to a real square matrix');
+        endif
+        if (issparse (J1) && issparse (J2))
+          options.havejacsparse = true;  # Jac is sparse fun
         endif
       else
         error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Jacobian'"]);
+               'ode15i: invalid value assigned to field "Jacobian"');
       endif
     else
-        error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Jacobian'"]);
+      error ("Octave:invalid-input-arg",
+             'ode15i: "Jacobian" field must be a function handle or 2-element cell array of square matrices');
     endif
   endif
 
@@ -225,7 +225,7 @@
 
   if (numel (options.AbsTol) != 1 && numel (options.AbsTol) != n)
     error ("Octave:invalid-input-arg",
-           [solver ": invalid value assigned to field 'AbsTol'"]);
+           'ode15i: invalid value assigned to field "AbsTol"');
 
   elseif (numel (options.AbsTol) == n)
     options.haveabstolvec = true;
--- a/scripts/ode/ode15s.m	Tue Feb 04 16:29:37 2020 -0800
+++ b/scripts/ode/ode15s.m	Wed Feb 05 09:14:08 2020 -0800
@@ -113,13 +113,13 @@
     if (ischar (options.Mass))
       if (! exist (options.Mass))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.Mass "' not found"]);
+               ['ode15s: "Mass" function "' options.Mass '" not found']);
       endif
       options.Mass = str2func (options.Mass);
     endif
     if (! is_function_handle (options.Mass) && ! isnumeric (options.Mass))
       error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'Mass'"]);
+             'ode15s: "Mass" field must be a function handle or square matrix');
     endif
   endif
 
@@ -127,14 +127,14 @@
     if (ischar (options.Jacobian))
       if (! exist (options.Jacobian))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.Jacobian "' not found"]);
+               ['ode15s: "Jacobian" function "' options.Jacobian '" not found']);
       endif
       options.Jacobian = str2func (options.Jacobian);
     endif
     if (! is_function_handle (options.Jacobian)
         && ! isnumeric (options.Jacobian))
       error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'Jacobian'"]);
+             'ode15s: "Jacobian" field must be a function handle or square matrix');
     endif
   endif
 
@@ -142,13 +142,13 @@
     if (ischar (options.OutputFcn))
       if (! exist (options.OutputFcn))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.OutputFcn "' not found"]);
+               ['ode15s: "OutputFcn" function "' options.OutputFcn '" not found']);
       endif
       options.OutputFcn = str2func (options.OutputFcn);
     endif
     if (! is_function_handle (options.OutputFcn))
       error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'OutputFcn'"]);
+             'ode15s: "OutputFcn" must be a valid function handle');
     endif
   endif
 
@@ -156,13 +156,13 @@
     if (ischar (options.Events))
       if (! exist (options.Events))
         error ("Octave:invalid-input-arg",
-               [solver ": function '" options.Events "' not found"]);
+               ['ode15s: "Events" function "' options.Events '" not found']);
       endif
       options.Events = str2func (options.Events);
     endif
     if (! is_function_handle (options.Events))
       error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'Events'"]);
+             'ode15s: "Events" must be a valid function handle');
     endif
   endif
 
@@ -186,33 +186,30 @@
       if (nargin (options.Mass) == 2)
         options.havestatedep = true;
         M = options.Mass (trange(1), y0);
-        options.havemasssparse = issparse (M);
-        if (any (size (M) != [n n]) || ! isnumeric (M) || ! isreal (M))
+        if (! issquare (M) || rows (M) != n || ! isnumeric (M) || ! isreal (M))
           error ("Octave:invalid-input-arg",
-                 [solver ": invalid value assigned to field 'Mass'"]);
+                 'ode15s: "Mass" function must evaluate to a real square matrix');
         endif
+        options.havemasssparse = issparse (M);
       elseif (nargin (options.Mass) == 1)
         options.havetimedep = true;
         M = options.Mass (trange(1));
-        options.havemasssparse = issparse (M);
-        if (any (size (M) != [n n]) || ! isnumeric (M) || ! isreal (M))
+        if (! issquare (M) || rows (M) != n || ! isnumeric (M) || ! isreal (M))
           error ("Octave:invalid-input-arg",
-                 [solver ": invalid value assigned to field 'Mass'"]);
+                 'ode15s: "Mass" function must evaluate to a real square matrix');
         endif
+        options.havemasssparse = issparse (M);
       else
         error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Mass'"]);
+               'ode15s: invalid value assigned to field "Mass"');
       endif
-    elseif (ismatrix (options.Mass))
-      options.havemasssparse = issparse (options.Mass);
-      if (any (size (options.Mass) != [n n])
+    else    # matrix Mass input
+      if (! issquare (options.Mass) || rows (options.Mass) != n
           || ! isnumeric (options.Mass) || ! isreal (options.Mass))
         error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Mass'"]);
+               'ode15s: "Mass" matrix must be a real square matrix');
       endif
-    else
-      error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'Mass'"]);
+      options.havemasssparse = issparse (options.Mass);
     endif
   endif
 
@@ -226,29 +223,23 @@
     if (is_function_handle (options.Jacobian))
       options.havejacfun = true;
       if (nargin (options.Jacobian) == 2)
-        [A] = options.Jacobian (trange(1), y0);
-        if (issparse (A))
-          options.havejacsparse = true;  # Jac is sparse fun
+        A = options.Jacobian (trange(1), y0);
+        if (! issquare (A) || rows (A) != n || ! isnumeric (A) || ! isreal (A))
+          error ("Octave:invalid-input-arg",
+                 'ode15s: "Jacobian" function must evaluate to a real square matrix');
         endif
-        if (any (size (A) != [n n]) || ! isnumeric (A) || ! isreal (A))
-          error ("Octave:invalid-input-arg",
-                 [solver ": invalid value assigned to field 'Jacobian'"]);
-        endif
+        options.havejacsparse = issparse (A);  # Jac is sparse fun
       else
         error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Jacobian'"]);
-      endif
-    elseif (ismatrix (options.Jacobian))
-      if (issparse (options.Jacobian))
-        options.havejacsparse = true;    # Jac is sparse matrix
+               'ode15s: invalid value assigned to field "Jacobian"');
       endif
-      if (! issquare (options.Jacobian))
+    else  # matrix input 
+      if (! issquare (options.Jacobian) || rows (options.Jacobian) != n
+          || ! isnumeric (options.Jacobian) || ! isreal (options.Jacobian))
         error ("Octave:invalid-input-arg",
-               [solver ": invalid value assigned to field 'Jacobian'"]);
+               'ode15s: "Jacobian" matrix must be a real square matrix');
       endif
-    else
-      error ("Octave:invalid-input-arg",
-             [solver ": invalid value assigned to field 'Jacobian'"]);
+      options.havejacsparse = issparse (options.Jacobian);
     endif
   endif
 
@@ -259,8 +250,8 @@
       options.Jacobian = [];
       warning ("ode15s:mass_state_dependent_provided",
               ["with MStateDependence != 'none' an internal", ...
-               " approximation of Jacobian Matrix will be used.", ...
-               " Set MStateDependence equal to 'none' if you want ", ...
+               " approximation of the Jacobian Matrix will be used.", ...
+               "  Set MStateDependence equal to 'none' if you want", ...
                " to provide a constant or time-dependent Jacobian"]);
     endif
   endif
@@ -290,7 +281,7 @@
 
   if (numel (options.AbsTol) != 1 && numel (options.AbsTol) != n)
     error ("Octave:invalid-input-arg",
-           [solver ": invalid value assigned to field 'AbsTol'"]);
+           'ode15s: invalid value assigned to field "AbsTol"');
   elseif (numel (options.AbsTol) == n)
     options.haveabstolvec = true;
   endif
--- a/scripts/ode/ode23.m	Tue Feb 04 16:29:37 2020 -0800
+++ b/scripts/ode/ode23.m	Wed Feb 05 09:14:08 2020 -0800
@@ -98,8 +98,8 @@
     print_usage ();
   endif
 
+  solver = "ode23";
   order  = 3;
-  solver = "ode23";
 
   if (nargin >= 4)
     if (! isstruct (varargin{1}))
@@ -110,7 +110,8 @@
       ## varargin{1} is an ODE options structure opt
       odeopts = varargin{1};
       funarguments = {varargin{2:numel (varargin)}};
-    else  # if (isstruct (varargin{1}))
+    else
+      ## varargin{1} is an ODE options structure opt
       odeopts = varargin{1};
       funarguments = {};
     endif
@@ -144,13 +145,13 @@
   if (ischar (fun))
     if (! exist (fun))
       error ("Octave:invalid-input-arg",
-             [solver ": function '" fun "' not found"]);
+             ['ode23: function "' fun '" not found']);
     endif
     fun = str2func (fun);
   endif
   if (! is_function_handle (fun))
     error ("Octave:invalid-input-arg",
-           [solver ": FUN must be a valid function handle"]);
+           "ode23: FUN must be a valid function handle");
   endif
 
   ## Start preprocessing, have a look which options are set in odeopts,
@@ -199,13 +200,18 @@
                                              odeopts.funarguments);
   endif
 
-  if (! isempty (odeopts.Mass) && isnumeric (odeopts.Mass))
-    havemasshandle = false;
-    mass = odeopts.Mass;    # constant mass
-  elseif (is_function_handle (odeopts.Mass))
-    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);
+  if (! isempty (odeopts.Mass))
+    if (isnumeric (odeopts.Mass))
+      havemasshandle = false;
+      mass = odeopts.Mass;  # constant mass
+    elseif (is_function_handle (odeopts.Mass))
+      havemasshandle = true;    # mass defined by a function handle
+    else
+      error ("Octave:invalid-input-arg",
+             'ode45: "Mass" field must be a function handle or square matrix');
+    endif
+  else  # no mass matrix - create a diag-matrix of ones for mass
+    havemasshandle = false;   # mass = diag (ones (length (init), 1), 0);
   endif
 
   ## Starting the initialization of the core solver ode23
@@ -262,8 +268,8 @@
     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 (row vector)
-    varargout{1}.y = solution.x.';   # Results are saved in field y (row vector)
+    varargout{1}.x = solution.t.';  # Time stamps saved in field x (row vector)
+    varargout{1}.y = solution.x.';  # Results are saved in field y (row vector)
     varargout{1}.solver = solver;   # Solver name is saved in field solver
     if (! isempty (odeopts.Events))
       varargout{1}.xe = solution.event{3};  # Time info when an event occurred
--- a/scripts/ode/ode45.m	Tue Feb 04 16:29:37 2020 -0800
+++ b/scripts/ode/ode45.m	Wed Feb 05 09:14:08 2020 -0800
@@ -95,8 +95,8 @@
     print_usage ();
   endif
 
+  solver = "ode45";
   order  = 5;  # runge_kutta_45_dorpri uses local extrapolation
-  solver = "ode45";
 
   if (nargin >= 4)
     if (! isstruct (varargin{1}))
@@ -107,7 +107,8 @@
       ## varargin{1} is an ODE options structure opt
       odeopts = varargin{1};
       funarguments = {varargin{2:numel (varargin)}};
-    else  # if (isstruct (varargin{1}))
+    else
+      ## varargin{1} is an ODE options structure opt
       odeopts = varargin{1};
       funarguments = {};
     endif
@@ -141,13 +142,13 @@
   if (ischar (fun))
     if (! exist (fun))
       error ("Octave:invalid-input-arg",
-             [solver ": function '" fun "' not found"]);
+             ['ode45: function "' fun '" not found']);
     endif
     fun = str2func (fun);
   endif
   if (! is_function_handle (fun))
     error ("Octave:invalid-input-arg",
-           [solver ": FUN must be a valid function handle"]);
+           "ode45: FUN must be a valid function handle");
   endif
 
   ## Start preprocessing, have a look which options are set in odeopts,
@@ -199,12 +200,17 @@
                                              odeopts.funarguments);
   endif
 
-  if (! isempty (odeopts.Mass) && isnumeric (odeopts.Mass))
-    havemasshandle = false;
-    mass = odeopts.Mass;  # constant mass
-  elseif (is_function_handle (odeopts.Mass))
-    havemasshandle = true;    # mass defined by a function handle
-  else  # no mass matrix - creating a diag-matrix of ones for mass
+  if (! isempty (odeopts.Mass))
+    if (isnumeric (odeopts.Mass))
+      havemasshandle = false;
+      mass = odeopts.Mass;  # constant mass
+    elseif (is_function_handle (odeopts.Mass))
+      havemasshandle = true;    # mass defined by a function handle
+    else
+      error ("Octave:invalid-input-arg",
+             'ode45: "Mass" field must be a function handle or square matrix');
+    endif
+  else  # no mass matrix - create a diag-matrix of ones for mass
     havemasshandle = false;   # mass = diag (ones (length (init), 1), 0);
   endif