Minsky
rungeKutta.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2021
3  @author Russell Standish
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "minsky.h"
21 #include "rungeKutta.h"
22 #include "variableValue.h"
23 #include "error.h"
24 #include "matrix.h"
25 #include "rungeKutta.rcd"
26 #include "rungeKutta.xcd"
27 #include "simulation.rcd"
28 #include "minsky_epilogue.h"
29 
30 #include <gsl/gsl_errno.h>
31 #include <gsl/gsl_odeiv2.h>
32 #include <vector>
33 
34 //#include <thread>
35 // std::thread apparently not supported on MXE for now...
36 #include <boost/thread.hpp>
37 
38 using namespace std;
39 
40 namespace minsky
41 {
44  // void doOneEvent(bool idleTasksOnly);
45 
46  /*
47  For using GSL Runge-Kutta routines
48  */
49 
50  int RungeKutta::RKfunction(double t, const double y[], double f[], void *params)
51  {
52  if (params==NULL) return GSL_EBADFUNC;
53  try
54  {
55  ((RungeKutta*)params)->evalEquations(f,t,y);
56  }
57  catch (std::exception& e)
58  {
59  ((RungeKutta*)params)->threadErrMsg=e.what();
60  return GSL_EBADFUNC;
61  }
62  return GSL_SUCCESS;
63  }
64 
65  int RungeKutta::jacobian(double t, const double y[], double * dfdy, double dfdt[], void * params)
66  {
67  if (params==NULL) return GSL_EBADFUNC;
68  Matrix jac(stockVars.size(), dfdy);
69  try
70  {
71  ((RungeKutta*)params)->evalJacobian(jac,t,y);
72  }
73  catch (std::exception& e)
74  {
75  ((RungeKutta*)params)->threadErrMsg=e.what();
76  return GSL_EBADFUNC;
77  }
78  return GSL_SUCCESS;
79  }
80 
81  struct RKdata
82  {
83  gsl_odeiv2_system sys;
84  gsl_odeiv2_driver* driver;
85 
86  static void errHandler(const char* reason, const char* file, int line, int gsl_errno) {
87  throw ecolab::error("gsl: %s:%d: %s",file,line,reason);
88  }
89 
91  gsl_set_error_handler(errHandler);
92  sys.function=RungeKutta::RKfunction;
93  sys.jacobian=RungeKutta::jacobian;
94  sys.dimension=minsky->stockVars.size();
95  sys.params=minsky;
96  const gsl_odeiv2_step_type* stepper;
97  switch (minsky->order)
98  {
99  case 1:
100  if (!minsky->implicit)
101  throw ecolab::error("First order explicit solver not available");
102  stepper=gsl_odeiv2_step_rk1imp;
103  break;
104  case 2:
105  stepper=minsky->implicit? gsl_odeiv2_step_rk2imp: gsl_odeiv2_step_rk2;
106  break;
107  case 4:
108  stepper=minsky->implicit? gsl_odeiv2_step_rk4imp: gsl_odeiv2_step_rkf45;
109  break;
110  default:
111  throw ecolab::error("order %d solver not supported",minsky->order);
112  }
113  driver = gsl_odeiv2_driver_alloc_y_new
114  (&sys, stepper, minsky->stepMax, minsky->epsAbs,
115  minsky->epsRel);
116  gsl_odeiv2_driver_set_hmax(driver, minsky->stepMax);
117  gsl_odeiv2_driver_set_hmin(driver, minsky->stepMin);
118  }
119  ~RKdata() {gsl_odeiv2_driver_free(driver);}
120  };
121 
122  void RungeKutta::rkreset()
123  {
124  if (order==1 && !implicit)
125  ode.reset(); // do explicit Euler
126  else
127  ode.reset(new RKdata(this)); // set up GSL ODE routines
128  }
129 
130  void RungeKutta::rkstep()
131  {
132  if (nSteps<1) return;
133  running=true; // this ensure reset flag is cleared
134  resetIfFlagged();
135  running=true;
136 
137  // create a private copy for worker thread use
138  auto stockVarsCopy(stockVars);
139  RKThreadRunning=true;
140  int err=GSL_SUCCESS;
141  // run RK algorithm on a separate worker thread so as to not block UI. See ticket #6
142  boost::thread rkThread([&]() {
143  try
144  {
145  double tp=reverse? -t: t;
146  if (ode)
147  {
148  gsl_odeiv2_driver_set_nmax(ode->driver, nSteps);
149  // we need to update Minsky's t synchronously to support the t operator
150  // potentially means t and stockVars out of sync on GUI, but should still be thread safe
151  err=gsl_odeiv2_driver_apply(ode->driver, &tp, numeric_limits<double>::max(),
152  stockVarsCopy.data());
153  }
154  else // do explicit Euler method
155  {
156  vector<double> d(stockVarsCopy.size());
157  for (int i=0; i<nSteps; ++i, tp+=stepMax)
158  {
159  evalEquations(d.data(), tp, stockVarsCopy.data());
160  for (size_t j=0; j<d.size(); ++j)
161  stockVarsCopy[j]+=d[j];
162  }
163  }
164  t=reverse? -tp:tp;
165  }
166  catch (const std::exception& ex)
167  {
168  // catch any thrown exception, and report back to GUI thread
169  threadErrMsg=ex.what();
170  }
171  catch (...)
172  {
173  threadErrMsg="Unknown exception thrown on ODE solver thread";
174  }
175  RKThreadRunning=false;
176  });
177 
178  while (RKThreadRunning)
179  {
180  // while waiting for thread to finish, check and process any UI events
181  usleep(1000);
182  doOneEvent(false);
183  }
184  rkThread.join();
185 
186  if (!threadErrMsg.empty())
187  {
188  auto msg=std::move(threadErrMsg);
189  threadErrMsg.clear(); // to be sure, to be sure
190  // rethrow exception so message gets displayed to user
191  throw runtime_error(msg);
192  }
193 
194  if (resetIfFlagged())
195  return; // in case reset() was called during the step evaluation
196 
197  switch (err)
198  {
199  case GSL_SUCCESS: case GSL_EMAXITER: break;
200  case GSL_FAILURE:
201  throw error("unspecified error GSL_FAILURE returned");
202  case GSL_EBADFUNC:
203  gsl_odeiv2_driver_reset(ode->driver);
204  throw error("Invalid arithmetic operation detected");
205  default:
206  throw error("gsl error: %s",gsl_strerror(err));
207  }
208 
209  stockVars.swap(stockVarsCopy);
210 
211  // update flow variables
212  evalEquations();
213 
214  }
215 
216  void RungeKutta::evalJacobian(Matrix& jac, double t, const double sv[])
217  {
218  EvalOpBase::t=reverse? -t: t;
219  const double reverseFactor=reverse? -1: 1;
220  // firstly evaluate the flow variables. Initialise to flowVars so
221  // that no input vars are correctly initialised
222  auto flow=flowVars;
223  for (size_t i=0; i<equations.size(); ++i)
224  equations[i]->eval(flow.data(), flow.size(), sv);
225 
226  // then determine the derivatives with respect to variable j
227  for (size_t j=0; j<stockVars.size(); ++j)
228  {
229  vector<double> ds(stockVars.size()), df(flowVars.size());
230  ds[j]=1;
231  for (size_t i=0; i<equations.size(); ++i)
232  equations[i]->deriv(df.data(), df.size(), ds.data(), sv, flow.data());
233  vector<double> d(stockVars.size());
234  evalGodley.eval(d.data(), df.data());
235  for (vector<Integral>::iterator i=integrals.begin();
236  i!=integrals.end(); ++i)
237  {
238  assert(i->stock->idx()>=0 && i->input().idx()>=0);
239  d[i->stock->idx()] =
240  i->input().isFlowVar()? df[i->input().idx()]: ds[i->input().idx()];
241  }
242  for (size_t i=0; i<stockVars.size(); i++)
243  jac(i,j)=reverseFactor*d[i];
244  }
245 
246  }
247 
248  void RungeKutta::evalEquations(double result[], double t, const double vars[])
249  {
250  EvalOpBase::t=reverse? -t: t;
251  const double reverseFactor=reverse? -1: 1;
252  // firstly evaluate the flow variables. Initialise to flowVars so
253  // that no input vars are correctly initialised
254  auto flow(flowVars);
255  for (size_t i=0; i<equations.size(); ++i)
256  equations[i]->eval(flow.data(), flow.size(), vars);
257 
258  // then create the result using the Godley table
259  for (size_t i=0; i<stockVars.size(); ++i) result[i]=0;
260  evalGodley.eval(result, flow.data());
261 
262  // integrations are kind of a copy
263  for (vector<Integral>::iterator i=integrals.begin(); i<integrals.end(); ++i)
264  {
265  if (i->input().idx()<0)
266  {
267  if (i->operation)
268  minsky().displayErrorItem(*i->operation);
269  throw error("integral not wired");
270  }
271  // enable element-wise integration of tensor variables. for feature 147
272  assert(i->input().size()==i->stock->size());
273  for (size_t j=0; j<i->input().size(); ++j)
274  result[i->stock->idx()+j] = reverseFactor *
275  (i->input().isFlowVar()? flow[i->input().idx()+j] : vars[i->input().idx()+j]);
276  }
277  }
278 
279 
280 }
function f
Definition: canvas.m:1
void doOneEvent(bool idletasksOnly)
checks if any GUI events are waiting, and proces an event if so
Definition: tclmain.cc:161
void displayErrorItem(const Item &op) const
indicate operation item has error, if visible, otherwise contining group
Definition: minsky.cc:1230
convenience class for accessing matrix elements from a data array
Definition: matrix.h:26
static void errHandler(const char *reason, const char *file, int line, int gsl_errno)
Definition: rungeKutta.cc:86
minsky::Minsky minsky
Definition: pyminsky.cc:28
STL namespace.
gsl_odeiv2_driver * driver
Definition: rungeKutta.cc:84
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
RKdata(RungeKutta *minsky)
Definition: rungeKutta.cc:90
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::RungeKutta)
gsl_odeiv2_system sys
Definition: rungeKutta.cc:83