diff --git a/dumux/common/timeloop.hh b/dumux/common/timeloop.hh index a34e90ef9abfec17d25d9b201138eb4794b43c1b..efcbbd46933daa07516f32e6fa7a8b2ba43541f1 100644 --- a/dumux/common/timeloop.hh +++ b/dumux/common/timeloop.hh @@ -245,7 +245,13 @@ public: { using std::min; timeStepSize_ = min(dt, maxTimeStepSize()); - if (!finished() && Dune::FloatCmp::le(timeStepSize_, 0.0, 1e-14*(endTime_ - startTime_))) + // Warn if dt is so small w.r.t. current time that it renders float addition meaningless + // For instance, consider (may depend on architecture): + // double cien = 100; + // double mil = 1000; + // if (cien + 1e-14 == cien) std::cout << "Will not be printed" << std::endl; + // if (mil + 1e-14 == mil) std::cout << "Will be printed" << std::endl; + if (!finished() && (time_ + timeStepSize_ == time_)) std::cerr << Fmt::format("You have set a very small timestep size (dt = {:.5g}).", timeStepSize_) << " This might lead to numerical problems!\n"; } @@ -301,7 +307,7 @@ public: */ bool finished() const override { - return finished_ || (endTime_ - time_) < 1e-10*(time_ - startTime_); + return finished_ || (endTime_ - time_) < baseEps_*(time_ - startTime_); } /*! @@ -310,7 +316,7 @@ public: */ bool willBeFinished() const { - return finished() || (endTime_ - time_ - timeStepSize_) < 1e-10*timeStepSize_; + return finished() || (endTime_ - time_ - timeStepSize_) < baseEps_*timeStepSize_; } /*! @@ -375,6 +381,8 @@ public: */ protected: + static constexpr Scalar baseEps_ = 1e-10; + Dune::Timer timer_; Scalar time_; Scalar endTime_; @@ -416,14 +424,14 @@ public: //! Check point management, TimeLoop::isCheckPoint() has to be called after this! // if we reached a periodic check point - if (periodicCheckPoints_ && Dune::FloatCmp::eq(newTime - lastPeriodicCheckPoint_, deltaPeriodicCheckPoint_, 1e-7)) + if (periodicCheckPoints_ && fuzzyEqual_(newTime - lastPeriodicCheckPoint_, deltaPeriodicCheckPoint_)) { lastPeriodicCheckPoint_ += deltaPeriodicCheckPoint_; isCheckPoint_ = true; } // or a manually set check point - else if (!checkPoints_.empty() && Dune::FloatCmp::eq(newTime - checkPoints_.front(), 0.0, 1e-7)) + else if (!checkPoints_.empty() && fuzzyEqual_(newTime - checkPoints_.front(), 0.0)) { checkPoints_.pop(); isCheckPoint_ = true; @@ -506,7 +514,7 @@ public: << Fmt::format("with the next check point at {:.5g} seconds.\n", lastPeriodicCheckPoint_ + interval); // check if the current time point is a check point - if (Dune::FloatCmp::eq(this->time()-lastPeriodicCheckPoint_, 0.0, 1e-7)) + if (fuzzyEqual_(this->time()-lastPeriodicCheckPoint_, 0.0)) isCheckPoint_ = true; // make sure we respect this check point on the next time step @@ -575,14 +583,17 @@ public: } private: + bool fuzzyEqual_(const Scalar t0, const Scalar t1) const + { return Dune::FloatCmp::eq(t0, t1, this->baseEps_*this->timeStepSize()); } + //! Adds a check point to the queue void setCheckPoint_(Scalar t) { - if (Dune::FloatCmp::le(t - this->time(), 0.0, this->timeStepSize()*1e-7)) + if (Dune::FloatCmp::le(t - this->time(), 0.0, this->timeStepSize()*this->baseEps_)) { if (this->verbose()) std::cerr << Fmt::format("Couldn't insert checkpoint at t = {:.5g} ", t) - << Fmt::format("because that's in the past! (current simulation time is {:.5g})\n", this->time()); + << Fmt::format("because that's not in the future! (current simulation time is {:.5g})\n", this->time()); return; } diff --git a/test/common/timeloop/test_timeloop.cc b/test/common/timeloop/test_timeloop.cc index 177e6a7d0527d7c6f190853c7b0265fa993bad04..07c6d23253f917841a67b1981bae7b726cc83851 100644 --- a/test/common/timeloop/test_timeloop.cc +++ b/test/common/timeloop/test_timeloop.cc @@ -10,6 +10,7 @@ #include <chrono> #include <thread> #include <limits> +#include <optional> #include <dune/common/parallel/mpihelper.hh> #include <dune/common/exceptions.hh> @@ -17,18 +18,20 @@ #include <dumux/common/initialize.hh> #include <dumux/common/timeloop.hh> -int main(int argc, char* argv[]) +void testTimeLoops(double tStart, + double tEnd, + double dt, + std::optional<double> dtInLoop = {}) { - // maybe initialize MPI and/or multithreading backend - Dumux::initialize(argc, argv); - const auto& mpiHelper = Dune::MPIHelper::instance(); - - //! Standard time loop - double tStart = 0; double tEnd = 1; double dt = 0.1; + std::cout << "Testing with dt = " << dt + << ", tStart = " << tStart + << ", tEnd = " << tEnd + <<std::endl; + const auto timeSpan = tEnd - tStart; //! Standard time loop { - if (mpiHelper.rank() == 0) std::cout << "------- Test time loop ----------" << std::endl; + std::cout << "------- Test time loop ----------" << std::endl; Dumux::TimeLoop<double> timeLoop(tStart, dt, tEnd); if (std::abs(timeLoop.time()-tStart) > 1e-15) @@ -38,8 +41,9 @@ int main(int argc, char* argv[]) if (std::abs(timeLoop.timeStepSize()-dt) > 1e-15) DUNE_THROW(Dune::InvalidStateException, "Wrong time step size!"); - timeLoop.setMaxTimeStepSize(0.03333333333333333); - if (std::abs(timeLoop.timeStepSize()-0.03333333333333333) > 1e-15) + const auto smallerMaxDt = dt*0.03; + timeLoop.setMaxTimeStepSize(smallerMaxDt); + if (std::abs(timeLoop.timeStepSize()-smallerMaxDt) > 1e-15) DUNE_THROW(Dune::InvalidStateException, "Wrong time step size!"); timeLoop.start(); // starts the timer @@ -59,20 +63,21 @@ int main(int argc, char* argv[]) std::this_thread::sleep_for(std::chrono::milliseconds(5)); timeLoop.advanceTimeStep(); timeLoop.reportTimeStep(); - timeLoop.setTimeStepSize(timeLoop.timeStepSize()); + timeLoop.setMaxTimeStepSize(dtInLoop.value_or(timeLoop.timeStepSize())); + timeLoop.setTimeStepSize(dtInLoop.value_or(timeLoop.timeStepSize())); } timeLoop.finalize(); - if (std::abs(timeLoop.time()-tEnd) > 1e-15) + if (std::abs(timeLoop.time()-tEnd) > 1e-15*timeSpan) DUNE_THROW(Dune::InvalidStateException, "Ended with wrong end time!"); } // check point timeLoop { - if (mpiHelper.rank() == 0) std::cout << std::endl << "------- Test check point time loop ----------" << std::endl; + std::cout << std::endl << "------- Test check point time loop ----------" << std::endl; Dumux::CheckPointTimeLoop<double> timeLoop(tStart, dt, tEnd); - timeLoop.setPeriodicCheckPoint(0.03, -0.03); + timeLoop.setPeriodicCheckPoint(0.03*dt, -0.03*dt); if (!timeLoop.isCheckPoint()) DUNE_THROW(Dune::InvalidStateException, "This is supposed to be a check point! t = " << timeLoop.time()); @@ -80,7 +85,7 @@ int main(int argc, char* argv[]) std::this_thread::sleep_for(std::chrono::milliseconds(5)); timeLoop.advanceTimeStep(); timeLoop.reportTimeStep(); - timeLoop.setPeriodicCheckPoint(0.03); + timeLoop.setPeriodicCheckPoint(0.03*dt); if (std::signbit(timeLoop.timeStepSize())) DUNE_THROW(Dune::InvalidStateException, "Time step size is negative! dt = " << timeLoop.timeStepSize()); @@ -88,8 +93,8 @@ int main(int argc, char* argv[]) if (!timeLoop.isCheckPoint()) DUNE_THROW(Dune::InvalidStateException, "This is supposed to be a check point! t = " << timeLoop.time()); - timeLoop.setCheckPoint(0.02); // can't be set because it's in the past - timeLoop.setCheckPoint(0.08); + timeLoop.setCheckPoint(0.02*dt); // can't be set because it's in the past + timeLoop.setCheckPoint(0.08*dt); std::this_thread::sleep_for(std::chrono::milliseconds(5)); timeLoop.advanceTimeStep(); timeLoop.reportTimeStep(); @@ -100,8 +105,8 @@ int main(int argc, char* argv[]) if (!timeLoop.isCheckPoint()) DUNE_THROW(Dune::InvalidStateException, "This is supposed to be a check point! t = " << timeLoop.time()); - timeLoop.setEndTime(1e9); - timeLoop.setTimeStepSize(1e7); + timeLoop.setEndTime(1e9*timeSpan); + timeLoop.setTimeStepSize(1e7*timeSpan); std::this_thread::sleep_for(std::chrono::milliseconds(5)); timeLoop.advanceTimeStep(); @@ -113,9 +118,10 @@ int main(int argc, char* argv[]) if (!timeLoop.isCheckPoint()) DUNE_THROW(Dune::InvalidStateException, "This is supposed to be a check point! t = " << timeLoop.time()); + const double scaling = 1.0e6; timeLoop.removeAllCheckPoints(); - timeLoop.setTimeStepSize(0.5e-6); - timeLoop.setPeriodicCheckPoint(1.0e6, 1.0e6); + timeLoop.setTimeStepSize(0.5e-6*timeSpan); + timeLoop.setPeriodicCheckPoint(scaling*timeSpan, scaling*timeSpan); std::this_thread::sleep_for(std::chrono::milliseconds(5)); timeLoop.advanceTimeStep(); @@ -124,6 +130,7 @@ int main(int argc, char* argv[]) if (std::signbit(timeLoop.timeStepSize())) DUNE_THROW(Dune::InvalidStateException, "Time step size is negative! dt = " << timeLoop.timeStepSize()); + timeLoop.setTimeStepSize(dtInLoop.value_or(timeLoop.timeStepSize())*scaling); while (!timeLoop.finished()) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); @@ -131,11 +138,11 @@ int main(int argc, char* argv[]) timeLoop.reportTimeStep(); timeLoop.setTimeStepSize(std::numeric_limits<double>::max()); - if (std::abs(timeLoop.time() - 2.0e6) < 1e-15 && !timeLoop.isCheckPoint()) + if (std::abs(timeLoop.time() - 2.0e6*timeSpan) < 1e-15 && !timeLoop.isCheckPoint()) DUNE_THROW(Dune::InvalidStateException, "This is supposed to be a check point! t = " << timeLoop.time()); if (timeLoop.timeStepIndex() == 10) - timeLoop.setPeriodicCheckPoint(1.0e8); + timeLoop.setPeriodicCheckPoint(1.0e8*timeSpan); if (std::signbit(timeLoop.timeStepSize())) DUNE_THROW(Dune::InvalidStateException, "Time step size is negative! dt = " << timeLoop.timeStepSize()); @@ -143,22 +150,22 @@ int main(int argc, char* argv[]) timeLoop.finalize(); - if (std::abs(timeLoop.time()-1e9) > 1e-15) + if (std::abs(timeLoop.time()-1e9*timeSpan) > 1e9*1e-15*timeSpan) DUNE_THROW(Dune::InvalidStateException, "Ended with wrong end time!"); } // check if time loops remembers time step before check point { - if (mpiHelper.rank() == 0) std::cout << std::endl << "------- Test check point time loop ----------" << std::endl; + std::cout << std::endl << "------- Test check point time loop ----------" << std::endl; Dumux::CheckPointTimeLoop<double> timeLoop(tStart, dt, tEnd); - timeLoop.setCheckPoint(0.101); + timeLoop.setCheckPoint(1.01*dt); timeLoop.start(); timeLoop.advanceTimeStep(); timeLoop.reportTimeStep(); timeLoop.advanceTimeStep(); timeLoop.reportTimeStep(); - if (!(std::abs(timeLoop.timeStepSize()-0.1) < 1e-14)) + if (!(std::abs(timeLoop.timeStepSize()-dt) < 1e-14)) DUNE_THROW(Dune::InvalidStateException, "Time Loop reduced time step size to " << timeLoop.timeStepSize() << " after check point unnecessarily!"); @@ -178,11 +185,11 @@ int main(int argc, char* argv[]) // get result and reset buffer const auto result = cerrBuffer.str(); std::cerr.rdbuf(cerrOriginal); - std::cout << "Setting check point at the current time printed '" << result << "' to std::cerr" << std::endl; + std::cout << "Setting check point at the current time. Printed error message: " << result << std::endl; if (result.empty()) - DUNE_THROW(Dune::Exception, "Setting a checkpoint at the current time should print a warning to std::cerr"); - if (Dune::FloatCmp::eq(timeLoop.timeStepSize(), 0.0, 1e-10*tEnd)) + DUNE_THROW(Dune::Exception, "Setting a checkpoint at the current time should print a warning to the error stream"); + if (Dune::FloatCmp::eq(timeLoop.timeStepSize(), 0.0, 1e-10)) DUNE_THROW(Dune::Exception, "Time Loop reduced time step size to 0!"); } @@ -198,11 +205,29 @@ int main(int argc, char* argv[]) // get result and reset buffer const auto result = cerrBuffer.str(); std::cerr.rdbuf(cerrOriginal); - std::cout << "Setting zero time step printed '" << result << "' to std::cerr" << std::endl; + std::cout << "Setting zero time step. Printed error message: " << result << std::endl; if (result.empty()) - DUNE_THROW(Dune::Exception, "Setting a zero timeStepSize should print a warning to std::cerr"); + DUNE_THROW(Dune::Exception, "Setting a zero timeStepSize should print a warning to the error stream"); } +} + +int main(int argc, char* argv[]) +{ + // maybe initialize MPI and/or multithreading backend + Dumux::initialize(argc, argv); + + // unit time + testTimeLoops(0.0, 1.0, 0.1); + + // microseconds + testTimeLoops(0.0, 1.0e-6, 1e-7); + + // large time scales + testTimeLoops(0.0, 1.0e12, 1e9, {1e11}); + + // large time scales but small initial time step + testTimeLoops(0.0, 1.0e12, 0.1, {1e11}); return 0; }