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