GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
dassl.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 <string>
31 
32 #include "DASSL.h"
33 
34 #include "defun.h"
35 #include "error.h"
36 #include "errwarn.h"
37 #include "interpreter-private.h"
38 #include "interpreter.h"
39 #include "ovl.h"
40 #include "ov-fcn.h"
41 #include "ov-cell.h"
42 #include "pager.h"
43 #include "unwind-prot.h"
44 #include "utils.h"
45 #include "variables.h"
46 
47 #include "DASSL-opts.cc"
48 
50 
51 // Global pointer for user defined function required by dassl.
52 static octave_value dassl_fcn;
53 
54 // Global pointer for optional user defined jacobian function.
55 static octave_value dassl_jac;
56 
57 // Have we warned about imaginary values returned from user function?
58 static bool warned_fcn_imaginary = false;
59 static bool warned_jac_imaginary = false;
60 
61 // Is this a recursive call?
62 static int call_depth = 0;
63 
64 static ColumnVector
65 dassl_user_function (const ColumnVector& x, const ColumnVector& xdot,
66  double t, octave_idx_type& ires)
67 {
68  ColumnVector retval;
69 
70  error_unless (x.numel () == xdot.numel ());
71 
72  octave_value_list args;
73 
74  args(2) = t;
75  args(1) = xdot;
76  args(0) = x;
77 
78  if (dassl_fcn.is_defined ())
79  {
81 
82  try
83  {
84  interpreter& interp = __get_interpreter__ ();
85 
86  tmp = interp.feval (dassl_fcn, args, 1);
87  }
88  catch (execution_exception& ee)
89  {
90  err_user_supplied_eval (ee, "dassl");
91  }
92 
93  int tlen = tmp.length ();
94  if (tlen == 0 || ! tmp(0).is_defined ())
95  err_user_supplied_eval ("dassl");
96 
97  if (! warned_fcn_imaginary && tmp(0).iscomplex ())
98  {
99  warning ("dassl: ignoring imaginary part returned from user-supplied function");
100  warned_fcn_imaginary = true;
101  }
102 
103  retval = tmp(0).vector_value ();
104 
105  if (tlen > 1)
106  ires = tmp(1).int_value ();
107 
108  if (retval.isempty ())
109  err_user_supplied_eval ("dassl");
110  }
111 
112  return retval;
113 }
114 
115 static Matrix
116 dassl_user_jacobian (const ColumnVector& x, const ColumnVector& xdot,
117  double t, double cj)
118 {
119  Matrix retval;
120 
121  error_unless (x.numel () == xdot.numel ());
122 
123  octave_value_list args;
124 
125  args(3) = cj;
126  args(2) = t;
127  args(1) = xdot;
128  args(0) = x;
129 
130  if (dassl_jac.is_defined ())
131  {
132  octave_value_list tmp;
133 
134  try
135  {
136  interpreter& interp = __get_interpreter__ ();
137 
138  tmp = interp.feval (dassl_jac, args, 1);
139  }
140  catch (execution_exception& ee)
141  {
142  err_user_supplied_eval (ee, "dassl");
143  }
144 
145  int tlen = tmp.length ();
146  if (tlen == 0 || ! tmp(0).is_defined ())
147  err_user_supplied_eval ("dassl");
148 
149  if (! warned_jac_imaginary && tmp(0).iscomplex ())
150  {
151  warning ("dassl: ignoring imaginary part returned from user-supplied jacobian function");
152  warned_jac_imaginary = true;
153  }
154 
155  retval = tmp(0).matrix_value ();
156 
157  if (retval.isempty ())
158  err_user_supplied_eval ("dassl");
159  }
160 
161  return retval;
162 }
163 
164 DEFMETHOD (dassl, interp, args, nargout,
165  doc: /* -*- texinfo -*-
166 @deftypefn {} {[@var{x}, @var{xdot}, @var{istate}, @var{msg}] =} dassl (@var{fcn}, @var{x_0}, @var{xdot_0}, @var{t}, @var{t_crit})
167 Solve a set of differential-algebraic equations.
168 
169 @code{dassl} solves the set of equations
170 @tex
171 $$ 0 = f (x, \dot{x}, t) $$
172 with
173 $$ x(t_0) = x_0, \dot{x}(t_0) = \dot{x}_0 $$
174 @end tex
175 @ifnottex
176 
177 @example
178 0 = f (x, xdot, t)
179 @end example
180 
181 @noindent
182 with
183 
184 @example
185 x(t_0) = x_0, xdot(t_0) = xdot_0
186 @end example
187 
188 @end ifnottex
189 The solution is returned in the matrices @var{x} and @var{xdot},
190 with each row in the result matrices corresponding to one of the
191 elements in the vector @var{t}. The first element of @var{t}
192 should be @math{t_0} and correspond to the initial state of the
193 system @var{x_0} and its derivative @var{xdot_0}, so that the first
194 row of the output @var{x} is @var{x_0} and the first row
195 of the output @var{xdot} is @var{xdot_0}.
196 
197 The first argument, @var{fcn}, is a string, inline, or function handle
198 that names the function @math{f} to call to compute the vector of
199 residuals for the set of equations. It must have the form
200 
201 @example
202 @var{res} = f (@var{x}, @var{xdot}, @var{t})
203 @end example
204 
205 @noindent
206 in which @var{x}, @var{xdot}, and @var{res} are vectors, and @var{t} is a
207 scalar.
208 
209 If @var{fcn} is a two-element string array or a two-element cell array
210 of strings, inline functions, or function handles, the first element names
211 the function @math{f} described above, and the second element names a
212 function to compute the modified Jacobian
213 
214 @tex
215 $$
216 J = {\partial f \over \partial x}
217  + c {\partial f \over \partial \dot{x}}
218 $$
219 @end tex
220 @ifnottex
221 
222 @example
223 @group
224  df df
225 jac = -- + c ------
226  dx d xdot
227 @end group
228 @end example
229 
230 @end ifnottex
231 
232 The modified Jacobian function must have the form
233 
234 @example
235 @group
236 
237 @var{jac} = j (@var{x}, @var{xdot}, @var{t}, @var{c})
238 
239 @end group
240 @end example
241 
242 The second and third arguments to @code{dassl} specify the initial
243 condition of the states and their derivatives, and the fourth argument
244 specifies a vector of output times at which the solution is desired,
245 including the time corresponding to the initial condition.
246 
247 The set of initial states and derivatives are not strictly required to
248 be consistent. In practice, however, @sc{dassl} is not very good at
249 determining a consistent set for you, so it is best if you ensure that
250 the initial values result in the function evaluating to zero.
251 
252 The fifth argument is optional, and may be used to specify a set of
253 times that the DAE solver should not integrate past. It is useful for
254 avoiding difficulties with singularities and points where there is a
255 discontinuity in the derivative.
256 
257 After a successful computation, the value of @var{istate} will be
258 greater than zero (consistent with the Fortran version of @sc{dassl}).
259 
260 If the computation is not successful, the value of @var{istate} will be
261 less than zero and @var{msg} will contain additional information.
262 
263 You can use the function @code{dassl_options} to set optional
264 parameters for @code{dassl}.
265 @seealso{daspk, dasrt, lsode}
266 @end deftypefn */)
267 {
268  int nargin = args.length ();
269 
270  if (nargin < 4 || nargin > 5)
271  print_usage ();
272 
273  warned_fcn_imaginary = false;
274  warned_jac_imaginary = false;
275 
276  octave_value_list retval (4);
277 
278  unwind_protect_var<int> restore_var (call_depth);
279  call_depth++;
280 
281  if (call_depth > 1)
282  error ("dassl: invalid recursive call");
283 
284  std::string fcn_name, fname, jac_name, jname;
285 
286  dassl_fcn = octave_value ();
287  dassl_jac = octave_value ();
288 
289  octave_value f_arg = args(0);
290 
291  std::list<std::string> fcn_param_names ({"x", "xdot", "t"});
292  std::list<std::string> jac_param_names ({"x", "xdot", "t", "cj"});
293 
294  if (f_arg.iscell ())
295  {
296  Cell c = f_arg.cell_value ();
297  if (c.numel () == 1)
298  f_arg = c(0);
299  else if (c.numel () == 2)
300  {
301  dassl_fcn = get_function_handle (interp, c(0), fcn_param_names);
302 
303  if (dassl_fcn.is_defined ())
304  {
305  dassl_jac = get_function_handle (interp, c(1), jac_param_names);
306 
307  if (dassl_jac.is_undefined ())
308  dassl_fcn = octave_value ();
309  }
310  }
311  else
312  error ("dassl: incorrect number of elements in cell array");
313  }
314 
315  if (dassl_fcn.is_undefined () && ! f_arg.iscell ())
316  {
317  if (f_arg.is_function_handle () || f_arg.is_inline_function ())
318  dassl_fcn = f_arg;
319  else
320  {
321  switch (f_arg.rows ())
322  {
323  case 1:
324  dassl_fcn = get_function_handle (interp, f_arg, fcn_param_names);
325  break;
326 
327  case 2:
328  {
329  string_vector tmp = f_arg.string_vector_value ();
330 
331  dassl_fcn = get_function_handle (interp, tmp(0),
332  fcn_param_names);
333 
334  if (dassl_fcn.is_defined ())
335  {
336  dassl_jac = get_function_handle (interp, tmp(1),
337  jac_param_names);
338 
339  if (dassl_jac.is_undefined ())
340  dassl_fcn = octave_value ();
341  }
342  }
343  break;
344 
345  default:
346  error ("dassl: first arg should be a string or 2-element string array");
347  }
348  }
349  }
350 
351  if (dassl_fcn.is_undefined ())
352  error ("dassl: FCN argument is not a valid function name or handle");
353 
354  ColumnVector state = args(1).xvector_value ("dassl: initial state X_0 must be a vector");
355 
356  ColumnVector deriv = args(2).xvector_value ("dassl: initial derivatives XDOT_0 must be a vector");
357 
358  ColumnVector out_times = args(3).xvector_value ("dassl: output time variable T must be a vector");
359 
360  ColumnVector crit_times;
361  int crit_times_set = 0;
362  if (nargin > 4)
363  {
364  crit_times = args(4).xvector_value ("dassl: list of critical times T_CRIT must be a vector");
365 
366  crit_times_set = 1;
367  }
368 
369  if (state.numel () != deriv.numel ())
370  error ("dassl: X and XDOT_0 must have the same size");
371 
372  double tzero = out_times (0);
373 
374  DAEFunc fcn (dassl_user_function);
375  if (dassl_jac.is_defined ())
376  fcn.set_jacobian_function (dassl_user_jacobian);
377 
378  DASSL dae (state, deriv, tzero, fcn);
379 
380  dae.set_options (dassl_opts);
381 
382  Matrix output;
383  Matrix deriv_output;
384 
385  if (crit_times_set)
386  output = dae.integrate (out_times, deriv_output, crit_times);
387  else
388  output = dae.integrate (out_times, deriv_output);
389 
390  std::string msg = dae.error_message ();
391 
392  if (dae.integration_ok ())
393  {
394  retval(0) = output;
395  retval(1) = deriv_output;
396  }
397  else
398  {
399  if (nargout < 3)
400  error ("dassl: %s", msg.c_str ());
401 
402  retval(0) = Matrix ();
403  retval(1) = Matrix ();
404  }
405 
406  retval(2) = static_cast<double> (dae.integration_state ());
407  retval(3) = msg;
408 
409  return retval;
410 }
411 
412 /*
413 ## dassl-1.m
414 ##
415 ## Test dassl() function
416 ##
417 ## Author: David Billinghurst (David.Billinghurst@riotinto.com.au)
418 ## Comalco Research and Technology
419 ## 20 May 1998
420 ##
421 ## Problem
422 ##
423 ## y1' = -y2, y1(0) = 1
424 ## y2' = y1, y2(0) = 0
425 ##
426 ## Solution
427 ##
428 ## y1(t) = cos(t)
429 ## y2(t) = sin(t)
430 ##
431 %!function res = __f (x, xdot, t)
432 %! res = [xdot(1)+x(2); xdot(2)-x(1)];
433 %!endfunction
434 
435 %!test
436 %!
437 %! x0 = [1; 0];
438 %! xdot0 = [0; 1];
439 %! t = (0:1:10)';
440 %!
441 %! tol = 100 * dassl_options ("relative tolerance");
442 %!
443 %! [x, xdot] = dassl ("__f", x0, xdot0, t);
444 %!
445 %! y = [cos(t), sin(t)];
446 %!
447 %! assert (x, y, tol);
448 
449 ## dassl-2.m
450 ##
451 ## Test dassl() function
452 ##
453 ## Author: David Billinghurst (David.Billinghurst@riotinto.com.au)
454 ## Comalco Research and Technology
455 ## 20 May 1998
456 ##
457 ## Based on SLATEC quick check for DASSL by Linda Petzold
458 ##
459 ## Problem
460 ##
461 ## x1' + 10*x1 = 0, x1(0) = 1
462 ## x1 + x2 = 1, x2(0) = 0
463 ##
464 ##
465 ## Solution
466 ##
467 ## x1(t) = exp(-10*t)
468 ## x2(t) = 1 - x(1)
469 ##
470 %!function res = __f (x, xdot, t)
471 %! res = [xdot(1)+10*x(1); x(1)+x(2)-1];
472 %!endfunction
473 
474 %!test
475 %!
476 %! x0 = [1; 0];
477 %! xdot0 = [-10; 10];
478 %! t = (0:0.2:1)';
479 %!
480 %! tol = 500 * dassl_options ("relative tolerance");
481 %!
482 %! [x, xdot] = dassl ("__f", x0, xdot0, t);
483 %!
484 %! y = [exp(-10*t), 1-exp(-10*t)];
485 %!
486 %! assert (x, y, tol);
487 
488 %!test
489 %! old_tol = dassl_options ("absolute tolerance");
490 %! dassl_options ("absolute tolerance", eps);
491 %! assert (dassl_options ("absolute tolerance") == eps);
492 %! ## Restore old value of tolerance
493 %! dassl_options ("absolute tolerance", old_tol);
494 
495 %!error dassl_options ("foo", 1, 2)
496 */
497 
498 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
DAEFunc & set_jacobian_function(DAEJacFunc j)
Definition: DAEFunc.h:86
void set_options(const DASSL_options &opt)
Definition: DASSL-opts.h:80
Definition: DASSL.h:41
std::string error_message() const
Definition: DASSL.cc:502
Matrix integrate(const ColumnVector &tout, Matrix &xdot_out)
Definition: DASSL.cc:354
Definition: dMatrix.h:42
octave_idx_type integration_state() const
Definition: base-de.h:102
bool integration_ok() const
Definition: base-de.h:100
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.
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
DEFMETHOD(dassl, interp, args, nargout, doc:)
Definition: dassl.cc:164
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value dassl_fcn
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
#define error_unless(cond)
Definition: error.h:530
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
return octave_value(v1.char_array_value() . concat(v2.char_array_value(), ra_idx),((a1.is_sq_string()||a2.is_sq_string()) ? '\'' :'"'))