Version: SMASH-3.4
clock.h
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2020,2022-2026
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #ifndef SRC_INCLUDE_SMASH_CLOCK_H_
11 #define SRC_INCLUDE_SMASH_CLOCK_H_
12 
13 #include <algorithm>
14 #include <cmath>
15 #include <cstdint>
16 #include <cstdio>
17 #include <limits>
18 #include <stdexcept>
19 #include <vector>
20 
21 #include "logging.h"
22 #include "numeric_cast.h"
23 
24 namespace smash {
25 
26 static constexpr int LClock = LogArea::Clock::id;
27 
28 /**
29  * Clock tracks the time in the simulation.
30  *
31  * The basic unit is 1 fm in natural units which correspond to
32  * \f$\frac{10^{-15}}{299\,792\,458}\,\mathrm{s} \approx
33  * 0.33\cdot10^{-23}\,\mathrm{s}\f$ in the international system of units. The
34  * resolution of the clock is \f$0.000001\,\mathrm{fm}=10^{-6}\,\mathrm{fm}\f$,
35  * i.e. only multiples of \f$0.000001\,\mathrm{fm}\f$ are internally
36  * representable.
37  *
38  * \attention
39  * There are two very different types of clocks implemented.
40  * -# The UniformClock is meant to track time in a simulation and it has the
41  * quite peculiar feature that it is aware of the end time of the simulation.
42  * Therefore, although it can be ticked beyond such a time, its methods about
43  * the current time, the next time and the time-step duration will adjust
44  * their return value when the clock gets to or past the simulation end time.
45  * -# The CustomClock is meant to track a list of time points and it is best
46  * suited for output. Ticking this clock means to consider the following
47  * point in time. Ticking beyond the last time point is considered an error.
48  *
49  * <h3> Potential usage for adapting time steps: </h3>
50  *
51  * \code
52  * const double end_time = 10.;
53  * UniformClock lab_time(0., 0.1, end_time);
54  * while (lab_time < end_time) {
55  * // do something
56  * // adapt the timestep size to external circumstances:
57  * if (system_is_very_dense()) {
58  * lab_time.set_timestep_duration(lab_time.timestep_duration() / 2.);
59  * }
60  * if (system_is_very_dilute()) {
61  * lab_time.set_timestep_duration(lab_time.timestep_duration() * 2.);
62  * }
63  * // let the clock tick
64  * ++lab_time;
65  * }
66  * \endcode
67  *
68  * <h3> Possible actions for Clock </h3>
69  *
70  * \li look at it and find out the current time
71  * \see current_time()
72  * \see next_time()
73  * \li advance the clock (by one tick, by several ticks, or by a given
74  * time)
75  * \see operator++()
76  * \see UniformClock::operator+=(T)
77  * \see operator+=(Representation)
78  * \li set / retrieve the timestep (length of one tick)
79  * \see UniformClock::set_timestep_duration(double)
80  * \see timestep_duration()
81  * \li compare time against different clock or fixed value
82  * \see operator<(const Clock&) const
83  * \see operator<(double) const
84  * \see operator>(double) const
85  *
86  */
87 class Clock {
88  public:
89  /// The type used for counting ticks/time.
90  using Representation = std::int64_t;
91  /// \return the duration of the current time step
92  virtual double timestep_duration() const = 0;
93  /// \return the current time
94  virtual double current_time() const = 0;
95  /// \return the time of the next time step
96  virtual double next_time() const = 0;
97  /**
98  * Reset the clock to the starting time of the simulation
99  *
100  * \param[in] start_time starting time of the simulation
101  * \param[in] is_output_clock whether this is an output clock rather than a
102  * lab clock
103  */
104  virtual void reset(double start_time, bool is_output_clock) = 0;
105  /**
106  * Remove output times before the starting time of the simulation if this
107  * is a custom clock.
108  *
109  * \param[in] start_time starting time of the simulation
110  */
111  virtual void remove_times_in_past(double start_time) = 0;
112  /**
113  * Advances the clock by one tick.
114  *
115  * This operator is used as `++clock`. The operator `clock++` is not
116  * implemented deliberately, because that requires a copy of the clock
117  * being created.
118  */
120  // guard against overflow:
121  if (counter_ >= std::numeric_limits<Representation>::max() - 1) {
122  throw std::overflow_error("Too many timesteps, clock overflow imminent");
123  }
124  ++counter_;
125  return *this;
126  }
127 
128  /**
129  * Advances the clock by an arbitrary number of ticks.
130  *
131  * \param[in] advance_several_timesteps Number of the time steps added
132  * to the clock
133  * \throw OverflowError if the number of the added time steps exceeds
134  * the maximum value.
135  */
136  Clock& operator+=(Representation advance_several_timesteps) {
137  if (counter_ >= std::numeric_limits<Representation>::max() -
138  advance_several_timesteps) {
139  throw std::overflow_error("Too many timesteps, clock overflow imminent");
140  }
141  counter_ += advance_several_timesteps;
142  return *this;
143  }
144 
145  /**
146  * Compares the internal times of two clocks.
147  *
148  * \param[in] rhs The other clock.
149  */
150  bool operator<(const Clock& rhs) const {
152  }
153 
154  /**
155  * Compares the internal time of the clock against a fixed time.
156  *
157  * \param[in] time The other time.
158  */
159  bool operator<(double time) const { return present_internal_time() < time; }
160 
161  /**
162  * Compares the internal time of the clock against a fixed time.
163  *
164  * \param[in] time The other time.
165  */
166  bool operator>(double time) const { return present_internal_time() > time; }
167 
168  virtual ~Clock() = default;
169 
170  protected:
171  /**
172  * This function \b always returns the clock time, even if children might
173  * attribute a different behaviour to \c current_time method (as UniformClock
174  * does).
175  *
176  * \warning It is important to have this method that is in turn used in the
177  * comparison operators, so that clock comparisons are independent from
178  * other possible existing mechanism (like that of the UniformClock).
179  *
180  * \return The present internal clock time.
181  */
182  virtual double present_internal_time() const = 0;
183  /**
184  * Internally used to count the number of time steps.
185  */
187 };
188 
189 /** Clock with uniformly spaced time steps
190  *
191  * <h3> Internal clock mechanisms </h3>
192  *
193  * This clock stores a time step size \f$\Delta t\f$, a base time \f$t_0\f$ as
194  * well as an end time \f$t_{end}\f$ and a counter \f$n\f$. The current time is
195  * calculated from \f$t = t_0 + n \cdot \Delta t\f$. When \f$\Delta t\f$ is
196  * changed, \f$t_0\f$ is reset to the present time and \f$n\f$ is set to 0.
197  * As soon as \f$t\geq t_{end}\f$, the clock will always return \f$t_{end}\f$ as
198  * current and next time. The time step size can be retrieved and the returned
199  * value is
200  * \f[
201  * \begin{aligned}
202  * \Delta t \qquad&\mbox{if}& &t\leq t_{end}-\Delta t \\
203  * t_{end}-t \qquad&\mbox{if}& t_{end}-\Delta t<{}&t<t_{end} \\
204  * 0.0 \qquad&\mbox{if}& &t\geq t_{end} \\
205  * \end{aligned}
206  * \f]
207  * In the last case, i.e. if the time step size is required when the clock
208  * ticked beyond the simulation end, a warning is given to the user.
209  */
210 class UniformClock : public Clock {
211  /**
212  * Defines the resolution of the clock (namely the smallest representable time
213  * difference).
214  *
215  * The value 0.000001 is very well suited because
216  * \li It should be \f$10^{-n},\;n\in\mathbb{N}\f$. That's because we want to
217  * use it to convert user input/output and that's in decimal representation.
218  * \li The floating-point representation of the constant should have a small
219  * error. 0.000001 has the smallest error (i.e. 0.022 ulp) in the range
220  * \f$1\leq n \leq 10\f$. The small error helps to convert the internal
221  * integer representation as precise as possible to floating-point.
222  */
223  static constexpr double resolution = 0.000001;
224 
225  public:
226  /// default initializer: Timestep size is set to 0!
227  UniformClock() = default;
228  /**
229  * Initialize with base time and time step size.
230  *
231  * \param[in] time base time
232  * \param[in] dt step size
233  * \param[in] time_end end time of particle propagation
234  */
235  UniformClock(double time, double dt, double time_end)
237  reset_time_(convert(time)),
238  time_end_(convert(time_end)) {
239  if (dt <= 0.) {
240  throw std::range_error("Time increment must be positive and non-zero");
241  }
242  if (reset_time_ >= time_end_) {
243  throw std::range_error(
244  "The initial time of UniformClock must be smaller than the end time. "
245  "(Attempt to set initial time to " +
246  std::to_string(time) + " and end time to " +
247  std::to_string(time_end) + " not possible)");
248  }
249  }
250  /**
251  * \return the current time or the end time if the clock ticked beyond it.
252  */
253  double current_time() const override {
254  auto present_time = present_internal_time();
255  // Do comparison in internal representation unit and return converted values
256  if (convert(present_time) > time_end_) {
257  return convert(time_end_);
258  } else {
259  return present_time;
260  }
261  }
262  /**
263  * \return the time in the next tick or the end time if the clock ticked
264  * beyond it.
265  *
266  * \note This function is needed, because current_time() + timestep_duration()
267  * is not the same as the next tick (numerically; this is due to
268  * floating point arithmetic).
269  */
270  double next_time() const override {
272  std::numeric_limits<Representation>::max() - timestep_duration_) {
273  throw std::overflow_error("Too many timesteps, clock overflow imminent");
274  }
275  auto next_point_in_time = reset_time_ + timestep_duration_ * (counter_ + 1);
276  if (next_point_in_time > time_end_) {
277  return convert(time_end_);
278  } else {
279  return convert(next_point_in_time);
280  }
281  }
282 
283  /**
284  * \return the time step size from the current time. If a full tick would
285  * result in a time larger then the end time, a smaller size is returned. If
286  * the clock is already beyond the end time, 0.0 is returned and a warning is
287  * printed. \see UniformClock description.
288  */
289  double timestep_duration() const override {
290  auto present_time = convert(present_internal_time());
291  if (present_time > time_end_) {
292  logg[LClock].warn() << "UniformClock asked for timestep duration beyond "
293  "end of simulation, returning 0.";
294  return 0.0;
295  } else if (present_time + timestep_duration_ > time_end_) {
296  return convert(time_end_ - present_time);
297  } else {
298  return convert(timestep_duration_);
299  }
300  }
301  /**
302  * Sets the time step size (and resets the counter).
303  *
304  * \param[in] dt new time step size
305  */
306  void set_timestep_duration(double dt) {
307  if (dt <= 0.) {
308  throw std::range_error("Time increment must be positive and non-zero!");
309  }
311  counter_ = 0;
313  }
314 
315  /**
316  * Resets the time to the starting time of an event.
317  *
318  * \param[in] start_time Starting time of the simulation
319  * \param[in] is_output_clock whether this is an output clock or a lab clock
320  */
321  void reset(double start_time, bool is_output_clock) override {
322  double reset_time;
323  if (is_output_clock) {
324  auto delta_t = convert(timestep_duration_);
325  reset_time = std::floor(start_time / delta_t) * delta_t;
326  } else {
327  reset_time = start_time;
328  }
329  if (reset_time < current_time()) {
330  logg[LClock].debug("Resetting clock from", current_time(), " fm to ",
331  reset_time, " fm");
332  }
333  reset_time_ = convert(reset_time);
334  counter_ = 0;
335  }
336 
337  void remove_times_in_past(double) override{};
338 
339  /**
340  * Advances the clock by an arbitrary timestep (multiple of 0.000001 fm).
341  *
342  * \tparam T type of the timestep
343  * \param[in] big_timestep Time step by which the clock is advanced.
344  * \note It uses a template parameter only for disambiguation with the
345  * overload below.
346  */
347  template <typename T>
348  typename std::enable_if<std::is_floating_point<T>::value, Clock&>::type
349  operator+=(T big_timestep) {
350  if (big_timestep < 0.) {
351  throw std::range_error("The clock cannot be turned back.");
352  }
353  reset_time_ += convert(big_timestep);
354  return *this;
355  }
356  /**
357  * advances the clock by an arbitrary number of ticks.
358  *
359  * \param[in] advance_several_timesteps Number of the timesteps added
360  * to the clock
361  * \throw OverflowError if the number of the added timesteps exceeds
362  * the maximum value.
363  */
364  Clock& operator+=(Representation advance_several_timesteps) {
365  if (counter_ >= std::numeric_limits<Representation>::max() -
366  advance_several_timesteps) {
367  throw std::overflow_error("Too many timesteps, clock overflow imminent");
368  }
369  counter_ += advance_several_timesteps;
370  return *this;
371  }
372 
373  protected:
374  /**
375  * Access the internal time of the clock, independently from the end time.
376  *
377  * \return the internal clock time.
378  */
379  double present_internal_time() const override {
381  }
382 
383  private:
384  /// A multiplier transferring the internal integer to the real time.
385  static constexpr double to_double = resolution;
386  /// A multiplier transferring the real time to the internal integer.
387  static constexpr double from_double = 1. / resolution;
388 
389  /// Convert a double \p x into the internal int representation.
390  static Representation convert(double x) {
391  return numeric_cast<Representation>(std::round(x * from_double));
392  }
393  /// Convert an internal int value \p x into the double representation.
394  static double convert(Representation x) { return x * to_double; }
395 
396  /// The time step size \f$\Delta t\f$ in \f$10^{-6}\,\mathrm{fm}\f$.
398  /// The time of last reset (when counter_ was set to 0).
400  /// The end time of the particle propagation
402 };
403 
404 /// Clock with explicitly defined time steps
405 class CustomClock : public Clock {
406  public:
407  /**
408  * Initialises a custom clock with explicitly given output times
409  *
410  * \param[in] times vector of desired output times
411  */
412  explicit CustomClock(std::vector<double> times) : custom_times_(times) {
413  std::sort(custom_times_.begin(), custom_times_.end());
414  counter_ = -1;
415  }
416 
417  /**
418  * \return The start time if the clock has never been ticked or the current
419  * time otherwise.
420  * \throw std::out_of_range if the clock has ticked beyond the last time.
421  * \throw std::runtime_error if the clock has an internal broken state.
422  */
423  double current_time() const override {
424  if (counter_ == -1) {
425  // current time before the first output should be the starting time
426  return start_time_;
427  } else if (counter_ < -1) {
428  throw std::runtime_error(
429  "Trying to access time of clock in invalid state.");
430  } else {
431  return custom_times_.at(counter_);
432  }
433  }
434 
435  /**
436  * \return The next custom time.
437  * \throw std::out_of_range if the clock has ticked beyond last time.
438  */
439  double next_time() const override { return custom_times_.at(counter_ + 1); }
440 
441  /// \copydoc Clock::timestep_duration
442  double timestep_duration() const override {
443  return next_time() - current_time();
444  }
445 
446  /**
447  * Reset the clock to the starting time of the simulation.
448  *
449  * \param[in] start_time starting time of the simulation
450  *
451  * \note The second \c bool parameter is irrelevant and unused here.
452  */
453  void reset(double start_time, bool) override {
454  counter_ = -1;
455  start_time_ = start_time;
456  }
457 
458  /**
459  * Remove all custom times before start_time.
460  *
461  * \param[in] start_time starting time of the simulation
462  */
463  void remove_times_in_past(double start_time) override {
464  custom_times_.erase(
465  std::remove_if(
466  custom_times_.begin(), custom_times_.end(),
467  [start_time](double t) {
468  if (t < start_time) {
469  logg[LClock].warn("Removing custom output time ", t,
470  " fm since it is earlier than the "
471  "starting time of the simulation");
472  return true;
473  } else if (t == start_time) {
474  logg[LClock].debug(
475  "The start time ", t,
476  " fm has to be removed from the 'custom_times_' vector "
477  "since it will be otherwise considered twice in the actual "
478  "output time steps");
479  return true;
480  } else {
481  return false;
482  }
483  }),
484  custom_times_.end());
485  }
486 
487  protected:
488  /**
489  * For the CustomClock, the internal time is basically by design the same as
490  * what the current_time() method returns.
491  *
492  * \return the same as \c current_time does.
493  */
494  double present_internal_time() const override { return current_time(); }
495 
496  private:
497  /// Vector of times where output is generated
498  std::vector<double> custom_times_;
499  /// Starting time of the simulation
500  double start_time_ = 0.;
501 };
502 } // namespace smash
503 
504 #endif // SRC_INCLUDE_SMASH_CLOCK_H_
Clock tracks the time in the simulation.
Definition: clock.h:87
bool operator<(double time) const
Compares the internal time of the clock against a fixed time.
Definition: clock.h:159
virtual ~Clock()=default
bool operator<(const Clock &rhs) const
Compares the internal times of two clocks.
Definition: clock.h:150
std::int64_t Representation
The type used for counting ticks/time.
Definition: clock.h:90
virtual double current_time() const =0
virtual double timestep_duration() const =0
virtual double present_internal_time() const =0
This function always returns the clock time, even if children might attribute a different behaviour t...
virtual void reset(double start_time, bool is_output_clock)=0
Reset the clock to the starting time of the simulation.
Representation counter_
Internally used to count the number of time steps.
Definition: clock.h:186
bool operator>(double time) const
Compares the internal time of the clock against a fixed time.
Definition: clock.h:166
Clock & operator++()
Advances the clock by one tick.
Definition: clock.h:119
virtual double next_time() const =0
virtual void remove_times_in_past(double start_time)=0
Remove output times before the starting time of the simulation if this is a custom clock.
Clock & operator+=(Representation advance_several_timesteps)
Advances the clock by an arbitrary number of ticks.
Definition: clock.h:136
Clock with explicitly defined time steps.
Definition: clock.h:405
double next_time() const override
Definition: clock.h:439
CustomClock(std::vector< double > times)
Initialises a custom clock with explicitly given output times.
Definition: clock.h:412
double start_time_
Starting time of the simulation.
Definition: clock.h:500
void remove_times_in_past(double start_time) override
Remove all custom times before start_time.
Definition: clock.h:463
std::vector< double > custom_times_
Vector of times where output is generated.
Definition: clock.h:498
void reset(double start_time, bool) override
Reset the clock to the starting time of the simulation.
Definition: clock.h:453
double present_internal_time() const override
For the CustomClock, the internal time is basically by design the same as what the current_time() met...
Definition: clock.h:494
double timestep_duration() const override
Definition: clock.h:442
double current_time() const override
Definition: clock.h:423
Clock with uniformly spaced time steps.
Definition: clock.h:210
void reset(double start_time, bool is_output_clock) override
Resets the time to the starting time of an event.
Definition: clock.h:321
static constexpr double from_double
A multiplier transferring the real time to the internal integer.
Definition: clock.h:387
void set_timestep_duration(double dt)
Sets the time step size (and resets the counter).
Definition: clock.h:306
static constexpr double to_double
A multiplier transferring the internal integer to the real time.
Definition: clock.h:385
Representation timestep_duration_
The time step size in .
Definition: clock.h:397
void remove_times_in_past(double) override
Remove output times before the starting time of the simulation if this is a custom clock.
Definition: clock.h:337
UniformClock(double time, double dt, double time_end)
Initialize with base time and time step size.
Definition: clock.h:235
double next_time() const override
Definition: clock.h:270
static constexpr double resolution
Defines the resolution of the clock (namely the smallest representable time difference).
Definition: clock.h:223
Representation time_end_
The end time of the particle propagation.
Definition: clock.h:401
static Representation convert(double x)
Convert a double x into the internal int representation.
Definition: clock.h:390
Representation reset_time_
The time of last reset (when counter_ was set to 0).
Definition: clock.h:399
UniformClock()=default
default initializer: Timestep size is set to 0!
double timestep_duration() const override
Definition: clock.h:289
std::enable_if< std::is_floating_point< T >::value, Clock & >::type operator+=(T big_timestep)
Advances the clock by an arbitrary timestep (multiple of 0.000001 fm).
Definition: clock.h:349
Clock & operator+=(Representation advance_several_timesteps)
advances the clock by an arbitrary number of ticks.
Definition: clock.h:364
static double convert(Representation x)
Convert an internal int value x into the double representation.
Definition: clock.h:394
double current_time() const override
Definition: clock.h:253
double present_internal_time() const override
Access the internal time of the clock, independently from the end time.
Definition: clock.h:379
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > & logg
An array that stores all pre-configured Logger objects.
Definition: logging.h:245
Definition: action.h:24
static constexpr int LClock
Definition: clock.h:26
std::string to_string(ThermodynamicQuantity quantity)
Convert a ThermodynamicQuantity enum value to its corresponding string.
Definition: stringify.cc:26