1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2002-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 <cinttypes>
31 #include <sstream>
32 
33 #include "DASRT.h"
34 #include "f77-fcn.h"
35 #include "lo-error.h"
36 #include "quit.h"
37 
38 typedef F77_INT (*dasrt_fcn_ptr) (const double&, const double*, const double*,
39                                   double*, F77_INT&, double*, F77_INT*);
40 
41 typedef F77_INT (*dasrt_jac_ptr) (const double&, const double*, const double*,
42                                   double*, const double&, double*, F77_INT*);
43 
44 typedef F77_INT (*dasrt_constr_ptr) (const F77_INT&, const double&,
45                                      const double*, const F77_INT&,
46                                      double*, double*, F77_INT*);
47 
48 extern "C"
49 {
50   F77_RET_T
51   F77_FUNC (ddasrt, DDASRT) (dasrt_fcn_ptr, const F77_INT&, F77_DBLE&,
52                              F77_DBLE*, F77_DBLE*, const F77_DBLE&, F77_INT*,
53                              const F77_DBLE*, const F77_DBLE*, F77_INT&,
54                              F77_DBLE*, const F77_INT&, F77_INT*,
55                              const F77_INT&, F77_DBLE*, F77_INT*,
56                              dasrt_jac_ptr, dasrt_constr_ptr, const F77_INT&,
57                              F77_INT*);
58 }
59 
60 static DAEFunc::DAERHSFunc user_fsub;
61 static DAEFunc::DAEJacFunc user_jsub;
62 static DAERTFunc::DAERTConstrFunc user_csub;
63 
64 static F77_INT nn;
65 
66 static F77_INT
ddasrt_f(const double & t,const double * state,const double * deriv,double * delta,F77_INT & ires,double *,F77_INT *)67 ddasrt_f (const double& t, const double *state, const double *deriv,
68           double *delta, F77_INT& ires, double *, F77_INT *)
69 {
70   ColumnVector tmp_state (nn);
71   ColumnVector tmp_deriv (nn);
72 
73   for (F77_INT i = 0; i < nn; i++)
74     {
75       tmp_state(i) = state[i];
76       tmp_deriv(i) = deriv[i];
77     }
78 
79   octave_idx_type tmp_ires = ires;
80 
81   ColumnVector tmp_fval = (*user_fsub) (tmp_state, tmp_deriv, t, tmp_ires);
82 
83   ires = octave::to_f77_int (tmp_ires);
84 
85   if (tmp_fval.isempty ())
86     ires = -2;
87   else
88     {
89       for (F77_INT i = 0; i < nn; i++)
90         delta[i] = tmp_fval(i);
91     }
92 
93   return 0;
94 }
95 
96 F77_INT
ddasrt_j(const double & time,const double * state,const double * deriv,double * pd,const double & cj,double *,F77_INT *)97 ddasrt_j (const double& time, const double *state, const double *deriv,
98           double *pd, const double& cj, double *, F77_INT *)
99 {
100   // FIXME: would be nice to avoid copying the data.
101 
102   ColumnVector tmp_state (nn);
103   ColumnVector tmp_deriv (nn);
104 
105   for (F77_INT i = 0; i < nn; i++)
106     {
107       tmp_deriv.elem (i) = deriv[i];
108       tmp_state.elem (i) = state[i];
109     }
110 
111   Matrix tmp_pd = (*user_jsub) (tmp_state, tmp_deriv, time, cj);
112 
113   for (F77_INT j = 0; j < nn; j++)
114     for (F77_INT i = 0; i < nn; i++)
115       pd[nn * j + i] = tmp_pd.elem (i, j);
116 
117   return 0;
118 }
119 
120 static F77_INT
ddasrt_g(const F77_INT & neq,const double & t,const double * state,const F77_INT & ng,double * gout,double *,F77_INT *)121 ddasrt_g (const F77_INT& neq, const double& t, const double *state,
122           const F77_INT& ng, double *gout, double *, F77_INT *)
123 {
124   F77_INT n = neq;
125 
126   ColumnVector tmp_state (n);
127   for (F77_INT i = 0; i < n; i++)
128     tmp_state(i) = state[i];
129 
130   ColumnVector tmp_fval = (*user_csub) (tmp_state, t);
131 
132   for (F77_INT i = 0; i < ng; i++)
133     gout[i] = tmp_fval(i);
134 
135   return 0;
136 }
137 
138 void
integrate(double tout)139 DASRT::integrate (double tout)
140 {
141   // I suppose this is the safe thing to do.  If this is the first
142   // call, or if anything about the problem has changed, we should
143   // start completely fresh.
144 
145   if (! initialized || restart
146       || DAEFunc::reset || DAERTFunc::reset || DASRT_options::reset)
147     {
148       integration_error = false;
149 
150       initialized = true;
151 
152       info.resize (dim_vector (15, 1));
153 
154       for (F77_INT i = 0; i < 15; i++)
155         info(i) = 0;
156 
157       F77_INT n = octave::to_f77_int (size ());
158 
159       nn = n;
160 
161       // DAERTFunc
162 
163       user_csub = DAERTFunc::constraint_function ();
164 
165       if (user_csub)
166         {
167           ColumnVector tmp = (*user_csub) (x, t);
168           ng = octave::to_f77_int (tmp.numel ());
169         }
170       else
171         ng = 0;
172 
173       F77_INT maxord = octave::to_f77_int (maximum_order ());
174       if (maxord >= 0)
175         {
176           if (maxord > 0 && maxord < 6)
177             {
178               info(8) = 1;
179               iwork(2) = maxord;
180             }
181           else
182             {
183               (*current_liboctave_error_handler)
184                 ("dassl: invalid value for maximum order");
185               integration_error = true;
186               return;
187             }
188         }
189 
190       liw = 21 + n;
191       lrw = 50 + 9*n + n*n + 3*ng;
192 
193       iwork.resize (dim_vector (liw, 1));
194       rwork.resize (dim_vector (lrw, 1));
195 
196       info(0) = 0;
197 
198       if (stop_time_set)
199         {
200           info(3) = 1;
201           rwork(0) = stop_time;
202         }
203       else
204         info(3) = 0;
205 
206       restart = false;
207 
208       // DAEFunc
209 
210       user_fsub = DAEFunc::function ();
211       user_jsub = DAEFunc::jacobian_function ();
212 
213       if (user_fsub)
214         {
215           octave_idx_type ires = 0;
216 
217           ColumnVector fval = (*user_fsub) (x, xdot, t, ires);
218 
219           if (fval.numel () != x.numel ())
220             {
221               (*current_liboctave_error_handler)
222                 ("dasrt: inconsistent sizes for state and residual vectors");
223 
224               integration_error = true;
225               return;
226             }
227         }
228       else
229         {
230           (*current_liboctave_error_handler)
231             ("dasrt: no user supplied RHS subroutine!");
232 
233           integration_error = true;
234           return;
235         }
236 
237       info(4) = (user_jsub ? 1 : 0);
238 
239       DAEFunc::reset = false;
240 
241       jroot.resize (dim_vector (ng, 1), 1);
242 
243       DAERTFunc::reset = false;
244 
245       // DASRT_options
246 
247       double mss = maximum_step_size ();
248       if (mss >= 0.0)
249         {
250           rwork(1) = mss;
251           info(6) = 1;
252         }
253       else
254         info(6) = 0;
255 
256       double iss = initial_step_size ();
257       if (iss >= 0.0)
258         {
259           rwork(2) = iss;
260           info(7) = 1;
261         }
262       else
263         info(7) = 0;
264 
265       F77_INT sl = octave::to_f77_int (step_limit ());
266       if (sl >= 0)
267         {
268           info(11) = 1;
269           iwork(20) = sl;
270         }
271       else
272         info(11) = 0;
273 
274       abs_tol = absolute_tolerance ();
275       rel_tol = relative_tolerance ();
276 
277       F77_INT abs_tol_len = octave::to_f77_int (abs_tol.numel ());
278       F77_INT rel_tol_len = octave::to_f77_int (rel_tol.numel ());
279 
280       if (abs_tol_len == 1 && rel_tol_len == 1)
281         {
282           info.elem (1) = 0;
283         }
284       else if (abs_tol_len == n && rel_tol_len == n)
285         {
286           info.elem (1) = 1;
287         }
288       else
289         {
290           (*current_liboctave_error_handler)
291             ("dasrt: inconsistent sizes for tolerance arrays");
292 
293           integration_error = true;
294           return;
295         }
296 
297       DASRT_options::reset = false;
298     }
299 
300   double *px = x.fortran_vec ();
301   double *pxdot = xdot.fortran_vec ();
302 
303   F77_INT *pinfo = info.fortran_vec ();
304 
305   double *prel_tol = rel_tol.fortran_vec ();
306   double *pabs_tol = abs_tol.fortran_vec ();
307 
308   double *prwork = rwork.fortran_vec ();
309   F77_INT *piwork = iwork.fortran_vec ();
310 
311   F77_INT *pjroot = jroot.fortran_vec ();
312 
313   double *dummy = nullptr;
314   F77_INT *idummy = nullptr;
315 
316   F77_INT tmp_istate = octave::to_f77_int (istate);
317 
318   F77_XFCN (ddasrt, DDASRT, (ddasrt_f, nn, t, px, pxdot, tout, pinfo,
319                              prel_tol, pabs_tol, tmp_istate, prwork, lrw,
320                              piwork, liw, dummy, idummy, ddasrt_j,
321                              ddasrt_g, ng, pjroot));
322 
323   istate = tmp_istate;
324 
325   switch (istate)
326     {
327     case 1: // A step was successfully taken in intermediate-output
328             // mode.  The code has not yet reached TOUT.
329     case 2: // The integration to TOUT was successfully completed
330             // (T=TOUT) by stepping exactly to TOUT.
331     case 3: // The integration to TOUT was successfully completed
332             // (T=TOUT) by stepping past TOUT.  Y(*) is obtained by
333             // interpolation.  YPRIME(*) is obtained by interpolation.
334       t = tout;
335       break;
336 
337     case 4: //  The integration was successfully completed
338             // by finding one or more roots of G at T.
339       break;
340 
341     case -1: // A large amount of work has been expended.
342     case -2: // The error tolerances are too stringent.
343     case -3: // The local error test cannot be satisfied because you
344              // specified a zero component in ATOL and the
345              // corresponding computed solution component is zero.
346              // Thus, a pure relative error test is impossible for
347              // this component.
348     case -6: // DDASRT had repeated error test failures on the last
349              // attempted step.
350     case -7: // The corrector could not converge.
351     case -8: // The matrix of partial derivatives is singular.
352     case -9: // The corrector could not converge.  There were repeated
353              // error test failures in this step.
354     case -10: // The corrector could not converge because IRES was
355               // equal to minus one.
356     case -11: // IRES equal to -2 was encountered and control is being
357               // returned to the calling program.
358     case -12: // DASSL failed to compute the initial YPRIME.
359     case -33: // The code has encountered trouble from which it cannot
360               // recover.  A message is printed explaining the trouble
361               // and control is returned to the calling program.  For
362               // example, this occurs when invalid input is detected.
363       integration_error = true;
364       break;
365 
366     default:
367       integration_error = true;
368       (*current_liboctave_error_handler)
369         ("unrecognized value of istate (= %" OCTAVE_IDX_TYPE_FORMAT ") "
370          "returned from ddasrt", istate);
371       break;
372     }
373 }
374 
375 DASRT_result
integrate(const ColumnVector & tout)376 DASRT::integrate (const ColumnVector& tout)
377 {
378   DASRT_result retval;
379 
380   Matrix x_out;
381   Matrix xdot_out;
382   ColumnVector t_out = tout;
383 
384   octave_idx_type n_out = tout.numel ();
385   F77_INT n = octave::to_f77_int (size ());
386 
387   if (n_out > 0 && n > 0)
388     {
389       x_out.resize (n_out, n);
390       xdot_out.resize (n_out, n);
391 
392       for (F77_INT i = 0; i < n; i++)
393         {
394           x_out(0,i) = x(i);
395           xdot_out(0,i) = xdot(i);
396         }
397 
398       for (octave_idx_type j = 1; j < n_out; j++)
399         {
400           integrate (tout(j));
401 
402           if (integration_error)
403             {
404               retval = DASRT_result (x_out, xdot_out, t_out);
405               return retval;
406             }
407 
408           if (istate == 4)
409             t_out(j) = t;
410           else
411             t_out(j) = tout(j);
412 
413           for (F77_INT i = 0; i < n; i++)
414             {
415               x_out(j,i) = x(i);
416               xdot_out(j,i) = xdot(i);
417             }
418 
419           if (istate == 4)
420             {
421               x_out.resize (j+1, n);
422               xdot_out.resize (j+1, n);
423               t_out.resize (j+1);
424               break;
425             }
426         }
427     }
428 
429   retval = DASRT_result (x_out, xdot_out, t_out);
430 
431   return retval;
432 }
433 
434 DASRT_result
integrate(const ColumnVector & tout,const ColumnVector & tcrit)435 DASRT::integrate (const ColumnVector& tout, const ColumnVector& tcrit)
436 {
437   DASRT_result retval;
438 
439   Matrix x_out;
440   Matrix xdot_out;
441   ColumnVector t_outs = tout;
442 
443   octave_idx_type n_out = tout.numel ();
444   F77_INT n = octave::to_f77_int (size ());
445 
446   if (n_out > 0 && n > 0)
447     {
448       x_out.resize (n_out, n);
449       xdot_out.resize (n_out, n);
450 
451       octave_idx_type n_crit = tcrit.numel ();
452 
453       if (n_crit > 0)
454         {
455           octave_idx_type i_crit = 0;
456           octave_idx_type i_out = 1;
457           double next_crit = tcrit(0);
458           double next_out;
459           while (i_out < n_out)
460             {
461               bool do_restart = false;
462 
463               next_out = tout(i_out);
464               if (i_crit < n_crit)
465                 next_crit = tcrit(i_crit);
466 
467               bool save_output = false;
468               double t_out;
469 
470               if (next_crit == next_out)
471                 {
472                   set_stop_time (next_crit);
473                   t_out = next_out;
474                   save_output = true;
475                   i_out++;
476                   i_crit++;
477                   do_restart = true;
478                 }
479               else if (next_crit < next_out)
480                 {
481                   if (i_crit < n_crit)
482                     {
483                       set_stop_time (next_crit);
484                       t_out = next_crit;
485                       save_output = false;
486                       i_crit++;
487                       do_restart = true;
488                     }
489                   else
490                     {
491                       clear_stop_time ();
492                       t_out = next_out;
493                       save_output = true;
494                       i_out++;
495                     }
496                 }
497               else
498                 {
499                   set_stop_time (next_crit);
500                   t_out = next_out;
501                   save_output = true;
502                   i_out++;
503                 }
504 
505               integrate (t_out);
506 
507               if (integration_error)
508                 {
509                   retval = DASRT_result (x_out, xdot_out, t_outs);
510                   return retval;
511                 }
512 
513               if (istate == 4)
514                 t_out = t;
515 
516               if (save_output)
517                 {
518                   for (F77_INT i = 0; i < n; i++)
519                     {
520                       x_out(i_out-1,i) = x(i);
521                       xdot_out(i_out-1,i) = xdot(i);
522                     }
523 
524                   t_outs(i_out-1) = t_out;
525 
526                   if (istate == 4)
527                     {
528                       x_out.resize (i_out, n);
529                       xdot_out.resize (i_out, n);
530                       t_outs.resize (i_out);
531                       i_out = n_out;
532                     }
533                 }
534 
535               if (do_restart)
536                 force_restart ();
537             }
538 
539           retval = DASRT_result (x_out, xdot_out, t_outs);
540         }
541       else
542         {
543           retval = integrate (tout);
544 
545           if (integration_error)
546             return retval;
547         }
548     }
549 
550   return retval;
551 }
552 
553 std::string
error_message(void) const554 DASRT::error_message (void) const
555 {
556   std::string retval;
557 
558   std::ostringstream buf;
559   buf << t;
560   std::string t_curr = buf.str ();
561 
562   switch (istate)
563     {
564     case 1:
565       retval = "a step was successfully taken in intermediate-output mode.";
566       break;
567 
568     case 2:
569       retval = "integration completed by stepping exactly to TOUT";
570       break;
571 
572     case 3:
573       retval = "integration to tout completed by stepping past TOUT";
574       break;
575 
576     case 4:
577       retval = "integration completed by finding one or more roots of G at T";
578       break;
579 
580     case -1:
581       retval = "a large amount of work has been expended (t =" + t_curr + ')';
582       break;
583 
584     case -2:
585       retval = "the error tolerances are too stringent";
586       break;
587 
588     case -3:
589       retval = "error weight became zero during problem. (t = " + t_curr +
590                "; solution component i vanished, and atol or atol(i) == 0)";
591       break;
592 
593     case -6:
594       retval = "repeated error test failures on the last attempted step (t = "
595                + t_curr + ')';
596       break;
597 
598     case -7:
599       retval = "the corrector could not converge (t = " + t_curr + ')';
600       break;
601 
602     case -8:
603       retval = "the matrix of partial derivatives is singular (t = " + t_curr +
604                ')';
605       break;
606 
607     case -9:
608       retval = "the corrector could not converge (t = " + t_curr +
609                "; repeated test failures)";
610       break;
611 
612     case -10:
613       retval = "corrector could not converge because IRES was -1 (t = "
614                + t_curr + ')';
615       break;
616 
617     case -11:
618       retval = "return requested in user-supplied function (t = " + t_curr +
619                ')';
620       break;
621 
622     case -12:
623       retval = "failed to compute consistent initial conditions";
624       break;
625 
626     case -33:
627       retval = "unrecoverable error (see printed message)";
628       break;
629 
630     default:
631       retval = "unknown error state";
632       break;
633     }
634 
635   return retval;
636 }
637