GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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()) ? '\'' :'"'))