MUQ  0.4.3
ODE.cpp
Go to the documentation of this file.
1 #include "MUQ/Modeling/ODE.h"
2 #include "MUQ/Modeling/ODEData.h"
3 
4 #include <cvodes/cvodes.h> /* access to CVODE */
5 #include <nvector/nvector_serial.h> /* access to serial N_Vector */
6 #include <sunmatrix/sunmatrix_dense.h> /* access to dense SUNMatrix */
7 #include <sunlinsol/sunlinsol_dense.h> /* access to dense SUNLinearSolver */
8 #include <sunnonlinsol/sunnonlinsol_newton.h> /* access to the newton SUNNonlinearSolver */
9 #include <sunnonlinsol/sunnonlinsol_fixedpoint.h> /* access to the fixed point SUNNonlinearSolver */
10 
11 #include <sunlinsol/sunlinsol_spgmr.h>
12 #include <sunlinsol/sunlinsol_spfgmr.h>
13 #include <sunlinsol/sunlinsol_spbcgs.h>
14 #include <sunlinsol/sunlinsol_sptfqmr.h>
15 #include <sunlinsol/sunlinsol_pcg.h>
16 
17 //#include <sundials/sundials_types.h> // definition of type
18 //#include <sundials/sundials_math.h> // contains the macros ABS, SQR, and EXP
19 
20 using namespace muq::Modeling;
21 
22 
23 
24 ODE::ODE(std::shared_ptr<ModPiece> const& rhsIn,
25  Eigen::VectorXd const& evalTimesIn,
26  bool isAuto,
27  boost::property_tree::ptree opts) : ModPiece(GetInputSizes(rhsIn, isAuto),
28  GetOutputSizes(rhsIn, evalTimesIn)),
29  isAutonomous(isAuto),
30  startTime(GetStartTime(evalTimesIn, opts)),
31  evalTimes(evalTimesIn),
32  rhs(rhsIn)
33 {
34  // Set the nonlinear solver options if possible
35  if(opts.count("NonlinearSolver")>0)
36  nonlinOpts.SetOptions(opts.get_child("NonlinearSolver"));
37 
38  // Set the linear solver options if possible
39  if(opts.count("LinearSolver")>0)
40  linOpts.SetOptions(opts.get_child("LinearSolver"));
41 
42  // Set the integrator options if possible
43  if(opts.count("Integrator")>0)
44  intOpts.SetOptions(opts.get_child("Integrator"));
45 
46 }
47 
48 double ODE::GetStartTime(Eigen::VectorXd const& evalTimesIn,
49  boost::property_tree::ptree const& opts)
50 {
51  // Make sure the evalTimes are in ascending order
52  for(int i=0; i<evalTimesIn.size()-1; ++i){
53  if(evalTimesIn(i+1)<=evalTimesIn(i)){
54  std::stringstream msg;
55  msg << "ERROR: Evaluation times are not strictly increasing: t_{" << i+1 << "}-t_{" << i << "}=" << evalTimesIn(i+1)-evalTimesIn(i);
56  throw std::runtime_error(msg.str());
57  }
58  }
59 
60  // Check to see if a start time was set in the options
61  double startTime;
62  if(opts.count("StartTime")>0){
63  startTime = opts.get<double>("StartTime");
64 
65  // Throw an error if the specified start time is AFTER the first evaluation time
66  if(startTime>evalTimesIn(0) + std::numeric_limits<double>::epsilon()){
67  throw std::runtime_error("ERROR: Start time specified in options to ODE is after first evaluation time.");
68  }
69 
70  }else{
71  startTime = evalTimesIn(0);
72  }
73 
74  return startTime;
75 }
76 
77 Eigen::VectorXi ODE::GetInputSizes(std::shared_ptr<ModPiece> const& rhsIn,
78  bool isAuto)
79 {
80  if(isAuto){
81  return rhsIn->inputSizes;
82  }else{
83  return rhsIn->inputSizes.tail(rhsIn->inputSizes.size()-1);
84  }
85 }
86 
87 Eigen::VectorXi ODE::GetOutputSizes(std::shared_ptr<ModPiece> const& rhsIn,
88  Eigen::VectorXd const& evalTimes)
89 {
90  int numTimes = evalTimes.size();
91  int stateDim = rhsIn->outputSizes(0);
92 
93  Eigen::VectorXi output(1);
94  output(0) = numTimes*stateDim;
95  return output;
96 }
97 
98 
99 
100 
101 
102 void ODE::NonlinearSolverOptions::SetOptions(boost::property_tree::ptree const& opts)
103 {
104  method = opts.get("Method", "Newton");
105  maxIters = opts.get("MaximumIterations", -1);
106 }
107 
108 void ODE::LinearSolverOptions::SetOptions(boost::property_tree::ptree const& opts)
109 {
110  method = opts.get("Method", "Dense");
111  maxIters = opts.get("MaximumIterations", -1);
112 }
113 
114 void ODE::IntegratorOptions::SetOptions(boost::property_tree::ptree const& opts)
115 {
116  method = opts.get("Method", "BDF");
117  reltol = opts.get("RelativeTolerance", 1e-5);
118  abstol = opts.get("AbsoluteTolerance", 1e-5);
119 
120  sensRelTol = opts.get("SensRelTol", -1);
121  sensAbsTol = opts.get("SensAbsTol", -1);
122 
123  adjRelTol = opts.get("SensRelTol", -1);
124  adjAbsTol = opts.get("SensAbsTol", -1);
125 
126  maxStepSize = opts.get("MaximumStepSize", -1);
127  maxNumSteps = opts.get("MaximumSteps", -1);
128 
129  checkPtGap = opts.get("CheckPointSteps", 100);
130 }
131 
132 
133 std::pair<SUNLinearSolver, SUNMatrix> ODE::CreateLinearSolver(void *cvode_mem, N_Vector const& state)
134 {
135  int flag;
136  int stateSize = NV_LENGTH_S(state);
137 
138  SUNMatrix rhsJac = NULL; // jacobian matrix of RHS
139  SUNLinearSolver LS = NULL;
140 
141  // If the nonlinear solver is Newton's method, then we need a linear solver too.
142  if(nonlinOpts.method=="Newton"){
143 
144  if(linOpts.method=="Dense")
145  rhsJac = SUNDenseMatrix(stateSize, stateSize);
146 
147  if(linOpts.method=="Dense"){
148  LS = SUNLinSol_Dense(state, rhsJac);
149  CheckFlag((void *)LS, "SUNLinSol_Dense", 0);
150 
151  }else if(linOpts.method=="SPGMR"){
152  LS = SUNLinSol_SPGMR(state, 0, linOpts.maxIters);
153  CheckFlag((void *)LS, "SUNLinSol_SPGMR", 0);
154  }else if(linOpts.method=="SPFGMR"){
155  LS = SUNLinSol_SPFGMR(state, 0, linOpts.maxIters);
156  CheckFlag((void *)LS, "SUNLinSol_SPFGMR", 0);
157  }else if(linOpts.method=="SPBCGS"){
158  LS = SUNLinSol_SPBCGS(state, 0, linOpts.maxIters);
159  CheckFlag((void *)LS, "SUNLinSol_SPBCGS", 0);
160  }else if(linOpts.method=="SPTFQMR"){
161  LS = SUNLinSol_SPTFQMR(state, 0, linOpts.maxIters);
162  CheckFlag((void *)LS, "SUNLinSol_SPTFQMR", 0);
163  }else if(linOpts.method=="PCG"){
164  LS = SUNLinSol_PCG(state, 0, linOpts.maxIters);
165  CheckFlag((void *)LS, "SUNLinSol_PGC", 0);
166  }else{
167  std::stringstream msg;
168  msg << "ERROR: Invalid linear solver method \"" << linOpts.method << "\". Valid options are \"Dense\", \"SPGMR\", \"SPFGMR\", \"SPBCGS\", \"SPTFQMR\", or \"PCG\"\n";
169  throw std::runtime_error(msg.str());
170  }
171 
172  flag = CVodeSetLinearSolver(cvode_mem, LS, rhsJac);
173  CheckFlag(&flag, "CVodeSetLinearSolver", 1);
174 
175  /* Set a user-supplied Jacobian function */
176  if(linOpts.method=="Dense"){
177  flag = CVodeSetJacFn(cvode_mem, Interface::RHSJacobian);
178  CheckFlag(&flag, "CVodeSetJacFn", 1);
179  }else{
180  flag = CVodeSetJacTimes(cvode_mem, NULL, Interface::RHSJacobianAction);
181  CheckFlag(&flag, "CVodeSetJacTimes", 1);
182  }
183 
184  }
185 
186  return std::make_pair(LS, rhsJac);
187 }
188 
189 SUNNonlinearSolver ODE::CreateNonlinearSolver(void *cvode_mem, N_Vector const& state)
190 {
191  int flag;
192 
193  SUNNonlinearSolver NLS = NULL;
194  if(nonlinOpts.method=="Newton"){
195  NLS = SUNNonlinSol_Newton(state);
196  CheckFlag( (void *)NLS, "SUNNonlinSol_Newton", 0);
197 
198  }else if(nonlinOpts.method=="Iter"){
199 
200  /* create fixed point nonlinear solver object */
201  NLS = SUNNonlinSol_FixedPoint(state, 0);
202  CheckFlag((void *)NLS, "SUNNonlinSol_FixedPoint", 0);
203  }
204 
205  // Set the maximum nubmer of iterations on the nonlinear solver
206  if(nonlinOpts.maxIters>0){
207  flag = SUNNonlinSolSetMaxIters(NLS, nonlinOpts.maxIters);
208  CheckFlag(&flag, "SUNNonlinSolSetMaxIters", 1);
209  }
210 
211  flag = CVodeSetNonlinearSolver(cvode_mem, NLS);
212  CheckFlag(&flag, "CVodeSetNonlinearSolver", 1);
213 
214  return NLS;
215 }
216 
217 
218 std::pair<SUNLinearSolver, SUNMatrix> ODE::CreateAdjointLinearSolver(void *cvode_mem, N_Vector const& state, int indexB)
219 {
220  int flag;
221  int stateSize = NV_LENGTH_S(state);
222 
223  SUNMatrix rhsJac = NULL; // jacobian matrix of RHS
224  SUNLinearSolver LS = NULL;
225 
226  // If the nonlinear solver is Newton's method, then we need a linear solver too.
227  if(nonlinOpts.method=="Newton"){
228 
229  if(linOpts.method=="Dense")
230  rhsJac = SUNDenseMatrix(stateSize, stateSize);
231 
232  if(linOpts.method=="Dense"){
233  LS = SUNLinSol_Dense(state, rhsJac);
234  CheckFlag((void *)LS, "SUNLinSol_Dense", 0);
235 
236  }else if(linOpts.method=="SPGMR"){
237  LS = SUNLinSol_SPGMR(state, 0, linOpts.maxIters);
238  CheckFlag((void *)LS, "SUNLinSol_SPGMR", 0);
239  }else if(linOpts.method=="SPFGMR"){
240  LS = SUNLinSol_SPFGMR(state, 0, linOpts.maxIters);
241  CheckFlag((void *)LS, "SUNLinSol_SPFGMR", 0);
242  }else if(linOpts.method=="SPBCGS"){
243  LS = SUNLinSol_SPBCGS(state, 0, linOpts.maxIters);
244  CheckFlag((void *)LS, "SUNLinSol_SPBCGS", 0);
245  }else if(linOpts.method=="SPTFQMR"){
246  LS = SUNLinSol_SPTFQMR(state, 0, linOpts.maxIters);
247  CheckFlag((void *)LS, "SUNLinSol_SPTFQMR", 0);
248  }else if(linOpts.method=="PCG"){
249  LS = SUNLinSol_PCG(state, 0, linOpts.maxIters);
250  CheckFlag((void *)LS, "SUNLinSol_PGC", 0);
251  }else{
252  std::stringstream msg;
253  msg << "ERROR: Invalid linear solver method \"" << linOpts.method << "\". Valid options are \"Dense\", \"SPGMR\", \"SPFGMR\", \"SPBCGS\", \"SPTFQMR\", or \"PCG\"\n";
254  throw std::runtime_error(msg.str());
255  }
256 
257  flag = CVodeSetLinearSolverB(cvode_mem, indexB, LS, rhsJac);
258  CheckFlag(&flag, "CVodeSetLinearSolverB", 1);
259 
260  /* Set a user-supplied Jacobian function */
261  if(linOpts.method=="Dense"){
262  flag = CVodeSetJacFnB(cvode_mem, indexB, &Interface::AdjointJacobian);
263  CheckFlag(&flag, "CVodeSetJacFnB", 1);
264  }else{
265  flag = CVodeSetJacTimesB(cvode_mem, indexB, NULL, &Interface::AdjointJacobianAction);
266  CheckFlag(&flag, "CVodeSetJacTimesB", 1);
267  }
268  }
269 
270  return std::make_pair(LS, rhsJac);
271 }
272 
273 SUNNonlinearSolver ODE::CreateAdjointNonlinearSolver(void *cvode_mem, N_Vector const& state, int indexB)
274 {
275  int flag;
276 
277  SUNNonlinearSolver NLS = NULL;
278  if(nonlinOpts.method=="Newton"){
279  NLS = SUNNonlinSol_Newton(state);
280  CheckFlag( (void *)NLS, "SUNNonlinSol_Newton", 0);
281 
282  }else if(nonlinOpts.method=="Iter"){
283 
284  /* create fixed point nonlinear solver object */
285  NLS = SUNNonlinSol_FixedPoint(state, 0);
286  CheckFlag((void *)NLS, "SUNNonlinSol_FixedPoint", 0);
287  }
288 
289  // Set the maximum nubmer of iterations on the nonlinear solver
290  if(nonlinOpts.maxIters>0){
291  flag = SUNNonlinSolSetMaxIters(NLS, nonlinOpts.maxIters);
292  CheckFlag(&flag, "SUNNonlinSolSetMaxIters", 1);
293  }
294 
295  flag = CVodeSetNonlinearSolverB(cvode_mem, indexB, NLS);
296  CheckFlag(&flag, "CVodeSetNonlinearSolverB", 1);
297 
298  return NLS;
299 }
300 
301 
303 {
304  // Used for monitoring the success of CVODE calls
305  int flag;
306 
307  // The initial condition
308  Eigen::VectorXd const& x0 = inputs.at(0).get();
309  const unsigned int stateSize = x0.size();
310 
311  auto rhsData = std::make_shared<ODEData>(rhs,
312  inputs,
313  isAutonomous,
314  -1);
315 
316  // Create an NVector version of the state that CVODES understands.
317  // Also map the memory in that vector to an object Eigen can understand.
318  N_Vector state = N_VNew_Serial(stateSize);
319  Eigen::Map<Eigen::VectorXd> stateMap(NV_DATA_S(state), stateSize);
320 
321  // Set the initial condition
322  stateMap = x0;
323 
324  // Set up for forward integration
325  void *cvode_mem = NULL;
326  if(intOpts.method=="BDF"){
327  cvode_mem = CVodeCreate(CV_BDF);
328  }else if(intOpts.method=="Adams"){
329  cvode_mem = CVodeCreate(CV_ADAMS);
330  }else{
331  std::stringstream msg;
332  msg << "ERROR: Invalid integrator method \"" << intOpts.method << "\". Valid options are \"BDF\" or \"ADAMS\"\n";
333  throw std::runtime_error(msg.str());
334  }
335 
336  // Initialize CVODES with the RHS function, initial time, and initial state.
337  flag = CVodeInit(cvode_mem, Interface::RHS, startTime, state);
338  CheckFlag(&flag, "CVodeSetUserData", 1);
339 
340  flag = CVodeSStolerances(cvode_mem, intOpts.reltol, intOpts.abstol);
341  CheckFlag(&flag, "CVodeSStolerances", 1);
342 
343  flag = CVodeSetUserData(cvode_mem, rhsData.get());
344  CheckFlag(&flag, "CVodeSetUserData", 1);
345 
346  flag = CVodeSetStopTime(cvode_mem, evalTimes(evalTimes.size()-1));
347  CheckFlag(&flag, "CVodeSetStopTime", 1);
348 
349  /* -------- Linear Solver and Jacobian ----------- */
350  SUNMatrix rhsJac = NULL;
351  SUNLinearSolver LS;
352  std::tie(LS,rhsJac) = CreateLinearSolver(cvode_mem, state);
353 
354  /* -------- Nonlinear solver ----------- */
355  SUNNonlinearSolver NLS = NULL;
356  NLS = CreateNonlinearSolver(cvode_mem, state);
357 
358  /* Set max steps between outputs */
359  if(intOpts.maxNumSteps>0){
360  flag = CVodeSetMaxNumSteps(cvode_mem, intOpts.maxNumSteps);
361  CheckFlag(&flag, "CVodeSetMaxNumSteps", 1);
362  }
363 
364  // Resize the output vector
365  outputs.resize(1);
366  outputs.at(0).resize(outputSizes(0));
367 
368  // The current time
369  double t = startTime;
370  unsigned int tind=0;
371  // Check to see if the starting time is equal to the first evalTime
372  if(std::fabs(evalTimes(0)-startTime)<1e-14){
373  // copy the initial state into the output
374  outputs.at(0).segment(tind*stateSize, stateSize) = x0;
375  // increment the time index
376  ++tind;
377  }
378 
379  // loop through all the times where we want evaluations
380  for(; tind<evalTimes.size(); ++tind ) {
381  flag = CVode(cvode_mem, evalTimes(tind), state, &t, CV_NORMAL);
382  CheckFlag(&flag, "CVode", 1);
383 
384  outputs.at(0).segment(tind*stateSize, stateSize) = stateMap;
385  }
386 
387  N_VDestroy(state);
388  if(rhsJac != NULL)
389  SUNMatDestroy(rhsJac);
390 
391  CVodeFree(&cvode_mem);
392 
393  if(LS != NULL)
394  SUNLinSolFree(LS);
395 
396  SUNNonlinSolFree(NLS);
397 }
398 
399 void ODE::GradientImpl(unsigned int outWrt,
400  unsigned int inWrt,
401  ref_vector<Eigen::VectorXd> const& inputs,
402  Eigen::VectorXd const& sens)
403 {
404  // Used for monitoring the success of CVODE calls
405  int flag;
406 
407  // The initial condition
408  Eigen::VectorXd const& x0 = inputs.at(0).get();
409  const unsigned int stateSize = x0.size();
410 
411  auto rhsData = std::make_shared<ODEData>(rhs,
412  inputs,
413  isAutonomous,
414  inWrt);
415 
416  // Create an NVector version of the state that CVODES understands.
417  // Also map the memory in that vector to an object Eigen can understand.
418  N_Vector state = N_VNew_Serial(stateSize);
419  Eigen::Map<Eigen::VectorXd> stateMap(NV_DATA_S(state), stateSize);
420 
421  // Set the initial condition
422  stateMap = x0;
423 
424  // Set up for forward integration
425  void *cvode_mem = NULL;
426  if(intOpts.method=="BDF"){
427  cvode_mem = CVodeCreate(CV_BDF);
428  }else if(intOpts.method=="Adams"){
429  cvode_mem = CVodeCreate(CV_ADAMS);
430  }else{
431  std::stringstream msg;
432  msg << "ERROR: Invalid integrator method \"" << intOpts.method << "\". Valid options are \"BDF\" or \"ADAMS\"\n";
433  throw std::runtime_error(msg.str());
434  }
435 
436  // Initialize CVODES with the RHS function, initial time, and initial state.
437  flag = CVodeInit(cvode_mem, Interface::RHS, startTime, state);
438  CheckFlag(&flag, "CVodeSetUserData", 1);
439 
440  flag = CVodeSStolerances(cvode_mem, intOpts.reltol, intOpts.abstol);
441  CheckFlag(&flag, "CVodeSStolerances", 1);
442 
443  flag = CVodeSetUserData(cvode_mem, rhsData.get());
444  CheckFlag(&flag, "CVodeSetUserData", 1);
445 
446  flag = CVodeSetStopTime(cvode_mem, evalTimes(evalTimes.size()-1));
447  CheckFlag(&flag, "CVodeSetStopTime", 1);
448 
449 
450  /* -------- Linear Solver and Jacobian ----------- */
451  SUNMatrix rhsJac = NULL;
452  SUNLinearSolver LS;
453  std::tie(LS,rhsJac) = CreateLinearSolver(cvode_mem, state);
454 
455  /* -------- Nonlinear solver ----------- */
456  SUNNonlinearSolver NLS = NULL;
457  NLS = CreateNonlinearSolver(cvode_mem, state);
458 
459  /* Set max steps between outputs */
460  if(intOpts.maxNumSteps>0){
461  flag = CVodeSetMaxNumSteps(cvode_mem, intOpts.maxNumSteps);
462  CheckFlag(&flag, "CVodeSetMaxNumSteps", 1);
463  }
464 
465  // Initialize the adjoint problem and specify the number of steps between checkpoints
466  flag = CVodeAdjInit(cvode_mem, intOpts.checkPtGap, CV_HERMITE);
467  CheckFlag(&flag, "CVodeAdjInit", 1);
468 
469  /* --------- Forward Integration ---------------- */
470 
471  // Resize the output vector
472  outputs.resize(1);
473  outputs.at(0).resize(outputSizes(0));
474 
475  // The current time
476  double t = startTime;
477  int tind=0;
478  int ncheck = 0;
479 
480  // Check to see if the starting time is equal to the first evalTime
481  if(std::fabs(evalTimes(0)-startTime)<1e-14){
482  // copy the initial state into the output
483  outputs.at(0).segment(tind*stateSize, stateSize) = x0;
484  // increment the time index
485  ++tind;
486  }
487 
488  // loop through all the times where we want evaluations
489  for(; tind<evalTimes.size(); ++tind ) {
490  flag = CVodeF(cvode_mem, evalTimes(tind), state, &t, CV_NORMAL, &ncheck);
491  CheckFlag(&flag, "CVodeF", 1);
492 
493  outputs.at(0).segment(tind*stateSize, stateSize) = stateMap;
494  }
495 
496 
497  /* --------- Set up for adjoint solve ---------------- */
498  const unsigned int paramSize = inputSizes(inWrt);
499 
500  N_Vector lambda = N_VNew_Serial(stateSize);
501  CheckFlag((void *)lambda, "N_VNew", 0);
502 
503  N_Vector nvGrad = N_VNew_Serial(paramSize);
504  CheckFlag((void *)nvGrad, "N_VNew", 0);
505 
506  Eigen::Map<Eigen::VectorXd> lambdaMap(NV_DATA_S(lambda), stateSize);
507  Eigen::Map<Eigen::VectorXd> nvGradMap(NV_DATA_S(nvGrad), paramSize);
508 
509  lambdaMap = -1.0*sens.tail(stateSize);
510  nvGradMap = Eigen::VectorXd::Zero(paramSize);
511 
512  // Create the backward integrator
513  int indexB;
514  if(intOpts.method=="BDF"){
515  flag = CVodeCreateB(cvode_mem, CV_BDF, &indexB);
516  }else if(intOpts.method=="Adams"){
517  flag = CVodeCreateB(cvode_mem, CV_ADAMS, &indexB);
518  }else{
519  std::stringstream msg;
520  msg << "ERROR: Invalid integrator method \"" << intOpts.method << "\". Valid options are \"BDF\" or \"ADAMS\"\n";
521  throw std::runtime_error(msg.str());
522  }
523  CheckFlag(&flag, "CVodeCreateB", 1);
524 
525  // Initialize the backward integrator
526  double finalTime = evalTimes(evalTimes.size()-1);
527  flag = CVodeInitB(cvode_mem, indexB, Interface::AdjointRHS, finalTime, lambda);
528  CheckFlag(&flag, "CVodeInitB", 1);
529 
530  // Set the scalar relative and absolute tolerance
531  flag = CVodeSStolerancesB(cvode_mem, indexB, intOpts.reltol, intOpts.abstol);
532  CheckFlag(&flag, "CVodeSStolerancesB", 1);
533 
534  // Attach user data
535  flag = CVodeSetUserDataB(cvode_mem, indexB, rhsData.get());
536  CheckFlag(&flag, "CVodeSetUserDataB", 1);
537 
538 
539  // Set up the linear and nonlinear solvers for the adjoint problem.
540  SUNMatrix adjRhsJac = NULL;
541  SUNLinearSolver adjLS;
542  std::tie(adjLS,adjRhsJac) = CreateAdjointLinearSolver(cvode_mem, lambda, indexB);
543 
544  SUNNonlinearSolver adjNLS = NULL;
545  adjNLS = CreateAdjointNonlinearSolver(cvode_mem, lambda, indexB);
546 
547  // Initialize backward quadrature to accumulate sensitivity wrt parameters
548  flag = CVodeQuadInitB(cvode_mem, indexB, &Interface::AdjointQuad, nvGrad);
549  CheckFlag(&flag, "CVodeQuadInitB", 1);
550 
551  // Tell CVODES whether or not the quadrature should be included in the error control
552  flag = CVodeSetQuadErrConB(cvode_mem, indexB, true);
553  CheckFlag(&flag, "CVodeSetQuadErrConB", 1);
554 
555  // Set the quadrature tolerances
556  flag = CVodeQuadSStolerancesB(cvode_mem, indexB, intOpts.reltol, intOpts.abstol);
557  CheckFlag(&flag, "CVodeQuadSStolerancesB", 1);
558 
559  /* --------- Adjoint (Backward) Integration ---------------- */
560 
561  for(tind=evalTimes.size()-2; tind>=0; --tind ) {
562 
563  // integrate the adjoint variable back in time
564  flag = CVodeB(cvode_mem, evalTimes(tind), CV_NORMAL);
565  CheckFlag(&flag, "CVodeB", 1);
566 
567  // get the adjoint variable
568  flag = CVodeGetB(cvode_mem, indexB, &t, lambda);
569  CheckFlag(&flag, "CVodeGetB", 1);
570 
571  // get the quadrature
572  flag = CVodeGetQuadB(cvode_mem, indexB, &t, nvGrad);
573  CheckFlag(&flag, "CVodeGetQuadB", 1);
574 
575  // discontinuous change in variable
576  lambdaMap -= sens.segment(tind*stateSize, stateSize);
577 
578  // reinitialize the adjoint integrator because of the discontinuity in the state
579  flag = CVodeReInitB(cvode_mem, indexB, evalTimes(tind), lambda);
580  CheckFlag(&flag, "CVodeReInitB", 1);
581  flag = CVodeQuadReInitB(cvode_mem, indexB, nvGrad);
582  CheckFlag(&flag, "CVodeQuadReInitB", 1);
583  }
584 
585  // Make sure we integrate back to the starting time
586  if( std::fabs(t-startTime)>1.0e-14 ) {
587  // integrate the adjoint variable back in time
588  flag = CVodeB(cvode_mem, startTime, CV_NORMAL);
589  CheckFlag(&flag, "CVodeB", 1);
590 
591  // get the adjoint variable
592  flag = CVodeGetB(cvode_mem, indexB, &t, lambda);
593  CheckFlag(&flag, "CVodeGetB", 1);
594  }
595 
596  // Extract the gradient
597  flag = CVodeGetQuadB(cvode_mem, indexB, &t, nvGrad);
598  CheckFlag(&flag, "CVodeGetQuadB", 1);
599 
600  gradient = nvGradMap;
601 
602  // if wrt to the initial conditions add identity*sens
603  if( inWrt==0 ) {
604  for(tind=0; tind<evalTimes.size(); ++tind ) {
605  gradient += sens.segment(tind*stateSize, paramSize);
606  }
607  }
608 
609  N_VDestroy(state);
610  N_VDestroy(lambda);
611  N_VDestroy(nvGrad);
612 
613  CVodeFree(&cvode_mem);
614 
615  SUNNonlinSolFree(NLS);
616  SUNNonlinSolFree(adjNLS);
617 
618  if(LS != NULL)
619  SUNLinSolFree(LS);
620  if(adjLS != NULL)
621  SUNLinSolFree(adjLS);
622 
623  if(rhsJac != NULL)
624  SUNMatDestroy(rhsJac);
625  if(adjRhsJac != NULL)
626  SUNMatDestroy(adjRhsJac);
627 
628  // Make sure to update the one-step cache since we've updated the outputs vector
629  if(cacheEnabled){
630  cacheInput.resize(inputs.size());
631  for(int i=0; i<inputs.size(); ++i)
632  cacheInput.at(i) = inputs.at(i);
633  }
634 }
635 
636 
637 
638 void ODE::JacobianImpl(unsigned int outWrt,
639  unsigned int inWrt,
640  ref_vector<Eigen::VectorXd> const& inputs)
641 {
642  // Used for monitoring the success of CVODE calls
643  int flag;
644 
645  // The initial condition
646  Eigen::VectorXd const& x0 = inputs.at(0).get();
647  const unsigned int stateSize = x0.size();
648 
649  auto rhsData = std::make_shared<ODEData>(rhs,
650  inputs,
651  isAutonomous,
652  inWrt);
653 
654  // Create an NVector version of the state that CVODES understands.
655  // Also map the memory in that vector to an object Eigen can understand.
656  N_Vector state = N_VNew_Serial(stateSize);
657  Eigen::Map<Eigen::VectorXd> stateMap(NV_DATA_S(state), stateSize);
658 
659  // Set the initial condition
660  stateMap = x0;
661 
662  // Set up for forward integration
663  void *cvode_mem = NULL;
664  if(intOpts.method=="BDF"){
665  cvode_mem = CVodeCreate(CV_BDF);
666  }else if(intOpts.method=="Adams"){
667  cvode_mem = CVodeCreate(CV_ADAMS);
668  }else{
669  std::stringstream msg;
670  msg << "ERROR: Invalid integrator method \"" << intOpts.method << "\". Valid options are \"BDF\" or \"ADAMS\"\n";
671  throw std::runtime_error(msg.str());
672  }
673 
674  // Initialize CVODES with the RHS function, initial time, and initial state.
675  flag = CVodeInit(cvode_mem, Interface::RHS, startTime, state);
676  CheckFlag(&flag, "CVodeSetUserData", 1);
677 
678  flag = CVodeSStolerances(cvode_mem, intOpts.reltol, intOpts.abstol);
679  CheckFlag(&flag, "CVodeSStolerances", 1);
680 
681  flag = CVodeSetUserData(cvode_mem, rhsData.get());
682  CheckFlag(&flag, "CVodeSetUserData", 1);
683 
684  flag = CVodeSetStopTime(cvode_mem, evalTimes(evalTimes.size()-1));
685  CheckFlag(&flag, "CVodeSetStopTime", 1);
686 
687  /* Set max steps between outputs */
688  if(intOpts.maxNumSteps>0){
689  flag = CVodeSetMaxNumSteps(cvode_mem, intOpts.maxNumSteps);
690  CheckFlag(&flag, "CVodeSetMaxNumSteps", 1);
691  }
692 
693  /* -------- Linear Solver and Jacobian ----------- */
694  SUNMatrix rhsJac = NULL;
695  SUNLinearSolver LS;
696  std::tie(LS,rhsJac) = CreateLinearSolver(cvode_mem, state);
697 
698  /* -------- Nonlinear solver ----------- */
699  SUNNonlinearSolver NLS = NULL;
700  NLS = CreateNonlinearSolver(cvode_mem, state);
701 
702  /* -------- Sensitivity Setup ----------- */
703  const unsigned int paramSize = inputSizes(inWrt);
704 
705  N_Vector *sensState = nullptr;
706  sensState = N_VCloneVectorArray_Serial(paramSize, state);
707  CheckFlag((void *)sensState, "N_VCloneVectorArray_Serial", 0);
708 
709  // initialize the sensitivies.
710  for( int is=0; is<paramSize; ++is )
711  N_VConst(0.0, sensState[is]);
712 
713  if(inWrt==0){ // If wrt the initial state, the initial sensitivities form the identity
714  for(int is=0; is<paramSize; ++is){
715  NV_Ith_S(sensState[is],is) = 1.0;
716  }
717  }
718 
719  flag = CVodeSensInit(cvode_mem, paramSize, CV_SIMULTANEOUS, Interface::SensitivityRHS, sensState);
720  CheckFlag(&flag, "CVodeSensInit", 1);
721 
722  SUNNonlinearSolver sensNLS = NULL;
723  if(nonlinOpts.method=="Newton"){
724  sensNLS = SUNNonlinSol_NewtonSens(paramSize+1,state);
725  }else{
726  sensNLS = SUNNonlinSol_FixedPointSens(paramSize+1, state, 0);
727  }
728 
729  flag = CVodeSetNonlinearSolverSensSim(cvode_mem, sensNLS);
730  CheckFlag((void *)sensState, "CVodeSetNonlinearSolverSensSim", 0);
731 
732  // set sensitivity tolerances
733  if((intOpts.sensRelTol<=0)||(intOpts.sensAbsTol<=0)){
734  flag = CVodeSensEEtolerances(cvode_mem);
735  CheckFlag(&flag, "CVodeSensEEtolerances", 1);
736  }else{
737  Eigen::VectorXd sensAbsTol = intOpts.sensAbsTol*Eigen::VectorXd::Ones(paramSize);
738  flag = CVodeSensSStolerances(cvode_mem, intOpts.sensRelTol, sensAbsTol.data());
739  }
740 
741  //Eigen::VectorXd absTolVec = Eigen::VectorXd::Constant(paramSize, intOpts.abstol);
742  // flag = CVodeSensSStolerances(cvode_mem, intOpts.reltol, absTolVec.data());
743  // CheckFlag(&flag, "CVodeSensSStolerances", 1);
744 
745  // // error control strategy should test the sensitivity variables
746  // flag = CVodeSetSensErrCon(cvode_mem, true);
747  // CheckFlag(&flag, "CVodeSetSensErrCon", 1);
748 
749  // Resize the output vector
750  outputs.resize(1);
751  outputs.at(0).resize(outputSizes(0));
752 
753  // Resize the jacobian vector
754  jacobian = Eigen::MatrixXd::Zero(outputSizes(0), paramSize);
755 
756  // The current time
757  double t = startTime;
758  unsigned int tind=0;
759 
760  // Check to see if the starting time is equal to the first evalTime
761  if(std::fabs(evalTimes(0)-startTime)<1e-14){
762 
763  // copy the initial state into the output
764  outputs.at(0).segment(tind*stateSize, stateSize) = x0;
765 
766  // If Jacobian is wrt state, then initial jac will be the identity, otherwise initial jac will be zero
767  if(inWrt==0){
768  jacobian.block(tind*stateSize, 0, stateSize, paramSize) = Eigen::MatrixXd::Identity(stateSize, paramSize);
769  }
770 
771  // increment the time index
772  ++tind;
773  }
774 
775  // loop through all the times where we want evaluations
776  for(; tind<evalTimes.size(); ++tind ) {
777 
778  // Integrate the ODE and the sensitivity equations forward in time
779  flag = CVode(cvode_mem, evalTimes(tind), state, &t, CV_NORMAL);
780  CheckFlag(&flag, "CVode", 1);
781 
782  // Extract the ODE state
783  outputs.at(0).segment(tind*stateSize, stateSize) = stateMap;
784 
785  // Extract the sensitivities
786  int flag = CVodeGetSens(cvode_mem, &t, sensState);
787  CheckFlag(&flag, "CVodeGetSense", 1);
788 
789  for( unsigned int i=0; i<stateSize; ++i ) {
790  for( unsigned int j=0; j<paramSize; ++j ) {
791  jacobian(tind*stateSize+i, j) += NV_Ith_S(sensState[j], i);
792  }
793  }
794 
795  } // end of time loop
796 
797 
798  N_VDestroy(state);
799  N_VDestroyVectorArray_Serial(sensState, paramSize);
800 
801  if(rhsJac != NULL)
802  SUNMatDestroy(rhsJac);
803 
804  CVodeFree(&cvode_mem);
805 
806  if(LS != NULL)
807  SUNLinSolFree(LS);
808 
809  SUNNonlinSolFree(NLS);
810  SUNNonlinSolFree(sensNLS);
811 
812  // Make sure to update the one-step cache since we've updated the outputs vector
813  if(cacheEnabled){
814  cacheInput.resize(inputs.size());
815  for(int i=0; i<inputs.size(); ++i)
816  cacheInput.at(i) = inputs.at(i);
817  }
818 }
819 
820 
821 
822 void ODE::ApplyJacobianImpl(unsigned int outWrt,
823  unsigned int inWrt,
824  ref_vector<Eigen::VectorXd> const& inputs,
825  Eigen::VectorXd const& vec)
826 {
827  // Used for monitoring the success of CVODE calls
828  int flag;
829 
830  // The initial condition
831  Eigen::VectorXd const& x0 = inputs.at(0).get();
832  const unsigned int stateSize = x0.size();
833 
834  auto rhsData = std::make_shared<ODEData>(rhs,
835  inputs,
836  isAutonomous,
837  inWrt,
838  vec);
839 
840  // Create an NVector version of the state that CVODES understands.
841  // Also map the memory in that vector to an object Eigen can understand.
842  N_Vector state = N_VNew_Serial(stateSize);
843  Eigen::Map<Eigen::VectorXd> stateMap(NV_DATA_S(state), stateSize);
844 
845  // Set the initial condition
846  stateMap = x0;
847 
848  // Set up for forward integration
849  void *cvode_mem = NULL;
850  if(intOpts.method=="BDF"){
851  cvode_mem = CVodeCreate(CV_BDF);
852  }else if(intOpts.method=="Adams"){
853  cvode_mem = CVodeCreate(CV_ADAMS);
854  }else{
855  std::stringstream msg;
856  msg << "ERROR: Invalid integrator method \"" << intOpts.method << "\". Valid options are \"BDF\" or \"ADAMS\"\n";
857  throw std::runtime_error(msg.str());
858  }
859 
860  // Initialize CVODES with the RHS function, initial time, and initial state.
861  flag = CVodeInit(cvode_mem, Interface::RHS, startTime, state);
862  CheckFlag(&flag, "CVodeSetUserData", 1);
863 
864  flag = CVodeSStolerances(cvode_mem, intOpts.reltol, intOpts.abstol);
865  CheckFlag(&flag, "CVodeSStolerances", 1);
866 
867  flag = CVodeSetUserData(cvode_mem, rhsData.get());
868  CheckFlag(&flag, "CVodeSetUserData", 1);
869 
870  flag = CVodeSetStopTime(cvode_mem, evalTimes(evalTimes.size()-1));
871  CheckFlag(&flag, "CVodeSetStopTime", 1);
872 
873  /* Set max steps between outputs */
874  if(intOpts.maxNumSteps>0){
875  flag = CVodeSetMaxNumSteps(cvode_mem, intOpts.maxNumSteps);
876  CheckFlag(&flag, "CVodeSetMaxNumSteps", 1);
877  }
878 
879  /* -------- Linear Solver and Jacobian ----------- */
880  SUNMatrix rhsJac = NULL;
881  SUNLinearSolver LS;
882  std::tie(LS,rhsJac) = CreateLinearSolver(cvode_mem, state);
883 
884  /* -------- Nonlinear solver ----------- */
885  SUNNonlinearSolver NLS = NULL;
886  NLS = CreateNonlinearSolver(cvode_mem, state);
887 
888  /* -------- Sensitivity Setup ----------- */
889  const unsigned int paramSize = 1;
890 
891  N_Vector *sensState = nullptr;
892  sensState = N_VCloneVectorArray_Serial(paramSize, state);
893  CheckFlag((void *)sensState, "N_VCloneVectorArray_Serial", 0);
894 
895  // initialize the sensitivies.
896  N_VConst(0.0, sensState[0]);
897 
898  if(inWrt==0){ // If wrt the initial state, the initial sensitivity is the vector
899  for(int ind=0; ind<stateSize; ++ind){
900  NV_Ith_S(sensState[0],ind) = vec(ind);
901  }
902  }
903 
904  flag = CVodeSensInit(cvode_mem, paramSize, CV_SIMULTANEOUS, Interface::SensitivityRHS, sensState);
905  CheckFlag(&flag, "CVodeSensInit", 1);
906 
907  SUNNonlinearSolver sensNLS = NULL;
908  if(nonlinOpts.method=="Newton"){
909  sensNLS = SUNNonlinSol_NewtonSens(paramSize+1,state);
910  }else{
911  sensNLS = SUNNonlinSol_FixedPointSens(paramSize+1, state, 0);
912  }
913 
914  flag = CVodeSetNonlinearSolverSensSim(cvode_mem, sensNLS);
915  CheckFlag((void *)sensState, "CVodeSetNonlinearSolverSensSim", 0);
916 
917  // set sensitivity tolerances
918  flag = CVodeSensEEtolerances(cvode_mem);
919  CheckFlag(&flag, "CVodeSensEEtolerances", 1);
920 
921  //Eigen::VectorXd absTolVec = Eigen::VectorXd::Constant(paramSize, intOpts.abstol);
922  // flag = CVodeSensSStolerances(cvode_mem, intOpts.reltol, absTolVec.data());
923  // CheckFlag(&flag, "CVodeSensSStolerances", 1);
924 
925  // // error control strategy should test the sensitivity variables
926  // flag = CVodeSetSensErrCon(cvode_mem, true);
927  // CheckFlag(&flag, "CVodeSetSensErrCon", 1);
928 
929  // Resize the output vector
930  outputs.resize(1);
931  outputs.at(0).resize(evalTimes.size()*stateSize);
932 
933  // Resize the jacobian vector
934  jacobianAction = Eigen::VectorXd::Zero(evalTimes.size()*stateSize);
935 
936  // The current time
937  double t = startTime;
938  unsigned int tind=0;
939 
940  // Check to see if the starting time is equal to the first evalTime
941  if(std::fabs(evalTimes(0)-startTime)<1e-14){
942 
943  // copy the initial state into the output
944  outputs.at(0).segment(tind*stateSize, stateSize) = x0;
945 
946  // If Jacobian is wrt state, then initial jac will be the identity, otherwise initial jac will be zero
947  if(inWrt==0){
948  jacobianAction.segment(tind*stateSize,stateSize) = vec;
949  }
950 
951  // increment the time index
952  ++tind;
953  }
954 
955  // loop through all the times where we want evaluations
956  for(; tind<evalTimes.size(); ++tind ) {
957 
958  // Integrate the ODE and the sensitivity equations forward in time
959  flag = CVode(cvode_mem, evalTimes(tind), state, &t, CV_NORMAL);
960  CheckFlag(&flag, "CVode", 1);
961 
962  // Extract the ODE state
963  outputs.at(0).segment(tind*stateSize, stateSize) = stateMap;
964 
965  // Extract the sensitivities
966  int flag = CVodeGetSens(cvode_mem, &t, sensState);
967  CheckFlag(&flag, "CVodeGetSense", 1);
968 
969  for( unsigned int i=0; i<stateSize; ++i ) {
970  jacobianAction(tind*stateSize + i) += NV_Ith_S(sensState[0], i);
971  }
972 
973  } // end of time loop
974 
975 
976  N_VDestroy(state);
977  N_VDestroyVectorArray_Serial(sensState, paramSize);
978 
979  if(rhsJac != NULL)
980  SUNMatDestroy(rhsJac);
981 
982  CVodeFree(&cvode_mem);
983 
984  if(LS != NULL)
985  SUNLinSolFree(LS);
986 
987  SUNNonlinSolFree(NLS);
988  SUNNonlinSolFree(sensNLS);
989 
990  // Make sure to update the one-step cache since we've updated the outputs vector
991  if(cacheEnabled){
992  cacheInput.resize(inputs.size());
993  for(int i=0; i<inputs.size(); ++i)
994  cacheInput.at(i) = inputs.at(i);
995  }
996 }
997 
998 
999 int ODE::Interface::RHS(realtype time, N_Vector state, N_Vector timeDeriv, void *userData)
1000 {
1001  // get the data type
1002  ODEData* data = (ODEData*)userData;
1003  assert(data);
1004  assert(data->rhs);
1005 
1006  // set the state input
1007  Eigen::Map<Eigen::VectorXd> stateMap(NV_DATA_S(state), NV_LENGTH_S(state));
1008  data->UpdateInputs(stateMap, time);
1009 
1010  // evaluate the rhs
1011  Eigen::Map<Eigen::VectorXd> timeDerivMap(NV_DATA_S(timeDeriv), NV_LENGTH_S(timeDeriv));
1012  timeDerivMap = data->rhs->Evaluate(data->inputs) [0];
1013 
1014  return 0;
1015 }
1016 
1017 
1018 int ODE::Interface::RHSJacobianAction(N_Vector v, // The vector we want to apply the Jacobian to
1019  N_Vector Jv, // The Jacobian-vector product (output of this function)
1020  double time, // The current time
1021  N_Vector state, // The current state
1022  N_Vector timeDeriv, // The current time derivative
1023  void *userData,
1024  N_Vector tmp)
1025 {
1026 
1027  // get the data type
1028  ODEData* data = (ODEData*) userData;
1029  assert(data);
1030  assert(data->rhs);
1031 
1032  // set the state input
1033  Eigen::Map<Eigen::VectorXd> stateref(NV_DATA_S(state), NV_LENGTH_S(state));
1034  data->UpdateInputs(stateref, time);
1035 
1036  Eigen::VectorXd vEig = Eigen::Map<Eigen::VectorXd>(NV_DATA_S(v), NV_LENGTH_S(v));
1037 
1038  // Compute the action of the jacobian (wrt the state) on a vector
1039  Eigen::Map<Eigen::VectorXd> JvMap(NV_DATA_S(Jv), NV_LENGTH_S(Jv));
1040  JvMap = data->rhs->ApplyJacobian(0, data->autonomous? 0 : 1, data->inputs, vEig);
1041 
1042  return 0;
1043 }
1044 
1046  N_Vector state,
1047  N_Vector rhs,
1048  SUNMatrix jac,
1049  void *userData,
1050  N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) {
1051  // get the data type
1052  ODEData* data = (ODEData*)userData;
1053  assert(data);
1054  assert(data->rhs);
1055 
1056  // set the state input
1057  Eigen::Map<Eigen::VectorXd> stateMap(NV_DATA_S(state), NV_LENGTH_S(state));
1058  data->UpdateInputs(stateMap, time);
1059 
1060  // evaluate the jacobian
1061  Eigen::Map<Eigen::MatrixXd> jacMap(SUNDenseMatrix_Data(jac),
1062  SUNDenseMatrix_Rows(jac),
1063  SUNDenseMatrix_Columns(jac));
1064 
1065  jacMap = data->rhs->Jacobian(0, data->autonomous? 0 : 1, data->inputs);
1066 
1067  return 0;
1068 }
1069 
1070 
1071 int ODE::Interface::SensitivityRHS(int Ns, realtype time, N_Vector y, N_Vector ydot, N_Vector *ys, N_Vector *ySdot, void *user_data, N_Vector tmp1, N_Vector tmp2) {
1072 
1073  // get the data type
1074  ODEData* data = (ODEData*)user_data;
1075  assert(data);
1076  assert(data->rhs);
1077  unsigned int stateWrt = data->autonomous? 0 : 1; // Which input of the rhs holds the state?
1078 
1079  // set the state input
1080  Eigen::Map<Eigen::VectorXd> stateref(NV_DATA_S(y), NV_LENGTH_S(y));
1081  data->UpdateInputs(stateref, time);
1082 
1083  for( unsigned int i=0; i<Ns; ++i ) {
1084  Eigen::Map<Eigen::VectorXd> rhsMap(NV_DATA_S(ySdot[i]), stateref.size());
1085  Eigen::Map<Eigen::VectorXd> sensMap(NV_DATA_S(ys[i]), stateref.size());
1086 
1087  // Fill in (df/dy)*s_i
1088  rhsMap = data->rhs->ApplyJacobian(0, stateWrt, data->inputs, (Eigen::VectorXd)sensMap);
1089  }
1090 
1091  /* Check if the derivative is with respect to the initial condition or wrt
1092  to a parameter. If wrt to the initial conditions, then df/dp = 0 here.
1093  */
1094  if(data->wrtIn>0){
1095 
1096  // the derivative of the rhs wrt the parameter
1097  unsigned int rhsWrt = data->autonomous? data->wrtIn : data->wrtIn+1;
1098 
1099  if(data->isAction){
1100 
1101  Eigen::Map<Eigen::VectorXd> rhsMap(NV_DATA_S(ySdot[0]), stateref.size());
1102  //Eigen::Map<Eigen::VectorXd> sensMap(NV_DATA_S(ys[0]), stateref.size());
1103  rhsMap += data->rhs->ApplyJacobian(0,rhsWrt, data->inputs, data->actionVec);
1104 
1105  }else{
1106  const Eigen::MatrixXd& dfdp = data->rhs->Jacobian(0, rhsWrt, data->inputs);
1107 
1108  // add the df/dp_i component for each index of the parameter p_i
1109  for( unsigned int i=0; i<Ns; ++i ) {
1110  Eigen::Map<Eigen::VectorXd> rhsMap(NV_DATA_S(ySdot[i]), stateref.size());
1111  //Eigen::Map<Eigen::VectorXd> sensMap(NV_DATA_S(ys[i]), stateref.size());
1112 
1113  rhsMap += dfdp.col(i);
1114  }
1115  }
1116  }
1117 
1118  return 0;
1119 }
1120 
1121 
1122 
1123 int ODE::Interface::AdjointRHS(realtype time, N_Vector state, N_Vector lambda, N_Vector deriv, void *user_data) {
1124 
1125  // get the data type
1126  ODEData* data = (ODEData*)user_data;
1127  assert(data);
1128  assert(data->rhs);
1129 
1130  // set the state input
1131  Eigen::Map<Eigen::VectorXd> stateref(NV_DATA_S(state), NV_LENGTH_S(state));
1132  data->UpdateInputs(stateref, time);
1133 
1134  Eigen::Map<Eigen::VectorXd> lambdaMap(NV_DATA_S(lambda), NV_LENGTH_S(lambda));
1135  Eigen::VectorXd lam = lambdaMap;
1136 
1137  Eigen::Map<Eigen::VectorXd> derivMap(NV_DATA_S(deriv), NV_LENGTH_S(deriv));
1138 
1139  derivMap = -1.0*data->rhs->Gradient(0, data->autonomous? 0 : 1, data->inputs, lam);
1140 
1141  return 0;
1142 }
1143 
1144 
1145 int ODE::Interface::AdjointJacobianAction(N_Vector target, N_Vector output, realtype time, N_Vector state, N_Vector lambda, N_Vector adjRhs, void *user_data, N_Vector tmp) {
1146 
1147  // get the data type
1148  ODEData* data = (ODEData*)user_data;
1149  assert(data);
1150  assert(data->rhs);
1151 
1152  // set the state input
1153  Eigen::Map<Eigen::VectorXd> stateref(NV_DATA_S(state), NV_LENGTH_S(state));
1154  data->UpdateInputs(stateref, time);
1155 
1156  Eigen::Map<Eigen::VectorXd> targetMap(NV_DATA_S(target), NV_LENGTH_S(target));
1157  Eigen::VectorXd targ = targetMap;
1158 
1159  Eigen::Map<Eigen::VectorXd> outputMap(NV_DATA_S(output), NV_LENGTH_S(output));
1160  outputMap = -1.0*data->rhs->Gradient(0, data->autonomous? 0 : 1, data->inputs, targ);
1161 
1162  return 0;
1163 }
1164 
1165 
1166 
1167 int ODE::Interface::AdjointJacobian(double time, N_Vector state, N_Vector lambda, N_Vector rhs, SUNMatrix jac, void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) {
1168 
1169  // get the data type
1170  ODEData* data = (ODEData*)user_data;
1171  assert(data);
1172  assert(data->rhs);
1173 
1174  // set the state input
1175  Eigen::Map<Eigen::VectorXd> stateref(NV_DATA_S(state), NV_LENGTH_S(state));
1176  data->UpdateInputs(stateref, time);
1177 
1178  // evaluate the jacobian
1179  Eigen::Map<Eigen::MatrixXd> jacMap(SUNDenseMatrix_Data(jac),
1180  SUNDenseMatrix_Rows(jac),
1181  SUNDenseMatrix_Columns(jac));
1182 
1183  jacMap = -1.0*data->rhs->Jacobian(0, data->autonomous? 0 : 1, data->inputs).transpose();
1184 
1185  return 0;
1186 }
1187 
1188 int ODE::Interface::AdjointQuad(realtype time, N_Vector state, N_Vector lambda, N_Vector quadRhs, void *user_data) {
1189 
1190  // get the data type
1191  ODEData* data = (ODEData*)user_data;
1192  assert(data);
1193  assert(data->rhs);
1194 
1195  // set the state input
1196  Eigen::Map<Eigen::VectorXd> stateref(NV_DATA_S(state), NV_LENGTH_S(state));
1197  data->UpdateInputs(stateref, time);
1198 
1199  Eigen::Map<Eigen::VectorXd> lambdaMap(NV_DATA_S(lambda), NV_LENGTH_S(lambda));
1200  Eigen::VectorXd lam = lambdaMap;
1201 
1202  Eigen::Map<Eigen::VectorXd> nvQuadMap(NV_DATA_S(quadRhs), NV_LENGTH_S(quadRhs));
1203 
1204  nvQuadMap = data->rhs->Gradient(0, data->autonomous? data->wrtIn : data->wrtIn+1, data->inputs, lam);
1205 
1206  return 0;
1207 }
1208 
1209 
1210 
1211 
1212 void ODE::CheckFlag(void *returnvalue, const char *funcname, int opt)
1213 {
1214  int *retval;
1215  std::stringstream msg;
1216 
1217  /* Check if SUNDIALS function returned NULL pointer - no memory allocated */
1218  if (opt == 0 && returnvalue == NULL) {
1219  msg << "SUNDIALS_ERROR: " << funcname << " failed - returned NULL pointer";
1220  throw std::runtime_error(msg.str());
1221 
1222  /* Check if retval < 0 */
1223  }else if (opt == 1) {
1224  retval = (int *) returnvalue;
1225 
1226  if (*retval < 0){
1227  msg << "SUNDIALS_ERROR: " << funcname << " failed with retval = " << *retval;
1228  throw std::runtime_error(msg.str());
1229  }
1230 
1231  /* Check if function returned NULL pointer - no memory allocated */
1232  }else if (opt == 2 && returnvalue == NULL) {
1233  msg << "MEMORY_ERROR: " << funcname << " failed - returned NULL pointer";
1234  throw std::runtime_error(msg.str());
1235  }
1236 
1237 }
Provides an abstract interface for defining vector-valued model components.
Definition: ModPiece.h:148
Eigen::MatrixXd jacobian
Definition: ModPiece.h:506
const Eigen::VectorXi inputSizes
Definition: ModPiece.h:469
std::vector< Eigen::VectorXd > outputs
Definition: ModPiece.h:503
Eigen::VectorXd gradient
Definition: ModPiece.h:504
const Eigen::VectorXi outputSizes
Definition: ModPiece.h:472
std::vector< Eigen::VectorXd > cacheInput
Definition: ModPiece.h:480
Eigen::VectorXd jacobianAction
Definition: ModPiece.h:505
A helper class for Sundial's time integration.
Definition: ODEData.h:18
const bool autonomous
Is the RHS autonomous?
Definition: ODEData.h:67
const bool isAction
Definition: ODEData.h:73
ref_vector< Eigen::VectorXd > inputs
Definition: ODEData.h:64
Eigen::VectorXd actionVec
Definition: ODEData.h:72
void UpdateInputs(Eigen::Ref< const Eigen::VectorXd > const &newState, double const time)
Update the time and state inputs.
Definition: ODEData.cpp:47
const int wrtIn
The input we are computing the derivative wrt — negative indicates no derivative is being computed.
Definition: ODEData.h:70
std::shared_ptr< ModPiece > rhs
The right hand side of the ODE.
Definition: ODEData.h:56
const double startTime
The time when this simulation starts – either set in the opts ptree or set to the minimum of evalTime...
Definition: ODE.h:51
static Eigen::VectorXi GetInputSizes(std::shared_ptr< ModPiece > const &rhsIn, bool isAuto)
Definition: ODE.cpp:77
static void CheckFlag(void *returnvalue, const char *funcname, int opt)
Checks the return value produced by CVODES. Throws and exception if error occured.
Definition: ODE.cpp:1212
SUNNonlinearSolver CreateNonlinearSolver(void *cvode_mem, N_Vector const &state)
Definition: ODE.cpp:189
std::pair< SUNLinearSolver, SUNMatrix > CreateAdjointLinearSolver(void *cvode_mem, N_Vector const &state, int indexB)
Definition: ODE.cpp:218
virtual void JacobianImpl(unsigned int outWrt, unsigned int inWrt, ref_vector< Eigen::VectorXd > const &inputs) override
Definition: ODE.cpp:638
static Eigen::VectorXi GetOutputSizes(std::shared_ptr< ModPiece > const &rhsIn, Eigen::VectorXd const &evalTimes)
Definition: ODE.cpp:87
virtual void GradientImpl(unsigned int outWrt, unsigned int inWrt, ref_vector< Eigen::VectorXd > const &inputs, Eigen::VectorXd const &sens) override
Definition: ODE.cpp:399
IntegratorOptions intOpts
Definition: ODE.h:186
std::shared_ptr< ModPiece > rhs
Definition: ODE.h:192
LinearSolverOptions linOpts
Definition: ODE.h:185
std::pair< SUNLinearSolver, SUNMatrix > CreateLinearSolver(void *cvode_mem, N_Vector const &state)
Definition: ODE.cpp:133
virtual void ApplyJacobianImpl(unsigned int outWrt, unsigned int inWrt, ref_vector< Eigen::VectorXd > const &inputs, Eigen::VectorXd const &vec) override
Definition: ODE.cpp:822
SUNNonlinearSolver CreateAdjointNonlinearSolver(void *cvode_mem, N_Vector const &state, int indexB)
Definition: ODE.cpp:273
const bool isAutonomous
Does the RHS explicitly depend on time?
Definition: ODE.h:48
ODE(std::shared_ptr< ModPiece > const &rhsIn, Eigen::VectorXd const &evalTimesIn, boost::property_tree::ptree opts=boost::property_tree::ptree())
Definition: ODE.h:35
const Eigen::VectorXd evalTimes
Definition: ODE.h:189
NonlinearSolverOptions nonlinOpts
Definition: ODE.h:184
static double GetStartTime(Eigen::VectorXd const &evalTimesIn, boost::property_tree::ptree const &opts)
Definition: ODE.cpp:48
virtual void EvaluateImpl(ref_vector< Eigen::VectorXd > const &inputs) override
Integrates the ODE forward in time.
Definition: ODE.cpp:302
std::vector< std::reference_wrapper< const T > > ref_vector
A vector of references to something ...
Definition: WorkPiece.h:37
int int diyfp diyfp v
Definition: json.h:15163
void SetOptions(boost::property_tree::ptree const &opts)
Definition: ODE.cpp:114
static int RHSJacobianAction(N_Vector v, N_Vector Jv, realtype time, N_Vector state, N_Vector rhs, void *user_data, N_Vector tmp)
Definition: ODE.cpp:1018
static int RHSJacobian(realtype time, N_Vector state, N_Vector rhs, SUNMatrix jac, void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3)
Definition: ODE.cpp:1045
static int RHS(realtype time, N_Vector state, N_Vector deriv, void *user_data)
Definition: ODE.cpp:999
void SetOptions(boost::property_tree::ptree const &opts)
Definition: ODE.cpp:108
void SetOptions(boost::property_tree::ptree const &opts)
Definition: ODE.cpp:102