Simbody  3.4 (development)
GeodesicIntegrator.h
Go to the documentation of this file.
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_
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines