faunus
average.h
1 #pragma once
2 #include <limits>
3 #include <ostream>
4 #include <istream>
5 #include <cmath>
6 #include <fstream>
7 #include <concepts>
8 #include <format>
9 #include <nlohmann/json.hpp>
10 
11 namespace Faunus {
12 
17 template <std::floating_point value_type = double,
18  std::unsigned_integral counter_type = unsigned long int>
19 class Average
20 {
21  protected:
22  counter_type number_of_samples = 0;
23  value_type value_sum = 0.0;
24  public:
25  void clear() { *this = Average(); }
26 
27  [[nodiscard]] bool empty() const;
28 
29  auto size() const { return number_of_samples; }
30 
31  auto avg() const { return value_sum / static_cast<value_type>(number_of_samples); }
32 
33  explicit operator value_type() const { return avg(); }
34 
35  bool operator<(const Average& other) const { return avg() < other.avg(); }
36 
37  explicit operator bool() const { return number_of_samples > 0; }
38 
44  void add(const value_type value)
45  {
46  if (number_of_samples == std::numeric_limits<counter_type>::max()) {
47  throw std::overflow_error("max. number of samples reached");
48  }
49  value_sum += value;
50  number_of_samples++;
51  }
52 
58  Average& operator+=(const value_type value)
59  {
60  add(value);
61  return *this;
62  }
63 
64  bool operator==(const Average& other) const
65  {
66  return (size() == other.size()) && (value_sum == other.value_sum);
67  }
68 
70  Average& operator=(const value_type value)
71  {
72  clear();
73  add(value);
74  return *this;
75  }
76 
83  auto operator+(const Average& other) const
84  {
85  if (std::numeric_limits<counter_type>::max() - other.number_of_samples <
86  number_of_samples) {
87  throw std::overflow_error("maximum number of samples reached");
88  }
89  if (std::numeric_limits<value_type>::max() - other.value_sum < value_sum) {
90  throw std::overflow_error("value too large");
91  }
92  Average summed_average = *this;
93  summed_average.number_of_samples += other.number_of_samples;
94  summed_average.value_sum += other.value_sum;
95  return summed_average;
96  }
97 
98  friend std::ostream& operator<<(std::ostream& stream, const Average& average)
99  {
100  stream << average.number_of_samples << " " << average.value_sum;
101  return stream;
102  } // serialize to stream
103 
104  Average& operator<<(std::istream& stream)
105  {
106  stream >> number_of_samples >> value_sum;
107  return *this;
108  } // de-serialize from stream
109 
110  template <class Archive> void serialize(Archive& archive)
111  {
112  archive(value_sum, number_of_samples);
113  }
114 };
115 
116 template <std::floating_point value_type, std::unsigned_integral counter_type>
118 {
119  return number_of_samples == 0;
120 }
121 
126 template <std::floating_point value_type = double,
127  std::unsigned_integral counter_type = unsigned long int>
128 class AverageStdev : public Average<value_type, counter_type>
129 {
130  private:
131  value_type squared_value_sum = 0.0;
133  using base::number_of_samples;
134  using base::value_sum;
135 
136  public:
137  using base::avg;
138  using base::empty;
139 
140  void clear() { *this = AverageStdev(); }
141 
142  auto rms() const
143  {
144  return std::sqrt(squared_value_sum / static_cast<value_type>(number_of_samples));
145  }
146 
147  auto rsd() const
148  {
149  return empty() ? 0.0 : stdev() / avg();
150  }
151 
152  value_type stdev() const
153  {
154  if (empty()) {
155  return 0.0;
156  }
157  const auto mean = avg();
158  return std::sqrt((squared_value_sum +
159  static_cast<value_type>(number_of_samples) * mean * mean -
160  2.0 * value_sum * mean) /
161  static_cast<value_type>(number_of_samples - 1));
162  }
163 
169  void add(const value_type value)
170  {
171  base::add(value);
172  squared_value_sum += value * value;
173  }
174 
180  AverageStdev& operator+=(const value_type value)
181  {
182  add(value);
183  return *this;
184  }
185 
186  bool operator==(const AverageStdev& other) const
187  {
188  return (this->size() == other.size()) && (value_sum == other.value_sum) &&
189  (squared_value_sum == other.squared_value_sum);
190  }
191 
193  AverageStdev& operator=(const value_type value)
194  {
195  clear();
196  add(value);
197  return *this;
198  }
199 
200  friend std::ostream& operator<<(std::ostream& stream, const AverageStdev& average)
201  {
202  stream << average.number_of_samples << " " << average.value_sum << " "
203  << average.squared_value_sum;
204  return stream;
205  } // serialize to stream
206 
207  AverageStdev& operator<<(std::istream& stream)
208  {
209  stream >> number_of_samples >> value_sum >> squared_value_sum;
210  return *this;
211  } // de-serialize from stream
212 
213  template <class Archive> void serialize(AverageStdev& archive)
214  {
215  archive(value_sum, squared_value_sum, number_of_samples);
216  }
217 };
218 
239 template <std::floating_point value_type = double,
240  std::unsigned_integral counter_type = unsigned long int>
242 {
243  private:
245  unsigned int nsamples = 0;
246  std::vector<Statistic> blocked_statistics = {Statistic()};
247  std::vector<value_type> waiting_sample = {0.0};
248  std::vector<bool> waiting_sample_exists = {false};
249 
250  public:
251  Decorrelation& add(value_type new_sample)
252  {
253  nsamples++;
254  if (nsamples >= static_cast<unsigned int>(std::pow(2, blocked_statistics.size()))) {
255  blocked_statistics.emplace_back();
256  waiting_sample.push_back(0.0);
257  waiting_sample_exists.push_back(false);
258  }
259  std::floating_point auto carry = new_sample;
260  blocked_statistics.at(0) += new_sample;
261 
262  for (size_t i = 1; i < blocked_statistics.size(); i++) {
263  if (waiting_sample_exists.at(i)) {
264  new_sample = 0.5 * (waiting_sample.at(i) + carry);
265  carry = new_sample;
266  blocked_statistics.at(i) += new_sample;
267  waiting_sample_exists.at(i) = false;
268  }
269  else {
270  waiting_sample_exists.at(i) = true;
271  waiting_sample.at(i) = carry;
272  break;
273  }
274  }
275  return *this;
276  }
277 
278  auto size() const { return nsamples; }
279 
280  [[nodiscard]] bool empty() const { return nsamples == 0; }
281 
282  [[maybe_unused]] void to_disk(const std::string& filename) const
283  {
284  if (auto stream = std::ofstream(filename); stream) {
285  for (size_t i = 0; i < blocked_statistics.size(); i++) {
286  stream << std::format("{} {} {}\n", i, blocked_statistics[i].avg(),
287  blocked_statistics[i].stdev());
288  }
289  }
290  }
291 
292  void to_json(nlohmann::json& j) const
293  {
294  j = nlohmann::json::array();
295  for (size_t i = 0; i < blocked_statistics.size(); i++) {
296  j.push_back(nlohmann::json::array(
297  {blocked_statistics[i].avg(), blocked_statistics[i].stdev()}));
298  }
299  }
300 };
301 
303 template <class T>
304 concept Averageable = requires(T a) {
305  a * 1.0; // please implement `T operator*(double) const`
306  // a* a; // please implement `T operator*(const T&) const`
307  a += a; // please implement `T& operator+=(const T&)`
308 };
309 
320 template <Averageable T, typename counter_type = unsigned long int> class AverageObj
321 {
322  protected:
323  counter_type number_of_samples = 0;
324  T sum; // make sure constructors zero this!
325  public:
326  AverageObj()
327  : sum(T()) {};
328 
329  explicit AverageObj(const T& value)
330  : number_of_samples(1)
331  , sum(value) {};
332 
334  AverageObj& operator+=(const T& value)
335  {
336  if (number_of_samples == std::numeric_limits<counter_type>::max()) {
337  throw std::overflow_error("maximum samples reached");
338  }
339  sum += value;
341  return *this;
342  }
343 
345  T avg() const
346  {
347  if (number_of_samples > 0) {
348  return sum * (1.0 / static_cast<double>(number_of_samples));
349  }
350  return sum;
351  }
352 
354  operator T() const { return avg(); }
355 
357  bool operator<(const AverageObj& other) const { return avg() < other.avg(); }
358 
360  [[nodiscard]] bool empty() const { return number_of_samples == 0; }
361 
363  void clear() { *this = AverageObj<T>(); }
364 
366  auto size() const { return number_of_samples; }
367 };
368 
369 /*
370  * Experimental extension of AverageObj that includes stdev() and rms(). This
371  * requires a pow(T, double) function for squaring and taking the square-root.
372  * How could this best be implemented and maintain compatibility when T=double?
373  */
374 template <typename T, typename int_t = unsigned long int>
375 class AverageObjStdev : public AverageObj<T>
376 {
377  private:
378  T sum_squared; // make sure constructors zero this!
379  using AverageObj<T>::avg;
380  using AverageObj<T>::sum;
382 
383  public:
385  : sum_squared(T()) {};
386 
387  explicit AverageObjStdev(const T& value)
388  : AverageObj<T>(value)
389  , sum_squared(value * value) {};
390 
392  AverageObjStdev& operator+=(const T& value)
393  {
394  AverageObj<T>::operator+=()(value);
395  sum_squared += std::pow(value, 2);
396  return *this;
397  }
398 
400  T rms() const
401  {
402  if (number_of_samples == 0) {
403  return T();
404  }
405  return std::pow(sum_squared * (1.0 / static_cast<double>(number_of_samples)), 0.5);
406  }
407 
409  T stdev() const
410  {
411  if (number_of_samples == 0) {
412  return T();
413  }
414  const auto N = static_cast<double>(number_of_samples);
415  const auto mean = avg();
416  return std::pow((sum_squared + N * mean * mean - 2.0 * sum * mean) * (1.0 / (N - 1.0)),
417  0.5);
418  }
419 
421  void clear() { *this = AverageObjStdev<T>(); }
422 };
423 
424 } // namespace Faunus
counter_type number_of_samples
number of values in average
Definition: average.h:22
bool empty() const
True if empty.
Definition: average.h:117
Class to collect averages.
Definition: average.h:19
auto rsd() const
Relative standard deviation or coefficient of variation.
Definition: average.h:147
Definition: average.h:375
double T
floating point size
Definition: units.h:73
T stdev() const
Standard deviation.
Definition: average.h:409
Average & operator+=(const value_type value)
Add value to average.
Definition: average.h:58
Class to collect averages and standard deviation.
Definition: average.h:128
AverageStdev & operator+=(const value_type value)
Add value to average.
Definition: average.h:180
bool operator<(const Average &other) const
Compare means.
Definition: average.h:35
Average & operator=(const value_type value)
Clear and assign a new value.
Definition: average.h:70
void clear()
Clear all data.
Definition: average.h:140
auto operator+(const Average &other) const
Merge two averages with correct weights.
Definition: average.h:83
void clear()
Clear all data.
Definition: average.h:25
AverageStdev & operator=(const value_type value)
Clear and assign a new value.
Definition: average.h:193
value_type value_sum
Sum of all recorded values.
Definition: average.h:23
auto avg() const
Average.
Definition: average.h:31
void add(const value_type value)
Add value to average.
Definition: average.h:169
AverageObj(const T &value)
Construct from empty object.
Definition: average.h:329
Cell list class templates.
Definition: actions.cpp:11
bool empty() const
True if empty.
Definition: average.h:360
AverageObj & operator+=(const T &value)
Add to average.
Definition: average.h:334
auto size() const
Number of samples.
Definition: average.h:366
T rms() const
Root-mean-square.
Definition: average.h:400
concept Averageable
Requirements for types used with AverageObj
Definition: average.h:304
void add(const value_type value)
Add value to average.
Definition: average.h:44
value_type stdev() const
Standard deviation.
Definition: average.h:152
AverageObjStdev(const T &value)
Construct from empty object.
Definition: average.h:387
auto size() const
Number of samples.
Definition: average.h:29
Simple class to average data contained in objects.
Definition: average.h:320
AverageObjStdev & operator+=(const T &value)
Add to average.
Definition: average.h:392
bool operator<(const AverageObj &other) const
Compare operator.
Definition: average.h:357
T avg() const
Calculate average.
Definition: average.h:345
"Decorrelation" class from https://dx.doi.org/10.1002/jcc.20746
Definition: average.h:241
auto rms() const
Root-mean-square.
Definition: average.h:142