Ultra 0.9.4
Loading...
Searching...
No Matches
distribution.tcc
Go to the documentation of this file.
1
12
13#if !defined(ULTRA_DISTRIBUTION_H)
14# error "Don't include this file directly, include the specific .h instead"
15#endif
16
17#if !defined(ULTRA_DISTRIBUTION_TCC)
18#define ULTRA_DISTRIBUTION_TCC
19
23template<ArithmeticFloatingType T>
24void distribution<T>::clear()
25{
26 *this = distribution();
27}
28
32template<ArithmeticFloatingType T>
33bool distribution<T>::empty() const noexcept
34{
35 return size() == 0;
36}
37
41template<ArithmeticFloatingType T>
42std::size_t distribution<T>::size() const noexcept
43{
44 return size_;
45}
46
50template<ArithmeticFloatingType T>
51T distribution<T>::max() const noexcept
52{
53 Expects(size());
54 return max_;
55}
56
60template<ArithmeticFloatingType T>
61T distribution<T>::min() const noexcept
62{
63 Expects(size());
64 return min_;
65}
66
70template<ArithmeticFloatingType T>
71T distribution<T>::mean() const noexcept
72{
73 Expects(size());
74 return mean_;
75}
76
80template<ArithmeticFloatingType T>
81T distribution<T>::variance() const
82{
83 Expects(size());
84 return m2_ / static_cast<T>(size());
85}
86
95template<ArithmeticFloatingType T>
96void distribution<T>::add(T val)
97{
98 using std::isnan;
99 if (isnan(val))
100 return;
101
102 if (!size())
103 min_ = max_ = mean_ = val;
104 else if (val < min())
105 min_ = val;
106 else if (val > max())
107 max_ = val;
108
109 ++size_;
110
111 // Values are rounded before being recorded in `seen_`.
112 // The histogram (and derived quantities such as entropy) is therefore
113 // computed over _rounded bins_, not raw sample values.
114 //
115 // Rationale:
116 // - avoids pathological growth of histogram keys for continuous domains;
117 // - treats numerically close values as belonging to the same bin.
118 //
119 // Consequences:
120 // - entropy reflects the distribution of rounded values;
121 // - distinct raw values may collapse into the same bin.
122 ++seen_[round_to(val)];
123
124 update_variance(val);
125}
126
127template<ArithmeticFloatingType T>
128const std::map<T, std::uintmax_t> &distribution<T>::seen() const
129{
130 return seen_;
131}
132
149template<ArithmeticFloatingType T>
150double distribution<T>::entropy() const
151{
152 if (empty())
153 return 0.0;
154
155 const double c(1.0 / std::log(2.0));
156
157 double h(0.0);
158 for (const auto &f : seen()) // f.first: fitness, f.second: sightings
159 {
160 const auto p(static_cast<double>(f.second) / static_cast<double>(size()));
161
162 h -= p * std::log(p) * c;
163 }
164
165 return h;
166}
167
183template<ArithmeticFloatingType T>
184void distribution<T>::update_variance(T val)
185{
186 Expects(size());
187
188 // Common Kahan summation helper.
189 static const auto kahan_add([](T &acc, T &comp, T x)
190 {
191 // `comp` is zero the first time around.
192 const T y(x - comp);
193
194 // Alas, `acc` is big, `y` small, so low-order digits of `y` are lost.
195 const T t(acc + y);
196
197 // `(t - acc)` cancels the high-order part of y;
198 // subtracting `y` recovers negative (low part of `y`)
199 comp = (t - acc) - y;
200 // Algebraically, `comp` should always be zero.
201
202 acc = t;
203 });
204
205 const T delta(val - mean_);
206
207 kahan_add(mean_, mean_comp_, delta / static_cast<T>(size_));
208 kahan_add(m2_, m2_comp_, delta * (val - mean_));
209}
210
214template<ArithmeticFloatingType T>
215T distribution<T>::standard_deviation() const
216{
217 // This way, for "regular" types we'll use std::sqrt ("taken in" by the
218 // using statement), while for our types the overload will prevail due to
219 // Koenig lookup (http://www.gotw.ca/gotw/030.htm).
220 using std::sqrt;
221
222 return sqrt(variance());
223}
224
231template<ArithmeticFloatingType T>
232bool distribution<T>::save(std::ostream &out) const
233{
234 out << size() << '\n';
235
236 if (size())
237 {
238 SAVE_FLAGS(out);
239
240 out << std::scientific
241 << std::setprecision(std::numeric_limits<T>::digits10 + 1)
242 << mean() << '\n'
243 << min() << '\n'
244 << max() << '\n'
245 << m2_ << '\n';
246
247 out << seen().size() << '\n';
248 for (const auto &elem : seen())
249 out << elem.first << ' ' << elem.second << '\n';
250 }
251
252 return out.good();
253}
254
264template<ArithmeticFloatingType T>
265bool distribution<T>::load(std::istream &in)
266{
267 distribution<T> temp;
268
269 if (!(in >> temp.size_))
270 return false;
271
272 if (!temp.size_)
273 {
274 clear();
275 return true;
276 }
277
278 SAVE_FLAGS(in);
279
280 in >> std::scientific
281 >> std::setprecision(std::numeric_limits<T>::digits10 + 1);
282
283 if (!(in >> temp.mean_))
284 return false;
285
286 if (!(in >> temp.min_))
287 return false;
288
289 if (!(in >> temp.max_))
290 return false;
291
292 if (!(in >> temp.m2_))
293 return false;
294
295 typename decltype(seen_)::size_type n;
296 if (!(in >> n))
297 return false;
298
299 for (decltype(n) i(0); i < n; ++i)
300 {
301 typename decltype(seen_)::key_type key;
302 typename decltype(seen_)::mapped_type val;
303 if (!(in >> key >> val))
304 return false;
305
306 temp.seen_[key] = val;
307 }
308
309 if (!temp.is_valid())
310 return false;
311
312 *this = std::move(temp);
313 return true;
314}
315
319template<ArithmeticFloatingType T>
320bool distribution<T>::is_valid() const
321{
322 if (!size())
323 {
324 distribution<T> empty_dist;
325
326 return seen_ == empty_dist.seen_ && issmall(m2_ - empty_dist.m2_)
327 && issmall(m2_comp_ - empty_dist.m2_comp_)
328 && issmall(mean_ - empty_dist.mean_)
329 && issmall(mean_comp_ - empty_dist.mean_comp_)
330 && issmall(max_ - empty_dist.max_) && issmall(min_ - empty_dist.min_);
331 }
332
333 // This way, for "regular" types we'll use `std::infinite` / `std::isnan`
334 // ("taken in" by the using statement), while for our types the overload
335 // will prevail due to Koenig lookup (http://www.gotw.ca/gotw/030.htm).
336 using std::isfinite;
337 using std::isnan;
338
339 if (isfinite(min()) && isfinite(mean()) && min() > mean())
340 {
341 ultraERROR("Distribution: min={} > mean={}",
342 internal::streamed(min()),
343 internal::streamed(mean()));
344 return false;
345 }
346
347 if (isfinite(max()) && isfinite(mean()) && max() < mean())
348 {
349 ultraERROR("Distribution: max={} < mean={}",
350 internal::streamed(max()),
351 internal::streamed(mean()));
352 return false;
353 }
354
355 if (isnan(variance()) || !isnonnegative(variance()))
356 {
357 ultraERROR("Distribution: negative variance");
358 return false;
359 }
360
361 std::size_t total(0);
362 for (const auto values(seen_ | std::views::values); auto v : values)
363 {
364 if (!v)
365 {
366 ultraERROR("Seen histogram contains zero-count bin");
367 return false;
368 }
369
370 if (v > std::numeric_limits<std::size_t>::max() - total)
371 {
372 ultraERROR("Seen histogram total overflow");
373 return false;
374 }
375
376 total += v;
377 }
378
379 if (total != size())
380 {
381 ultraERROR("Seen histogram total {} does not match distribution size {}",
382 total, size_);
383 return false;
384 }
385
386 return true;
387}
388
399template<ArithmeticFloatingType T>
400void distribution<T>::merge(const distribution<T> &d2)
401{
402 if (empty())
403 {
404 *this = d2;
405 return;
406 }
407
408 if (!d2.size())
409 return;
410
411 const auto max1(max());
412 const auto max2(d2.max());
413
414 const auto min1(min());
415 const auto min2(d2.min());
416
417 const auto size1(size());
418 const auto size2(d2.size());
419 const auto new_size(size1 + size2);
420
421 const auto n1(static_cast<T>(size1));
422 const auto n2(static_cast<T>(size2));
423 const auto n(static_cast<T>(new_size));
424
425 const auto delta(d2.mean() - mean());
426 const auto new_mean(size1 >= size2
427 ? mean() + delta * (n2 / n)
428 : d2.mean() - delta * (n1 / n));
429
430 m2_ += d2.m2_ + delta * delta * (n1 * n2 / n);
431 mean_ = new_mean;
432
433 mean_comp_ = T{};
434 m2_comp_ = T{};
435
436 max_ = std::max(max1, max2);
437 min_ = std::min(min1, min2);
438
439 size_ = new_size;
440
441 for (auto &[k, v] : d2.seen_)
442 seen_[k] += v;
443}
444
445#endif // include guard
fitnd sqrt(fitnd f)
Definition fitness.cc:411