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