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