Chaste Release::3.1
TimeStepper.cpp
00001 /*
00002 
00003 Copyright (c) 2005-2012, University of Oxford.
00004 All rights reserved.
00005 
00006 University of Oxford means the Chancellor, Masters and Scholars of the
00007 University of Oxford, having an administrative office at Wellington
00008 Square, Oxford OX1 2JD, UK.
00009 
00010 This file is part of Chaste.
00011 
00012 Redistribution and use in source and binary forms, with or without
00013 modification, are permitted provided that the following conditions are met:
00014  * Redistributions of source code must retain the above copyright notice,
00015    this list of conditions and the following disclaimer.
00016  * Redistributions in binary form must reproduce the above copyright notice,
00017    this list of conditions and the following disclaimer in the documentation
00018    and/or other materials provided with the distribution.
00019  * Neither the name of the University of Oxford nor the names of its
00020    contributors may be used to endorse or promote products derived from this
00021    software without specific prior written permission.
00022 
00023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00024 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00025 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00026 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
00027 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00028 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
00029 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00030 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
00032 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00033 
00034 */
00035 
00036 #include <cassert>
00037 #include <cmath>
00038 
00039 #include "TimeStepper.hpp"
00040 #include "Exception.hpp"
00041 #include "MathsCustomFunctions.hpp"
00042 
00043 TimeStepper::TimeStepper(double startTime, double endTime, double dt, bool enforceConstantTimeStep, std::vector<double> additionalTimes)
00044     : mStart(startTime),
00045       mEnd(endTime),
00046       mDt(dt),
00047       mTotalTimeStepsTaken(0),
00048       mAdditionalTimesReached(0),
00049       mTime(startTime),
00050       mEpsilon(DBL_EPSILON)
00051 {
00052     if (startTime > endTime)
00053     {
00054         EXCEPTION("The simulation duration must be positive, not " << endTime-startTime);
00055     }
00056 
00057     // Remove any additionalTimes entries which fall too close to a time when the stepper would stop anyway
00058     for (unsigned i=0; i<additionalTimes.size(); i++)
00059     {
00060         if (i > 0)
00061         {
00062             if (additionalTimes[i-1] >= additionalTimes[i])
00063             {
00064                 EXCEPTION("The additional times vector should be in ascending numerical order; "
00065                           "entry " << i << " is less than or equal to entry " << i-1 << ".");
00066             }
00067         }
00068 
00069         double time_interval = additionalTimes[i] - startTime;
00070 
00071         // When mDt divides this interval (and the interval is positive) then we are going there anyway
00072         if (!Divides(mDt, time_interval) && (time_interval > DBL_EPSILON))
00073         {
00074             mAdditionalTimes.push_back(additionalTimes[i]);
00075         }
00076     }
00077 
00078     /*
00079      * Note that when mEnd is large then the error of subtracting two numbers of
00080      * that magnitude is about DBL_EPSILON*mEnd (1e-16*mEnd). When mEnd is small
00081      * then the error should be around DBL_EPSILON.
00082      */
00083     if (mEnd > 1.0)
00084     {
00085         mEpsilon = DBL_EPSILON*mEnd;
00086     }
00087 
00088     // If enforceConstantTimeStep check whether the times are such that we won't have a variable dt
00089     if (enforceConstantTimeStep)
00090     {
00091         double expected_end_time = mStart + mDt*EstimateTimeSteps();
00092 
00093         if (fabs( mEnd - expected_end_time ) > mEpsilon)
00094         {
00095             EXCEPTION("TimeStepper estimates non-constant timesteps will need to be used: check timestep "
00096                       "divides (end_time-start_time) (or divides printing timestep). "
00097                       "[End time=" << mEnd << "; start=" << mStart << "; dt=" << mDt << "; error="
00098                       << fabs(mEnd-expected_end_time) << "]");
00099         }
00100     }
00101 
00102     mNextTime = CalculateNextTime();
00103 }
00104 
00105 double TimeStepper::CalculateNextTime()
00106 {
00107     double next_time = mStart + (mTotalTimeStepsTaken - mAdditionalTimesReached + 1)*mDt;
00108 
00109     // Does the next time bring us very close to the end time?
00110     // Note that the inequality in this guard matches the inversion of the guard in the enforceConstantTimeStep
00111     // calculation of the constructor
00112     if (mEnd - next_time <= mEpsilon)
00113     {
00114         next_time = mEnd;
00115     }
00116 
00117     if (!mAdditionalTimes.empty())
00118     {
00119         if (mAdditionalTimesReached < mAdditionalTimes.size())
00120         {
00121             // Does this next step take us very close to, or over, an additional time?
00122             double next_additional_time = mAdditionalTimes[mAdditionalTimesReached];
00123             double epsilon = next_additional_time > 1 ? next_additional_time*DBL_EPSILON : DBL_EPSILON;
00124             if (next_additional_time - next_time <= epsilon)
00125             {
00126                 next_time = next_additional_time;
00127                 mAdditionalTimesReached++;
00128             }
00129         }
00130     }
00131     return next_time;
00132 }
00133 
00134 void TimeStepper::AdvanceOneTimeStep()
00135 {
00136     mTotalTimeStepsTaken++;
00137     if (mTotalTimeStepsTaken == 0)
00138     {
00139         EXCEPTION("Time step counter has overflowed.");
00140     }
00141     if (mTime == mNextTime)
00142     {
00143         EXCEPTION("TimeStepper incremented beyond end time.");
00144     }
00145     mTime = mNextTime;
00146 
00147     mNextTime = CalculateNextTime();
00148 }
00149 
00150 double TimeStepper::GetTime() const
00151 {
00152     return mTime;
00153 }
00154 
00155 double TimeStepper::GetNextTime() const
00156 {
00157     return mNextTime;
00158 }
00159 
00160 double TimeStepper::GetNextTimeStep()
00161 {
00162     double dt = mDt;
00163 
00164     if (mNextTime == mEnd)
00165     {
00166         dt = mEnd - mTime;
00167     }
00168 
00169     // If the next time or the current time is one of the additional times, the timestep will not be mDt
00170     if (mAdditionalTimesReached > 0)
00171     {
00172         if ((mNextTime == mAdditionalTimes[mAdditionalTimesReached-1]) || (mTime == mAdditionalTimes[mAdditionalTimesReached-1]))
00173         {
00174             dt = mNextTime - mTime;
00175             assert(dt > 0);
00176         }
00177     }
00178 
00179     return dt;
00180 }
00181 double TimeStepper::GetIdealTimeStep()
00182 {
00183     return(mDt);
00184 }
00185 
00186 bool TimeStepper::IsTimeAtEnd() const
00187 {
00188     return (mTime >= mEnd);
00189 }
00190 
00191 unsigned TimeStepper::EstimateTimeSteps() const
00192 {
00193     return (unsigned) floor((mEnd - mStart)/mDt + 0.5) + mAdditionalTimes.size();
00194 }
00195 
00196 unsigned TimeStepper::GetTotalTimeStepsTaken() const
00197 {
00198     return mTotalTimeStepsTaken;
00199 }
00200 
00201 void TimeStepper::ResetTimeStep(double dt)
00202 {
00203     assert(dt > 0);
00204     /*
00205      * The error in subtracting two numbers of the same magnitude is about
00206      * DBL_EPSILON times that magnitude (we use the sum of the two numbers
00207      * here as a conservative estimate of their maximum). When both mDt and
00208      * dt are small then the error should be around DBL_EPSILON.
00209      */
00210     double scale = DBL_EPSILON*(mDt + dt);
00211     if (mDt + dt < 1.0)
00212     {
00213         scale = DBL_EPSILON;
00214     }
00215     if (fabs(mDt-dt) > scale)
00216     {
00217         mDt = dt;
00218         mStart = mTime;
00219         mTotalTimeStepsTaken = 0;
00220 
00221         mNextTime = CalculateNextTime();
00222     }
00223 }