Line | Branch | Exec | Source |
---|---|---|---|
1 | // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- | ||
2 | // vi: set et ts=4 sw=4 sts=4: | ||
3 | // | ||
4 | // SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder | ||
5 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
6 | // | ||
7 | /*! | ||
8 | * \file | ||
9 | * \ingroup Core | ||
10 | * \brief Manages the handling of time dependent problems | ||
11 | */ | ||
12 | #ifndef DUMUX_TIME_LOOP_HH | ||
13 | #define DUMUX_TIME_LOOP_HH | ||
14 | |||
15 | #include <algorithm> | ||
16 | #include <queue> | ||
17 | #include <iomanip> | ||
18 | #include <chrono> | ||
19 | #include <type_traits> | ||
20 | #include <initializer_list> | ||
21 | #include <optional> | ||
22 | |||
23 | #include <dune/common/float_cmp.hh> | ||
24 | #include <dune/common/timer.hh> | ||
25 | |||
26 | #include <dune/common/parallel/communication.hh> | ||
27 | |||
28 | #include <dune/common/parallel/mpihelper.hh> | ||
29 | #include <dune/common/exceptions.hh> | ||
30 | |||
31 | #include <dumux/common/typetraits/typetraits.hh> | ||
32 | #include <dumux/common/parameters.hh> | ||
33 | #include <dumux/io/format.hh> | ||
34 | |||
35 | namespace Dumux { | ||
36 | |||
37 | |||
38 | #ifndef DOXYGEN | ||
39 | namespace Detail::TimeLoop { | ||
40 | |||
41 | template<typename Scalar, typename R, typename P> | ||
42 | 22 | Scalar toSeconds(std::chrono::duration<R, P> duration) | |
43 | { | ||
44 | using Second = std::chrono::duration<Scalar, std::ratio<1>>; | ||
45 | 4 | return std::chrono::duration_cast<Second>(duration).count(); | |
46 | } | ||
47 | |||
48 | template<typename Scalar, typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true> | ||
49 | Scalar toSeconds(T duration) | ||
50 | { return static_cast<Scalar>(duration); } | ||
51 | |||
52 | // overload for nice static_assert message in case of unuspported type | ||
53 | template<typename Scalar, typename T, std::enable_if_t<!std::is_floating_point_v<T>, bool> = true> | ||
54 | Scalar toSeconds(T duration) | ||
55 | { static_assert(AlwaysFalse<T>::value, "Given type not supported for representation of time values"); } | ||
56 | |||
57 | } // namespace Detail::TimeLoop | ||
58 | #endif // DOXYGEN | ||
59 | |||
60 | /*! | ||
61 | * \ingroup Core | ||
62 | * \brief Manages the handling of time dependent problems. | ||
63 | * | ||
64 | * This class facilitates the time management of the simulation. | ||
65 | * It doesn't manage any user data, but keeps track of what the | ||
66 | * current time, time step size and "episode" of the | ||
67 | * simulation is. It triggers the initialization of the problem and | ||
68 | * is responsible for the time control of a simulation run. | ||
69 | * | ||
70 | * The time manager allows to specify a sequence of "episodes" which | ||
71 | * determine the boundary conditions of a problem. This approach | ||
72 | * is handy if the problem is not static, i.e. the boundary | ||
73 | * conditions change over time. | ||
74 | * | ||
75 | * An episode is a span of simulated time in which | ||
76 | * the problem behaves in a specific way. It is characterized by | ||
77 | * the (simulation) time it starts, its length and a consecutive | ||
78 | * index starting at 0. | ||
79 | * | ||
80 | * \note Time and time step sizes are in units of seconds | ||
81 | */ | ||
82 | template<class S> | ||
83 | 385 | class TimeLoopBase | |
84 | { | ||
85 | public: | ||
86 | using Scalar = S; | ||
87 | |||
88 | //! Abstract base class needs virtual constructor | ||
89 | 25 | virtual ~TimeLoopBase() {}; | |
90 | |||
91 | /*! | ||
92 | * \brief Return the time \f$\mathrm{[s]}\f$ before the time integration. | ||
93 | * To get the time after the time integration you have to add timeStepSize() to | ||
94 | * time(). | ||
95 | */ | ||
96 | virtual Scalar time() const = 0; | ||
97 | |||
98 | /*! | ||
99 | * \brief Returns the suggested time step length \f$\mathrm{[s]}\f$ | ||
100 | */ | ||
101 | virtual Scalar timeStepSize() const = 0; | ||
102 | |||
103 | /*! | ||
104 | * \brief Get the maximum possible time step size \f$\mathrm{[s]}\f$ | ||
105 | */ | ||
106 | virtual Scalar maxTimeStepSize() const = 0; | ||
107 | |||
108 | /*! | ||
109 | * \brief Advance to the next time step. | ||
110 | */ | ||
111 | virtual void advanceTimeStep() = 0; | ||
112 | |||
113 | /*! | ||
114 | * \brief Set the current time step size to a given value. | ||
115 | * \param dt The new value for the time step size \f$\mathrm{[s]}\f$ | ||
116 | */ | ||
117 | virtual void setTimeStepSize(Scalar dt) = 0; | ||
118 | |||
119 | /*! | ||
120 | * \brief Set the current time step size to a given value. | ||
121 | * \param dt The new value for the time step size \f$\mathrm{[s]}\f$ | ||
122 | */ | ||
123 | template<class Rep, class Period> | ||
124 | 4 | void setTimeStepSize(std::chrono::duration<Rep, Period> dt) | |
125 | 4 | { setTimeStepSize(Detail::TimeLoop::toSeconds<Scalar>(dt)); } | |
126 | |||
127 | /*! | ||
128 | * \brief Returns true if the simulation is finished. | ||
129 | */ | ||
130 | virtual bool finished() const = 0; | ||
131 | }; | ||
132 | |||
133 | /*! | ||
134 | * \ingroup Core | ||
135 | * \brief The default time loop for instationary simulations | ||
136 | */ | ||
137 | template <class Scalar> | ||
138 | 10 | class TimeLoop : public TimeLoopBase<Scalar> | |
139 | { | ||
140 | public: | ||
141 | 385 | TimeLoop(Scalar startTime, Scalar dt, Scalar tEnd, bool verbose = true) | |
142 | 385 | : timer_(false) | |
143 | { | ||
144 | 385 | reset(startTime, dt, tEnd, verbose); | |
145 | 385 | } | |
146 | |||
147 | template<class Rep1, class Period1, | ||
148 | class Rep2, class Period2, | ||
149 | class Rep3, class Period3> | ||
150 | 5 | TimeLoop(std::chrono::duration<Rep1, Period1> startTime, | |
151 | std::chrono::duration<Rep2, Period2> dt, | ||
152 | std::chrono::duration<Rep3, Period3> tEnd, | ||
153 | bool verbose = true) | ||
154 | : TimeLoop( | ||
155 | Detail::TimeLoop::toSeconds<Scalar>(startTime), | ||
156 | Detail::TimeLoop::toSeconds<Scalar>(dt), | ||
157 | Detail::TimeLoop::toSeconds<Scalar>(tEnd), | ||
158 | verbose | ||
159 | 8 | ){} | |
160 | |||
161 | /*! | ||
162 | * \name Simulated time and time step management | ||
163 | * @{ | ||
164 | */ | ||
165 | |||
166 | /*! | ||
167 | * \brief Tells the time loop to start tracking the time. | ||
168 | */ | ||
169 | 369 | void start() | |
170 | { | ||
171 |
9/15✓ Branch 0 taken 351 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 18082 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 4 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 4 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 5 taken 6 times.
✓ Branch 8 taken 6 times.
✓ Branch 2 taken 47 times.
|
20516 | timer_.start(); |
172 | } | ||
173 | |||
174 | /*! | ||
175 | * \brief Tells the time loop to stop tracking the time. | ||
176 | * \return the wall clock time (CPU time) spent until now | ||
177 | */ | ||
178 | 4 | double stop() | |
179 | { | ||
180 | 4 | return timer_.stop(); | |
181 | } | ||
182 | |||
183 | /*! | ||
184 | * \brief Reset the timer | ||
185 | */ | ||
186 | 4 | void resetTimer() | |
187 | { | ||
188 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | timer_.reset(); |
189 | } | ||
190 | |||
191 | /*! | ||
192 | * \brief Reset the time loop | ||
193 | */ | ||
194 | template<class Rep1, class Period1, | ||
195 | class Rep2, class Period2, | ||
196 | class Rep3, class Period3> | ||
197 | 4 | void reset(std::chrono::duration<Rep1, Period1> startTime, | |
198 | std::chrono::duration<Rep2, Period2> dt, | ||
199 | std::chrono::duration<Rep3, Period3> tEnd, | ||
200 | bool verbose = true) | ||
201 | { | ||
202 | 4 | reset( | |
203 | Detail::TimeLoop::toSeconds<Scalar>(startTime), | ||
204 | Detail::TimeLoop::toSeconds<Scalar>(dt), | ||
205 | Detail::TimeLoop::toSeconds<Scalar>(tEnd) | ||
206 | ); | ||
207 | } | ||
208 | |||
209 | /*! | ||
210 | * \brief Reset the time loop | ||
211 | */ | ||
212 | 389 | void reset(Scalar startTime, Scalar dt, Scalar tEnd, bool verbose = true) | |
213 | { | ||
214 | 389 | verbose_ = | |
215 |
1/2✓ Branch 0 taken 389 times.
✗ Branch 1 not taken.
|
389 | verbose && |
216 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 369 times.
|
389 | Dune::MPIHelper::getCommunication().rank() == 0; |
217 | |||
218 | 389 | startTime_ = startTime; | |
219 | 389 | time_ = startTime; | |
220 | 389 | endTime_ = tEnd; | |
221 | |||
222 | 389 | previousTimeStepSize_ = 0.0; | |
223 | 389 | userSetMaxTimeStepSize_ = std::numeric_limits<Scalar>::max(); | |
224 | 389 | timeStepIdx_ = 0; | |
225 | 389 | finished_ = false; | |
226 | 389 | timeAfterLastTimeStep_ = 0.0; | |
227 | 389 | timeStepWallClockTime_ = 0.0; | |
228 | |||
229 | // ensure that dt is not greater than tEnd-startTime | ||
230 | 389 | setTimeStepSize(dt); | |
231 | |||
232 | 389 | timer_.stop(); | |
233 | 389 | timer_.reset(); | |
234 | 389 | } | |
235 | |||
236 | /*! | ||
237 | * \brief Advance time step. | ||
238 | */ | ||
239 | 20886 | void advanceTimeStep() override | |
240 | { | ||
241 | 20886 | timeStepIdx_++; | |
242 | 20886 | time_ += timeStepSize_; | |
243 | 20886 | previousTimeStepSize_ = timeStepSize_; | |
244 | |||
245 | // compute how long the last time step took | ||
246 | 20886 | const auto cpuTime = wallClockTime(); | |
247 | 20886 | timeStepWallClockTime_ = cpuTime - timeAfterLastTimeStep_; | |
248 | 20886 | timeAfterLastTimeStep_ = cpuTime; | |
249 | |||
250 | // ensure that using current dt we don't exceed tEnd in next time step | ||
251 | 20886 | setTimeStepSize(timeStepSize_); | |
252 | 20886 | } | |
253 | |||
254 | /*! | ||
255 | * \brief Set the current simulated time, don't change the current | ||
256 | * time step index. | ||
257 | * | ||
258 | * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to | ||
259 | */ | ||
260 | template<class ScalarOrDuration> | ||
261 | 4 | void setTime(ScalarOrDuration t) | |
262 | 4 | { time_ = Detail::TimeLoop::toSeconds<Scalar>(t); } | |
263 | |||
264 | /*! | ||
265 | * \brief Set the current simulated time and the time step index. | ||
266 | * | ||
267 | * \param t The time \f$\mathrm{[s]}\f$ which should be jumped to | ||
268 | * \param stepIdx The new time step index | ||
269 | */ | ||
270 | template<class ScalarOrDuration> | ||
271 | void setTime(ScalarOrDuration t, int stepIdx) | ||
272 | { | ||
273 | time_ = Detail::TimeLoop::toSeconds<Scalar>(t); | ||
274 | timeStepIdx_ = stepIdx; | ||
275 | } | ||
276 | |||
277 | /*! | ||
278 | * \brief Return the time \f$\mathrm{[s]}\f$ before the time integration. | ||
279 | * To get the time after the time integration you have to add timeStepSize() to | ||
280 | * time(). | ||
281 | */ | ||
282 | 104888186 | Scalar time() const final | |
283 |
14/25✓ Branch 0 taken 98629565 times.
✓ Branch 1 taken 4794003 times.
✓ Branch 2 taken 466150 times.
✓ Branch 3 taken 728926 times.
✓ Branch 4 taken 88016 times.
✓ Branch 5 taken 98307 times.
✓ Branch 6 taken 40 times.
✓ Branch 7 taken 146 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 56 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✓ Branch 20 taken 4 times.
✓ Branch 21 taken 64 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✓ Branch 26 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 931 times.
✓ Branch 14 taken 9 times.
|
104806674 | { return time_; } |
284 | |||
285 | /*! | ||
286 | * \brief Returns the number of (simulated) seconds which the simulation runs. | ||
287 | */ | ||
288 | 12 | Scalar endTime() const | |
289 |
5/12✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
|
12 | { return endTime_; } |
290 | |||
291 | /*! | ||
292 | * \brief Set the time of simulated seconds at which the simulation runs. | ||
293 | * | ||
294 | * \param t The time \f$\mathrm{[s]}\f$ at which the simulation is finished | ||
295 | */ | ||
296 | 4 | void setEndTime(Scalar t) | |
297 | { | ||
298 | 4 | endTime_ = t; | |
299 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (verbose_) |
300 | 8 | std::cout << Fmt::format("Set new end time to t = {:.5g} seconds.\n", t); | |
301 | 4 | } | |
302 | |||
303 | /*! | ||
304 | * \brief Returns the current wall clock time (cpu time) spend in this time loop | ||
305 | */ | ||
306 | 41045 | double wallClockTime() const | |
307 | 41045 | { return timer_.elapsed(); } | |
308 | |||
309 | using TimeLoopBase<Scalar>::setTimeStepSize; | ||
310 | /*! | ||
311 | * \brief Set the current time step size to a given value. | ||
312 | * | ||
313 | * If the step size would exceed the length of the current | ||
314 | * episode, the timeStep() method will take care that the step | ||
315 | * size won't exceed the episode or the end of the simulation, | ||
316 | * though. | ||
317 | * | ||
318 | * \param dt The new value for the time step size \f$\mathrm{[s]}\f$ | ||
319 | */ | ||
320 | 58854 | void setTimeStepSize(Scalar dt) final | |
321 | { | ||
322 | using std::min; | ||
323 |
2/2✓ Branch 1 taken 11125 times.
✓ Branch 2 taken 47729 times.
|
58854 | timeStepSize_ = min(dt, maxTimeStepSize()); |
324 | // Warn if dt is so small w.r.t. current time that it renders float addition meaningless | ||
325 | // For instance, consider (may depend on architecture): | ||
326 | // double cien = 100; | ||
327 | // double mil = 1000; | ||
328 | // if (cien + 1e-14 == cien) std::cout << "Will not be printed" << std::endl; | ||
329 | // if (mil + 1e-14 == mil) std::cout << "Will be printed" << std::endl; | ||
330 |
4/4✓ Branch 1 taken 58139 times.
✓ Branch 2 taken 715 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 58135 times.
|
58854 | if (!finished() && (time_ + timeStepSize_ == time_)) |
331 | 4 | std::cerr << Fmt::format("You have set a very small timestep size (dt = {:.5g}).", timeStepSize_) | |
332 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
12 | << " This might lead to numerical problems!\n"; |
333 | 58854 | } | |
334 | |||
335 | /*! | ||
336 | * \brief Set the maximum time step size to a given value. | ||
337 | * | ||
338 | * \param maxDt The new value for the maximum time step size \f$\mathrm{[s]}\f$ | ||
339 | * \note This may also reduce the currently set timestep size if needed to comply with the set maximum | ||
340 | */ | ||
341 | template<class ScalarOrDuration> | ||
342 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
335 | void setMaxTimeStepSize(ScalarOrDuration maxDt) |
343 | { | ||
344 | 1031 | userSetMaxTimeStepSize_ = Detail::TimeLoop::toSeconds<Scalar>(maxDt); | |
345 |
1/2✓ Branch 1 taken 335 times.
✗ Branch 2 not taken.
|
1031 | setTimeStepSize(timeStepSize_); |
346 | 335 | } | |
347 | |||
348 | /*! | ||
349 | * \brief Returns the suggested time step length \f$\mathrm{[s]}\f$ so that we | ||
350 | * don't miss the beginning of the next episode or cross | ||
351 | * the end of the simulation. | ||
352 | */ | ||
353 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
723495110 | Scalar timeStepSize() const final |
354 |
25/46✓ Branch 0 taken 63 times.
✓ Branch 1 taken 16505 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 3915 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 96 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
✓ Branch 10 taken 10 times.
✓ Branch 11 taken 2 times.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 4 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 4 times.
✓ Branch 16 taken 22 times.
✓ Branch 17 taken 666 times.
✓ Branch 18 taken 22 times.
✓ Branch 19 taken 666 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 4 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✓ Branch 26 taken 4 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✓ Branch 31 taken 4 times.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✓ Branch 36 taken 4 times.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✓ Branch 41 taken 68 times.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✓ Branch 46 taken 4 times.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 50 not taken.
✓ Branch 51 taken 4 times.
|
41043051 | { return timeStepSize_; } |
355 | |||
356 | /*! | ||
357 | * \brief Returns number of time steps which have been | ||
358 | * executed since the beginning of the simulation. | ||
359 | */ | ||
360 | 3312 | int timeStepIndex() const | |
361 |
4/4✓ Branch 0 taken 1535 times.
✓ Branch 1 taken 1175 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 595 times.
|
3310 | { return timeStepIdx_; } |
362 | |||
363 | /*! | ||
364 | * \brief The previous time step size | ||
365 | */ | ||
366 | 14851 | Scalar previousTimeStepSize() const | |
367 | 14851 | { return previousTimeStepSize_; } | |
368 | |||
369 | /*! | ||
370 | * \brief Specify whether the simulation is finished | ||
371 | * | ||
372 | * \param finished If true the simulation is considered finished | ||
373 | * before the end time is reached, else it is only | ||
374 | * considered finished if the end time is reached. | ||
375 | */ | ||
376 | void setFinished(bool finished = true) | ||
377 | { finished_ = finished; } | ||
378 | |||
379 | /*! | ||
380 | * \brief Returns true if the simulation is finished. | ||
381 | * | ||
382 | * This is the case if either setFinished(true) has been called or | ||
383 | * if the end time is reached. | ||
384 | */ | ||
385 | 162217 | bool finished() const override | |
386 | { | ||
387 |
9/12✓ Branch 0 taken 161453 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1964 times.
✓ Branch 3 taken 159489 times.
✓ Branch 4 taken 692 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 688 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 72 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✓ Branch 11 taken 68 times.
|
162217 | return finished_ || (endTime_ - time_) < baseEps_*(time_ - startTime_); |
388 | } | ||
389 | |||
390 | /*! | ||
391 | * \brief Returns true if the simulation is finished after the | ||
392 | * time level is incremented by the current time step size. | ||
393 | */ | ||
394 | 14851 | bool willBeFinished() const | |
395 | { | ||
396 |
4/4✓ Branch 0 taken 14723 times.
✓ Branch 1 taken 128 times.
✓ Branch 2 taken 14611 times.
✓ Branch 3 taken 112 times.
|
14851 | return finished() || (endTime_ - time_ - timeStepSize_) < baseEps_*timeStepSize_; |
397 | } | ||
398 | |||
399 | /*! | ||
400 | * \brief The current maximum time step size | ||
401 | * \note This gets aligned on every setTimeStepSize call to end time | ||
402 | * and other possible check points | ||
403 | */ | ||
404 | 58859 | Scalar maxTimeStepSize() const override | |
405 | { | ||
406 |
2/2✓ Branch 1 taken 58144 times.
✓ Branch 2 taken 715 times.
|
58859 | if (finished()) |
407 | return 0.0; | ||
408 | |||
409 | using std::min; using std::max; | ||
410 |
3/4✓ Branch 0 taken 58144 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20398 times.
✓ Branch 3 taken 37746 times.
|
136686 | return min(userSetMaxTimeStepSize_, max<Scalar>(0.0, endTime_ - time_)); |
411 | } | ||
412 | |||
413 | /*! | ||
414 | * \brief State info on cpu time. | ||
415 | * \note Always call this after TimeLoop::advanceTimeStep() | ||
416 | */ | ||
417 | 20886 | void reportTimeStep() const | |
418 | { | ||
419 |
2/2✓ Branch 0 taken 20159 times.
✓ Branch 1 taken 727 times.
|
20886 | if (verbose_) |
420 | { | ||
421 | 20159 | const auto cpuTime = wallClockTime(); | |
422 | using std::round; | ||
423 | 20159 | const auto percent = round( (time_ - startTime_) / (endTime_ - startTime_) * 100 ); | |
424 | std::cout << Fmt::format("[{:3.0f}%] ", percent) | ||
425 | 20159 | << Fmt::format("Time step {} done in {:.2g} seconds. ", timeStepIdx_, timeStepWallClockTime_) | |
426 |
2/4✓ Branch 2 taken 20159 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20159 times.
✗ Branch 6 not taken.
|
80636 | << Fmt::format("Wall clock time: {:.5g}, time: {:.5g}, time step size: {:.5g}\n", cpuTime, time_, previousTimeStepSize_); |
427 | } | ||
428 | 20886 | } | |
429 | |||
430 | /*! | ||
431 | * \brief Print final status and stops tracking the time. | ||
432 | */ | ||
433 | template< class Communicator = Dune::Communication<typename Dune::MPIHelper::MPICommunicator> > | ||
434 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
352 | void finalize(const Communicator& comm = Dune::MPIHelper::getCommunication()) |
435 | { | ||
436 | 352 | auto cpuTime = timer_.stop(); | |
437 | |||
438 |
2/2✓ Branch 0 taken 333 times.
✓ Branch 1 taken 19 times.
|
352 | if (verbose_) |
439 | 666 | std::cout << Fmt::format("Simulation took {:.5g} seconds on {} processes.\n", cpuTime, comm.size()); | |
440 | |||
441 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 283 times.
|
321 | if (comm.size() > 1) |
442 | 38 | cpuTime = comm.sum(cpuTime); | |
443 | |||
444 |
2/2✓ Branch 0 taken 333 times.
✓ Branch 1 taken 19 times.
|
352 | if (verbose_) |
445 | 666 | std::cout << Fmt::format("The cumulative CPU time was {:.5g} seconds.\n", cpuTime); | |
446 | 352 | } | |
447 | |||
448 | //! If the time loop has verbose output | ||
449 | 366 | bool verbose() const | |
450 | 366 | { return verbose_; } | |
451 | |||
452 | //! Sets time loop verbosity | ||
453 | void setVerbose(bool verbose = true) | ||
454 | { verbose_ = verbose; } | ||
455 | |||
456 | /* | ||
457 | * @} | ||
458 | */ | ||
459 | |||
460 | protected: | ||
461 | static constexpr Scalar baseEps_ = 1e-10; | ||
462 | |||
463 | Dune::Timer timer_; | ||
464 | Scalar time_; | ||
465 | Scalar endTime_; | ||
466 | Scalar startTime_; | ||
467 | |||
468 | Scalar timeStepSize_; | ||
469 | Scalar previousTimeStepSize_; | ||
470 | Scalar userSetMaxTimeStepSize_; | ||
471 | Scalar timeAfterLastTimeStep_, timeStepWallClockTime_; | ||
472 | int timeStepIdx_; | ||
473 | bool finished_; | ||
474 | bool verbose_; | ||
475 | }; | ||
476 | |||
477 | // always fall back to floating-point representation | ||
478 | template<class Rep1, class Period1, | ||
479 | class Rep2, class Period2, | ||
480 | class Rep3, class Period3> | ||
481 | TimeLoop(std::chrono::duration<Rep1, Period1>, | ||
482 | std::chrono::duration<Rep2, Period2>, | ||
483 | std::chrono::duration<Rep3, Period3>, | ||
484 | bool verbose = true) -> TimeLoop<std::conditional_t< | ||
485 | std::is_floating_point_v<std::common_type_t<Rep1, Rep2, Rep3>>, | ||
486 | std::common_type_t<Rep1, Rep2, Rep3>, | ||
487 | double | ||
488 | >>; | ||
489 | |||
490 | /*! | ||
491 | * \ingroup Core | ||
492 | * \brief A time loop with a check point mechanism | ||
493 | */ | ||
494 | template <class Scalar> | ||
495 | 15 | class CheckPointTimeLoop : public TimeLoop<Scalar> | |
496 | { | ||
497 | 14906 | class CheckPointType { | |
498 | static constexpr std::size_t manualIdx = 0; | ||
499 | static constexpr std::size_t periodicIdx = 1; | ||
500 | |||
501 | public: | ||
502 |
2/2✓ Branch 0 taken 847 times.
✓ Branch 1 taken 13908 times.
|
14868 | bool isPeriodic() const { return set_[periodicIdx]; } |
503 | 14851 | bool isManual() const { return set_[manualIdx]; } | |
504 | 14851 | bool isAny() const { return set_.any(); } | |
505 | |||
506 | CheckPointType& withPeriodic(bool value) { set_[periodicIdx] = value; return *this; } | ||
507 | 29812 | CheckPointType& withManual(bool value) { set_[manualIdx] = value; return *this; } | |
508 | |||
509 | private: | ||
510 | std::bitset<2> set_; | ||
511 | }; | ||
512 | |||
513 | public: | ||
514 | template<class... Args> | ||
515 | 155 | CheckPointTimeLoop(Args&&... args) | |
516 | 155 | : TimeLoop<Scalar>(std::forward<Args>(args)...) | |
517 | { | ||
518 | 155 | periodicCheckPoints_ = false; | |
519 | 155 | deltaPeriodicCheckPoint_ = 0.0; | |
520 | 155 | lastPeriodicCheckPoint_ = this->startTime_; | |
521 | 155 | isCheckPoint_ = false; | |
522 | 155 | } | |
523 | |||
524 | /*! | ||
525 | * \brief Advance time step. | ||
526 | */ | ||
527 | 14851 | void advanceTimeStep() override | |
528 | { | ||
529 | 14851 | const auto dt = this->timeStepSize(); | |
530 | 14851 | const auto newTime = this->time()+dt; | |
531 | |||
532 | //! Check point management, TimeLoop::isCheckPoint() has to be called after this! | ||
533 |
2/2✓ Branch 0 taken 79 times.
✓ Branch 1 taken 14772 times.
|
14851 | const auto cpType = nextCheckPointType_(newTime); |
534 |
5/6✓ Branch 0 taken 79 times.
✓ Branch 1 taken 14772 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
✓ Branch 5 taken 28 times.
|
14859 | if (cpType.isManual()) checkPoints_.pop(); |
535 |
2/2✓ Branch 0 taken 915 times.
✓ Branch 1 taken 13936 times.
|
14851 | if (cpType.isPeriodic()) lastPeriodicCheckPoint_ += deltaPeriodicCheckPoint_; |
536 | 14851 | isCheckPoint_ = cpType.isAny(); | |
537 | |||
538 | 14851 | const auto previousTimeStepSize = this->previousTimeStepSize(); | |
539 | |||
540 | // advance the time step like in the parent class | ||
541 | 14851 | TimeLoop<Scalar>::advanceTimeStep(); | |
542 | |||
543 | // if this is a check point we might have reduced the time step to reach this check point | ||
544 | // reset the time step size to the time step size before this time step | ||
545 | 14851 | if (!this->willBeFinished()) | |
546 | { | ||
547 | using std::max; | ||
548 |
2/2✓ Branch 0 taken 924 times.
✓ Branch 1 taken 13687 times.
|
14611 | if (isCheckPoint_) |
549 |
2/2✓ Branch 0 taken 100 times.
✓ Branch 1 taken 824 times.
|
1024 | this->setTimeStepSize(max(dt, previousTimeStepSize)); |
550 | |||
551 | // if there is a check point soon check if the time step after the next time step would be smaller | ||
552 | // than 20% of the next time step, if yes increase the suggested next time step to exactly reach the check point | ||
553 | // (in the limits of the maximum time step size) | ||
554 | 14611 | auto nextDt = this->timeStepSize(); | |
555 | 14611 | const auto threshold = 0.2*nextDt; | |
556 | 14611 | const auto nextTime = this->time() + nextDt; | |
557 | |||
558 | 14611 | const auto nextDtToCheckPoint = maxDtToCheckPoint_(nextTime); | |
559 |
4/4✓ Branch 0 taken 13688 times.
✓ Branch 1 taken 923 times.
✓ Branch 2 taken 13669 times.
✓ Branch 3 taken 19 times.
|
14611 | if (nextDtToCheckPoint > Scalar{0} && Dune::FloatCmp::le(nextDtToCheckPoint, threshold)) |
560 | 19 | nextDt += nextDtToCheckPoint; | |
561 | |||
562 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14611 times.
|
14611 | assert(nextDt > 0.0); |
563 | 14611 | this->setTimeStepSize(nextDt); | |
564 | } | ||
565 | 14851 | } | |
566 | |||
567 | /*! | ||
568 | * \brief The current maximum time step size | ||
569 | * \note This gets aligned on every setTimeStepSize call to end time | ||
570 | * and other possible check points | ||
571 | */ | ||
572 | 45390 | Scalar maxTimeStepSize() const override | |
573 | { | ||
574 | using std::min; | ||
575 | 45390 | const auto maxCheckPointDt = timeStepSizeToNextCheckPoint(); | |
576 |
2/2✓ Branch 0 taken 10571 times.
✓ Branch 1 taken 34819 times.
|
45390 | const auto maxDtParent = TimeLoop<Scalar>::maxTimeStepSize(); |
577 | 45390 | return min(maxDtParent, maxCheckPointDt); | |
578 | } | ||
579 | |||
580 | /*! | ||
581 | * \brief Set a periodic check point | ||
582 | * \note You can query if we are at a time check point with isCheckPoint() | ||
583 | * \param interval Set a periodic checkout every [interval] seconds | ||
584 | * \param offset time from which the periodic check points are supposed to start (simulation time) | ||
585 | * the first checkpoint will be at time = offset. | ||
586 | * \note If offset is in the past the first check point will be at the next | ||
587 | * periodic check point greater or equal than time | ||
588 | * \note This also updates the time step size and potentially reduces the time step size to meet the next check point | ||
589 | */ | ||
590 | template<class ScalarOrDuration1, class ScalarOrDuration2 = Scalar> | ||
591 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
39 | void setPeriodicCheckPoint(ScalarOrDuration1 interval, ScalarOrDuration2 offset = 0.0) |
592 | { | ||
593 |
5/10✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 4 times.
✗ Branch 14 not taken.
|
55 | setPeriodicCheckPoint_( |
594 | Detail::TimeLoop::toSeconds<Scalar>(interval), | ||
595 | Detail::TimeLoop::toSeconds<Scalar>(offset) | ||
596 | ); | ||
597 | 55 | } | |
598 | |||
599 | //! disable periodic check points | ||
600 | void disablePeriodicCheckPoints() | ||
601 | { periodicCheckPoints_ = false; } | ||
602 | |||
603 | //! remove all check points | ||
604 | 4 | void removeAllCheckPoints() | |
605 | { | ||
606 | 4 | periodicCheckPoints_ = false; | |
607 | 4 | while (!checkPoints_.empty()) | |
608 |
1/4✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | checkPoints_.pop(); |
609 | 4 | } | |
610 | |||
611 | /*! | ||
612 | * \brief Whether now is a time checkpoint | ||
613 | * \note has to be called after TimeLoop::advanceTimeStep() | ||
614 | */ | ||
615 | 11625 | bool isCheckPoint() const | |
616 |
7/10✓ Branch 0 taken 3384 times.
✓ Branch 1 taken 7950 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 273 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
|
11625 | { return isCheckPoint_; } |
617 | |||
618 | /*! | ||
619 | * \brief add a checkpoint to the queue | ||
620 | * \note checkpoints have to be provided in ascending order | ||
621 | * \param t the check point (in seconds) | ||
622 | * \note This also updates the time step size and potentially reduces the time step size to meet the next check point | ||
623 | */ | ||
624 | template<class ScalarOrDuration> | ||
625 | 30 | void setCheckPoint(ScalarOrDuration t) | |
626 | { | ||
627 | // set the check point | ||
628 |
4/8✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
30 | setCheckPoint_(Detail::TimeLoop::toSeconds<Scalar>(t)); |
629 | |||
630 | // make sure we respect this check point on the next time step | ||
631 |
4/8✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
30 | this->setTimeStepSize(this->timeStepSize()); |
632 | 30 | } | |
633 | |||
634 | /*! | ||
635 | * \brief add checkpoints to the queue from a list of time points | ||
636 | * \note checkpoints have to be provided in ascending order | ||
637 | * \param checkPoints the list of check points | ||
638 | * \note This also updates the time step size and potentially reduces the time step size to meet the next check point | ||
639 | */ | ||
640 | template<class ScalarOrDuration> | ||
641 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | void setCheckPoint(const std::initializer_list<ScalarOrDuration>& checkPoints) |
642 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | { setCheckPoint(checkPoints.begin(), checkPoints.end()); } |
643 | |||
644 | /*! | ||
645 | * \brief add checkpoints to the queue from a container from the first iterator to the last iterator | ||
646 | * \note checkpoints have to be provided in ascending order | ||
647 | * \param first iterator to the first element to be inserted | ||
648 | * \param last iterator to the one-after-last element to be inserted | ||
649 | * \note This also updates the time step size and potentially reduces the time step size to meet the next check point | ||
650 | */ | ||
651 | template<class ForwardIterator> | ||
652 | 21 | void setCheckPoint(ForwardIterator first, ForwardIterator last) | |
653 | { | ||
654 | // set the check points | ||
655 |
2/2✓ Branch 0 taken 281 times.
✓ Branch 1 taken 21 times.
|
302 | for (; first != last; ++first) |
656 | 281 | setCheckPoint_(Detail::TimeLoop::toSeconds<Scalar>(*first)); | |
657 | |||
658 | // make sure we respect this check point on the next time step | ||
659 | 21 | this->setTimeStepSize(this->timeStepSize()); | |
660 | 21 | } | |
661 | |||
662 | /*! | ||
663 | * \brief Return the time step size to exactly reach the next check point | ||
664 | * In case there is no check point in the future, the largest representable scalar is returned | ||
665 | */ | ||
666 | 45390 | Scalar timeStepSizeToNextCheckPoint() const | |
667 | 45390 | { return maxDtToCheckPoint_(this->time()); } | |
668 | |||
669 | private: | ||
670 | 12340 | bool fuzzyEqual_(const Scalar t0, const Scalar t1) const | |
671 |
2/2✓ Branch 0 taken 4731 times.
✓ Branch 1 taken 79 times.
|
4810 | { return Dune::FloatCmp::eq<Scalar, Dune::FloatCmp::absolute>(t0, t1, this->baseEps_*this->timeStepSize()); } |
672 | |||
673 | 55 | void setPeriodicCheckPoint_(Scalar interval, Scalar offset = 0.0) | |
674 | { | ||
675 | using std::signbit; | ||
676 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
|
55 | if (signbit(interval)) |
677 | ✗ | DUNE_THROW(Dune::InvalidStateException, "Interval has to be positive!"); | |
678 | |||
679 | 55 | periodicCheckPoints_ = true; | |
680 | 55 | deltaPeriodicCheckPoint_ = interval; | |
681 | 55 | lastPeriodicCheckPoint_ = offset; | |
682 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 55 times.
|
63 | while (lastPeriodicCheckPoint_ + interval - this->time() < 1e-14*interval) |
683 | 8 | lastPeriodicCheckPoint_ += interval; | |
684 | |||
685 |
1/2✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
|
55 | if (this->verbose()) |
686 | std::cout << Fmt::format("Enabled periodic check points every {:.5g} seconds ", interval) | ||
687 |
1/2✓ Branch 2 taken 55 times.
✗ Branch 3 not taken.
|
165 | << Fmt::format("with the next check point at {:.5g} seconds.\n", lastPeriodicCheckPoint_ + interval); |
688 | |||
689 | // check if the current time point is a periodic check point | ||
690 |
2/2✓ Branch 0 taken 47 times.
✓ Branch 1 taken 8 times.
|
55 | if (nextCheckPointType_(this->time() + deltaPeriodicCheckPoint_).isPeriodic()) |
691 | 47 | isCheckPoint_ = true; | |
692 | |||
693 | // make sure we respect this check point on the next time step | ||
694 | 55 | this->setTimeStepSize(this->timeStepSize()); | |
695 | 55 | } | |
696 | |||
697 | //! Adds a check point to the queue | ||
698 | 311 | void setCheckPoint_(Scalar t) | |
699 | { | ||
700 |
2/2✓ Branch 0 taken 307 times.
✓ Branch 1 taken 4 times.
|
311 | if (Dune::FloatCmp::le<Scalar, Dune::FloatCmp::absolute>(t - this->time(), 0.0, this->timeStepSize()*this->baseEps_)) |
701 | { | ||
702 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (this->verbose()) |
703 | std::cerr << Fmt::format("Couldn't insert checkpoint at t = {:.5g} ", t) | ||
704 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
24 | << Fmt::format("because that's not in the future! (current simulation time is {:.5g})\n", this->time()); |
705 | 8 | return; | |
706 | } | ||
707 | |||
708 |
2/2✓ Branch 0 taken 266 times.
✓ Branch 1 taken 37 times.
|
303 | if (!checkPoints_.empty()) |
709 | { | ||
710 |
4/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 263 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 260 times.
|
269 | if (t <= checkPoints_.back()) |
711 | { | ||
712 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
|
6 | if (this->verbose()) |
713 | std::cerr << Fmt::format("Couldn't insert checkpoint at t = {:.5g} ", t) | ||
714 | 8 | << Fmt::format("because it's earlier than or equal to the last check point (t = {:.5g}) in the queue.\n", checkPoints_.back()) | |
715 |
4/8✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
|
12 | << "Checkpoints can only be inserted in ascending order." << std::endl; |
716 | 6 | return; | |
717 | } | ||
718 | } | ||
719 | |||
720 |
2/2✓ Branch 0 taken 294 times.
✓ Branch 1 taken 3 times.
|
297 | checkPoints_.push(t); |
721 |
2/2✓ Branch 0 taken 282 times.
✓ Branch 1 taken 15 times.
|
297 | if (this->verbose()) |
722 | 564 | std::cout << Fmt::format("Set check point at t = {:.5g} seconds.\n", t); | |
723 | } | ||
724 | |||
725 | /*! | ||
726 | * \brief Return the type of (next) check point at the given time | ||
727 | */ | ||
728 | 14906 | CheckPointType nextCheckPointType_(Scalar t) | |
729 | { | ||
730 | return CheckPointType{} | ||
731 |
4/4✓ Branch 0 taken 7530 times.
✓ Branch 1 taken 7376 times.
✓ Branch 2 taken 6568 times.
✓ Branch 3 taken 962 times.
|
14906 | .withPeriodic(periodicCheckPoints_ && fuzzyEqual_(t - lastPeriodicCheckPoint_, deltaPeriodicCheckPoint_)) |
732 |
4/4✓ Branch 0 taken 4810 times.
✓ Branch 1 taken 10096 times.
✓ Branch 2 taken 4731 times.
✓ Branch 3 taken 79 times.
|
14906 | .withManual(!checkPoints_.empty() && fuzzyEqual_(t - checkPoints_.front(), 0.0)); |
733 | } | ||
734 | |||
735 | /*! | ||
736 | * \brief Compute a time step size respecting upcoming checkpoints, starting from the given time t. | ||
737 | */ | ||
738 | 60001 | Scalar maxDtToCheckPoint_(Scalar t) const | |
739 | { | ||
740 | static constexpr auto unset = std::numeric_limits<Scalar>::max(); | ||
741 | 60001 | const auto dtToPeriodic = dtToNextPeriodicCheckPoint_(t); | |
742 |
2/2✓ Branch 0 taken 19302 times.
✓ Branch 1 taken 40699 times.
|
60001 | const auto dtToManual = dtToNextManualCheckPoint_(t); |
743 | |||
744 | using std::min; | ||
745 | 60001 | return min( | |
746 |
2/2✓ Branch 0 taken 19298 times.
✓ Branch 1 taken 40703 times.
|
60001 | dtToPeriodic.value_or(unset), |
747 |
2/2✓ Branch 0 taken 30678 times.
✓ Branch 1 taken 29323 times.
|
60001 | dtToManual.value_or(unset) |
748 | 60001 | ); | |
749 | } | ||
750 | |||
751 | /*! | ||
752 | * \brief Compute the time step size to the next periodic check point. | ||
753 | */ | ||
754 | 60001 | std::optional<Scalar> dtToNextPeriodicCheckPoint_(Scalar t) const | |
755 | { | ||
756 |
2/2✓ Branch 0 taken 30678 times.
✓ Branch 1 taken 29323 times.
|
60001 | if (periodicCheckPoints_) |
757 | 30678 | return lastPeriodicCheckPoint_ + deltaPeriodicCheckPoint_ - t; | |
758 | return {}; | ||
759 | } | ||
760 | |||
761 | /*! | ||
762 | * \brief Compute a time step size respecting the next manual check point. | ||
763 | */ | ||
764 | 60001 | std::optional<Scalar> dtToNextManualCheckPoint_(Scalar t) const | |
765 | { | ||
766 |
2/2✓ Branch 0 taken 19302 times.
✓ Branch 1 taken 40699 times.
|
60001 | if (!checkPoints_.empty()) |
767 | 19302 | return checkPoints_.front() - t; | |
768 | return {}; | ||
769 | } | ||
770 | |||
771 | bool periodicCheckPoints_; | ||
772 | Scalar deltaPeriodicCheckPoint_; | ||
773 | Scalar lastPeriodicCheckPoint_; | ||
774 | std::queue<Scalar> checkPoints_; | ||
775 | bool isCheckPoint_; | ||
776 | }; | ||
777 | |||
778 | template<class... Args> | ||
779 | CheckPointTimeLoop(Args&&... args) -> CheckPointTimeLoop< | ||
780 | typename decltype(TimeLoop{std::forward<Args>(args)...})::Scalar | ||
781 | >; | ||
782 | |||
783 | } // end namespace Dumux | ||
784 | |||
785 | #endif | ||
786 |