1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1996-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING.  If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 #  include "config.h"
28 #endif
29 
30 #include <list>
31 #include <string>
32 
33 #include "LSODE.h"
34 #include "lo-mappers.h"
35 
36 #include "defun.h"
37 #include "error.h"
38 #include "errwarn.h"
39 #include "interpreter-private.h"
40 #include "ovl.h"
41 #include "ov-fcn.h"
42 #include "ov-cell.h"
43 #include "pager.h"
44 #include "parse.h"
45 #include "pr-output.h"
46 #include "unwind-prot.h"
47 #include "utils.h"
48 #include "variables.h"
49 
50 #include "LSODE-opts.cc"
51 
52 // Global pointer for user defined function required by lsode.
53 static octave_value lsode_fcn;
54 
55 // Global pointer for optional user defined jacobian function used by lsode.
56 static octave_value lsode_jac;
57 
58 // Have we warned about imaginary values returned from user function?
59 static bool warned_fcn_imaginary = false;
60 static bool warned_jac_imaginary = false;
61 
62 // Is this a recursive call?
63 static int call_depth = 0;
64 
65 ColumnVector
lsode_user_function(const ColumnVector & x,double t)66 lsode_user_function (const ColumnVector& x, double t)
67 {
68   ColumnVector retval;
69 
70   octave_value_list args;
71   args(1) = t;
72   args(0) = x;
73 
74   if (lsode_fcn.is_defined ())
75     {
76       octave_value_list tmp;
77 
78       try
79         {
80           tmp = octave::feval (lsode_fcn, args, 1);
81         }
82       catch (octave::execution_exception& e)
83         {
84           err_user_supplied_eval (e, "lsode");
85         }
86 
87       if (tmp.empty () || ! tmp(0).is_defined ())
88         err_user_supplied_eval ("lsode");
89 
90       if (! warned_fcn_imaginary && tmp(0).iscomplex ())
91         {
92           warning ("lsode: ignoring imaginary part returned from user-supplied function");
93           warned_fcn_imaginary = true;
94         }
95 
96       retval = tmp(0).xvector_value ("lsode: expecting user supplied function to return numeric vector");
97 
98       if (retval.isempty ())
99         err_user_supplied_eval ("lsode");
100     }
101 
102   return retval;
103 }
104 
105 Matrix
lsode_user_jacobian(const ColumnVector & x,double t)106 lsode_user_jacobian (const ColumnVector& x, double t)
107 {
108   Matrix retval;
109 
110   octave_value_list args;
111   args(1) = t;
112   args(0) = x;
113 
114   if (lsode_jac.is_defined ())
115     {
116       octave_value_list tmp;
117 
118       try
119         {
120           tmp = octave::feval (lsode_jac, args, 1);
121         }
122       catch (octave::execution_exception& e)
123         {
124           err_user_supplied_eval (e, "lsode");
125         }
126 
127       if (tmp.empty () || ! tmp(0).is_defined ())
128         err_user_supplied_eval ("lsode");
129 
130       if (! warned_jac_imaginary && tmp(0).iscomplex ())
131         {
132           warning ("lsode: ignoring imaginary part returned from user-supplied jacobian function");
133           warned_jac_imaginary = true;
134         }
135 
136       retval = tmp(0).xmatrix_value ("lsode: expecting user supplied jacobian function to return numeric array");
137 
138       if (retval.isempty ())
139         err_user_supplied_eval ("lsode");
140     }
141 
142   return retval;
143 }
144 
145 DEFMETHOD (lsode, interp, args, nargout,
146            doc: /* -*- texinfo -*-
147 @deftypefn  {} {[@var{x}, @var{istate}, @var{msg}] =} lsode (@var{fcn}, @var{x_0}, @var{t})
148 @deftypefnx {} {[@var{x}, @var{istate}, @var{msg}] =} lsode (@var{fcn}, @var{x_0}, @var{t}, @var{t_crit})
149 Ordinary Differential Equation (ODE) solver.
150 
151 The set of differential equations to solve is
152 @tex
153 $$ {dx \over dt} = f (x, t) $$
154 with
155 $$ x(t_0) = x_0 $$
156 @end tex
157 @ifnottex
158 
159 @example
160 @group
161 dx
162 -- = f (x, t)
163 dt
164 @end group
165 @end example
166 
167 @noindent
168 with
169 
170 @example
171 x(t_0) = x_0
172 @end example
173 
174 @end ifnottex
175 The solution is returned in the matrix @var{x}, with each row
176 corresponding to an element of the vector @var{t}.  The first element
177 of @var{t} should be @math{t_0} and should correspond to the initial
178 state of the system @var{x_0}, so that the first row of the output
179 is @var{x_0}.
180 
181 The first argument, @var{fcn}, is a string, inline, or function handle
182 that names the function @math{f} to call to compute the vector of right
183 hand sides for the set of equations.  The function must have the form
184 
185 @example
186 @var{xdot} = f (@var{x}, @var{t})
187 @end example
188 
189 @noindent
190 in which @var{xdot} and @var{x} are vectors and @var{t} is a scalar.
191 
192 If @var{fcn} is a two-element string array or a two-element cell array
193 of strings, inline functions, or function handles, the first element names
194 the function @math{f} described above, and the second element names a
195 function to compute the Jacobian of @math{f}.  The Jacobian function
196 must have the form
197 
198 @example
199 @var{jac} = j (@var{x}, @var{t})
200 @end example
201 
202 @noindent
203 in which @var{jac} is the matrix of partial derivatives
204 @tex
205 $$ J = {\partial f_i \over \partial x_j} = \left[\matrix{
206 {\partial f_1 \over \partial x_1}
207   & {\partial f_1 \over \partial x_2}
208   & \cdots
209   & {\partial f_1 \over \partial x_N} \cr
210 {\partial f_2 \over \partial x_1}
211   & {\partial f_2 \over \partial x_2}
212   & \cdots
213   & {\partial f_2 \over \partial x_N} \cr
214  \vdots & \vdots & \ddots & \vdots \cr
215 {\partial f_3 \over \partial x_1}
216   & {\partial f_3 \over \partial x_2}
217   & \cdots
218   & {\partial f_3 \over \partial x_N} \cr}\right]$$
219 @end tex
220 @ifnottex
221 
222 @example
223 @group
224              | df_1  df_1       df_1 |
225              | ----  ----  ...  ---- |
226              | dx_1  dx_2       dx_N |
227              |                       |
228              | df_2  df_2       df_2 |
229              | ----  ----  ...  ---- |
230       df_i   | dx_1  dx_2       dx_N |
231 jac = ---- = |                       |
232       dx_j   |  .    .     .    .    |
233              |  .    .      .   .    |
234              |  .    .       .  .    |
235              |                       |
236              | df_N  df_N       df_N |
237              | ----  ----  ...  ---- |
238              | dx_1  dx_2       dx_N |
239 @end group
240 @end example
241 
242 @end ifnottex
243 
244 The second argument specifies the initial state of the system @math{x_0}.  The
245 third argument is a vector, @var{t}, specifying the time values for which a
246 solution is sought.
247 
248 The fourth argument is optional, and may be used to specify a set of
249 times that the ODE solver should not integrate past.  It is useful for
250 avoiding difficulties with singularities and points where there is a
251 discontinuity in the derivative.
252 
253 After a successful computation, the value of @var{istate} will be 2
254 (consistent with the Fortran version of @sc{lsode}).
255 
256 If the computation is not successful, @var{istate} will be something
257 other than 2 and @var{msg} will contain additional information.
258 
259 You can use the function @code{lsode_options} to set optional
260 parameters for @code{lsode}.
261 
262 See @nospell{Alan C. Hindmarsh},
263 @cite{ODEPACK, A Systematized Collection of ODE Solvers},
264 in Scientific Computing, @nospell{R. S. Stepleman}, editor, (1983)
265 or @url{https://computing.llnl.gov/projects/odepack}
266 for more information about the inner workings of @code{lsode}.
267 
268 Example: Solve the @nospell{Van der Pol} equation
269 
270 @example
271 @group
272 fvdp = @@(@var{y},@var{t}) [@var{y}(2); (1 - @var{y}(1)^2) * @var{y}(2) - @var{y}(1)];
273 @var{t} = linspace (0, 20, 100);
274 @var{y} = lsode (fvdp, [2; 0], @var{t});
275 @end group
276 @end example
277 @seealso{daspk, dassl, dasrt}
278 @end deftypefn */)
279 {
280   int nargin = args.length ();
281 
282   if (nargin < 3 || nargin > 4)
283     print_usage ();
284 
285   warned_fcn_imaginary = false;
286   warned_jac_imaginary = false;
287 
288   octave::unwind_protect frame;
289 
290   frame.protect_var (call_depth);
291   call_depth++;
292 
293   if (call_depth > 1)
294     error ("lsode: invalid recursive call");
295 
296   octave::symbol_table& symtab = interp.get_symbol_table ();
297 
298   std::string fcn_name, fname, jac_name, jname;
299 
300   lsode_fcn = octave_value ();
301   lsode_jac = octave_value ();
302 
303   octave_value f_arg = args(0);
304 
305   std::list<std::string> parameter_names ({"x", "t"});
306 
307   if (f_arg.iscell ())
308     {
309       Cell c = f_arg.cell_value ();
310       if (c.numel () == 1)
311         f_arg = c(0);
312       else if (c.numel () == 2)
313         {
314           lsode_fcn = octave::get_function_handle (interp, c(0),
315                                                    parameter_names);
316 
317           if (lsode_fcn.is_defined ())
318             {
319               lsode_jac = octave::get_function_handle (interp, c(1),
320                                                        parameter_names);
321 
322               if (lsode_jac.is_undefined ())
323                 lsode_fcn = octave_value ();
324             }
325         }
326       else
327         error ("lsode: incorrect number of elements in cell array");
328     }
329 
330   if (lsode_fcn.is_undefined () && ! f_arg.iscell ())
331     {
332       if (f_arg.is_function_handle () || f_arg.is_inline_function ())
333         lsode_fcn = f_arg;
334       else
335         {
336           switch (f_arg.rows ())
337             {
338             case 1:
339               lsode_fcn = octave::get_function_handle (interp, f_arg,
340                                                        parameter_names);
341               break;
342 
343             case 2:
344               {
345                 string_vector tmp = f_arg.string_vector_value ();
346 
347                 lsode_fcn = octave::get_function_handle (interp, tmp(0),
348                                                          parameter_names);
349 
350                 if (lsode_fcn.is_defined ())
351                   {
352                     lsode_jac = octave::get_function_handle (interp, tmp(1),
353                                                              parameter_names);
354 
355                     if (lsode_jac.is_undefined ())
356                       lsode_fcn = octave_value ();
357                   }
358               }
359               break;
360 
361             default:
362               error ("lsode: first arg should be a string or 2-element string array");
363             }
364         }
365     }
366 
367   if (lsode_fcn.is_undefined ())
368     error ("lsode: FCN argument is not a valid function name or handle");
369 
370   ColumnVector state = args(1).xvector_value ("lsode: initial state X_0 must be a vector");
371   ColumnVector out_times = args(2).xvector_value ("lsode: output time variable T must be a vector");
372 
373   ColumnVector crit_times;
374 
375   int crit_times_set = 0;
376   if (nargin > 3)
377     {
378       crit_times = args(3).xvector_value ("lsode: list of critical times T_CRIT must be a vector");
379 
380       crit_times_set = 1;
381     }
382 
383   double tzero = out_times (0);
384 
385   ODEFunc func (lsode_user_function);
386 
387   if (lsode_jac.is_defined ())
388     func.set_jacobian_function (lsode_user_jacobian);
389 
390   LSODE ode (state, tzero, func);
391 
392   ode.set_options (lsode_opts);
393 
394   Matrix output;
395   if (crit_times_set)
396     output = ode.integrate (out_times, crit_times);
397   else
398     output = ode.integrate (out_times);
399 
400   if (fcn_name.length ())
401     symtab.clear_function (fcn_name);
402   if (jac_name.length ())
403     symtab.clear_function (jac_name);
404 
405   std::string msg = ode.error_message ();
406 
407   octave_value_list retval (3);
408 
409   if (ode.integration_ok ())
410     retval(0) = output;
411   else if (nargout < 2)
412     error ("lsode: %s", msg.c_str ());
413   else
414     retval(0) = Matrix ();
415 
416   retval(1) = static_cast<double> (ode.integration_state ());
417   retval(2) = msg;
418 
419   return retval;
420 }
421 
422 /*
423 
424 ## dassl-1.m
425 ##
426 ## Test lsode() function
427 ##
428 ## Author: David Billinghurst (David.Billinghurst@riotinto.com.au)
429 ##         Comalco Research and Technology
430 ##         20 May 1998
431 ##
432 ## Problem
433 ##
434 ##    y1' = -y2,   y1(0) = 1
435 ##    y2' =  y1,   y2(0) = 0
436 ##
437 ## Solution
438 ##
439 ##    y1(t) = cos(t)
440 ##    y2(t) = sin(t)
441 ##
442 %!function xdot = __f (x, t)
443 %!  xdot = [-x(2); x(1)];
444 %!endfunction
445 %!test
446 %!
447 %! x0 = [1; 0];
448 %! xdot0 = [0; 1];
449 %! t = (0:1:10)';
450 %!
451 %! tol = 500 * lsode_options ("relative tolerance");
452 %!
453 %! x = lsode ("__f", x0, t);
454 %!
455 %! y = [cos(t), sin(t)];
456 %!
457 %! assert (x, y, tol);
458 
459 %!function xdotdot = __f (x, t)
460 %!  xdotdot = [x(2); -x(1)];
461 %!endfunction
462 %!test
463 %!
464 %! x0 = [1; 0];
465 %! t = [0; 2*pi];
466 %! tol = 100 * dassl_options ("relative tolerance");
467 %!
468 %! x = lsode ("__f", x0, t);
469 %!
470 %! y = [1, 0; 1, 0];
471 %!
472 %! assert (x, y, tol);
473 
474 %!function xdot = __f (x, t)
475 %!  xdot = x;
476 %!endfunction
477 %!test
478 %!
479 %! x0 = 1;
480 %! t = [0; 1];
481 %! tol = 100 * dassl_options ("relative tolerance");
482 %!
483 %! x = lsode ("__f", x0, t);
484 %!
485 %! y = [1; e];
486 %!
487 %! assert (x, y, tol);
488 
489 %!test
490 %! lsode_options ("absolute tolerance", eps);
491 %! assert (lsode_options ("absolute tolerance") == eps);
492 
493 %!error lsode_options ("foo", 1, 2)
494 */
495