mbsim  4.0.0
MBSim Kernel
boost_odeint_integrator.h
1/* Copyright (C) 2017 Markus Friedrich
2
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 */
17
18#ifndef _BOOST_ODEINT_INTEGRATOR_H_
19#define _BOOST_ODEINT_INTEGRATOR_H_
20
21#include "root_finding_integrator.h"
22#include <mbsim/dynamic_system_solver.h>
23#include <mbsim/utils/eps.h>
24#include <boost/numeric/odeint/util/is_resizeable.hpp>
25#include <boost/numeric/odeint/stepper/stepper_categories.hpp>
26#include <boost/numeric/ublas/matrix.hpp>
27#include <boost/numeric/ublas/matrix_proxy.hpp>
28
29namespace boost {
30 namespace numeric {
31 namespace odeint {
32 // Enable fmatvec::Vec, the state type of MBSim, as a boost odeint state type.
33 template<>
34 struct is_resizeable<fmatvec::Vec> {
35 typedef boost::true_type type;
36 static const bool value=true;
37 };
38 }
39 }
40
41 // Enable fmatvec::Vec, the state type of MBSim, as a boost odeint state type.
42 inline int size(const fmatvec::Vec& v) {
43 return v.size();
44 }
45}
46
47namespace MBSim {
48
49 namespace BoostOdeintHelper {
50
51 // Convert a MBSim state (fmatvec::Vec) to the boost odeint state type.
52 // Most boost odeint integrators can handle arbitariy state type (see above how fmatvec::Vec is enabled for boost odeint).
53 // In this case the following function are defined as noop.
54 // If boost odeint requires a special state type then the state is copied.
55 inline void assign(fmatvec::Vec &d, const fmatvec::Vec &s) {
56 if(d.size()!=s.size()) d.resize(s.size(),fmatvec::NONINIT);
57 d=s;
58 }
59 template<typename Dst, typename Src>
60 inline void assign(Dst &d, const Src &s) {
61 if(static_cast<size_t>(d.size())!=static_cast<size_t>(s.size()))
62 d.resize(s.size());
63 std::copy(s.begin(), s.end(), d.begin());
64 }
65
66 // type to define a boost odeint system concept of type "system"
68 // type to define a boost odeint system concept of type "implicit system"
70
71 // Helper class to return a object of the boost odeint system concept of type SystemCategory
72 // giving functions for zd and jacobian.
73 // Declaration.
74 template<class SystemCategory, class ZdFunc, class JacFunc>
76 // Explicit spezialization for boost odeint system concept of type "system"
77 template<class ZdFunc, class JacFunc>
78 class BoostOdeintSystem<ExplicitSystemTag, ZdFunc, JacFunc> {
79 public:
80 BoostOdeintSystem(const ZdFunc& zdFunc__, const JacFunc&) : zdFunc_(zdFunc__) {}
81 const ZdFunc& operator()() const { return zdFunc_; }
82 private:
83 const ZdFunc& zdFunc_;
84 };
85 // Explicit spezialization for boost odeint system concept of type "implicit system"
86 template<class ZdFunc, class JacFunc>
87 class BoostOdeintSystem<ImplicitSystemTag, ZdFunc, JacFunc> {
88 public:
89 BoostOdeintSystem(const ZdFunc& zdFunc__, const JacFunc& jacFunc__) : implFunc_(make_pair(zdFunc__, jacFunc__)) {}
90 const std::pair<ZdFunc, JacFunc>& operator()() const { return implFunc_; }
91 private:
92 const std::pair<ZdFunc, JacFunc> implFunc_;
93 };
94 }
95
102 template<typename DOS>
104 protected:
105 // flag if the underlaying stepper is controlled
106 static constexpr bool isControlled{std::is_base_of<boost::numeric::odeint::controlled_stepper_tag,
107 typename DOS::UnderlayingStepperCategory>::value};
108 // helper typedef to enable member functions if the underlaying stepper is controlled
109 template<typename H>
110 using EnableIfControlled=typename std::enable_if<std::is_base_of<boost::numeric::odeint::controlled_stepper_tag,
111 typename H::UnderlayingStepperCategory>::value>::type;
112
113 public:
114 void integrate() override;
115 void preIntegrate() override;
116 void subIntegrate(double tSamplePoint) override;
117 void postIntegrate() override;
118
120 void setInitialStepSize(double dt0_) { dt0=dt0_; }
121
123 template<typename H=DOS, typename=EnableIfControlled<H>>
124 void setAbsoluteTolerance(double aTol_) { aTol=aTol_; }
125
127 template<typename H=DOS, typename=EnableIfControlled<H>>
128 void setRelativeTolerance(double rTol_) { rTol=rTol_; }
129
131 template<typename H=DOS, typename=EnableIfControlled<H>>
132 void setMaximumStepSize(double dtMax_) { dtMax=dtMax_; }
133
134 void initializeUsingXML(xercesc::DOMElement *element) override;
135 protected:
136 // boost odeint style member function calculating zd
137 void zd(const typename DOS::state_type &z, typename DOS::state_type &zd, const double t);
138 // boost odeint style member function calculating the jacobian
139 void jac(const typename DOS::state_type &z, boost::numeric::ublas::matrix<double> &jac, const double t,
140 typename DOS::state_type &ft);
141 // boost odeint style std::function's calculating zd
142 const std::function<void(const typename DOS::state_type&, typename DOS::state_type&, const double)> zdFunc
143 {bind(&BoostOdeintDOS<DOS>::zd, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)};
144 // boost odeint style std::function's calculating the jacobian
145 const std::function<void(const typename DOS::state_type&, boost::numeric::ublas::matrix<double>&c, const double,
146 typename DOS::state_type&)> jacFunc
147 {bind(&BoostOdeintDOS<DOS>::jac, this, std::placeholders::_1, std::placeholders::_2,
148 std::placeholders::_3, std::placeholders::_4)};
149 // Helper member which returns a object of the boost odeint system concept of type SystemCategory
150 // giving functions for zd and jacobian.
151 const BoostOdeintHelper::BoostOdeintSystem<typename DOS::SystemCategory, decltype(zdFunc), decltype(jacFunc)>
152 boostOdeintSystem{zdFunc, jacFunc};
153
154 // the boost odeint dense output stepper.
155 std::unique_ptr<DOS> dos;
156
157 // variables with corresponding setter functions
158 double dt0{1e-10};
159 double aTol{1e-6};
160 double rTol{1e-6};
161 double dtMax{0.1};
162
163 // internal variables
164 double tPlot;
165 size_t plotSample;
166 typename DOS::state_type zTemp;
167
168 // internal variables required for the numerical jacobian calculation
169 fmatvec::Vec zDisturbed;
170
171 // internal variables used for integrator statistics
172 size_t nrSteps;
173 size_t nrRHS;
174 size_t nrJacs;
175 size_t nrPlots;
176 size_t nrSVs;
177 size_t nrRoots;
178 size_t nrDriftCorr;
179 };
180
181 // implementation
182
183 template<typename DOS>
184 void BoostOdeintDOS<DOS>::zd(const typename DOS::state_type &z, typename DOS::state_type &zd, const double t) {
185 nrRHS++;
186 // RHS
187 system->setTime(t);
188 BoostOdeintHelper::assign(system->getState(), z);
189 system->resetUpToDate();
190 BoostOdeintHelper::assign(zd, system->evalzd());
191 if(dtPlot<0)
192 system->plot();
193 }
194
195 template<typename DOS>
196 void BoostOdeintDOS<DOS>::jac(const typename DOS::state_type &z, boost::numeric::ublas::matrix<double> &jac, const double t,
197 typename DOS::state_type &ft) {
198 nrJacs++;
199 // RHS jacobian
200 if(static_cast<size_t>(z.size())!=static_cast<size_t>(jac.size1()) ||
201 static_cast<size_t>(z.size())!=static_cast<size_t>(jac.size2()))
202 jac.resize(z.size(), z.size());
203 system->setTime(t);
204 BoostOdeintHelper::assign(system->getState(), z);
205 system->resetUpToDate();
206 BoostOdeintHelper::assign(zDisturbed, z);
207 fmatvec::Vec zd0=system->evalzd();
208
209 for(size_t i=0; i<static_cast<size_t>(z.size()); ++i) {
210 double delta=sqrt(macheps*std::max(1.e-5,abs(zDisturbed(i))));
211 double zDisturbediSave=zDisturbed(i);
212 zDisturbed(i)+=delta;
213 system->setState(zDisturbed);
214 system->resetUpToDate();
215 auto col=boost::numeric::ublas::column(jac, i);
216 auto zd=(system->evalzd()-zd0)/delta;
217 std::copy(zd.begin(), zd.end(), col.begin());
218 zDisturbed(i)=zDisturbediSave;
219 }
220
221 system->setTime(t+MBSim::epsroot);
222 system->setState(zDisturbed);
223 system->resetUpToDate();
224 BoostOdeintHelper::assign(ft, (system->evalzd()-zd0)*MBSim::epsrootInv);
225 }
226
227 template<typename DOS>
229 preIntegrate();
230 subIntegrate(tEnd);
231 postIntegrate();
232 }
233
234 template<typename DOS>
236 debugInit();
237
238 nrSteps=0;
239 nrRHS=0;
240 nrJacs=0;
241 nrPlots=0;
242 nrSVs=0;
243 nrRoots=0;
244 nrDriftCorr=0;
245
246 system->setTime(tStart);
247
248 // get initial state
249 if(z0.size()) {
250 if(z0.size()!=system->getzSize()+system->getisSize())
251 throwError("BoostOdeintDOS:: size of z0 does not match, must be " + std::to_string(system->getzSize()+system->getisSize()));
252 BoostOdeintHelper::assign(zTemp, z0(fmatvec::RangeV(0,system->getzSize()-1)));
253 system->setInternalState(z0(fmatvec::RangeV(system->getzSize(),z0.size()-1)));
254 }
255 else
256 BoostOdeintHelper::assign(zTemp, system->evalz0());
257
258 BoostOdeintHelper::assign(system->getState(), zTemp);
259 system->resetUpToDate();
260 system->computeInitialCondition();
261 nrPlots++;
262 system->plot();
263 plotSample=1;
264 tPlot=tStart+plotSample*dtPlot;
265 nrSVs++;
266 svLast <<= system->evalsv();
267 BoostOdeintHelper::assign(zTemp, system->getState()); // needed, as computeInitialCondition may change the state
268
269 // initialize odeint
270 dos.reset(new DOS(aTol, rTol, dtMax));
271 dos->initialize(zTemp, tStart, dt0);
272 }
273
274 template<typename DOS>
275 void BoostOdeintDOS<DOS>::subIntegrate(double tSamplePoint) {
276 // loop until at least tSamplePoint is reached
277 static constexpr double oneMinusEps = 1 - std::numeric_limits<double>::epsilon();
278 while(dos->current_time()<tSamplePoint*oneMinusEps) {
279 // make one step with odeint
280 nrSteps++;
281#if BOOST_VERSION >= 107100
282 // do not make a step which exceeds tSamplePoint
283 dos->setDtMax(std::min(dtMax, tSamplePoint-dos->current_time()));
284#endif
285 auto step=dos->do_step(boostOdeintSystem());
286
287 // check if a root exists in the current step
288 double curTimeAndState=dos->current_time(); // save current time/state as double: just used to avoid unnessesary system updates
289 system->setTime(dos->current_time());
290 BoostOdeintHelper::assign(system->getState(), dos->current_state());
291 system->resetUpToDate();
292 nrSVs++;
293 shift=signChangedWRTsvLast(system->evalsv());
294 // if a root exists in the current step ...
295 if(shift) {
296 // ... search the first root and set step.second to this time
297 double dt=step.second-step.first;
298 while(dt>dtRoot) {
299 dt/=2;
300 double tRoot=step.second-dt;
301 dos->calc_state(tRoot, zTemp);
302 curTimeAndState=tRoot;
303 system->setTime(tRoot);
304 BoostOdeintHelper::assign(system->getState(), zTemp);
305 system->resetUpToDate();
306 nrSVs++;
307 if(signChangedWRTsvLast(system->evalsv()))
308 step.second=tRoot;
309 }
310 // root found increase time by dt and set jsv
311 step.second+=dt;
312 dos->calc_state(step.second, zTemp);
313 curTimeAndState=step.second;
314 system->setTime(step.second);
315 BoostOdeintHelper::assign(system->getState(), zTemp);
316 system->resetUpToDate();
317 nrSVs++;
318 auto &sv=system->evalsv();
319 auto &jsv=system->getjsv();
320 for(int i=0; i<sv.size(); ++i)
321 jsv(i)=svLast(i)*sv(i)<0;
322 }
323
324 if(dtPlot>0) {
325 // plot every dtPlot up to the end of the current step (the current step end may already be shorted by roots)
326 static constexpr double onePlusEps = 1 + std::numeric_limits<double>::epsilon();
327 while(tPlot<=step.second*onePlusEps
328#if BOOST_VERSION < 107100
329 && tPlot<=tSamplePoint*onePlusEps
330#endif
331 ) {
332 dos->calc_state(tPlot, zTemp);
333 if(curTimeAndState!=tPlot) {
334 curTimeAndState=tPlot;
335 system->setTime(tPlot);
336 BoostOdeintHelper::assign(system->getState(), zTemp);
337 system->resetUpToDate();
338 }
339 nrPlots++;
340 system->plot();
341 if(msgAct(Status))
342 msg(Status)<<"t = "<<tPlot<<", dt="<<dos->current_time_step()<<" "<<std::flush;
343 system->updateInternalState();
344 plotSample++;
345 tPlot=tStart+plotSample*dtPlot;
346 }
347 }
348 else {
349 // for dtPlot<=0 plot every integrator step
350 nrPlots++;
351 system->plot();
352 if(msgAct(Status))
353 msg(Status)<<"t = "<<step.second<<", dt="<<dos->current_time_step()<<" "<<std::flush;
354 }
355
356#if BOOST_VERSION < 107100
357 if(step.second<tSamplePoint) {
358#endif
359 if(shift) {
360 // shift the system
361 dos->calc_state(step.second, zTemp);
362 if(curTimeAndState!=step.second) {
363 curTimeAndState=step.second;
364 system->setTime(step.second);
365 BoostOdeintHelper::assign(system->getState(), zTemp);
366 system->resetUpToDate();
367 }
368 if(plotOnRoot) {
369 nrPlots++;
370 system->plot();
371 }
372 nrRoots++;
373 system->resetUpToDate();
374 system->shift();
375 if(plotOnRoot) {
376 nrPlots++;
377 system->plot();
378 }
379 nrSVs++;
380 svLast=system->evalsv();
381 // reinit odeint with new state
382 BoostOdeintHelper::assign(zTemp, system->getState());
383 dos->initialize(zTemp, step.second, dos->current_time_step());
384 }
385 else {
386 // curTimeAndState may not be a step.second due to plotting -> reset DSS to this time/state if so
387 // (note that due to root finding curTimeAndSTate can also be different to step.second but in this case this code is not reached)
388 if((gMax>=0 || gdMax>=0) && curTimeAndState!=step.second) {
389 curTimeAndState=step.second;
390 system->setTime(step.second);
391 BoostOdeintHelper::assign(system->getState(), dos->current_state());
392 system->resetUpToDate();
393 }
394 // check if projection is needed (if a root was found projection is done anyway by shift())
395 bool reinitNeeded=false;
396 if(gMax>=0 and system->positionDriftCompensationNeeded(gMax)) {
397 system->projectGeneralizedPositions(3);
398 system->projectGeneralizedVelocities(3);
399 reinitNeeded=true;
400 }
401 else if(gdMax>=0 and system->velocityDriftCompensationNeeded(gdMax)) {
402 system->projectGeneralizedVelocities(3);
403 reinitNeeded=true;
404 }
405 if(reinitNeeded) {
406 nrDriftCorr++;
407 BoostOdeintHelper::assign(zTemp, system->getState());
408 dos->initialize(zTemp, step.second, dos->current_time_step());
409 }
410 system->updateStopVectorParameters();
411 }
412#if BOOST_VERSION < 107100
413 }
414 else {
415 // TODO: this code block (espezially the dos->initialize) should be avoided since initializting the integrator
416 // is expensive and not needed at macro sample points or updates of discrete states.
417 // For this the do_step call at the top must be restricted to reach at most tSamplePoint.
418 // However, this seem currently not possible with the boost odeint integrators.
419 // That's why we use this workaround here which has not the optimal performance.
420 dos->calc_state(tSamplePoint, zTemp);
421 curTimeAndState=tSamplePoint;
422 system->setTime(tSamplePoint);
423 BoostOdeintHelper::assign(system->getState(), zTemp);
424 system->resetUpToDate();
425 dos->initialize(zTemp, tSamplePoint, dos->current_time_step());
426 system->updateStopVectorParameters();
427 }
428#endif
429
430#if BOOST_VERSION >= 107100
431 // if the system time or state was changed after do_step (for plotting, root-finding, ...)
432 // we need to reset it to the integrator state
433 if(curTimeAndState!=dos->current_time()) {
434 system->setTime(dos->current_time());
435 BoostOdeintHelper::assign(system->getState(), dos->current_state());
436 system->resetUpToDate();
437 }
438#endif
439 system->updateInternalState();
440 }
441 }
442
443 template<typename DOS>
444 void BoostOdeintDOS<DOS>::postIntegrate() {
445 msg(Info)<<std::endl;
446 msg(Info)<<"Integration statistics:"<<std::endl;
447 msg(Info)<<"nrSteps = "<<nrSteps<<std::endl;
448 msg(Info)<<"nrRHS = "<<nrRHS<<std::endl;
449 msg(Info)<<"nrJacs = "<<nrJacs<<std::endl;
450 msg(Info)<<"nrPlots = "<<nrPlots<<std::endl;
451 msg(Info)<<"nrSVs = "<<nrSVs<<std::endl;
452 msg(Info)<<"nrRoots = "<<nrRoots<<std::endl;
453 msg(Info)<<"nrDriftCorr = "<<nrDriftCorr<<std::endl;
454 }
455
456 namespace {
457 // declaration
458 template<bool, typename Self>
459 struct InitializeUsingXMLControlled;
460
461 // initializeUsingXML only used if isControlled == true
462 template<typename Self>
463 struct InitializeUsingXMLControlled<true, Self> {
464 static void call(Self self, xercesc::DOMElement* element) {
465 xercesc::DOMElement *e;
466
467 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"absoluteToleranceScalar");
468 if(e) self->setAbsoluteTolerance(MBXMLUtils::E(e)->getText<double>());
469
470 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"relativeToleranceScalar");
471 if(e) self->setRelativeTolerance(MBXMLUtils::E(e)->getText<double>());
472
473 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"maximumStepSize");
474 if(e) self->setMaximumStepSize(MBXMLUtils::E(e)->getText<double>());
475 }
476 };
477
478 // initializeUsingXML only used if isControlled == false
479 template<typename Self>
480 struct InitializeUsingXMLControlled<false, Self> {
481 static void call(Self self, xercesc::DOMElement* element) {
482 xercesc::DOMElement *e;
483
484 // we are not checking in the XML schema for isControlled -> do it here at runtime
485
486 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"absoluteToleranceScalar");
487 if(e) self->throwError("absoluteToleranceScalar element used for an fixed step-size stepper.");
488
489 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"relativeToleranceScalar");
490 if(e) self->throwError("relativeToleranceScalar element used for an fixed step-size stepper.");
491
492 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"maximumStepSize");
493 if(e) self->throwError("maximumStepSize element used for an fixed step-size stepper.");
494 }
495 };
496 }
497
498 template<typename DOS>
499 void BoostOdeintDOS<DOS>::initializeUsingXML(xercesc::DOMElement *element) {
501 xercesc::DOMElement *e;
502
503 InitializeUsingXMLControlled<isControlled, decltype(this)>::call(this, element);
504
505 e=MBXMLUtils::E(element)->getFirstElementChildNamed(MBSIM%"initialStepSize");
506 if(e) setInitialStepSize(MBXMLUtils::E(e)->getText<double>());
507 }
508
509}
510
511#endif
Definition: boost_odeint_integrator.h:103
void setMaximumStepSize(double dtMax_)
Set maximum step size.
Definition: boost_odeint_integrator.h:132
void setRelativeTolerance(double rTol_)
Set relative tolerance.
Definition: boost_odeint_integrator.h:128
void setInitialStepSize(double dt0_)
Set initial step size.
Definition: boost_odeint_integrator.h:120
void integrate() override
start the integration of the system set by setSystem. Each class implemeting this function should cal...
Definition: boost_odeint_integrator.h:228
void initializeUsingXML(xercesc::DOMElement *element) override
initialize integrator
Definition: boost_odeint_integrator.h:499
void setAbsoluteTolerance(double aTol_)
Set absolute tolerance.
Definition: boost_odeint_integrator.h:124
Definition: boost_odeint_integrator.h:75
Integrator with root-finding.
Definition: root_finding_integrator.h:32
virtual void initializeUsingXML(xercesc::DOMElement *element)
initialize integrator
Definition: root_finding_integrator.cc:44
namespace MBSim
Definition: bilateral_constraint.cc:30
Definition: boost_odeint_integrator.h:67
Definition: boost_odeint_integrator.h:69