1 #include "MUQ/Modeling/RootfindingIVP.h"
2
3 // Sundials includes
4 #include <cvodes/cvodes.h>
5 #include <nvector/nvector_serial.h>
6 #include <cvodes/cvodes_dense.h> /* prototype for CVDense */
7
8 namespace pt = boost::property_tree;
9 using namespace muq::Utilities;
10 using namespace muq::Modeling;
11
RootfindingIVP(std::shared_ptr<WorkPiece> rhs,std::shared_ptr<WorkPiece> root,pt::ptree const & pt,std::shared_ptr<AnyAlgebra> algebra)12 RootfindingIVP::RootfindingIVP(std::shared_ptr<WorkPiece> rhs, std::shared_ptr<WorkPiece> root, pt::ptree const& pt, std::shared_ptr<AnyAlgebra> algebra) : ODEBase(rhs, pt, algebra), root(root), maxSteps(pt.get<unsigned int>("Rootfinder.MaxSteps", (int)1e10)), maxTime(pt.get<double>("Rootfinder.MaxTime", 1.0e3)), maxErrorTests(pt.get<unsigned int>("Rootfinder.MaxErrorTests", 100)) {
13 // we must know the number of inputs for both the rhs and the root and they must have at least one (the state)
14 assert(rhs->numInputs>0);
15 assert(root->numInputs>0);
16
17 // we must know the number of outputs for the root
18 assert(root->numOutputs>0);
19
20 // set the input and output types
21 UpdateInputOutputTypes();
22 }
23
~RootfindingIVP()24 RootfindingIVP::~RootfindingIVP() {}
25
FindRoot(ref_vector<boost::any> const & inputs,int const wrtIn,int const wrtOut,DerivativeMode const & mode)26 Eigen::VectorXi RootfindingIVP::FindRoot(ref_vector<boost::any> const& inputs, int const wrtIn, int const wrtOut, DerivativeMode const& mode) {
27 // the number of inputs must be at least the the number of inputs required by the rhs and the root
28 assert(inputs.size()>=rhs->numInputs+root->numInputs-1-(autonomous? 0 : 1));
29
30 // clear the results
31 ClearResults();
32
33 // create the state vector (have to do a hard copy --- N_Vector is a pointer to the data, the pointer has been declared const, not the data)
34 N_Vector state;
35 DeepCopy(state, boost::any_cast<const N_Vector&>(inputs[0]));
36
37 // create a data structure to pass around in Sundials
38 auto data = std::make_shared<ODEData>(rhs, root, inputs, autonomous, wrtIn, wrtOut);
39 if( !autonomous ) {
40 const boost::any time = 0.0;
41 data->inputs.insert(data->inputs.begin(), time);
42 }
43
44 // set solver to null
45 void* cvode_mem = nullptr;
46
47 // create the solver memory
48 cvode_mem = CVodeCreate(multiStep, solveMethod);
49 assert(CheckFlag((void*)cvode_mem, "CVodeCreate", 0));
50
51 // initialize the solver
52 CreateSolverMemory(cvode_mem, state, data);
53
54 // sensState will hold several N_Vectors (because it's a Jacobian)
55 N_Vector *sensState = nullptr;
56 int paramSize = -1;
57
58 if( wrtIn>=0 && wrtOut==0 && wrtIn<rhs->numInputs ) { // we are computing the derivative wrt one of the rhs parameters
59 paramSize = algebra->Size(inputs[wrtIn]); // the dimension of the parameter
60
61 // set up sensitivity vector
62 sensState = N_VCloneVectorArray_Serial(paramSize, state);
63 assert(CheckFlag((void *)sensState, "N_VCloneVectorArray_Serial", 0));
64
65 // initialize the sensitivies to zero
66 for( int is=0; is<paramSize; ++is ) {
67 N_VConst(0.0, sensState[is]);
68 }
69
70 // set up solver for sensitivity
71 SetUpSensitivity(cvode_mem, paramSize, sensState);
72
73 // initalize the derivative information
74 InitializeDerivative(1, NV_LENGTH_S(state), paramSize, mode);
75 }
76
77 // tell the solver how evaluate the root
78 int flag = CVodeRootInit(cvode_mem, root->numOutputs, EvaluateRoot);
79 assert(CheckFlag(&flag, "CVodeRootInit", 1));
80
81 // set the maximum number of steps
82 flag = CVodeSetMaxNumSteps(cvode_mem, maxSteps);
83 assert(CheckFlag(&flag, "CVodeSetMaxNumSteps", 1));
84
85 // set the maximum number of error test failures
86 flag = CVodeSetMaxErrTestFails(cvode_mem, maxErrorTests);
87 assert(CheckFlag(&flag, "CVodeSetMaxErrorTestFails", 1));
88
89 // set the intial time
90 double t = 0.0;
91
92 // each element corresponds to a vector of desired times, first: the current index of that vector, second: the size of that vector
93 if( inputs.size()>rhs->numInputs+root->numInputs-1 ) {
94 ref_vector<boost::any> outputTimes(inputs.begin()+rhs->numInputs+root->numInputs-1, inputs.end());
95 std::vector<std::pair<unsigned int, unsigned int> > timeIndices = TimeIndices(outputTimes);
96
97 // first: the next time to integrate to, second: the output index
98 std::pair<double, int> nextTime;
99
100 while( NextTime(nextTime, timeIndices, outputTimes) ) {
101 // if we have to move forward --- i.e., not at the initial time or another output does not need the current time
102 if( std::fabs(nextTime.first-t)>1.0e-14 ) { // we have to move forward in time
103 flag = CVode(cvode_mem, nextTime.first, state, &t, CV_NORMAL);
104 assert(CheckFlag(&flag, "CVode", 1));
105 }
106
107 // if we are at the root, but the root time is not a time we asked for do not record!
108 if( std::fabs(nextTime.first-t)<1.0e-14 ) { // we do not have to move forward in time
109 // save the result at this timestep
110 if( timeIndices[nextTime.second].second>1 ) { // the output has more than one compnent ...
111 // .. save the current state as an element in the vector
112 DeepCopy(boost::any_cast<std::vector<N_Vector>&>(outputs[nextTime.second]) [timeIndices[nextTime.second].first-1], state);
113 } else { // the out has one component ...
114 // ... save the current state (not inside a vector)
115 DeepCopy(boost::any_cast<N_Vector&>(outputs[nextTime.second]), state);
116 }
117 }
118
119 if( flag==CV_ROOT_RETURN ) { break; }
120 }
121 }
122
123 if( flag!=CV_ROOT_RETURN ) {
124 // integrate forward in time
125 flag = CVode(cvode_mem, maxTime, state, &t, CV_NORMAL);
126 assert(CheckFlag(&flag, "CVode", 1));
127 }
128
129 // make sure we found a root
130 assert(flag==CV_ROOT_RETURN);
131
132 // set the output
133 outputs.insert(outputs.begin(), t);
134 outputs.insert(outputs.begin(), state);
135
136 if( sensState ) {
137 // get the sensitivity
138 flag = CVodeGetSens(cvode_mem, &t, sensState);
139 assert(CheckFlag(&flag, "CVodeGetSens", 1));
140
141 // create a new jacobian --- shallow copy
142 DlsMat& jac = boost::any_cast<DlsMat&>(*jacobian);
143 for( unsigned int col=0; col<paramSize; ++col ) {
144 DENSE_COL(jac, col) = NV_DATA_S(sensState[col]);
145 NV_DATA_S(sensState[col]) = nullptr;
146 }
147
148 // delete sensState
149 N_VDestroyVectorArray_Serial(sensState, paramSize);
150 }
151
152 // retrieve information about the root
153 Eigen::VectorXi rootsfound(root->numOutputs);
154 flag = CVodeGetRootInfo(cvode_mem, rootsfound.data());
155 assert(CheckFlag(&flag, "CVodeGetRootInfo", 1));
156
157 // free integrator memory (don't destory the state --- outputs is a pointer to it)
158 CVodeFree(&cvode_mem);
159
160 return rootsfound;
161 }
162
EvaluateImpl(ref_vector<boost::any> const & inputs)163 void RootfindingIVP::EvaluateImpl(ref_vector<boost::any> const& inputs) {
164 // find the first root to one of the root outputs
165 FindRoot(inputs);
166 }
167
JacobianImpl(unsigned int const wrtIn,unsigned int const wrtOut,ref_vector<boost::any> const & inputs)168 void RootfindingIVP::JacobianImpl(unsigned int const wrtIn, unsigned int const wrtOut, ref_vector<boost::any> const& inputs) {
169 if( wrtOut>0 ) {
170 std::cerr << std::endl << "ERROR: Cannot compute derivative infomration of muq::Modeling::RootfindingIVP for outputs other than the state at the root (the first output)" << std::endl << std::endl;
171
172 assert(wrtOut==0);
173 }
174
175 // find the first root to one of the root outputs
176 const Eigen::VectorXi& rootsfound = FindRoot(inputs, wrtIn, wrtOut);
177 unsigned int ind = 0;
178 for( ; ind<rootsfound.size(); ++ind ) {
179 if( rootsfound(ind)!=0 ) { break; }
180 }
181
182 // the inputs to the rhs function
183 ref_vector<boost::any> rhsIns;
184 rhsIns.push_back(outputs[0]);
185 rhsIns.insert(rhsIns.end(), inputs.begin()+1, inputs.begin()+rhs->numInputs);
186
187 // the inputs to the root function
188 ref_vector<boost::any> rootIns;
189 rootIns.push_back(outputs[0]);
190 rootIns.insert(rootIns.end(), inputs.begin()+rhs->numInputs, inputs.begin()+rhs->numInputs+root->numInputs-1);
191
192 // the derivative of the root function at the final time -- derivative of the root wrt the state
193 const boost::any& dgdy = root->Jacobian(0, ind, rootIns);
194 const DlsMat& dgdyref = boost::any_cast<const DlsMat&>(dgdy); // 1 x stateSize matrix
195
196 // evaluate the right hand side
197 const std::vector<boost::any>& f = rhs->Evaluate(rhsIns); // stateSize x 1 vector
198 const N_Vector& fref = boost::any_cast<N_Vector>(f[0]);
199 assert(NV_LENGTH_S(fref)==dgdyref->N);
200
201 double dum = 0.0;
202 for( unsigned int i=0; i<dgdyref->N; ++i ) {
203 dum += DENSE_ELEM(dgdyref, 0, i)*NV_Ith_S(fref, i);
204 }
205
206 if( wrtIn>=rhs->numInputs ) { // derivative wrt root parameter
207 boost::any dgdpara = root->Jacobian(wrtIn-rhs->numInputs+1, ind, rootIns);
208 const DlsMat& dgdpararef = boost::any_cast<const DlsMat&>(dgdpara); // 1 x paramSize matrix
209 DenseScale(-1.0/dum, dgdpararef);
210
211 jacobian = NewDenseMat(NV_LENGTH_S(fref), dgdpararef->N);
212 DlsMat& jac = boost::any_cast<DlsMat&>(*jacobian); // stateSize x paramSize matrix
213 SetToZero(jac);
214
215 // update the jacobian jac += f.transpose()*dgdpara (outer product)
216 for( unsigned int i=0; i<jac->M; ++i ) {
217 for( unsigned int j=0; j<jac->N; ++j ) {
218 DENSE_ELEM(jac, i, j) = NV_Ith_S(fref, i)*DENSE_ELEM(dgdpararef, 0, j);
219 }
220 }
221
222 return;
223 }
224
225 // get a reference to the jacobian
226 DlsMat& jac = boost::any_cast<DlsMat&>(*jacobian); // stateSize x paramSize matrix
227 assert(jac->M==dgdyref->N);
228 assert(NV_LENGTH_S(fref)==jac->M);
229
230 if( wrtIn==0 ) { // derivative wrt the initial conditions
231 // add the identity
232 AddIdentity(jac);
233 }
234
235 DenseScale(-1.0/dum, dgdyref);
236
237 // the derivative of the final time wrt to the parameter; dtfdpara = -jac_{state}(root)*jac/dot(jac_{state}(root), f)
238 N_Vector dtfdic = N_VNew_Serial(jac->N);
239 for( unsigned int i=0; i<jac->N; ++i ) {
240 NV_Ith_S(dtfdic, i) = 0.0;
241 for( unsigned int j=0; j<jac->M; ++j ) {
242 NV_Ith_S(dtfdic, i) += DENSE_ELEM(dgdyref, 0, j)*DENSE_ELEM(jac, j, i);
243 }
244 }
245
246 // update the jacobian jac += f.transpose()*dtfdpara (outer product)
247 for( unsigned int i=0; i<jac->M; ++i ) {
248 for( unsigned int j=0; j<jac->N; ++j ) {
249 DENSE_ELEM(jac, i, j) += NV_Ith_S(fref, i)*NV_Ith_S(dtfdic, j);
250 }
251 }
252
253 // destroy temp vectors/matrices
254 N_VDestroy(dtfdic);
255 }
256
UpdateInputOutputTypes()257 void RootfindingIVP::UpdateInputOutputTypes() {
258 if( autonomous ) {
259 // the type of the first input (the state) for the rhs and the root
260 assert(rhs->InputType(0, false).compare(root->InputType(0, false))==0);
261 } else { // non-autonomous
262 // the input type of the second input of the rhs is the input of the first input of the root
263 assert(rhs->InputType(1, false).compare(root->InputType(0, false))==0);
264 }
265
266 // the second set of input parameters are the parameters for the root
267 for( auto intype : root->InputTypes() ) {
268 if( intype.first==0 ) { // we've already set the state type
269 continue;
270 }
271
272 inputTypes[rhs->numInputs+intype.first-1] = intype.second;
273 }
274
275 // the first output type is the state
276 outputTypes[0] = root->InputType(0, false);
277
278 // the second output is the time where the root is reached
279 outputTypes[1] = typeid(double).name();
280 }
281
EvaluateRoot(realtype t,N_Vector state,realtype * root,void * user_data)282 int RootfindingIVP::EvaluateRoot(realtype t, N_Vector state, realtype *root, void *user_data) {
283 // get the data type
284 ODEData* data = (ODEData*)user_data;
285 assert(data);
286 assert(data->root);
287
288 // set the state input
289 const boost::any& anyref = state;
290
291 // the inputs the root function
292 ref_vector<boost::any> rootins(data->inputs.begin()+data->rhs->numInputs, data->inputs.begin()+data->rhs->numInputs+data->root->numInputs-1);
293 rootins.insert(rootins.begin(), anyref);
294
295 // evaluate the root
296 const std::vector<boost::any>& result = data->root->Evaluate(rootins);
297 assert(result.size()==data->root->numOutputs);
298 for( unsigned int i=0; i<result.size(); ++i ) {
299 root[i] = boost::any_cast<double>(result[i]);
300 }
301
302 return 0;
303 }
304