Simbody
3.4 (development)
|
00001 #ifndef SimTK_SIMMATH_GEODESIC_INTEGRATOR_H_ 00002 #define SimTK_SIMMATH_GEODESIC_INTEGRATOR_H_ 00003 00004 /* -------------------------------------------------------------------------- * 00005 * Simbody(tm): SimTKmath * 00006 * -------------------------------------------------------------------------- * 00007 * This is part of the SimTK biosimulation toolkit originating from * 00008 * Simbios, the NIH National Center for Physics-Based Simulation of * 00009 * Biological Structures at Stanford, funded under the NIH Roadmap for * 00010 * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. * 00011 * * 00012 * Portions copyright (c) 2012 Stanford University and the Authors. * 00013 * Authors: Michael Sherman * 00014 * Contributors: * 00015 * * 00016 * Licensed under the Apache License, Version 2.0 (the "License"); you may * 00017 * not use this file except in compliance with the License. You may obtain a * 00018 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * 00019 * * 00020 * Unless required by applicable law or agreed to in writing, software * 00021 * distributed under the License is distributed on an "AS IS" BASIS, * 00022 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 00023 * See the License for the specific language governing permissions and * 00024 * limitations under the License. * 00025 * -------------------------------------------------------------------------- */ 00026 00033 #include "SimTKcommon.h" 00034 #include "simmath/internal/common.h" 00035 00036 namespace SimTK { 00037 00133 template <class Eqn> 00134 class GeodesicIntegrator { 00135 public: 00136 enum { NQ = Eqn::NQ, NC = Eqn::NC, N = 2*NQ }; 00137 00141 GeodesicIntegrator(const Eqn& eqn, Real accuracy, Real constraintTol) 00142 : m_eqn(eqn), m_accuracy(accuracy), m_consTol(constraintTol), 00143 m_hInit(NaN), m_hLast(NaN), m_hNext(Real(0.1)), m_nInitialize(0) 00144 { reinitializeCounters(); } 00145 00150 void initialize(Real t, const Vec<N>& y) { 00151 ++m_nInitialize; 00152 reinitializeCounters(); 00153 m_hInit = m_hLast = NaN; 00154 m_hNext = Real(0.1); // override if you have a better idea 00155 m_t = t; m_y = y; 00156 if (!m_eqn.projectIfNeeded(m_consTol, m_t, m_y)) { 00157 Vec<NC> cerr; 00158 m_eqn.calcConstraintErrors(m_t, m_y, cerr); 00159 const Real consErr = calcNormInf(cerr); 00160 SimTK_ERRCHK2_ALWAYS(!"projection failed", 00161 "GeodesicIntegrator::initialize()", 00162 "Couldn't project constraints to tol=%g;" 00163 " largest error was %g.", m_consTol, consErr); 00164 } 00165 m_eqn.calcDerivs(m_t, m_y, m_ydot); 00166 } 00167 00171 void setTimeAndState(Real t, const Vec<N>& y) { 00172 m_t = t; m_y = y; 00173 m_eqn.calcDerivs(m_t, m_y, m_ydot); 00174 Vec<NC> cerr; 00175 m_eqn.calcConstraintErrors(m_t, m_y, cerr); 00176 const Real consErr = calcNormInf(cerr); 00177 if (consErr > m_consTol) 00178 SimTK_ERRCHK2_ALWAYS(!"constraints not satisfied", 00179 "GeodesicIntegrator::setTimeAndState()", 00180 "Supplied state failed to satisfy constraints to tol=%g;" 00181 " largest error was %g.", m_consTol, consErr); 00182 } 00183 00186 void setNextStepSizeToTry(Real h) {m_hNext=h;} 00189 Real getNextStepSizeToTry() const {return m_hNext;} 00190 00192 Real getRequiredAccuracy() const {return m_accuracy;} 00194 Real getConstraintTolerance() const {return m_consTol;} 00195 00198 Real getActualInitialStepSizeTaken() const {return m_hInit;} 00199 00202 int getNumStepsTaken() const {return m_nStepsTaken;} 00206 int getNumStepsAttempted() const {return m_nStepsAttempted;} 00211 int getNumErrorTestFailures() const {return m_nErrtestFailures;} 00215 int getNumProjectionFailures() const {return m_nProjectionFailures;} 00216 00219 int getNumInitializations() const {return m_nInitialize;} 00220 00225 void takeOneStep(Real tStop); 00226 00228 const Real& getTime() const {return m_t;} 00230 const Vec<N>& getY() const {return m_y;} 00232 const Vec<NQ>& getQ() const {return Vec<NQ>::getAs(&m_y[0]);} 00234 const Vec<NQ>& getU() const {return Vec<NQ>::getAs(&m_y[NQ]);} 00236 const Vec<N>& getYDot() const {return m_ydot;} 00238 const Vec<NQ>& getQDot() const {return Vec<NQ>::getAs(&m_ydot[0]);} 00240 const Vec<NQ>& getUDot() const {return Vec<NQ>::getAs(&m_ydot[NQ]);} 00241 00244 template <int Z> static Real calcNormInf(const Vec<Z>& v) { 00245 Real norm = 0; 00246 for (int i=0; i < Z; ++i) { 00247 Real aval = std::abs(v[i]); 00248 if (aval > norm) norm = aval; 00249 } 00250 return norm; 00251 } 00252 00255 template <int Z> static Real calcNormRMS(const Vec<Z>& v) { 00256 Real norm = 0; 00257 for (int i=0; i< Z; ++i) 00258 norm += square(v[i]); 00259 return std::sqrt(norm/Z); 00260 } 00261 00262 private: 00263 void takeRKMStep(Real h, Vec<N>& y1, Vec<N>& y1err) const; 00264 00265 const Eqn& m_eqn; // The DAE system to be solved. 00266 Real m_accuracy; // Absolute accuracy requirement for y. 00267 Real m_consTol; // Absolute tolerance for constraint errors. 00268 00269 Real m_t; // Current value of the independent variable. 00270 Vec<N> m_y; // Current q,u in that order. 00271 00272 Real m_hInit; // Actual initial step taken. 00273 Real m_hLast; // Last step taken. 00274 Real m_hNext; // max step size to try next 00275 00276 Vec<N> m_ydot; // ydot(t,y) 00277 00278 // Counters. 00279 int m_nInitialize; // zeroed on construction only 00280 void reinitializeCounters() { 00281 m_nStepsTaken=m_nStepsAttempted=0; 00282 m_nErrtestFailures=m_nProjectionFailures=0; 00283 } 00284 int m_nStepsTaken; 00285 int m_nStepsAttempted; 00286 int m_nErrtestFailures; 00287 int m_nProjectionFailures; 00288 }; 00289 00290 template <class Eqn> void 00291 GeodesicIntegrator<Eqn>::takeOneStep(Real tStop) { 00292 const Real Safety = Real(0.9), MinShrink = Real(0.1), MaxGrow = Real(5); 00293 const Real HysteresisLow = Real(0.9), HysteresisHigh = Real(1.2); 00294 const Real MaxStretch = Real(0.1); 00295 const Real hMin = m_t <= 1 ? SignificantReal : SignificantReal*m_t; 00296 const Real hStretch = MaxStretch*m_hNext; 00297 00298 // Figure out the target ending time for the next step. Choosing time 00299 // rather than step size lets us end at exactly tStop. 00300 Real t1 = m_t + m_hNext; // this is the usual case 00301 // If a small stretching of the next step would get us to tStop, try 00302 // to make it all the way in one step. 00303 if (t1 + hStretch > tStop) 00304 t1 = tStop; 00305 00306 Real h, errNorm; 00307 Vec<N> y1, y1err; 00308 00309 // Try smaller and smaller step sizes if necessary until we get one 00310 // that satisfies the error requirement, and for which projection 00311 // succeeds. 00312 while (true) { 00313 ++m_nStepsAttempted; 00314 h = t1 - m_t; assert(h>0); 00315 takeRKMStep(h, y1, y1err); 00316 errNorm = calcNormInf(y1err); 00317 if (errNorm > m_accuracy) { 00318 ++m_nErrtestFailures; 00319 // Failed to achieve required accuracy at this step size h. 00320 SimTK_ERRCHK4_ALWAYS(h > hMin, 00321 "GeodesicIntegrator::takeOneStep()", 00322 "Accuracy %g worse than required %g at t=%g with step size" 00323 " h=%g; can't go any smaller.", errNorm, m_accuracy, m_t, h); 00324 00325 // Shrink step by (acc/err)^(1/4) for 4th order. 00326 Real hNew = Safety * h * std::sqrt(std::sqrt(m_accuracy/errNorm)); 00327 hNew = clamp(MinShrink*h, hNew, HysteresisLow*h); 00328 t1 = m_t + hNew; 00329 continue; 00330 } 00331 // Accuracy achieved. Can we satisfy the constraints? 00332 if (m_eqn.projectIfNeeded(m_consTol, t1, y1)) 00333 break; // all good 00334 00335 // Constraint projection failed. Hopefully that's because the 00336 // step was too big. 00337 ++m_nProjectionFailures; 00338 00339 SimTK_ERRCHK3_ALWAYS(h > hMin, 00340 "GeodesicIntegrator::takeOneStep()", 00341 "Projection failed to reach constraint tolerance %g at t=%g " 00342 "with step size h=%g; can't shrink step further.", 00343 m_consTol, m_t, h); 00344 00345 const Real hNew = MinShrink*h; 00346 t1 = m_t + hNew; 00347 } 00348 00349 // We achieved desired accuracy at step size h, and satisfied the 00350 // constraints. (t1,y1) is now the final time and state; calculate 00351 // state derivatives which will be used at the start of the next step. 00352 ++m_nStepsTaken; 00353 if (m_nStepsTaken==1) m_hInit = h; // that was the initial step 00354 m_t = t1; m_y = y1; m_hLast = h; 00355 m_eqn.calcDerivs(m_t, m_y, m_ydot); 00356 00357 // If the step we just took ended right at tStop, don't use it to 00358 // predict a new step size; instead we'll just use the same hNext we 00359 // would have used here if it weren't for tStop. 00360 if (t1 < tStop) { 00361 // Possibly grow step for next time. 00362 Real hNew = errNorm == 0 ? MaxGrow*h 00363 : Safety * h * std::sqrt(std::sqrt(m_accuracy/errNorm)); 00364 if (hNew < HysteresisHigh*h) hNew = h; // don't bother 00365 hNew = std::min(hNew, MaxGrow*h); 00366 m_hNext = hNew; 00367 } 00368 } 00369 00370 template <class Eqn> void 00371 GeodesicIntegrator<Eqn>::takeRKMStep(Real h, Vec<N>& y1, Vec<N>& y1err) const { 00372 const Real h2=h/2, h3=h/3, h6=h/6, h8=h/8; 00373 const Real t0=m_t, t1=m_t+h; 00374 const Vec<N>& y0 = m_y; 00375 const Vec<N>& f0 = m_ydot; 00376 Vec<N> f1, f2, f3, f4, ysave; 00377 m_eqn.calcDerivs(t0+h3, y0 + h3* f0, f1); 00378 m_eqn.calcDerivs(t0+h3, y0 + h6*(f0 + f1), f2); 00379 m_eqn.calcDerivs(t0+h2, y0 + h8*(f0 + 3*f2), f3); 00380 00381 // We'll need this for error estimation. 00382 ysave = y0 + h2*(f0 - 3*f2 + 4*f3); 00383 m_eqn.calcDerivs(t1, ysave, f4); 00384 00385 // Final value. This is the 4th order accurate estimate for 00386 // y1=y(t0+h)+O(h^5): y1 = y0 + (h/6)*(f0 + 4 f3 + f4). 00387 // Don't evaluate here yet because we might reject this step or we 00388 // might need to do a projection. 00389 y1 = y0 + h6*(f0 + 4*f3 + f4); 00390 00391 // This is an embedded 3rd-order estimate y1hat=y(t0+h)+O(h^4). 00392 // y1hat = y0 + (h/10)*(f0 + 3 f2 + 4 f3 + 2 f4) 00393 // We don't actually have any need for y1hat, just its 4th-order 00394 // error estimate y1hat-y1=(1/5)(y1-ysave) (easily verified from the above). 00395 00396 for (int i=0; i<N; ++i) 00397 y1err[i] = std::abs(y1[i]-ysave[i]) / 5; 00398 } 00399 00400 } // namespace SimTK 00401 00402 #endif // SimTK_SIMMATH_GEODESIC_INTEGRATOR_H_