update boost

This commit is contained in:
2023-11-24 12:56:13 -06:00
parent cfc99971af
commit 19d727037a
9260 changed files with 849256 additions and 299957 deletions

View File

@@ -0,0 +1,177 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_COUNT_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_COUNT_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/atomic_number.hpp>
#include <boost/histogram/fwd.hpp> // for count<>
#include <type_traits> // for std::common_type
namespace boost {
namespace histogram {
namespace accumulators {
/**
Wraps a C++ arithmetic type with optionally thread-safe increments and adds.
This adaptor optionally uses atomic operations to make concurrent increments and
additions thread-safe for the stored arithmetic value, which can be integral or
floating point. For small histograms, the performance will still be poor because of
False Sharing, see https://en.wikipedia.org/wiki/False_sharing for details.
Warning: Assignment is not thread-safe in this implementation, so don't assign
concurrently.
This wrapper class can be used as a base class by users to add arbitrary metadata to
each bin of a histogram.
When weighted samples are accumulated and high precision is required, use
`accumulators::sum` instead (at the cost of lower performance). If a local variance
estimate for the weight distribution should be computed as well (generally needed for a
detailed statistical analysis), use `accumulators::weighted_sum`.
@tparam T C++ builtin arithmetic type (integer or floating point).
@tparam ThreadSafe Set to true to make increments and adds thread-safe.
*/
template <class ValueType, bool ThreadSafe>
class count {
using internal_type =
std::conditional_t<ThreadSafe, detail::atomic_number<ValueType>, ValueType>;
public:
using value_type = ValueType;
using const_reference = const value_type&;
count() noexcept = default;
/// Initialize count to value and allow implicit conversion
count(const_reference value) noexcept : value_{value} {}
/// Allow implicit conversion from other count
template <class T, bool B>
count(const count<T, B>& c) noexcept : count{c.value()} {}
/// Increment count by one
count& operator++() noexcept {
++value_;
return *this;
}
/// Increment count by value
count& operator+=(const_reference value) noexcept {
value_ += value;
return *this;
}
/// Add another count
count& operator+=(const count& s) noexcept {
value_ += s.value_;
return *this;
}
/// Scale by value
count& operator*=(const_reference value) noexcept {
value_ *= value;
return *this;
}
bool operator==(const count& rhs) const noexcept { return value_ == rhs.value_; }
bool operator!=(const count& rhs) const noexcept { return !operator==(rhs); }
/// Return count
value_type value() const noexcept { return value_; }
// conversion to value_type must be explicit
explicit operator value_type() const noexcept { return value_; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
auto v = value();
ar& make_nvp("value", v);
value_ = v;
}
static constexpr bool thread_safe() noexcept { return ThreadSafe; }
// begin: extra operators to make count behave like a regular number
count& operator*=(const count& rhs) noexcept {
value_ *= rhs.value_;
return *this;
}
count operator*(const count& rhs) const noexcept {
count x = *this;
x *= rhs;
return x;
}
count& operator/=(const count& rhs) noexcept {
value_ /= rhs.value_;
return *this;
}
count operator/(const count& rhs) const noexcept {
count x = *this;
x /= rhs;
return x;
}
bool operator<(const count& rhs) const noexcept { return value_ < rhs.value_; }
bool operator>(const count& rhs) const noexcept { return value_ > rhs.value_; }
bool operator<=(const count& rhs) const noexcept { return value_ <= rhs.value_; }
bool operator>=(const count& rhs) const noexcept { return value_ >= rhs.value_; }
friend bool operator==(const_reference x, const count& rhs) noexcept {
return x == rhs.value_;
}
friend bool operator!=(const_reference x, const count& rhs) noexcept {
return x != rhs.value_;
}
friend bool operator<(const_reference x, const count& rhs) noexcept {
return x < rhs.value_;
}
friend bool operator>(const_reference x, const count& rhs) noexcept {
return x > rhs.value_;
}
friend bool operator<=(const_reference x, const count& rhs) noexcept {
return x <= rhs.value_;
}
friend bool operator>=(const_reference x, const count& rhs) noexcept {
return x >= rhs.value_;
}
// end: extra operators
private:
internal_type value_{};
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U, bool B1, bool B2>
struct common_type<boost::histogram::accumulators::count<T, B1>,
boost::histogram::accumulators::count<U, B2>> {
using type = boost::histogram::accumulators::count<common_type_t<T, U>, (B1 || B2)>;
};
} // namespace std
#endif
#endif

View File

@@ -0,0 +1,130 @@
// Copyright 2022 Jay Gohil, Hans Dembinski
//
// Distributed under the Boost Software License, version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_FRACTION_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_FRACTION_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/fwd.hpp> // for fraction<>
#include <boost/histogram/utility/wilson_interval.hpp>
#include <type_traits> // for std::common_type
namespace boost {
namespace histogram {
namespace accumulators {
/**
Accumulate boolean samples and compute the fraction of true samples.
This accumulator should be used to calculate the efficiency or success fraction of a
random process as a function of process parameters. It returns the fraction of
successes, the variance of this fraction, and a two-sided confidence interval with 68.3
% confidence level for this fraction.
There is no unique way to compute an interval for a success fraction. This class returns
the Wilson score interval, because it is widely recommended in the literature for
general use. More interval computers can be found in `boost/histogram/utility`, which
can be used to compute intervals for other confidence levels.
*/
template <class ValueType>
class fraction {
public:
using value_type = ValueType;
using const_reference = const value_type&;
using real_type = typename std::conditional<std::is_floating_point<value_type>::value,
value_type, double>::type;
using interval_type = typename utility::wilson_interval<real_type>::interval_type;
fraction() noexcept = default;
/// Initialize to external successes and failures.
fraction(const_reference successes, const_reference failures) noexcept
: succ_(successes), fail_(failures) {}
/// Allow implicit conversion from fraction with a different value type.
template <class T>
fraction(const fraction<T>& e) noexcept
: fraction{static_cast<value_type>(e.successes()),
static_cast<value_type>(e.failures())} {}
/// Insert boolean sample x.
void operator()(bool x) noexcept {
if (x)
++succ_;
else
++fail_;
}
/// Add another accumulator.
fraction& operator+=(const fraction& rhs) noexcept {
succ_ += rhs.succ_;
fail_ += rhs.fail_;
return *this;
}
/// Return number of boolean samples that were true.
const_reference successes() const noexcept { return succ_; }
/// Return number of boolean samples that were false.
const_reference failures() const noexcept { return fail_; }
/// Return total number of boolean samples.
value_type count() const noexcept { return succ_ + fail_; }
/// Return success fraction of boolean samples.
real_type value() const noexcept { return static_cast<real_type>(succ_) / count(); }
/// Return variance of the success fraction.
real_type variance() const noexcept {
// We want to compute Var(p) for p = X / n with Var(X) = n p (1 - p)
// For Var(X) see
// https://en.wikipedia.org/wiki/Binomial_distribution#Expected_value_and_variance
// Error propagation: Var(p) = p'(X)^2 Var(X) = p (1 - p) / n
const real_type p = value();
return p * (1 - p) / count();
}
/// Return standard interval with 68.3 % confidence level (Wilson score interval).
interval_type confidence_interval() const noexcept {
return utility::wilson_interval<real_type>()(static_cast<real_type>(successes()),
static_cast<real_type>(failures()));
}
bool operator==(const fraction& rhs) const noexcept {
return succ_ == rhs.succ_ && fail_ == rhs.fail_;
}
bool operator!=(const fraction& rhs) const noexcept { return !operator==(rhs); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("successes", succ_);
ar& make_nvp("failures", fail_);
}
private:
value_type succ_{};
value_type fail_{};
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
/// Specialization for boost::histogram::accumulators::fraction.
struct common_type<boost::histogram::accumulators::fraction<T>,
boost::histogram::accumulators::fraction<U>> {
using type = boost::histogram::accumulators::fraction<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif

View File

@@ -0,0 +1,41 @@
// Copyright 2021 Hans Dembinski
//
// Distributed under the Boost Software License, version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_IS_THREAD_SAFE_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_IS_THREAD_SAFE_HPP
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/fwd.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
constexpr bool is_thread_safe_impl(priority<0>) {
return false;
}
template <class T>
constexpr auto is_thread_safe_impl(priority<1>) -> decltype(T::thread_safe()) {
return T::thread_safe();
}
} // namespace detail
namespace accumulators {
template <class T>
struct is_thread_safe
: std::integral_constant<bool,
detail::is_thread_safe_impl<T>(detail::priority<1>{})> {};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#endif

View File

@@ -7,9 +7,10 @@
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_MEAN_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_MEAN_HPP
#include <boost/histogram/fwd.hpp>
#include <cstddef>
#include <type_traits>
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/square.hpp>
#include <boost/histogram/fwd.hpp> // for mean<>
#include <type_traits> // for std::integral_constant, std::common_type
namespace boost {
namespace histogram {
@@ -20,56 +21,130 @@ namespace accumulators {
Uses Welfords's incremental algorithm to improve the numerical
stability of mean and variance computation.
*/
template <class RealType>
template <class ValueType>
class mean {
public:
mean() = default;
mean(const std::size_t n, const RealType& mean, const RealType& variance)
: sum_(n), mean_(mean), sum_of_deltas_squared_(variance * (sum_ - 1)) {}
using value_type = ValueType;
using const_reference = const value_type&;
void operator()(const RealType& x) {
sum_ += 1;
mean() = default;
/// Allow implicit conversion from mean<T>.
template <class T>
mean(const mean<T>& o) noexcept
: sum_{o.sum_}, mean_{o.mean_}, sum_of_deltas_squared_{o.sum_of_deltas_squared_} {}
/// Initialize to external count, mean, and variance.
mean(const_reference n, const_reference mean, const_reference variance) noexcept
: sum_(n), mean_(mean), sum_of_deltas_squared_(variance * (n - 1)) {}
/// Insert sample x.
void operator()(const_reference x) noexcept {
sum_ += static_cast<value_type>(1);
const auto delta = x - mean_;
mean_ += delta / sum_;
sum_of_deltas_squared_ += delta * (x - mean_);
}
template <class T>
mean& operator+=(const mean<T>& rhs) {
const auto tmp = mean_ * sum_ + static_cast<RealType>(rhs.mean_ * rhs.sum_);
/// Insert sample x with weight w.
void operator()(const weight_type<value_type>& w, const_reference x) noexcept {
sum_ += w.value;
const auto delta = x - mean_;
mean_ += w.value * delta / sum_;
sum_of_deltas_squared_ += w.value * delta * (x - mean_);
}
/// Add another mean accumulator.
mean& operator+=(const mean& rhs) noexcept {
if (rhs.sum_ == 0) return *this;
/*
sum_of_deltas_squared
= sum_i (x_i - mu)^2
= sum_i (x_i - mu)^2 + sum_k (x_k - mu)^2
= sum_i (x_i - mu1 + (mu1 - mu))^2 + sum_k (x_k - mu2 + (mu2 - mu))^2
first part:
sum_i (x_i - mu1 + (mu1 - mu))^2
= sum_i (x_i - mu1)^2 + n1 (mu1 - mu))^2 + 2 (mu1 - mu) sum_i (x_i - mu1)
= sum_i (x_i - mu1)^2 + n1 (mu1 - mu))^2
since sum_i (x_i - mu1) = n1 mu1 - n1 mu1 = 0
Putting it together:
sum_of_deltas_squared
= sum_of_deltas_squared_1 + n1 (mu1 - mu))^2
+ sum_of_deltas_squared_2 + n2 (mu2 - mu))^2
*/
const auto n1 = sum_;
const auto mu1 = mean_;
const auto n2 = rhs.sum_;
const auto mu2 = rhs.mean_;
sum_ += rhs.sum_;
mean_ = tmp / sum_;
sum_of_deltas_squared_ += static_cast<RealType>(rhs.sum_of_deltas_squared_);
mean_ = (n1 * mu1 + n2 * mu2) / sum_;
sum_of_deltas_squared_ += rhs.sum_of_deltas_squared_;
sum_of_deltas_squared_ += n1 * detail::square(mean_ - mu1);
sum_of_deltas_squared_ += n2 * detail::square(mean_ - mu2);
return *this;
}
mean& operator*=(const RealType& s) {
/** Scale by value.
This acts as if all samples were scaled by the value.
*/
mean& operator*=(const_reference s) noexcept {
mean_ *= s;
sum_of_deltas_squared_ *= s * s;
return *this;
}
template <class T>
bool operator==(const mean<T>& rhs) const noexcept {
bool operator==(const mean& rhs) const noexcept {
return sum_ == rhs.sum_ && mean_ == rhs.mean_ &&
sum_of_deltas_squared_ == rhs.sum_of_deltas_squared_;
}
template <class T>
bool operator!=(const mean<T>& rhs) const noexcept {
return !operator==(rhs);
}
bool operator!=(const mean& rhs) const noexcept { return !operator==(rhs); }
std::size_t count() const noexcept { return sum_; }
const RealType& value() const noexcept { return mean_; }
RealType variance() const { return sum_of_deltas_squared_ / (sum_ - 1); }
/** Return how many samples were accumulated.
count() should be used to check whether value() and variance() are defined,
see documentation of value() and variance(). count() can be used to compute
the variance of the mean by dividing variance() by count().
*/
const_reference count() const noexcept { return sum_; }
/** Return mean value of accumulated samples.
The result is undefined, if `count() < 1`.
*/
const_reference value() const noexcept { return mean_; }
/** Return variance of accumulated samples.
The result is undefined, if `count() < 2`.
*/
value_type variance() const noexcept { return sum_of_deltas_squared_ / (sum_ - 1); }
template <class Archive>
void serialize(Archive&, unsigned /* version */);
void serialize(Archive& ar, unsigned version) {
if (version == 0) {
// read only
std::size_t sum;
ar& make_nvp("sum", sum);
sum_ = static_cast<value_type>(sum);
} else {
ar& make_nvp("sum", sum_);
}
ar& make_nvp("mean", mean_);
ar& make_nvp("sum_of_deltas_squared", sum_of_deltas_squared_);
}
private:
std::size_t sum_ = 0;
RealType mean_ = 0, sum_of_deltas_squared_ = 0;
value_type sum_{};
value_type mean_{};
value_type sum_of_deltas_squared_{};
};
} // namespace accumulators
@@ -77,6 +152,21 @@ private:
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace boost {
namespace serialization {
template <class T>
struct version;
// version 1 for boost::histogram::accumulators::mean<T>
template <class T>
struct version<boost::histogram::accumulators::mean<T>> : std::integral_constant<int, 1> {
};
} // namespace serialization
} // namespace boost
namespace std {
template <class T, class U>
/// Specialization for boost::histogram::accumulators::mean.
@@ -85,6 +175,7 @@ struct common_type<boost::histogram::accumulators::mean<T>,
using type = boost::histogram::accumulators::mean<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif

View File

@@ -7,42 +7,100 @@
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_OSTREAM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_OSTREAM_HPP
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/fwd.hpp>
#include <iosfwd>
#include <ios>
/**
\file boost/histogram/accumulators/ostream.hpp
Simple streaming operators for the builtin accumulator types.
The text representation is not guaranteed to be stable between versions of
Boost.Histogram. This header is only included by
[boost/histogram/ostream.hpp](histogram/reference.html#header.boost.histogram.ostream_hpp).
To use your own, include your own implementation instead of this header and do not
include
[boost/histogram/ostream.hpp](histogram/reference.html#header.boost.histogram.ostream_hpp).
*/
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace boost {
namespace histogram {
namespace detail {
template <class CharT, class Traits, class T>
std::basic_ostream<CharT, Traits>& handle_nonzero_width(
std::basic_ostream<CharT, Traits>& os, const T& x) {
const auto w = os.width();
os.width(0);
std::streamsize count = 0;
{
auto g = make_count_guard(os, count);
os << x;
}
if (os.flags() & std::ios::left) {
os << x;
for (auto i = count; i < w; ++i) os << os.fill();
} else {
for (auto i = count; i < w; ++i) os << os.fill();
os << x;
}
return os;
}
} // namespace detail
namespace accumulators {
template <typename CharT, typename Traits, typename W>
template <class CharT, class Traits, class U, bool B>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const sum<W>& x) {
os << "sum(" << x.large() << " + " << x.small() << ")";
return os;
const count<U, B>& x) {
return os << x.value();
}
template <typename CharT, typename Traits, typename W>
template <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const weighted_sum<W>& x) {
os << "weighted_sum(" << x.value() << ", " << x.variance() << ")";
return os;
const sum<U>& x) {
if (os.width() == 0)
return os << "sum(" << x.large_part() << " + " << x.small_part() << ")";
return detail::handle_nonzero_width(os, x);
}
template <typename CharT, typename Traits, typename W>
template <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const mean<W>& x) {
os << "mean(" << x.count() << ", " << x.value() << ", " << x.variance() << ")";
return os;
const weighted_sum<U>& x) {
if (os.width() == 0)
return os << "weighted_sum(" << x.value() << ", " << x.variance() << ")";
return detail::handle_nonzero_width(os, x);
}
template <typename CharT, typename Traits, typename W>
template <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const weighted_mean<W>& x) {
os << "weighted_mean(" << x.sum_of_weights() << ", " << x.value() << ", "
<< x.variance() << ")";
return os;
const mean<U>& x) {
if (os.width() == 0)
return os << "mean(" << x.count() << ", " << x.value() << ", " << x.variance() << ")";
return detail::handle_nonzero_width(os, x);
}
template <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const weighted_mean<U>& x) {
if (os.width() == 0)
return os << "weighted_mean(" << x.sum_of_weights() << ", " << x.value() << ", "
<< x.variance() << ")";
return detail::handle_nonzero_width(os, x);
}
template <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const fraction<U>& x) {
if (os.width() == 0)
return os << "fraction(" << x.successes() << ", " << x.failures() << ")";
return detail::handle_nonzero_width(os, x);
}
} // namespace accumulators
} // namespace histogram
} // namespace boost

View File

@@ -7,87 +7,159 @@
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP
#include <boost/histogram/fwd.hpp>
#include <cmath>
#include <type_traits>
#include <boost/core/nvp.hpp>
#include <boost/histogram/fwd.hpp> // for sum<>
#include <cmath> // std::abs
#include <type_traits> // std::is_floating_point, std::common_type
namespace boost {
namespace histogram {
namespace accumulators {
/**
Uses Neumaier algorithm to compute accurate sums.
Uses Neumaier algorithm to compute accurate sums of floats.
The algorithm uses memory for two floats and is three to
five times slower compared to a simple floating point
number used to accumulate a sum, but the relative error
of the sum is at the level of the machine precision,
independent of the number of samples.
The algorithm is an improved Kahan algorithm
(https://en.wikipedia.org/wiki/Kahan_summation_algorithm). The algorithm uses memory for
two numbers and is three to five times slower compared to using a single number to
accumulate a sum, but the relative error of the sum is at the level of the machine
precision, independent of the number of samples.
A. Neumaier, Zeitschrift fuer Angewandte Mathematik
und Mechanik 54 (1974) 39-51.
A. Neumaier, Zeitschrift fuer Angewandte Mathematik und Mechanik 54 (1974) 39-51.
*/
template <typename RealType>
template <class ValueType>
class sum {
static_assert(std::is_floating_point<ValueType>::value,
"ValueType must be a floating point type");
public:
using value_type = ValueType;
using const_reference = const value_type&;
sum() = default;
/// Initialize sum to value
explicit sum(const RealType& value) noexcept : large_(value) {}
/// Initialize sum to value and allow implicit conversion
sum(const_reference value) noexcept : sum(value, 0) {}
/// Set sum to value
sum& operator=(const RealType& value) noexcept {
large_ = value;
small_ = 0;
/// Allow implicit conversion from sum<T>
template <class T>
sum(const sum<T>& s) noexcept : sum(s.large_part(), s.small_part()) {}
/// Initialize sum explicitly with large and small parts
sum(const_reference large_part, const_reference small_part) noexcept
: large_(large_part), small_(small_part) {}
/// Increment sum by one
sum& operator++() noexcept { return operator+=(1); }
/// Increment sum by value
sum& operator+=(const_reference value) noexcept {
// prevent compiler optimization from destroying the algorithm
// when -ffast-math is enabled
volatile value_type l;
value_type s;
if (std::abs(large_) >= std::abs(value)) {
l = large_;
s = value;
} else {
l = value;
s = large_;
}
large_ += value;
l = l - large_;
l = l + s;
small_ += l;
return *this;
}
/// Increment sum by one
sum& operator++() { return operator+=(1); }
/// Increment sum by value
sum& operator+=(const RealType& value) {
auto temp = large_ + value; // prevent optimization
if (std::abs(large_) >= std::abs(value))
small_ += (large_ - temp) + value;
else
small_ += (value - temp) + large_;
large_ = temp;
/// Add another sum
sum& operator+=(const sum& s) noexcept {
operator+=(s.large_);
small_ += s.small_;
return *this;
}
/// Scale by value
sum& operator*=(const RealType& value) {
sum& operator*=(const_reference value) noexcept {
large_ *= value;
small_ *= value;
return *this;
}
template <class T>
bool operator==(const sum<T>& rhs) const noexcept {
return large_ == rhs.large_ && small_ == rhs.small_;
bool operator==(const sum& rhs) const noexcept {
return large_ + small_ == rhs.large_ + rhs.small_;
}
template <class T>
bool operator!=(const T& rhs) const noexcept {
return !operator==(rhs);
}
bool operator!=(const sum& rhs) const noexcept { return !operator==(rhs); }
/// Return value of the sum.
value_type value() const noexcept { return large_ + small_; }
/// Return large part of the sum.
const RealType& large() const { return large_; }
const_reference large_part() const noexcept { return large_; }
/// Return small part of the sum.
const RealType& small() const { return small_; }
const_reference small_part() const noexcept { return small_; }
// note: windows.h illegially uses `#define small char`, cannot use method "small"
// allow implicit conversion to RealType
operator RealType() const { return large_ + small_; }
// lossy conversion to value type must be explicit
explicit operator value_type() const noexcept { return value(); }
template <class Archive>
void serialize(Archive&, unsigned /* version */);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("large", large_);
ar& make_nvp("small", small_);
}
// begin: extra operators to make sum behave like a regular number
sum& operator*=(const sum& rhs) noexcept {
const auto scale = static_cast<value_type>(rhs);
large_ *= scale;
small_ *= scale;
return *this;
}
sum operator*(const sum& rhs) const noexcept {
sum s = *this;
s *= rhs;
return s;
}
sum& operator/=(const sum& rhs) noexcept {
const auto scale = 1.0 / static_cast<value_type>(rhs);
large_ *= scale;
small_ *= scale;
return *this;
}
sum operator/(const sum& rhs) const noexcept {
sum s = *this;
s /= rhs;
return s;
}
bool operator<(const sum& rhs) const noexcept {
return operator value_type() < rhs.operator value_type();
}
bool operator>(const sum& rhs) const noexcept {
return operator value_type() > rhs.operator value_type();
}
bool operator<=(const sum& rhs) const noexcept {
return operator value_type() <= rhs.operator value_type();
}
bool operator>=(const sum& rhs) const noexcept {
return operator value_type() >= rhs.operator value_type();
}
// end: extra operators
private:
RealType large_ = RealType();
RealType small_ = RealType();
value_type large_{};
value_type small_{};
};
} // namespace accumulators

View File

@@ -7,7 +7,11 @@
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_WEIGHTED_MEAN_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_WEIGHTED_MEAN_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/square.hpp>
#include <boost/histogram/fwd.hpp> // for weighted_mean<>
#include <boost/histogram/weight.hpp>
#include <cassert>
#include <type_traits>
namespace boost {
@@ -20,72 +24,134 @@ namespace accumulators {
Uses West's incremental algorithm to improve numerical stability
of mean and variance computation.
*/
template <typename RealType>
template <class ValueType>
class weighted_mean {
public:
using value_type = ValueType;
using const_reference = const value_type&;
weighted_mean() = default;
weighted_mean(const RealType& wsum, const RealType& wsum2, const RealType& mean,
const RealType& variance)
/// Allow implicit conversion from other weighted_means.
template <class T>
weighted_mean(const weighted_mean<T>& o)
: sum_of_weights_{o.sum_of_weights_}
, sum_of_weights_squared_{o.sum_of_weights_squared_}
, weighted_mean_{o.weighted_mean_}
, sum_of_weighted_deltas_squared_{o.sum_of_weighted_deltas_squared_} {}
/// Initialize to external sum of weights, sum of weights squared, mean, and variance.
weighted_mean(const_reference wsum, const_reference wsum2, const_reference mean,
const_reference variance)
: sum_of_weights_(wsum)
, sum_of_weights_squared_(wsum2)
, weighted_mean_(mean)
, sum_of_weighted_deltas_squared_(
variance * (sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_)) {}
void operator()(const RealType& x) { operator()(1, x); }
/// Insert sample x.
void operator()(const_reference x) { operator()(weight(1), x); }
void operator()(const RealType& w, const RealType& x) {
sum_of_weights_ += w;
sum_of_weights_squared_ += w * w;
/// Insert sample x with weight w.
void operator()(const weight_type<value_type>& w, const_reference x) {
sum_of_weights_ += w.value;
sum_of_weights_squared_ += w.value * w.value;
const auto delta = x - weighted_mean_;
weighted_mean_ += w * delta / sum_of_weights_;
sum_of_weighted_deltas_squared_ += w * delta * (x - weighted_mean_);
weighted_mean_ += w.value * delta / sum_of_weights_;
sum_of_weighted_deltas_squared_ += w.value * delta * (x - weighted_mean_);
}
template <typename T>
weighted_mean& operator+=(const weighted_mean<T>& rhs) {
const auto tmp = weighted_mean_ * sum_of_weights_ +
static_cast<RealType>(rhs.weighted_mean_ * rhs.sum_of_weights_);
sum_of_weights_ += static_cast<RealType>(rhs.sum_of_weights_);
sum_of_weights_squared_ += static_cast<RealType>(rhs.sum_of_weights_squared_);
weighted_mean_ = tmp / sum_of_weights_;
sum_of_weighted_deltas_squared_ +=
static_cast<RealType>(rhs.sum_of_weighted_deltas_squared_);
/// Add another weighted_mean.
weighted_mean& operator+=(const weighted_mean& rhs) {
if (rhs.sum_of_weights_ == 0) return *this;
// see mean.hpp for derivation of correct formula
const auto n1 = sum_of_weights_;
const auto mu1 = weighted_mean_;
const auto n2 = rhs.sum_of_weights_;
const auto mu2 = rhs.weighted_mean_;
sum_of_weights_ += rhs.sum_of_weights_;
sum_of_weights_squared_ += rhs.sum_of_weights_squared_;
weighted_mean_ = (n1 * mu1 + n2 * mu2) / sum_of_weights_;
sum_of_weighted_deltas_squared_ += rhs.sum_of_weighted_deltas_squared_;
sum_of_weighted_deltas_squared_ += n1 * detail::square(weighted_mean_ - mu1);
sum_of_weighted_deltas_squared_ += n2 * detail::square(weighted_mean_ - mu2);
return *this;
}
weighted_mean& operator*=(const RealType& s) {
/** Scale by value.
This acts as if all samples were scaled by the value.
*/
weighted_mean& operator*=(const_reference s) noexcept {
weighted_mean_ *= s;
sum_of_weighted_deltas_squared_ *= s * s;
return *this;
}
template <typename T>
bool operator==(const weighted_mean<T>& rhs) const noexcept {
bool operator==(const weighted_mean& rhs) const noexcept {
return sum_of_weights_ == rhs.sum_of_weights_ &&
sum_of_weights_squared_ == rhs.sum_of_weights_squared_ &&
weighted_mean_ == rhs.weighted_mean_ &&
sum_of_weighted_deltas_squared_ == rhs.sum_of_weighted_deltas_squared_;
}
template <typename T>
bool operator!=(const T& rhs) const noexcept {
return !operator==(rhs);
bool operator!=(const weighted_mean& rhs) const noexcept { return !operator==(rhs); }
/// Return sum of weights.
const_reference sum_of_weights() const noexcept { return sum_of_weights_; }
/// Return sum of weights squared (variance of weight distribution).
const_reference sum_of_weights_squared() const noexcept {
return sum_of_weights_squared_;
}
const RealType& sum_of_weights() const noexcept { return sum_of_weights_; }
const RealType& value() const noexcept { return weighted_mean_; }
RealType variance() const {
/** Return effective counts.
This corresponds to the equivalent number of unweighted samples that would
have the same variance as this sample. count() should be used to check whether
value() and variance() are defined, see documentation of value() and variance().
count() can be used to compute the variance of the mean by dividing variance()
by count().
*/
value_type count() const noexcept {
// see https://en.wikipedia.org/wiki/Effective_sample_size#weighted_samples
return detail::square(sum_of_weights_) / sum_of_weights_squared_;
}
/** Return mean value of accumulated weighted samples.
The result is undefined, if count() == 0.
*/
const_reference value() const noexcept { return weighted_mean_; }
/** Return variance of accumulated weighted samples.
The result is undefined, if count() == 0 or count() == 1.
*/
value_type variance() const {
// see https://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Reliability_weights
return sum_of_weighted_deltas_squared_ /
(sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("sum_of_weights", sum_of_weights_);
ar& make_nvp("sum_of_weights_squared", sum_of_weights_squared_);
ar& make_nvp("weighted_mean", weighted_mean_);
ar& make_nvp("sum_of_weighted_deltas_squared", sum_of_weighted_deltas_squared_);
}
private:
RealType sum_of_weights_ = RealType(), sum_of_weights_squared_ = RealType(),
weighted_mean_ = RealType(), sum_of_weighted_deltas_squared_ = RealType();
value_type sum_of_weights_{};
value_type sum_of_weights_squared_{};
value_type weighted_mean_{};
value_type sum_of_weighted_deltas_squared_{};
};
} // namespace accumulators

View File

@@ -7,7 +7,9 @@
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_WEIGHTED_SUM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_WEIGHTED_SUM_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/square.hpp>
#include <boost/histogram/fwd.hpp> // for weighted_sum<>
#include <type_traits>
namespace boost {
@@ -15,74 +17,93 @@ namespace histogram {
namespace accumulators {
/// Holds sum of weights and its variance estimate
template <typename RealType>
template <class ValueType>
class weighted_sum {
public:
using value_type = ValueType;
using const_reference = const value_type&;
weighted_sum() = default;
explicit weighted_sum(const RealType& value) noexcept
: sum_of_weights_(value), sum_of_weights_squared_(value) {}
weighted_sum(const RealType& value, const RealType& variance) noexcept
/// Initialize sum to value and allow implicit conversion
weighted_sum(const_reference value) noexcept : weighted_sum(value, value) {}
/// Allow implicit conversion from sum<T>
template <class T>
weighted_sum(const weighted_sum<T>& s) noexcept
: weighted_sum(s.value(), s.variance()) {}
/// Initialize sum to value and variance
weighted_sum(const_reference value, const_reference variance) noexcept
: sum_of_weights_(value), sum_of_weights_squared_(variance) {}
/// Increment by one.
weighted_sum& operator++() { return operator+=(1); }
weighted_sum& operator++() {
++sum_of_weights_;
++sum_of_weights_squared_;
return *this;
}
/// Increment by value.
template <typename T>
weighted_sum& operator+=(const T& value) {
sum_of_weights_ += value;
sum_of_weights_squared_ += value * value;
/// Increment by weight.
weighted_sum& operator+=(const weight_type<value_type>& w) {
sum_of_weights_ += w.value;
sum_of_weights_squared_ += detail::square(w.value);
return *this;
}
/// Added another weighted sum.
template <typename T>
weighted_sum& operator+=(const weighted_sum<T>& rhs) {
sum_of_weights_ += static_cast<RealType>(rhs.sum_of_weights_);
sum_of_weights_squared_ += static_cast<RealType>(rhs.sum_of_weights_squared_);
weighted_sum& operator+=(const weighted_sum& rhs) {
sum_of_weights_ += rhs.sum_of_weights_;
sum_of_weights_squared_ += rhs.sum_of_weights_squared_;
return *this;
}
/// Divide by another weighted sum.
weighted_sum& operator/=(const weighted_sum& rhs) {
const auto v = sum_of_weights_;
const auto w = sum_of_weights_squared_;
const auto rv = rhs.sum_of_weights_;
const auto rw = rhs.sum_of_weights_squared_;
sum_of_weights_ /= rv;
// error propagation for independent a, b:
// c = a / b: var(c) = (var(a)/a^2 + var(b)/b^2) c^2
using detail::square;
sum_of_weights_squared_ = (w / square(v) + rw / square(rv)) * square(sum_of_weights_);
return *this;
}
/// Scale by value.
weighted_sum& operator*=(const RealType& x) {
weighted_sum& operator*=(const_reference x) {
sum_of_weights_ *= x;
sum_of_weights_squared_ *= x * x;
return *this;
}
bool operator==(const RealType& rhs) const noexcept {
return sum_of_weights_ == rhs && sum_of_weights_squared_ == rhs;
}
template <typename T>
bool operator==(const weighted_sum<T>& rhs) const noexcept {
bool operator==(const weighted_sum& rhs) const noexcept {
return sum_of_weights_ == rhs.sum_of_weights_ &&
sum_of_weights_squared_ == rhs.sum_of_weights_squared_;
}
template <typename T>
bool operator!=(const T& rhs) const noexcept {
return !operator==(rhs);
}
bool operator!=(const weighted_sum& rhs) const noexcept { return !operator==(rhs); }
/// Return value of the sum.
const RealType& value() const noexcept { return sum_of_weights_; }
const_reference value() const noexcept { return sum_of_weights_; }
/// Return estimated variance of the sum.
const RealType& variance() const noexcept { return sum_of_weights_squared_; }
const_reference variance() const noexcept { return sum_of_weights_squared_; }
// lossy conversion must be explicit
template <class T>
explicit operator T() const {
return static_cast<T>(sum_of_weights_);
}
explicit operator const_reference() const { return sum_of_weights_; }
template <class Archive>
void serialize(Archive&, unsigned /* version */);
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("sum_of_weights", sum_of_weights_);
ar& make_nvp("sum_of_weights_squared", sum_of_weights_squared_);
}
private:
RealType sum_of_weights_ = RealType();
RealType sum_of_weights_squared_ = RealType();
value_type sum_of_weights_{};
value_type sum_of_weights_squared_{};
};
} // namespace accumulators