Added thirdparty: boost library

This commit is contained in:
Viacheslav Demydiuk
2024-01-06 19:55:56 +02:00
parent bf49f439e1
commit bccd1e7051
15683 changed files with 3239840 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
// 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_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_HPP
/**
\file boost/histogram/accumulators.hpp
Includes all accumulator headers of the Boost.Histogram library.
Extra header not automatically included:
- [boost/histogram/accumulators/ostream.hpp][1]
[1]: histogram/reference.html#header.boost.histogram.accumulators.ostream_hpp
*/
#include <boost/histogram/accumulators/count.hpp>
#include <boost/histogram/accumulators/fraction.hpp>
#include <boost/histogram/accumulators/mean.hpp>
#include <boost/histogram/accumulators/sum.hpp>
#include <boost/histogram/accumulators/weighted_mean.hpp>
#include <boost/histogram/accumulators/weighted_sum.hpp>
#endif
+177
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
+130
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
+41
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
+181
View File
@@ -0,0 +1,181 @@
// Copyright 2015-2018 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_MEAN_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_MEAN_HPP
#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 {
namespace accumulators {
/** Calculates mean and variance of sample.
Uses Welfords's incremental algorithm to improve the numerical
stability of mean and variance computation.
*/
template <class ValueType>
class mean {
public:
using value_type = ValueType;
using const_reference = const value_type&;
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_);
}
/// 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_ = (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;
}
/** 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;
}
bool operator==(const mean& rhs) const noexcept {
return sum_ == rhs.sum_ && mean_ == rhs.mean_ &&
sum_of_deltas_squared_ == rhs.sum_of_deltas_squared_;
}
bool operator!=(const mean& rhs) const noexcept { return !operator==(rhs); }
/** 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& 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:
value_type sum_{};
value_type mean_{};
value_type sum_of_deltas_squared_{};
};
} // namespace accumulators
} // namespace histogram
} // 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.
struct common_type<boost::histogram::accumulators::mean<T>,
boost::histogram::accumulators::mean<U>> {
using type = boost::histogram::accumulators::mean<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2015-2017 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_OSTREAM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_OSTREAM_HPP
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/fwd.hpp>
#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 <class CharT, class Traits, class U, bool B>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const count<U, B>& x) {
return os << x.value();
}
template <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& 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 <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& 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 <class CharT, class Traits, class U>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& 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
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
#endif
+179
View File
@@ -0,0 +1,179 @@
// Copyright 2018 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_SUM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP
#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 of floats.
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.
*/
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 and allow implicit conversion
sum(const_reference value) noexcept : sum(value, 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;
}
/// Add another sum
sum& operator+=(const sum& s) noexcept {
operator+=(s.large_);
small_ += s.small_;
return *this;
}
/// Scale by value
sum& operator*=(const_reference value) noexcept {
large_ *= value;
small_ *= value;
return *this;
}
bool operator==(const sum& rhs) const noexcept {
return large_ + small_ == rhs.large_ + rhs.small_;
}
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_reference large_part() const noexcept { return large_; }
/// Return small part of the sum.
const_reference small_part() const noexcept { return small_; }
// note: windows.h illegially uses `#define small char`, cannot use method "small"
// lossy conversion to value type must be explicit
explicit operator value_type() const noexcept { return value(); }
template <class Archive>
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:
value_type large_{};
value_type small_{};
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
struct common_type<boost::histogram::accumulators::sum<T>,
boost::histogram::accumulators::sum<U>> {
using type = boost::histogram::accumulators::sum<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2018 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_WEIGHTED_MEAN_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_WEIGHTED_MEAN_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 {
namespace histogram {
namespace accumulators {
/**
Calculates mean and variance of weighted sample.
Uses West's incremental algorithm to improve numerical stability
of mean and variance computation.
*/
template <class ValueType>
class weighted_mean {
public:
using value_type = ValueType;
using const_reference = const value_type&;
weighted_mean() = default;
/// 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_)) {}
/// Insert sample x.
void operator()(const_reference x) { operator()(weight(1), x); }
/// 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.value * delta / sum_of_weights_;
sum_of_weighted_deltas_squared_ += w.value * delta * (x - weighted_mean_);
}
/// 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;
}
/** 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;
}
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_;
}
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_;
}
/** 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& 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:
value_type sum_of_weights_{};
value_type sum_of_weights_squared_{};
value_type weighted_mean_{};
value_type sum_of_weighted_deltas_squared_{};
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
/// Specialization for boost::histogram::accumulators::weighted_mean.
struct common_type<boost::histogram::accumulators::weighted_mean<T>,
boost::histogram::accumulators::weighted_mean<U>> {
using type = boost::histogram::accumulators::weighted_mean<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif
+133
View File
@@ -0,0 +1,133 @@
// Copyright 2015-2018 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_WEIGHTED_SUM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_WEIGHTED_SUM_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 {
namespace histogram {
namespace accumulators {
/// Holds sum of weights and its variance estimate
template <class ValueType>
class weighted_sum {
public:
using value_type = ValueType;
using const_reference = const value_type&;
weighted_sum() = default;
/// 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++() {
++sum_of_weights_;
++sum_of_weights_squared_;
return *this;
}
/// 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.
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_reference x) {
sum_of_weights_ *= x;
sum_of_weights_squared_ *= x * x;
return *this;
}
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_;
}
bool operator!=(const weighted_sum& rhs) const noexcept { return !operator==(rhs); }
/// Return value of the sum.
const_reference value() const noexcept { return sum_of_weights_; }
/// Return estimated variance of the sum.
const_reference variance() const noexcept { return sum_of_weights_squared_; }
// lossy conversion must be explicit
explicit operator const_reference() const { return sum_of_weights_; }
template <class Archive>
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:
value_type sum_of_weights_{};
value_type sum_of_weights_squared_{};
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
struct common_type<boost::histogram::accumulators::weighted_sum<T>,
boost::histogram::accumulators::weighted_sum<U>> {
using type = boost::histogram::accumulators::weighted_sum<common_type_t<T, U>>;
};
template <class T, class U>
struct common_type<boost::histogram::accumulators::weighted_sum<T>, U> {
using type = boost::histogram::accumulators::weighted_sum<common_type_t<T, U>>;
};
template <class T, class U>
struct common_type<T, boost::histogram::accumulators::weighted_sum<U>> {
using type = boost::histogram::accumulators::weighted_sum<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif
+20
View File
@@ -0,0 +1,20 @@
// 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_ALGORITHM_HPP
#define BOOST_HISTOGRAM_ALGORITHM_HPP
/**
\file boost/histogram/algorithm.hpp
Includes all algorithm headers of the Boost.Histogram library.
*/
#include <boost/histogram/algorithm/empty.hpp>
#include <boost/histogram/algorithm/project.hpp>
#include <boost/histogram/algorithm/reduce.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#endif
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2019 Henry Schreiner
//
// 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_ALGORITHM_EMPTY_HPP
#define BOOST_HISTOGRAM_ALGORITHM_EMPTY_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/indexed.hpp>
namespace boost {
namespace histogram {
namespace algorithm {
/** Check to see if all histogram cells are empty. Use coverage to include or
exclude the underflow/overflow bins.
This algorithm has O(N) complexity, where N is the number of cells.
Returns true if all cells are empty, and false otherwise.
*/
template <class A, class S>
auto empty(const histogram<A, S>& h, coverage cov) {
using value_type = typename histogram<A, S>::value_type;
const value_type default_value = value_type();
for (auto&& ind : indexed(h, cov)) {
if (*ind != default_value) { return false; }
}
return true;
}
} // namespace algorithm
} // namespace histogram
} // namespace boost
#endif
+104
View File
@@ -0,0 +1,104 @@
// Copyright 2015-2018 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_ALGORITHM_PROJECT_HPP
#define BOOST_HISTOGRAM_ALGORITHM_PROJECT_HPP
#include <algorithm>
#include <boost/histogram/axis/variant.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/make_default.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/histogram.hpp>
#include <boost/histogram/indexed.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/set.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>
#include <type_traits>
#include <vector>
namespace boost {
namespace histogram {
namespace algorithm {
/**
Returns a lower-dimensional histogram, summing over removed axes.
Arguments are the source histogram and compile-time numbers, the remaining indices of
the axes. Returns a new histogram which only contains the subset of axes. The source
histogram is summed over the removed axes.
*/
template <class A, class S, unsigned N, typename... Ns>
auto project(const histogram<A, S>& h, std::integral_constant<unsigned, N>, Ns...) {
using LN = mp11::mp_list<std::integral_constant<unsigned, N>, Ns...>;
static_assert(mp11::mp_is_set<LN>::value, "indices must be unique");
const auto& old_axes = unsafe_access::axes(h);
auto axes = detail::static_if<detail::is_tuple<A>>(
[&](const auto& old_axes) {
return std::make_tuple(std::get<N>(old_axes), std::get<Ns::value>(old_axes)...);
},
[&](const auto& old_axes) {
return std::decay_t<decltype(old_axes)>({old_axes[N], old_axes[Ns::value]...});
},
old_axes);
const auto& old_storage = unsafe_access::storage(h);
using A2 = decltype(axes);
auto result = histogram<A2, S>(std::move(axes), detail::make_default(old_storage));
auto idx = detail::make_stack_buffer<int>(unsafe_access::axes(result));
for (auto&& x : indexed(h, coverage::all)) {
auto i = idx.begin();
mp11::mp_for_each<LN>([&i, &x](auto J) { *i++ = x.index(J); });
result.at(idx) += *x;
}
return result;
}
/**
Returns a lower-dimensional histogram, summing over removed axes.
This version accepts a source histogram and an iterable range containing the remaining
indices.
*/
template <class A, class S, class Iterable, class = detail::requires_iterable<Iterable>>
auto project(const histogram<A, S>& h, const Iterable& c) {
using namespace boost::mp11;
const auto& old_axes = unsafe_access::axes(h);
// axes is always std::vector<...>, even if A is tuple
auto axes = detail::make_empty_dynamic_axes(old_axes);
axes.reserve(c.size());
auto seen = detail::make_stack_buffer(old_axes, false);
for (auto d : c) {
if (static_cast<unsigned>(d) >= h.rank())
BOOST_THROW_EXCEPTION(std::invalid_argument("invalid axis index"));
if (seen[d]) BOOST_THROW_EXCEPTION(std::invalid_argument("indices are not unique"));
seen[d] = true;
axes.emplace_back(detail::axis_get(old_axes, d));
}
const auto& old_storage = unsafe_access::storage(h);
auto result =
histogram<decltype(axes), S>(std::move(axes), detail::make_default(old_storage));
auto idx = detail::make_stack_buffer<int>(unsafe_access::axes(result));
for (auto&& x : indexed(h, coverage::all)) {
auto i = idx.begin();
for (auto d : c) *i++ = x.index(d);
result.at(idx) += *x;
}
return result;
}
} // namespace algorithm
} // namespace histogram
} // namespace boost
#endif
+470
View File
@@ -0,0 +1,470 @@
// Copyright 2018-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_ALGORITHM_REDUCE_HPP
#define BOOST_HISTOGRAM_ALGORITHM_REDUCE_HPP
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/make_default.hpp>
#include <boost/histogram/detail/reduce_command.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/indexed.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <initializer_list>
#include <stdexcept>
#include <string>
namespace boost {
namespace histogram {
namespace algorithm {
/** Holder for a reduce command.
Use this type to store reduce commands in a container. The internals of this type are an
implementation detail.
*/
using reduce_command = detail::reduce_command;
/** Shrink command to be used in `reduce`.
Command is applied to axis with given index.
Shrinking is based on an inclusive value interval. The bin which contains the first
value starts the range of bins to keep. The bin which contains the second value is the
last included in that range. When the second value is exactly equal to a lower bin edge,
then the previous bin is the last in the range.
The counts in removed bins are added to the corresponding underflow and overflow bins,
if they are present. If they are not present, the counts are discarded. Also see
`crop`, which always discards the counts.
@param iaxis which axis to operate on.
@param lower bin which contains lower is first to be kept.
@param upper bin which contains upper is last to be kept, except if upper is equal to
the lower edge.
*/
inline reduce_command shrink(unsigned iaxis, double lower, double upper) {
if (lower == upper)
BOOST_THROW_EXCEPTION(std::invalid_argument("lower != upper required"));
reduce_command r;
r.iaxis = iaxis;
r.range = reduce_command::range_t::values;
r.begin.value = lower;
r.end.value = upper;
r.merge = 1;
r.crop = false;
return r;
}
/** Shrink command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
Shrinking is based on an inclusive value interval. The bin which contains the first
value starts the range of bins to keep. The bin which contains the second value is the
last included in that range. When the second value is exactly equal to a lower bin edge,
then the previous bin is the last in the range.
The counts in removed bins are added to the corresponding underflow and overflow bins,
if they are present. If they are not present, the counts are discarded. Also see
`crop`, which always discards the counts.
@param lower bin which contains lower is first to be kept.
@param upper bin which contains upper is last to be kept, except if upper is equal to
the lower edge.
*/
inline reduce_command shrink(double lower, double upper) {
return shrink(reduce_command::unset, lower, upper);
}
/** Crop command to be used in `reduce`.
Command is applied to axis with given index.
Works like `shrink` (see shrink documentation for details), but counts in removed
bins are always discarded, whether underflow and overflow bins are present or not.
@param iaxis which axis to operate on.
@param lower bin which contains lower is first to be kept.
@param upper bin which contains upper is last to be kept, except if upper is equal to
the lower edge.
*/
inline reduce_command crop(unsigned iaxis, double lower, double upper) {
reduce_command r = shrink(iaxis, lower, upper);
r.crop = true;
return r;
}
/** Crop command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
Works like `shrink` (see shrink documentation for details), but counts in removed bins
are discarded, whether underflow and overflow bins are present or not. If the cropped
range goes beyond the axis range, then the content of the underflow
or overflow bin which overlaps with the range is kept.
If the counts in an existing underflow or overflow bin are discared by the crop, the
corresponding memory cells are not physically removed. Only their contents are set to
zero. This technical limitation may be lifted in the future, then crop may completely
remove the cropped memory cells.
@param lower bin which contains lower is first to be kept.
@param upper bin which contains upper is last to be kept, except if upper is equal to
the lower edge.
*/
inline reduce_command crop(double lower, double upper) {
return crop(reduce_command::unset, lower, upper);
}
/// Whether to behave like `shrink` or `crop` regarding removed bins.
enum class slice_mode { shrink, crop };
/** Slice command to be used in `reduce`.
Command is applied to axis with given index.
Slicing works like `shrink` or `crop`, but uses bin indices instead of values.
@param iaxis which axis to operate on.
@param begin first index that should be kept.
@param end one past the last index that should be kept.
@param mode whether to behave like `shrink` or `crop` regarding removed bins.
*/
inline reduce_command slice(unsigned iaxis, axis::index_type begin, axis::index_type end,
slice_mode mode = slice_mode::shrink) {
if (!(begin < end))
BOOST_THROW_EXCEPTION(std::invalid_argument("begin < end required"));
reduce_command r;
r.iaxis = iaxis;
r.range = reduce_command::range_t::indices;
r.begin.index = begin;
r.end.index = end;
r.merge = 1;
r.crop = mode == slice_mode::crop;
return r;
}
/** Slice command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
Slicing works like `shrink` or `crop`, but uses bin indices instead of values.
@param begin first index that should be kept.
@param end one past the last index that should be kept.
@param mode whether to behave like `shrink` or `crop` regarding removed bins.
*/
inline reduce_command slice(axis::index_type begin, axis::index_type end,
slice_mode mode = slice_mode::shrink) {
return slice(reduce_command::unset, begin, end, mode);
}
/** Rebin command to be used in `reduce`.
Command is applied to axis with given index.
The command merges N adjacent bins into one. This makes the axis coarser and the bins
wider. The original number of bins is divided by N. If there is a rest to this devision,
the axis is implicitly shrunk at the upper end by that rest.
@param iaxis which axis to operate on.
@param merge how many adjacent bins to merge into one.
*/
inline reduce_command rebin(unsigned iaxis, unsigned merge) {
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
reduce_command r;
r.iaxis = iaxis;
r.merge = merge;
r.range = reduce_command::range_t::none;
r.crop = false;
return r;
}
/** Rebin command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
The command merges N adjacent bins into one. This makes the axis coarser and the bins
wider. The original number of bins is divided by N. If there is a rest to this devision,
the axis is implicitly shrunk at the upper end by that rest.
@param merge how many adjacent bins to merge into one.
*/
inline reduce_command rebin(unsigned merge) {
return rebin(reduce_command::unset, merge);
}
/** Shrink and rebin command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
To shrink(unsigned, double, double) and rebin(unsigned, unsigned) in one command (see
the respective commands for more details). Equivalent to passing both commands for the
same axis to `reduce`.
@param iaxis which axis to operate on.
@param lower lowest bound that should be kept.
@param upper highest bound that should be kept. If upper is inside bin interval, the
whole interval is removed.
@param merge how many adjacent bins to merge into one.
*/
inline reduce_command shrink_and_rebin(unsigned iaxis, double lower, double upper,
unsigned merge) {
reduce_command r = shrink(iaxis, lower, upper);
r.merge = rebin(merge).merge;
return r;
}
/** Shrink and rebin command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
To `shrink` and `rebin` in one command (see the respective commands for more
details). Equivalent to passing both commands for the same axis to `reduce`.
@param lower lowest bound that should be kept.
@param upper highest bound that should be kept. If upper is inside bin interval, the
whole interval is removed.
@param merge how many adjacent bins to merge into one.
*/
inline reduce_command shrink_and_rebin(double lower, double upper, unsigned merge) {
return shrink_and_rebin(reduce_command::unset, lower, upper, merge);
}
/** Crop and rebin command to be used in `reduce`.
Command is applied to axis with given index.
To `crop` and `rebin` in one command (see the respective commands for more
details). Equivalent to passing both commands for the same axis to `reduce`.
@param iaxis which axis to operate on.
@param lower lowest bound that should be kept.
@param upper highest bound that should be kept. If upper is inside bin interval,
the whole interval is removed.
@param merge how many adjacent bins to merge into one.
*/
inline reduce_command crop_and_rebin(unsigned iaxis, double lower, double upper,
unsigned merge) {
reduce_command r = crop(iaxis, lower, upper);
r.merge = rebin(merge).merge;
return r;
}
/** Crop and rebin command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
To `crop` and `rebin` in one command (see the respective commands for more
details). Equivalent to passing both commands for the same axis to `reduce`.
@param lower lowest bound that should be kept.
@param upper highest bound that should be kept. If upper is inside bin interval,
the whole interval is removed.
@param merge how many adjacent bins to merge into one.
*/
inline reduce_command crop_and_rebin(double lower, double upper, unsigned merge) {
return crop_and_rebin(reduce_command::unset, lower, upper, merge);
}
/** Slice and rebin command to be used in `reduce`.
Command is applied to axis with given index.
To `slice` and `rebin` in one command (see the respective commands for more
details). Equivalent to passing both commands for the same axis to `reduce`.
@param iaxis which axis to operate on.
@param begin first index that should be kept.
@param end one past the last index that should be kept.
@param merge how many adjacent bins to merge into one.
@param mode slice mode, see slice_mode.
*/
inline reduce_command slice_and_rebin(unsigned iaxis, axis::index_type begin,
axis::index_type end, unsigned merge,
slice_mode mode = slice_mode::shrink) {
reduce_command r = slice(iaxis, begin, end, mode);
r.merge = rebin(merge).merge;
return r;
}
/** Slice and rebin command to be used in `reduce`.
Command is applied to corresponding axis in order of reduce arguments.
To `slice` and `rebin` in one command (see the respective commands for more
details). Equivalent to passing both commands for the same axis to `reduce`.
@param begin first index that should be kept.
@param end one past the last index that should be kept.
@param merge how many adjacent bins to merge into one.
@param mode slice mode, see slice_mode.
*/
inline reduce_command slice_and_rebin(axis::index_type begin, axis::index_type end,
unsigned merge,
slice_mode mode = slice_mode::shrink) {
return slice_and_rebin(reduce_command::unset, begin, end, merge, mode);
}
/** Shrink, crop, slice, and/or rebin axes of a histogram.
Returns a new reduced histogram and leaves the original histogram untouched.
The commands `rebin` and `shrink` or `slice` for the same axis are
automatically combined, this is not an error. Passing a `shrink` and a `slice`
command for the same axis or two `rebin` commands triggers an `invalid_argument`
exception. Trying to reducing a non-reducible axis triggers an `invalid_argument`
exception. Histograms with non-reducible axes can still be reduced along the
other axes that are reducible.
An overload allows one to pass reduce_command as positional arguments.
@param hist original histogram.
@param options iterable sequence of reduce commands: `shrink`, `slice`, `rebin`,
`shrink_and_rebin`, or `slice_and_rebin`. The element type of the iterable should be
`reduce_command`.
*/
template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
Histogram reduce(const Histogram& hist, const Iterable& options) {
using axis::index_type;
const auto& old_axes = unsafe_access::axes(hist);
auto opts = detail::make_stack_buffer(old_axes, reduce_command{});
detail::normalize_reduce_commands(opts, options);
auto axes =
detail::axes_transform(old_axes, [&opts](std::size_t iaxis, const auto& a_in) {
using A = std::decay_t<decltype(a_in)>;
using AO = axis::traits::get_options<A>;
auto& o = opts[iaxis];
o.is_ordered = axis::traits::ordered(a_in);
if (o.merge > 0) { // option is set?
o.use_underflow_bin = AO::test(axis::option::underflow);
o.use_overflow_bin = AO::test(axis::option::overflow);
return detail::static_if_c<axis::traits::is_reducible<A>::value>(
[&o](const auto& a_in) {
if (o.range == reduce_command::range_t::none) {
// no range restriction, pure rebin
o.begin.index = 0;
o.end.index = a_in.size();
} else {
// range striction, convert values to indices as needed
if (o.range == reduce_command::range_t::values) {
const auto end_value = o.end.value;
o.begin.index = axis::traits::index(a_in, o.begin.value);
o.end.index = axis::traits::index(a_in, o.end.value);
// end = index + 1, unless end_value equal to upper bin edge
if (axis::traits::value_as<double>(a_in, o.end.index) != end_value)
++o.end.index;
}
// crop flow bins if index range does not include them
if (o.crop) {
o.use_underflow_bin &= o.begin.index < 0;
o.use_overflow_bin &= o.end.index > a_in.size();
}
// now limit [begin, end] to [0, size()]
if (o.begin.index < 0) o.begin.index = 0;
if (o.end.index > a_in.size()) o.end.index = a_in.size();
}
// shorten the index range to a multiple of o.merge;
// example [1, 4] with merge = 2 is reduced to [1, 3]
o.end.index -=
(o.end.index - o.begin.index) % static_cast<index_type>(o.merge);
using A = std::decay_t<decltype(a_in)>;
return A(a_in, o.begin.index, o.end.index, o.merge);
},
[iaxis](const auto& a_in) {
return BOOST_THROW_EXCEPTION(std::invalid_argument(
"axis " + std::to_string(iaxis) + " is not reducible")),
a_in;
},
a_in);
} else {
// command was not set for this axis; fill noop values and copy original axis
o.use_underflow_bin = AO::test(axis::option::underflow);
o.use_overflow_bin = AO::test(axis::option::overflow);
o.merge = 1;
o.begin.index = 0;
o.end.index = a_in.size();
return a_in;
}
});
auto result =
Histogram(std::move(axes), detail::make_default(unsafe_access::storage(hist)));
auto idx = detail::make_stack_buffer<index_type>(unsafe_access::axes(result));
for (auto&& x : indexed(hist, coverage::all)) {
auto i = idx.begin();
auto o = opts.begin();
bool skip = false;
for (auto j : x.indices()) {
*i = (j - o->begin.index);
if (o->is_ordered && *i <= -1) {
*i = -1;
if (!o->use_underflow_bin) skip = true;
} else {
if (*i >= 0)
*i /= static_cast<index_type>(o->merge);
else
*i = o->end.index;
const auto reduced_axis_end =
(o->end.index - o->begin.index) / static_cast<index_type>(o->merge);
if (*i >= reduced_axis_end) {
*i = reduced_axis_end;
if (!o->use_overflow_bin) skip = true;
}
}
++i;
++o;
}
if (!skip) result.at(idx) += *x;
}
return result;
}
/** Shrink, slice, and/or rebin axes of a histogram.
Returns a new reduced histogram and leaves the original histogram untouched.
The commands `rebin` and `shrink` or `slice` for the same axis are
automatically combined, this is not an error. Passing a `shrink` and a `slice`
command for the same axis or two `rebin` commands triggers an invalid_argument
exception. It is safe to reduce histograms with some axis that are not reducible along
the other axes. Trying to reducing a non-reducible axis triggers an invalid_argument
exception.
An overload allows one to pass an iterable of reduce_command.
@param hist original histogram.
@param opt first reduce command; one of `shrink`, `slice`, `rebin`,
`shrink_and_rebin`, or `slice_or_rebin`.
@param opts more reduce commands.
*/
template <class Histogram, class... Ts>
Histogram reduce(const Histogram& hist, const reduce_command& opt, const Ts&... opts) {
// this must be in one line, because any of the ts could be a temporary
return reduce(hist, std::initializer_list<reduce_command>{opt, opts...});
}
} // namespace algorithm
} // namespace histogram
} // namespace boost
#endif
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2018 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_ALGORITHM_SUM_HPP
#define BOOST_HISTOGRAM_ALGORITHM_SUM_HPP
#include <boost/histogram/accumulators/sum.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/indexed.hpp>
#include <boost/mp11/utility.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace algorithm {
/** Compute the sum over all histogram cells (underflow/overflow included by default).
The implementation favors accuracy and protection against overflow over speed. If the
value type of the histogram is an integral or floating point type,
accumulators::sum<double> is used to compute the sum, else the original value type is
used. Compilation fails, if the value type does not support operator+=. The return type
is double if the value type of the histogram is integral or floating point, and the
original value type otherwise.
If you need a different trade-off, you can write your own loop or use `std::accumulate`:
```
// iterate over all bins
auto sum_all = std::accumulate(hist.begin(), hist.end(), 0.0);
// skip underflow/overflow bins
double sum = 0;
for (auto&& x : indexed(hist))
sum += *x; // dereference accessor
// or:
// auto ind = boost::histogram::indexed(hist);
// auto sum = std::accumulate(ind.begin(), ind.end(), 0.0);
```
@returns accumulator type or double
@param hist Const reference to the histogram.
@param cov Iterate over all or only inner bins (optional, default: all).
*/
template <class A, class S>
auto sum(const histogram<A, S>& hist, const coverage cov = coverage::all) {
using T = typename histogram<A, S>::value_type;
// T is arithmetic, compute sum accurately with high dynamic range
using sum_type = mp11::mp_if<std::is_arithmetic<T>, accumulators::sum<double>, T>;
sum_type sum;
if (cov == coverage::all)
for (auto&& x : hist) sum += x;
else
// sum += x also works if sum_type::operator+=(const sum_type&) exists
for (auto&& x : indexed(hist)) sum += *x;
using R = mp11::mp_if<std::is_arithmetic<T>, double, T>;
return static_cast<R>(sum);
}
} // namespace algorithm
} // namespace histogram
} // namespace boost
#endif
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2018 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_AXIS_HPP
#define BOOST_HISTOGRAM_AXIS_HPP
/**
\file boost/histogram/axis.hpp
Includes all axis headers of the Boost.Histogram library.
Extra header not automatically included:
- [boost/histogram/axis/ostream.hpp][1]
[1]: histogram/reference.html#header.boost.histogram.axis.ostream_hpp
*/
#include <boost/histogram/axis/boolean.hpp>
#include <boost/histogram/axis/category.hpp>
#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/axis/variable.hpp>
#include <boost/histogram/axis/variant.hpp>
#endif
+102
View File
@@ -0,0 +1,102 @@
// Copyright Hans Dembinski 2020
//
// 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_AXIS_BOOLEAN_HPP
#define BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <string>
namespace boost {
namespace histogram {
namespace axis {
/**
Discrete axis for boolean data.
Binning is a pass-though operation with zero cost, making this the
fastest possible axis. The axis has no internal state apart from the
optional metadata state. The axis has no under- and overflow bins.
It cannot grow and cannot be reduced.
@tparam MetaData type to store meta data.
*/
template <class MetaData>
class boolean : public iterator_mixin<boolean<MetaData>>,
public metadata_base_t<MetaData> {
using value_type = bool;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
public:
/** Construct a boolean axis.
@param meta description of the axis.
The constructor is nothrow if meta is nothrow move constructible.
*/
explicit boolean(metadata_type meta = {}) noexcept(
std::is_nothrow_move_constructible<metadata_type>::value)
: metadata_base(std::move(meta)) {}
/// Return index for value argument.
index_type index(value_type x) const noexcept { return static_cast<index_type>(x); }
/// Return value for index argument.
value_type value(index_type i) const noexcept { return static_cast<value_type>(i); }
/// Return bin for index argument.
value_type bin(index_type i) const noexcept { return value(i); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return 2; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept { return true; }
/// Returns the options.
static constexpr unsigned options() noexcept { return option::none_t::value; }
template <class M>
bool operator==(const boolean<M>& o) const noexcept {
return detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class M>
bool operator!=(const boolean<M>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("meta", this->metadata());
}
private:
template <class M>
friend class boolean;
};
#if __cpp_deduction_guides >= 201606
boolean()->boolean<null_type>;
template <class M>
boolean(M) -> boolean<detail::replace_type<std::decay_t<M>, const char*, std::string>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
+240
View File
@@ -0,0 +1,240 @@
// Copyright 2015-2018 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_AXIS_CATEGORY_HPP
#define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace histogram {
namespace axis {
/** Maps at a set of unique values to bin indices.
The axis maps a set of values to bins, following the order of arguments in the
constructor. The optional overflow bin for this axis counts input values that
are not part of the set. Binning has O(N) complexity, but with a very small
factor. For small N (the typical use case) it beats other kinds of lookup.
@tparam Value input value type, must be equal-comparable.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
The options `underflow` and `circular` are not allowed. The options `growth`
and `overflow` are mutually exclusive.
*/
template <class Value, class MetaData, class Options, class Allocator>
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type = detail::replace_default<Options, option::overflow_t>;
using allocator_type = Allocator;
using vector_type = std::vector<value_type, allocator_type>;
public:
constexpr category() = default;
explicit category(allocator_type alloc) : vec_(alloc) {}
/** Construct from forward iterator range of unique values.
@param begin begin of category range of unique values.
@param end end of category range of unique values.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
The constructor throws `std::invalid_argument` if iterator range is invalid. If the
range contains duplicated values, the behavior of the axis is undefined.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
template <class It, class = detail::requires_iterator<It>>
category(It begin, It end, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(alloc) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(!options.test(option::underflow),
"category axis cannot have underflow");
static_assert(!options.test(option::circular), "category axis cannot be circular");
static_assert(!(options.test(option::growth) && options.test(option::overflow)),
"growing category axis cannot have entries in overflow bin");
if (std::distance(begin, end) < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));
vec_.reserve(std::distance(begin, end));
while (begin != end) vec_.emplace_back(*begin++);
}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class It, class A, class = detail::requires_iterator<It>,
class = detail::requires_allocator<A>>
category(It begin, It end, metadata_type meta, A alloc)
: category(begin, end, std::move(meta), {}, std::move(alloc)) {}
/** Construct axis from iterable sequence of unique values.
@param iterable sequence of unique values.
@param meta description of the axis.
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use.
*/
template <class C, class = detail::requires_iterable<C>>
category(const C& iterable, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: category(std::begin(iterable), std::end(iterable), std::move(meta), options,
std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class C, class A, class = detail::requires_iterable<C>,
class = detail::requires_allocator<A>>
category(const C& iterable, metadata_type meta, A alloc)
: category(std::begin(iterable), std::end(iterable), std::move(meta), {},
std::move(alloc)) {}
/** Construct axis from an initializer list of unique values.
@param list `std::initializer_list` of unique values.
@param meta description of the axis.
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use.
*/
template <class U>
category(std::initializer_list<U> list, metadata_type meta = {},
options_type options = {}, allocator_type alloc = {})
: category(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_allocator<A>>
category(std::initializer_list<U> list, metadata_type meta, A alloc)
: category(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
category(const category& src, index_type begin, index_type end, unsigned merge)
// LCOV_EXCL_START: gcc-8 is missing the delegated ctor for no reason
: category(src.vec_.begin() + begin, src.vec_.begin() + end, src.metadata(), {},
src.get_allocator())
// LCOV_EXCL_STOP
{
if (merge > 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for category axis"));
}
/// Return index for value argument.
index_type index(const value_type& x) const noexcept {
const auto beg = vec_.begin();
const auto end = vec_.end();
return static_cast<index_type>(std::distance(beg, std::find(beg, end, x)));
}
/// Returns index and shift (if axis has grown) for the passed argument.
std::pair<index_type, index_type> update(const value_type& x) {
const auto i = index(x);
if (i < size()) return {i, 0};
vec_.emplace_back(x);
return {i, -1};
}
/// Return value for index argument.
/// Throws `std::out_of_range` if the index is out of bounds.
auto value(index_type idx) const
-> std::conditional_t<std::is_scalar<value_type>::value, value_type,
const value_type&> {
if (idx < 0 || idx >= size())
BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
return vec_[idx];
}
/// Return value for index argument; alias for value(...).
decltype(auto) bin(index_type idx) const { return value(idx); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
return options() & (option::overflow | option::growth);
}
/// Indicate that the axis is not ordered.
static constexpr bool ordered() noexcept { return false; }
template <class V, class M, class O, class A>
bool operator==(const category<V, M, O, A>& o) const noexcept {
const auto& a = vec_;
const auto& b = o.vec_;
return std::equal(a.begin(), a.end(), b.begin(), b.end(), detail::relaxed_equal{}) &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O, class A>
bool operator!=(const category<V, M, O, A>& o) const noexcept {
return !operator==(o);
}
allocator_type get_allocator() const { return vec_.get_allocator(); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("seq", vec_);
ar& make_nvp("meta", this->metadata());
}
private:
vector_type vec_;
template <class V, class M, class O, class A>
friend class category;
};
#if __cpp_deduction_guides >= 201606
template <class T>
category(std::initializer_list<T>)
-> category<detail::replace_cstring<std::decay_t<T>>, null_type>;
template <class T, class M>
category(std::initializer_list<T>, M)
-> category<detail::replace_cstring<std::decay_t<T>>,
detail::replace_cstring<std::decay_t<M>>>;
template <class T, class M, unsigned B>
category(std::initializer_list<T>, M, const option::bitset<B>&)
-> category<detail::replace_cstring<std::decay_t<T>>,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+239
View File
@@ -0,0 +1,239 @@
// Copyright 2015-2018 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_AXIS_INTEGER_HPP
#define BOOST_HISTOGRAM_AXIS_INTEGER_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/limits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace axis {
/** Axis for an interval of integer values with unit steps.
Binning is a O(1) operation. This axis bins even faster than a regular axis.
The options `growth` and `circular` are mutually exclusive. If the axis uses
integers and either `growth` or `circular` are set, the axis cannot have
the options `underflow` or `overflow` set.
@tparam Value input value type. Must be integer or floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
*/
template <class Value, class MetaData, class Options>
class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
index_type, real_index_type>;
public:
constexpr integer() = default;
/** Construct over semi-open integer interval [start, stop).
@param start first integer of covered range.
@param stop one past last integer of covered range.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The constructor throws `std::invalid_argument` if start is not less than stop.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
integer(value_type start, value_type stop, metadata_type meta = {},
options_type options = {})
: metadata_base(std::move(meta))
, size_(static_cast<index_type>(stop - start))
, min_(start) {
static_assert(
std::is_integral<value_type>::value || std::is_floating_point<value_type>::value,
"integer axis requires floating point or integral type");
static_assert(!(options.test(option::circular) && options.test(option::growth)),
"circular and growth options are mutually exclusive");
static_assert(
std::is_floating_point<value_type>::value ||
!((options.test(option::growth) || options.test(option::circular)) &&
(options.test(option::overflow) || options.test(option::underflow))),
"circular or growing integer axis with integral type "
"cannot have entries in underflow or overflow bins");
if (!(stop >= start)) // double negation so it works with NaN
BOOST_THROW_EXCEPTION(std::invalid_argument("stop >= start required"));
}
/// Constructor used by algorithm::reduce to shrink and rebin.
integer(const integer& src, index_type begin, index_type end, unsigned merge)
: integer(src.value(begin), src.value(end), src.metadata()) {
if (merge > 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for integer axis"));
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
}
/// Return index for value argument.
index_type index(value_type x) const noexcept {
return index_impl(options_type::test(axis::option::circular),
std::is_floating_point<value_type>{},
static_cast<double>(x - min_));
}
/// Returns index and shift (if axis has grown) for the passed argument.
auto update(value_type x) noexcept {
auto impl = [this](long x) -> std::pair<index_type, index_type> {
const auto i = x - min_;
if (i >= 0) {
const auto k = static_cast<axis::index_type>(i);
if (k < size()) return {k, 0};
const auto n = k - size() + 1;
size_ += n;
return {k, -n};
}
const auto k = static_cast<axis::index_type>(
detail::static_if<std::is_floating_point<value_type>>(
[](auto x) { return std::floor(x); }, [](auto x) { return x; }, i));
min_ += k;
size_ -= k;
return {0, -k};
};
return detail::static_if<std::is_floating_point<value_type>>(
[this, impl](auto x) -> std::pair<index_type, index_type> {
if (std::isfinite(x)) return impl(static_cast<long>(std::floor(x)));
return {x < 0 ? -1 : this->size(), 0};
},
impl, x);
}
/// Return value for index argument.
value_type value(local_index_type i) const noexcept {
if (!options_type::test(option::circular) &&
std::is_floating_point<value_type>::value) {
if (i < 0) return detail::lowest<value_type>();
if (i > size()) return detail::highest<value_type>();
}
return min_ + i;
}
/// Return bin for index argument.
decltype(auto) bin(index_type idx) const noexcept {
return detail::static_if<std::is_floating_point<value_type>>(
[this](auto idx) { return interval_view<integer>(*this, idx); },
[this](auto idx) { return this->value(idx); }, idx);
}
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return size_; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
// If axis has underflow and overflow, it is inclusive.
// If axis is growing or circular:
// - it is inclusive if value_type is an integer.
// - it is not inclusive if value_type is floating point, because of nan and inf.
constexpr bool full_flow = options_type().test(option::underflow | option::overflow);
return full_flow || (std::is_integral<value_type>::value &&
(options() & (option::growth | option::circular)));
}
template <class V, class M, class O>
bool operator==(const integer<V, M, O>& o) const noexcept {
return size() == o.size() && min_ == o.min_ &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O>
bool operator!=(const integer<V, M, O>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("size", size_);
ar& make_nvp("meta", this->metadata());
ar& make_nvp("min", min_);
}
private:
// axis not circular
template <class B>
index_type index_impl(std::false_type, B, double z) const noexcept {
if (z < size()) return z >= 0 ? static_cast<index_type>(z) : -1;
return size();
}
// value_type is integer, axis circular
index_type index_impl(std::true_type, std::false_type, double z) const noexcept {
return static_cast<index_type>(z - std::floor(z / size()) * size());
}
// value_type is floating point, must handle +/-infinite or nan, axis circular
index_type index_impl(std::true_type, std::true_type, double z) const noexcept {
if (std::isfinite(z)) return index_impl(std::true_type{}, std::false_type{}, z);
return z < size() ? -1 : size();
}
index_type size_{0};
value_type min_{0};
template <class V, class M, class O>
friend class integer;
};
#if __cpp_deduction_guides >= 201606
template <class T>
integer(T, T) -> integer<detail::convert_integer<T, index_type>, null_type>;
template <class T, class M>
integer(T, T, M)
-> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class T, class M, unsigned B>
integer(T, T, M, const option::bitset<B>&)
-> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>,
option::bitset<B>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2015-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_AXIS_INTERVAL_VIEW_HPP
#define BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP
#include <boost/histogram/fwd.hpp>
namespace boost {
namespace histogram {
namespace axis {
/**
Lightweight bin view.
Represents the current bin interval.
*/
template <class Axis>
class interval_view {
public:
interval_view(const Axis& axis, index_type idx) : axis_(axis), idx_(idx) {}
// avoid viewing a temporary that goes out of scope
interval_view(Axis&& axis, index_type idx) = delete;
/// Return lower edge of bin.
decltype(auto) lower() const noexcept { return axis_.value(idx_); }
/// Return upper edge of bin.
decltype(auto) upper() const noexcept { return axis_.value(idx_ + 1); }
/// Return center of bin.
decltype(auto) center() const noexcept { return axis_.value(idx_ + 0.5); }
/// Return width of bin.
decltype(auto) width() const noexcept { return upper() - lower(); }
template <class BinType>
bool operator==(const BinType& rhs) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <class BinType>
bool operator!=(const BinType& rhs) const noexcept {
return !operator==(rhs);
}
private:
const Axis& axis_;
const index_type idx_;
};
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2015-2017 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_AXIS_ITERATOR_HPP
#define BOOST_HISTOGRAM_AXIS_ITERATOR_HPP
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <iterator>
namespace boost {
namespace histogram {
namespace axis {
template <class Axis>
class iterator : public detail::iterator_adaptor<iterator<Axis>, index_type,
decltype(std::declval<Axis>().bin(0))> {
public:
using reference = typename iterator::iterator_adaptor_::reference;
/// Make iterator from axis and index.
iterator(const Axis& axis, index_type idx)
: iterator::iterator_adaptor_(idx), axis_(axis) {}
/// Return current bin object.
reference operator*() const { return axis_.bin(this->base()); }
private:
const Axis& axis_;
};
/// Uses CRTP to inject iterator logic into Derived.
template <class Derived>
class iterator_mixin {
public:
using const_iterator = iterator<Derived>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/// Bin iterator to beginning of the axis (read-only).
const_iterator begin() const noexcept {
return const_iterator(*static_cast<const Derived*>(this), 0);
}
/// Bin iterator to the end of the axis (read-only).
const_iterator end() const noexcept {
return const_iterator(*static_cast<const Derived*>(this),
static_cast<const Derived*>(this)->size());
}
/// Reverse bin iterator to the last entry of the axis (read-only).
const_reverse_iterator rbegin() const noexcept {
return std::make_reverse_iterator(end());
}
/// Reverse bin iterator to the end (read-only).
const_reverse_iterator rend() const noexcept {
return std::make_reverse_iterator(begin());
}
};
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+102
View File
@@ -0,0 +1,102 @@
// 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_AXIS_METADATA_BASE_HPP
#define BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <string>
#include <type_traits>
namespace boost {
namespace histogram {
namespace axis {
/** Meta data holder with space optimization for empty meta data types.
Allows write-access to metadata even if const.
@tparam Metadata Wrapped meta data type.
*/
template <class Metadata, bool Detail>
class metadata_base {
protected:
using metadata_type = Metadata;
static_assert(std::is_default_constructible<metadata_type>::value,
"metadata must be default constructible");
static_assert(std::is_copy_constructible<metadata_type>::value,
"metadata must be copy constructible");
static_assert(std::is_copy_assignable<metadata_type>::value,
"metadata must be copy assignable");
// std::string explicitly guarantees nothrow only in C++17
static_assert(std::is_same<metadata_type, std::string>::value ||
std::is_nothrow_move_constructible<metadata_type>::value,
"metadata must be nothrow move constructible");
metadata_base() = default;
metadata_base(const metadata_base&) = default;
metadata_base& operator=(const metadata_base&) = default;
// make noexcept because std::string is nothrow move constructible only in C++17
metadata_base(metadata_base&& o) noexcept : data_(std::move(o.data_)) {}
metadata_base(metadata_type&& o) noexcept : data_(std::move(o)) {}
// make noexcept because std::string is nothrow move constructible only in C++17
metadata_base& operator=(metadata_base&& o) noexcept {
data_ = std::move(o.data_);
return *this;
}
public:
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return data_; }
/// Returns reference to mutable metadata from const axis.
metadata_type& metadata() const noexcept { return data_; }
private:
mutable metadata_type data_;
};
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// specialization for empty metadata
template <class Metadata>
class metadata_base<Metadata, true> {
protected:
using metadata_type = Metadata;
metadata_base() = default;
metadata_base(metadata_type&&) {}
metadata_base& operator=(metadata_type&&) { return *this; }
public:
metadata_type& metadata() noexcept {
return static_cast<const metadata_base&>(*this).metadata();
}
metadata_type& metadata() const noexcept {
static metadata_type data;
return data;
}
};
template <class Metadata, class Detail = detail::replace_default<Metadata, std::string>>
using metadata_base_t =
metadata_base<Detail, (std::is_empty<Detail>::value && std::is_final<Detail>::value)>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2015-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_AXIS_OPTION_HPP
#define BOOST_HISTOGRAM_AXIS_OPTION_HPP
#include <type_traits>
/**
\file option.hpp Options for builtin axis types.
Options `circular` and `growth` are mutually exclusive.
Options `circular` and `underflow` are mutually exclusive.
*/
namespace boost {
namespace histogram {
namespace axis {
namespace option {
/// Holder of axis options.
template <unsigned Bits>
struct bitset : std::integral_constant<unsigned, Bits> {
/// Returns true if all option flags in the argument are set and false otherwise.
template <unsigned B>
static constexpr auto test(bitset<B>) {
// B + 0 needed to avoid false positive -Wtautological-compare in gcc-6
return std::integral_constant<bool, static_cast<bool>((Bits & B) == (B + 0))>{};
}
};
/// Set union of the axis option arguments.
template <unsigned B1, unsigned B2>
constexpr auto operator|(bitset<B1>, bitset<B2>) {
return bitset<(B1 | B2)>{};
}
/// Set intersection of the option arguments.
template <unsigned B1, unsigned B2>
constexpr auto operator&(bitset<B1>, bitset<B2>) {
return bitset<(B1 & B2)>{};
}
/// Set difference of the option arguments.
template <unsigned B1, unsigned B2>
constexpr auto operator-(bitset<B1>, bitset<B2>) {
return bitset<(B1 & ~B2)>{};
}
/**
Single option flag.
@tparam Pos position of the bit in the set.
*/
template <unsigned Pos>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using bit = bitset<(1 << Pos)>;
#else
struct bit;
#endif
/// All options off.
using none_t = bitset<0>;
/// Axis has an underflow bin. Mutually exclusive with `circular`.
using underflow_t = bit<0>;
/// Axis has overflow bin.
using overflow_t = bit<1>;
/// Axis is circular. Mutually exclusive with `growth` and `underflow`.
using circular_t = bit<2>;
/// Axis can grow. Mutually exclusive with `circular`.
using growth_t = bit<3>;
constexpr none_t none{}; ///< Instance of `none_t`.
constexpr underflow_t underflow{}; ///< Instance of `underflow_t`.
constexpr overflow_t overflow{}; ///< Instance of `overflow_t`.
constexpr circular_t circular{}; ///< Instance of `circular_t`.
constexpr growth_t growth{}; ///< Instance of `growth_t`.
} // namespace option
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+220
View File
@@ -0,0 +1,220 @@
// Copyright 2015-2017 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)
//
// String representations here evaluate correctly in Python.
#ifndef BOOST_HISTOGRAM_AXIS_OSTREAM_HPP
#define BOOST_HISTOGRAM_AXIS_OSTREAM_HPP
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <iomanip>
#include <iosfwd>
#include <sstream>
#include <stdexcept>
#include <type_traits>
/**
\file boost/histogram/axis/ostream.hpp
Simple streaming operators for the builtin axis 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 OStream, class T>
auto ostream_any_impl(OStream& os, const T& t, priority<1>) -> decltype(os << t) {
return os << t;
}
template <class OStream, class T>
OStream& ostream_any_impl(OStream& os, const T&, priority<0>) {
return os << type_name<T>();
}
template <class OStream, class T>
OStream& ostream_any(OStream& os, const T& t) {
return ostream_any_impl(os, t, priority<1>{});
}
template <class OStream, class... Ts>
OStream& ostream_any_quoted(OStream& os, const std::basic_string<Ts...>& s) {
return os << std::quoted(s);
}
template <class OStream, class T>
OStream& ostream_any_quoted(OStream& os, const T& t) {
return ostream_any(os, t);
}
template <class... Ts, class T>
std::basic_ostream<Ts...>& ostream_metadata(std::basic_ostream<Ts...>& os, const T& t,
const char* prefix = ", ") {
std::streamsize count = 0;
{
auto g = make_count_guard(os, count);
ostream_any(os, t);
}
if (!count) return os;
os << prefix << "metadata=";
return ostream_any_quoted(os, t);
}
template <class OStream>
void ostream_options(OStream& os, const unsigned bits) {
bool first = true;
os << ", options=";
#define BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(x) \
if (bits & axis::option::x) { \
if (first) \
first = false; \
else { \
os << " | "; \
} \
os << #x; \
}
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(underflow);
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(overflow);
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(circular);
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(growth);
#undef BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM
if (first) os << "none";
}
} // namespace detail
namespace axis {
template <class T>
class polymorphic_bin;
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const null_type&) {
return os; // do nothing
}
template <class... Ts, class U>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const interval_view<U>& i) {
return os << "[" << i.lower() << ", " << i.upper() << ")";
}
template <class... Ts, class U>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const polymorphic_bin<U>& i) {
if (i.is_discrete()) return os << static_cast<double>(i);
return os << "[" << i.lower() << ", " << i.upper() << ")";
}
namespace transform {
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const id&) {
return os;
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const log&) {
return os << "transform::log{}";
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const sqrt&) {
return os << "transform::sqrt{}";
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const pow& p) {
return os << "transform::pow{" << p.power << "}";
}
} // namespace transform
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const regular<Us...>& a) {
os << "regular(";
const auto pos = os.tellp();
os << a.transform();
if (os.tellp() > pos) os << ", ";
os << a.size() << ", " << a.value(0) << ", " << a.value(a.size());
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const integer<Us...>& a) {
os << "integer(" << a.value(0) << ", " << a.value(a.size());
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const variable<Us...>& a) {
os << "variable(" << a.value(0);
for (index_type i = 1, n = a.size(); i <= n; ++i) { os << ", " << a.value(i); }
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const category<Us...>& a) {
os << "category(";
for (index_type i = 0, n = a.size(); i < n; ++i) {
detail::ostream_any_quoted(os, a.value(i));
os << (i == (a.size() - 1) ? "" : ", ");
}
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class M>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const boolean<M>& a) {
os << "boolean(";
detail::ostream_metadata(os, a.metadata(), "");
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const variant<Us...>& v) {
visit([&os](const auto& x) { detail::ostream_any(os, x); }, v);
return os;
}
} // namespace axis
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
#endif
+86
View File
@@ -0,0 +1,86 @@
// Copyright 2018 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_AXIS_POLYMORPHIC_BIN_HPP
#define BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_HPP
namespace boost {
namespace histogram {
namespace axis {
/**
Holds the bin data of an axis::variant.
The interface is a superset of the axis::interval_view
class. In addition, the object is implicitly convertible to the value type,
returning the equivalent of a call to lower(). For discrete axes, lower() ==
upper(), and width() returns zero.
This is not a view like axis::interval_view for two reasons.
- Sequential calls to lower() and upper() would have to each loop through
the variant types. This is likely to be slower than filling all the data in
one loop.
- polymorphic_bin may be created from a temporary instance of axis::variant,
like in the call histogram::axis(0). Storing a reference to the axis would
result in a dangling reference. Rather than specialing the code to handle
this, it seems easier to just use a value instead of a view.
*/
template <class RealType>
class polymorphic_bin {
using value_type = RealType;
public:
polymorphic_bin(value_type lower, value_type upper)
: lower_or_value_(lower), upper_(upper) {}
/// Implicitly convert to bin value (for axis with discrete values).
operator const value_type&() const noexcept { return lower_or_value_; }
/// Return lower edge of bin.
value_type lower() const noexcept { return lower_or_value_; }
/// Return upper edge of bin.
value_type upper() const noexcept { return upper_; }
/// Return center of bin.
value_type center() const noexcept { return 0.5 * (lower() + upper()); }
/// Return width of bin.
value_type width() const noexcept { return upper() - lower(); }
template <class BinType>
bool operator==(const BinType& rhs) const noexcept {
return equal_impl(rhs, 0);
}
template <class BinType>
bool operator!=(const BinType& rhs) const noexcept {
return !operator==(rhs);
}
/// Return true if bin is discrete.
bool is_discrete() const noexcept { return lower_or_value_ == upper_; }
private:
bool equal_impl(const polymorphic_bin& rhs, int) const noexcept {
return lower_or_value_ == rhs.lower_or_value_ && upper_ == rhs.upper_;
}
template <class BinType>
auto equal_impl(const BinType& rhs, decltype(rhs.lower(), 0)) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <class BinType>
bool equal_impl(const BinType& rhs, float) const noexcept {
return is_discrete() && static_cast<value_type>(*this) == rhs;
}
const value_type lower_or_value_, upper_;
};
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+457
View File
@@ -0,0 +1,457 @@
// Copyright 2015-2018 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_AXIS_REGULAR_HPP
#define BOOST_HISTOGRAM_AXIS_REGULAR_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
using get_scale_type_helper = typename T::value_type;
template <class T>
using get_scale_type = mp11::mp_eval_or<T, detail::get_scale_type_helper, T>;
struct one_unit {};
template <class T>
T operator*(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <class T>
T operator/(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <class T>
using get_unit_type_helper = typename T::unit_type;
template <class T>
using get_unit_type = mp11::mp_eval_or<one_unit, detail::get_unit_type_helper, T>;
template <class T, class R = get_scale_type<T>>
R get_scale(const T& t) {
return t / get_unit_type<T>();
}
} // namespace detail
namespace axis {
namespace transform {
/// Identity transform for equidistant bins.
struct id {
/// Pass-through.
template <class T>
static T forward(T&& x) noexcept {
return std::forward<T>(x);
}
/// Pass-through.
template <class T>
static T inverse(T&& x) noexcept {
return std::forward<T>(x);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Log transform for equidistant bins in log-space.
struct log {
/// Returns log(x) of external value x.
template <class T>
static T forward(T x) {
return std::log(x);
}
/// Returns exp(x) for internal value x.
template <class T>
static T inverse(T x) {
return std::exp(x);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Sqrt transform for equidistant bins in sqrt-space.
struct sqrt {
/// Returns sqrt(x) of external value x.
template <class T>
static T forward(T x) {
return std::sqrt(x);
}
/// Returns x^2 of internal value x.
template <class T>
static T inverse(T x) {
return x * x;
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Pow transform for equidistant bins in pow-space.
struct pow {
double power = 1; /**< power index */
/// Make transform with index p.
explicit pow(double p) : power(p) {}
pow() = default;
/// Returns pow(x, power) of external value x.
template <class T>
auto forward(T x) const {
return std::pow(x, power);
}
/// Returns pow(x, 1/power) of external value x.
template <class T>
auto inverse(T x) const {
return std::pow(x, 1.0 / power);
}
bool operator==(const pow& o) const noexcept { return power == o.power; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("power", power);
}
};
} // namespace transform
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// Type envelope to mark value as step size
template <class T>
struct step_type {
T value;
};
#endif
/**
Helper function to mark argument as step size.
*/
template <class T>
step_type<T> step(T t) {
return step_type<T>{t};
}
/** Axis for equidistant intervals on the real line.
The most common binning strategy. Very fast. Binning is a O(1) operation.
If the axis has an overflow bin (the default), a value on the upper edge of the last
bin is put in the overflow bin. The axis range represents a semi-open interval.
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
still counted towards the last bin. The axis range represents a closed interval.
The options `growth` and `circular` are mutually exclusive.
@tparam Value input value type, must be floating point.
@tparam Transform builtin or user-defined transform type.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
*/
template <class Value, class Transform, class MetaData, class Options>
class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
protected detail::replace_default<Transform, transform::id>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using transform_type = detail::replace_default<Transform, transform::id>;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using unit_type = detail::get_unit_type<value_type>;
using internal_value_type = detail::get_scale_type<value_type>;
public:
constexpr regular() = default;
/** Construct n bins over real transformed range [start, stop).
@param trans transform instance to use.
@param n number of bins.
@param start low edge of first bin.
@param stop high edge of last bin.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The constructor throws `std::invalid_argument` if n is zero, or if start and stop
produce an invalid range after transformation.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
regular(transform_type trans, unsigned n, value_type start, value_type stop,
metadata_type meta = {}, options_type options = {})
: transform_type(std::move(trans))
, metadata_base(std::move(meta))
, size_(static_cast<index_type>(n))
, min_(this->forward(detail::get_scale(start)))
, delta_(this->forward(detail::get_scale(stop)) - min_) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(std::is_nothrow_move_constructible<transform_type>::value,
"transform must be no-throw move constructible");
static_assert(std::is_nothrow_move_assignable<transform_type>::value,
"transform must be no-throw move assignable");
static_assert(std::is_floating_point<internal_value_type>::value,
"regular axis requires floating point type");
static_assert(!(options.test(option::circular) && options.test(option::growth)),
"circular and growth options are mutually exclusive");
if (size() <= 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
if (!std::isfinite(min_) || !std::isfinite(delta_))
BOOST_THROW_EXCEPTION(
std::invalid_argument("forward transform of start or stop invalid"));
if (delta_ == 0)
BOOST_THROW_EXCEPTION(std::invalid_argument("range of axis is zero"));
}
/** Construct n bins over real range [start, stop).
@param n number of bins.
@param start low edge of first bin.
@param stop high edge of last bin.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
*/
explicit regular(unsigned n, value_type start, value_type stop, metadata_type meta = {},
options_type options = {})
: regular({}, n, start, stop, std::move(meta), options) {}
/** Construct bins with the given step size over real transformed range
[start, stop).
@param trans transform instance to use.
@param step width of a single bin.
@param start low edge of first bin.
@param stop upper limit of high edge of last bin (see below).
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The axis computes the number of bins as n = abs(stop - start) / step,
rounded down. This means that stop is an upper limit to the actual value
(start + n * step).
*/
template <class T>
explicit regular(transform_type trans, step_type<T> step, value_type start,
value_type stop, metadata_type meta = {}, options_type options = {})
: regular(trans, static_cast<index_type>(std::abs(stop - start) / step.value),
start,
start + static_cast<index_type>(std::abs(stop - start) / step.value) *
step.value,
std::move(meta), options) {}
/** Construct bins with the given step size over real range [start, stop).
@param step width of a single bin.
@param start low edge of first bin.
@param stop upper limit of high edge of last bin (see below).
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The axis computes the number of bins as n = abs(stop - start) / step,
rounded down. This means that stop is an upper limit to the actual value
(start + n * step).
*/
template <class T>
explicit regular(step_type<T> step, value_type start, value_type stop,
metadata_type meta = {}, options_type options = {})
: regular({}, step, start, stop, std::move(meta), options) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
regular(const regular& src, index_type begin, index_type end, unsigned merge)
: regular(src.transform(), (end - begin) / merge, src.value(begin), src.value(end),
src.metadata()) {
assert((end - begin) % merge == 0);
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
}
/// Return instance of the transform type.
const transform_type& transform() const noexcept { return *this; }
/// Return index for value argument.
index_type index(value_type x) const noexcept {
// Runs in hot loop, please measure impact of changes
auto z = (this->forward(x / unit_type{}) - min_) / delta_;
if (options_type::test(option::circular)) {
if (std::isfinite(z)) {
z -= std::floor(z);
return static_cast<index_type>(z * size());
}
} else {
if (z < 1) {
if (z >= 0)
return static_cast<index_type>(z * size());
else
return -1;
}
// upper edge of last bin is inclusive if overflow bin is not present
if (!options_type::test(option::overflow) && z == 1) return size() - 1;
}
return size(); // also returned if x is NaN
}
/// Returns index and shift (if axis has grown) for the passed argument.
std::pair<index_type, index_type> update(value_type x) noexcept {
assert(options_type::test(option::growth));
const auto z = (this->forward(x / unit_type{}) - min_) / delta_;
if (z < 1) { // don't use i here!
if (z >= 0) {
const auto i = static_cast<axis::index_type>(z * size());
return {i, 0};
}
if (z != -std::numeric_limits<internal_value_type>::infinity()) {
const auto stop = min_ + delta_;
const auto i = static_cast<axis::index_type>(std::floor(z * size()));
min_ += i * (delta_ / size());
delta_ = stop - min_;
size_ -= i;
return {0, -i};
}
// z is -infinity
return {-1, 0};
}
// z either beyond range, infinite, or NaN
if (z < std::numeric_limits<internal_value_type>::infinity()) {
const auto i = static_cast<axis::index_type>(z * size());
const auto n = i - size() + 1;
delta_ /= size();
delta_ *= size() + n;
size_ += n;
return {i, -n};
}
// z either infinite or NaN
return {size(), 0};
}
/// Return value for fractional index argument.
value_type value(real_index_type i) const noexcept {
auto z = i / size();
if (!options_type::test(option::circular) && z < 0.0)
z = -std::numeric_limits<internal_value_type>::infinity() * delta_;
else if (options_type::test(option::circular) || z <= 1.0)
z = (1.0 - z) * min_ + z * (min_ + delta_);
else {
z = std::numeric_limits<internal_value_type>::infinity() * delta_;
}
return static_cast<value_type>(this->inverse(z) * unit_type());
}
/// Return bin for index argument.
decltype(auto) bin(index_type idx) const noexcept {
return interval_view<regular>(*this, idx);
}
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return size_; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
template <class V, class T, class M, class O>
bool operator==(const regular<V, T, M, O>& o) const noexcept {
return detail::relaxed_equal{}(transform(), o.transform()) && size() == o.size() &&
min_ == o.min_ && delta_ == o.delta_ &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class T, class M, class O>
bool operator!=(const regular<V, T, M, O>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("transform", static_cast<transform_type&>(*this));
ar& make_nvp("size", size_);
ar& make_nvp("meta", this->metadata());
ar& make_nvp("min", min_);
ar& make_nvp("delta", delta_);
}
private:
index_type size_{0};
internal_value_type min_{0}, delta_{1};
template <class V, class T, class M, class O>
friend class regular;
};
#if __cpp_deduction_guides >= 201606
template <class T>
regular(unsigned, T, T)
-> regular<detail::convert_integer<T, double>, transform::id, null_type>;
template <class T, class M>
regular(unsigned, T, T, M) -> regular<detail::convert_integer<T, double>, transform::id,
detail::replace_cstring<std::decay_t<M>>>;
template <class T, class M, unsigned B>
regular(unsigned, T, T, M, const option::bitset<B>&)
-> regular<detail::convert_integer<T, double>, transform::id,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
template <class Tr, class T, class = detail::requires_transform<Tr, T>>
regular(Tr, unsigned, T, T) -> regular<detail::convert_integer<T, double>, Tr, null_type>;
template <class Tr, class T, class M>
regular(Tr, unsigned, T, T, M) -> regular<detail::convert_integer<T, double>, Tr,
detail::replace_cstring<std::decay_t<M>>>;
template <class Tr, class T, class M, unsigned B>
regular(Tr, unsigned, T, T, M, const option::bitset<B>&)
-> regular<detail::convert_integer<T, double>, Tr,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
#endif
/// Regular axis with circular option already set.
template <class Value = double, class MetaData = use_default, class Options = use_default>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using circular = regular<Value, transform::id, MetaData,
decltype(detail::replace_default<Options, option::overflow_t>{} |
option::circular)>;
#else
class circular;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+490
View File
@@ -0,0 +1,490 @@
// Copyright 2018 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_AXIS_TRAITS_HPP
#define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/args_type.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/try_cast.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
#include <string>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class Axis>
struct value_type_deducer {
using type =
std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
};
template <class Axis>
auto traits_options(priority<2>) -> axis::option::bitset<Axis::options()>;
template <class Axis>
auto traits_options(priority<1>) -> decltype(&Axis::update, axis::option::growth_t{});
template <class Axis>
auto traits_options(priority<0>) -> axis::option::none_t;
template <class Axis>
auto traits_is_inclusive(priority<1>) -> std::integral_constant<bool, Axis::inclusive()>;
template <class Axis>
auto traits_is_inclusive(priority<0>)
-> decltype(traits_options<Axis>(priority<2>{})
.test(axis::option::underflow | axis::option::overflow));
template <class Axis>
auto traits_is_ordered(priority<1>) -> std::integral_constant<bool, Axis::ordered()>;
template <class Axis, class ValueType = typename value_type_deducer<Axis>::type>
auto traits_is_ordered(priority<0>) -> typename std::is_arithmetic<ValueType>::type;
template <class I, class D, class A,
class J = std::decay_t<arg_type<decltype(&A::value)>>>
decltype(auto) value_method_switch(I&& i, D&& d, const A& a, priority<1>) {
return static_if<std::is_same<J, axis::index_type>>(std::forward<I>(i),
std::forward<D>(d), a);
}
template <class I, class D, class A>
double value_method_switch(I&&, D&&, const A&, priority<0>) {
// comma trick to make all compilers happy; some would complain about
// unreachable code after the throw, others about a missing return
return BOOST_THROW_EXCEPTION(
std::runtime_error(type_name<A>() + " has no value method")),
double{};
}
struct variant_access {
template <class T, class Variant>
static auto get_if(Variant* v) noexcept {
using T0 = mp11::mp_first<std::decay_t<Variant>>;
return static_if<std::is_pointer<T0>>(
[](auto* vptr) {
using TP = mp11::mp_if<std::is_const<std::remove_pointer_t<T0>>, const T*, T*>;
auto ptp = variant2::get_if<TP>(vptr);
return ptp ? *ptp : nullptr;
},
[](auto* vptr) { return variant2::get_if<T>(vptr); }, &(v->impl));
}
template <class T0, class Visitor, class Variant>
static decltype(auto) visit_impl(mp11::mp_identity<T0>, Visitor&& vis, Variant&& v) {
return variant2::visit(std::forward<Visitor>(vis), v.impl);
}
template <class T0, class Visitor, class Variant>
static decltype(auto) visit_impl(mp11::mp_identity<T0*>, Visitor&& vis, Variant&& v) {
return variant2::visit(
[&vis](auto&& x) -> decltype(auto) { return std::forward<Visitor>(vis)(*x); },
v.impl);
}
template <class Visitor, class Variant>
static decltype(auto) visit(Visitor&& vis, Variant&& v) {
using T0 = mp11::mp_first<std::decay_t<Variant>>;
return visit_impl(mp11::mp_identity<T0>{}, std::forward<Visitor>(vis),
std::forward<Variant>(v));
}
};
template <class A>
decltype(auto) metadata_impl(A&& a, decltype(a.metadata(), 0)) {
return std::forward<A>(a).metadata();
}
template <class A>
axis::null_type& metadata_impl(A&&, float) {
static axis::null_type null_value;
return null_value;
}
} // namespace detail
namespace axis {
namespace traits {
/** Value type for axis type.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns the value type.
The value type is deduced from the argument of the `Axis::index` method. Const
references are decayed to the their value types, for example, the type deduced for
`Axis::index(const int&)` is `int`.
The deduction always succeeds if the axis type models the Axis concept correctly. Errors
come from violations of the concept, in particular, an index method that is templated or
overloaded is not allowed.
@tparam Axis axis type.
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using value_type = typename detail::value_type_deducer<Axis>::type;
#else
struct value_type;
#endif
/** Whether axis is continuous or discrete.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns a compile-time boolean.
If the boolean is true, the axis is continuous (covers a continuous range of values).
Otherwise it is discrete (covers discrete values).
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_continuous = typename std::is_floating_point<traits::value_type<Axis>>::type;
#else
struct is_continuous;
#endif
/** Meta-function to detect whether an axis is reducible.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and represents compile-time boolean which is true or false, depending on
whether the axis can be reduced with boost::histogram::algorithm::reduce().
An axis can be made reducible by adding a special constructor, see Axis concept for
details.
@tparam Axis axis type.
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
axis::index_type, unsigned>;
#else
struct is_reducible;
#endif
/** Get axis options for axis type.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns the boost::histogram::axis::option::bitset.
If Axis::options() is valid and constexpr, get_options is the corresponding
option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
axis has a method `update`, else boost::histogram::axis::option::none_t.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using get_options = decltype(detail::traits_options<Axis>(detail::priority<2>{}));
#else
struct get_options;
#endif
/** Meta-function to detect whether an axis is inclusive.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and represents compile-time boolean which is true or false, depending on
whether the axis is inclusive or not.
An inclusive axis has a bin for every possible input value. In other words, all
possible input values always end up in a valid cell and there is no need to keep track
of input tuples that need to be discarded. A histogram which consists entirely of
inclusive axes can be filled more efficiently, which can be a factor 2 faster.
An axis with underflow and overflow bins is always inclusive, but an axis may be
inclusive under other conditions. The meta-function checks for the method `constexpr
static bool inclusive()`, and uses the result. If this method is not present, it uses
get_options<Axis> and checks whether the underflow and overflow bits are present.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_inclusive = decltype(detail::traits_is_inclusive<Axis>(detail::priority<1>{}));
#else
struct is_inclusive;
#endif
/** Meta-function to detect whether an axis is ordered.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns a compile-time boolean. If the boolean is true, the axis is
ordered.
The meta-function checks for the method `constexpr static bool ordered()`, and uses the
result. If this method is not present, it returns true if the value type of the Axis is
arithmetic and false otherwise.
An ordered axis has a value type that is ordered, which means that indices i <
j < k implies either value(i) < value(j) < value(k) or value(i) > value(j) > value(k)
for all i,j,k. For example, the integer axis is ordered, but the category axis is not.
Axis which are not ordered must not have underflow bins, because they only have an
"other" category, which is identified with the overflow bin if it is available.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_ordered = decltype(detail::traits_is_ordered<Axis>(detail::priority<1>{}));
#else
struct is_ordered;
#endif
/** Returns axis options as unsigned integer.
See get_options for details.
@param axis any axis instance
*/
template <class Axis>
constexpr unsigned options(const Axis& axis) noexcept {
(void)axis;
return get_options<Axis>::value;
}
// specialization for variant
template <class... Ts>
unsigned options(const variant<Ts...>& axis) noexcept {
return axis.options();
}
/** Returns true if axis is inclusive or false.
See is_inclusive for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool inclusive(const Axis& axis) noexcept {
(void)axis;
return is_inclusive<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool inclusive(const variant<Ts...>& axis) noexcept {
return axis.inclusive();
}
/** Returns true if axis is ordered or false.
See is_ordered for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool ordered(const Axis& axis) noexcept {
(void)axis;
return is_ordered<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool ordered(const variant<Ts...>& axis) noexcept {
return axis.ordered();
}
/** Returns true if axis is continuous or false.
See is_continuous for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool continuous(const Axis& axis) noexcept {
(void)axis;
return is_continuous<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool continuous(const variant<Ts...>& axis) noexcept {
return axis.continuous();
}
/** Returns axis size plus any extra bins for under- and overflow.
@param axis any axis instance
*/
template <class Axis>
index_type extent(const Axis& axis) noexcept {
const auto opt = options(axis);
return axis.size() + (opt & option::underflow ? 1 : 0) +
(opt & option::overflow ? 1 : 0);
}
/** Returns reference to metadata of an axis.
If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
the result. Otherwise, return a reference to a static instance of
boost::histogram::axis::null_type.
@param axis any axis instance
*/
template <class Axis>
decltype(auto) metadata(Axis&& axis) noexcept {
return detail::metadata_impl(std::forward<Axis>(axis), 0);
}
/** Returns axis value for index.
If the axis has no `value` method, throw std::runtime_error. If the method exists and
accepts a floating point index, pass the index and return the result. If the method
exists but accepts only integer indices, cast the floating point index to int, pass this
index and return the result.
@param axis any axis instance
@param index floating point axis index
*/
template <class Axis>
decltype(auto) value(const Axis& axis, real_index_type index) {
return detail::value_method_switch(
[index](const auto& a) { return a.value(static_cast<index_type>(index)); },
[index](const auto& a) { return a.value(index); }, axis, detail::priority<1>{});
}
/** Returns axis value for index if it is convertible to target type or throws.
Like boost::histogram::axis::traits::value, but converts the result into the requested
return type. If the conversion is not possible, throws std::runtime_error.
@tparam Result requested return type
@tparam Axis axis type
@param axis any axis instance
@param index floating point axis index
*/
template <class Result, class Axis>
Result value_as(const Axis& axis, real_index_type index) {
return detail::try_cast<Result, std::runtime_error>(
axis::traits::value(axis, index)); // avoid conversion warning
}
/** Returns axis index for value.
Throws std::invalid_argument if the value argument is not implicitly convertible.
@param axis any axis instance
@param value argument to be passed to `index` method
*/
template <class Axis, class U>
axis::index_type index(const Axis& axis, const U& value) noexcept(
std::is_convertible<U, value_type<Axis>>::value) {
return axis.index(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
}
// specialization for variant
template <class... Ts, class U>
axis::index_type index(const variant<Ts...>& axis, const U& value) {
return axis.index(value);
}
/** Return axis rank (how many arguments it processes).
@param axis any axis instance
*/
// gcc workaround: must use unsigned int not unsigned as return type
template <class Axis>
constexpr unsigned int rank(const Axis& axis) {
(void)axis;
using T = value_type<Axis>;
// cannot use mp_eval_or since T could be a fixed-sized sequence
return mp11::mp_eval_if_not<detail::is_tuple<T>, mp11::mp_size_t<1>, mp11::mp_size,
T>::value;
}
// specialization for variant
// gcc workaround: must use unsigned int not unsigned as return type
template <class... Ts>
unsigned int rank(const axis::variant<Ts...>& axis) {
return detail::variant_access::visit(
[](const auto& a) { return axis::traits::rank(a); }, axis);
}
/** Returns pair of axis index and shift for the value argument.
Throws `std::invalid_argument` if the value argument is not implicitly convertible to
the argument expected by the `index` method. If the result of
boost::histogram::axis::traits::get_options<decltype(axis)> has the growth flag set,
call `update` method with the argument and return the result. Otherwise, call `index`
and return the pair of the result and a zero shift.
@param axis any axis instance
@param value argument to be passed to `update` or `index` method
*/
template <class Axis, class U>
std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
std::is_convertible<U, value_type<Axis>>::value) {
return detail::static_if_c<get_options<Axis>::test(option::growth)>(
[&value](auto& a) {
return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
},
[&value](auto& a) -> std::pair<index_type, index_type> {
return {axis::traits::index(a, value), 0};
},
axis);
}
// specialization for variant
template <class... Ts, class U>
std::pair<index_type, index_type> update(variant<Ts...>& axis, const U& value) {
return visit([&value](auto& a) { return a.update(value); }, axis);
}
/** Returns bin width at axis index.
If the axis has no `value` method, throw std::runtime_error. If the method exists and
accepts a floating point index, return the result of `axis.value(index + 1) -
axis.value(index)`. If the method exists but accepts only integer indices, return 0.
@param axis any axis instance
@param index bin index
*/
template <class Axis>
decltype(auto) width(const Axis& axis, index_type index) {
return detail::value_method_switch(
[](const auto&) { return 0; },
[index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis,
detail::priority<1>{});
}
/** Returns bin width at axis index.
Like boost::histogram::axis::traits::width, but converts the result into the requested
return type. If the conversion is not possible, throw std::runtime_error.
@param axis any axis instance
@param index bin index
*/
template <class Result, class Axis>
Result width_as(const Axis& axis, index_type index) {
return detail::value_method_switch(
[](const auto&) { return Result{}; },
[index](const auto& a) {
return detail::try_cast<Result, std::runtime_error>(a.value(index + 1) -
a.value(index));
},
axis, detail::priority<1>{});
}
} // namespace traits
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+307
View File
@@ -0,0 +1,307 @@
// Copyright 2015-2018 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_AXIS_VARIABLE_HPP
#define BOOST_HISTOGRAM_AXIS_VARIABLE_HPP
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/limits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace histogram {
namespace axis {
/** Axis for non-equidistant bins on the real line.
Binning is a O(log(N)) operation. If speed matters and the problem domain
allows it, prefer a regular axis, possibly with a transform.
If the axis has an overflow bin (the default), a value on the upper edge of the last
bin is put in the overflow bin. The axis range represents a semi-open interval.
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
still counted towards the last bin. The axis range represents a closed interval. This
is the desired behavior for random numbers drawn from a bounded interval, which is
usually closed.
@tparam Value input value type, must be floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
*/
template <class Value, class MetaData, class Options, class Allocator>
class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using allocator_type = Allocator;
using vector_type = std::vector<Value, allocator_type>;
public:
constexpr variable() = default;
explicit variable(allocator_type alloc) : vec_(alloc) {}
/** Construct from forward iterator range of bin edges.
@param begin begin of edge sequence.
@param end end of edge sequence.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
The constructor throws `std::invalid_argument` if iterator range is invalid, if less
than two edges are provided or if bin edges are not in ascending order.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
template <class It, class = detail::requires_iterator<It>>
variable(It begin, It end, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(std::move(alloc)) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(
std::is_floating_point<value_type>::value,
"current version of variable axis requires floating point type; "
"if you need a variable axis with an integral type, please submit an issue");
static_assert((!options.test(option::circular) && !options.test(option::growth)) ||
(options.test(option::circular) ^ options.test(option::growth)),
"circular and growth options are mutually exclusive");
const auto n = std::distance(begin, end);
if (n < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));
if (n < 2) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 1 required"));
vec_.reserve(n);
vec_.emplace_back(*begin++);
bool strictly_ascending = true;
for (; begin != end; ++begin) {
strictly_ascending &= vec_.back() < *begin;
vec_.emplace_back(*begin);
}
if (!strictly_ascending)
BOOST_THROW_EXCEPTION(
std::invalid_argument("input sequence must be strictly ascending"));
}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class It, class A, class = detail::requires_iterator<It>,
class = detail::requires_allocator<A>>
variable(It begin, It end, metadata_type meta, A alloc)
: variable(begin, end, std::move(meta), {}, std::move(alloc)) {}
/** Construct variable axis from iterable range of bin edges.
@param iterable iterable range of bin edges.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
*/
template <class U, class = detail::requires_iterable<U>>
variable(const U& iterable, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: variable(std::begin(iterable), std::end(iterable), std::move(meta), options,
std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_iterable<U>,
class = detail::requires_allocator<A>>
variable(const U& iterable, metadata_type meta, A alloc)
: variable(std::begin(iterable), std::end(iterable), std::move(meta), {},
std::move(alloc)) {}
/** Construct variable axis from initializer list of bin edges.
@param list `std::initializer_list` of bin edges.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
*/
template <class U>
variable(std::initializer_list<U> list, metadata_type meta = {},
options_type options = {}, allocator_type alloc = {})
: variable(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_allocator<A>>
variable(std::initializer_list<U> list, metadata_type meta, A alloc)
: variable(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
variable(const variable& src, index_type begin, index_type end, unsigned merge)
: metadata_base(src), vec_(src.get_allocator()) {
assert((end - begin) % merge == 0);
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
vec_.reserve((end - begin) / merge);
const auto beg = src.vec_.begin();
for (index_type i = begin; i <= end; i += merge) vec_.emplace_back(*(beg + i));
}
/// Return index for value argument.
index_type index(value_type x) const noexcept {
if (options_type::test(option::circular)) {
const auto a = vec_[0];
const auto b = vec_[size()];
x -= std::floor((x - a) / (b - a)) * (b - a);
}
// upper edge of last bin is inclusive if overflow bin is not present
if (!options_type::test(option::overflow) && x == vec_.back()) return size() - 1;
return static_cast<index_type>(std::upper_bound(vec_.begin(), vec_.end(), x) -
vec_.begin() - 1);
}
std::pair<index_type, index_type> update(value_type x) noexcept {
const auto i = index(x);
if (std::isfinite(x)) {
if (0 <= i) {
if (i < size()) return std::make_pair(i, 0);
const auto d = value(size()) - value(size() - 0.5);
x = std::nextafter(x, (std::numeric_limits<value_type>::max)());
x = (std::max)(x, vec_.back() + d);
vec_.push_back(x);
return {i, -1};
}
const auto d = value(0.5) - value(0);
x = (std::min)(x, value(0) - d);
vec_.insert(vec_.begin(), x);
return {0, -i};
}
return {x < 0 ? -1 : size(), 0};
}
/// Return value for fractional index argument.
value_type value(real_index_type i) const noexcept {
if (options_type::test(option::circular)) {
auto shift = std::floor(i / size());
i -= shift * size();
double z;
const auto k = static_cast<index_type>(std::modf(i, &z));
const auto a = vec_[0];
const auto b = vec_[size()];
return (1.0 - z) * vec_[k] + z * vec_[k + 1] + shift * (b - a);
}
if (i < 0) return detail::lowest<value_type>();
if (i == size()) return vec_.back();
if (i > size()) return detail::highest<value_type>();
const auto k = static_cast<index_type>(i); // precond: i >= 0
const real_index_type z = i - k;
// check z == 0 needed to avoid returning nan when vec_[k + 1] is infinity
return (1.0 - z) * vec_[k] + (z == 0 ? 0 : z * vec_[k + 1]);
}
/// Return bin for index argument.
auto bin(index_type idx) const noexcept { return interval_view<variable>(*this, idx); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return static_cast<index_type>(vec_.size()) - 1; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
template <class V, class M, class O, class A>
bool operator==(const variable<V, M, O, A>& o) const noexcept {
const auto& a = vec_;
const auto& b = o.vec_;
return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O, class A>
bool operator!=(const variable<V, M, O, A>& o) const noexcept {
return !operator==(o);
}
/// Return allocator instance.
auto get_allocator() const { return vec_.get_allocator(); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("seq", vec_);
ar& make_nvp("meta", this->metadata());
}
private:
vector_type vec_;
template <class V, class M, class O, class A>
friend class variable;
};
#if __cpp_deduction_guides >= 201606
template <class T>
variable(std::initializer_list<T>)
-> variable<detail::convert_integer<T, double>, null_type>;
template <class T, class M>
variable(std::initializer_list<T>, M)
-> variable<detail::convert_integer<T, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class T, class M, unsigned B>
variable(std::initializer_list<T>, M, const option::bitset<B>&)
-> variable<detail::convert_integer<T, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>,
option::bitset<B>>;
template <class Iterable, class = detail::requires_iterable<Iterable>>
variable(Iterable) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
null_type>;
template <class Iterable, class M>
variable(Iterable, M) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class Iterable, class M, unsigned B>
variable(Iterable, M, const option::bitset<B>&) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>, option::bitset<B>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+348
View File
@@ -0,0 +1,348 @@
// Copyright 2015-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_AXIS_VARIANT_HPP
#define BOOST_HISTOGRAM_AXIS_VARIANT_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/polymorphic_bin.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/detail/variant_proxy.hpp>
#include <boost/mp11/algorithm.hpp> // mp_contains
#include <boost/mp11/list.hpp> // mp_first
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace axis {
/// Polymorphic axis type
template <class... Ts>
class variant : public iterator_mixin<variant<Ts...>> {
using impl_type = boost::variant2::variant<Ts...>;
template <class T>
using is_bounded_type = mp11::mp_contains<variant, std::decay_t<T>>;
template <class T>
using requires_bounded_type = std::enable_if_t<is_bounded_type<T>::value>;
using metadata_type =
std::remove_const_t<std::remove_reference_t<decltype(traits::metadata(
std::declval<std::remove_pointer_t<mp11::mp_first<variant>>>()))>>;
public:
// cannot import ctors with using directive, it breaks gcc and msvc
variant() = default;
variant(const variant&) = default;
variant& operator=(const variant&) = default;
variant(variant&&) = default;
variant& operator=(variant&&) = default;
template <class T, class = requires_bounded_type<T>>
variant(T&& t) : impl(std::forward<T>(t)) {}
template <class T, class = requires_bounded_type<T>>
variant& operator=(T&& t) {
impl = std::forward<T>(t);
return *this;
}
template <class... Us>
variant(const variant<Us...>& u) {
this->operator=(u);
}
template <class... Us>
variant& operator=(const variant<Us...>& u) {
visit(
[this](const auto& u) {
using U = std::decay_t<decltype(u)>;
detail::static_if<is_bounded_type<U>>(
[this](const auto& u) { this->operator=(u); },
[](const auto&) {
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::type_name<U>() + " is not convertible to a bounded type of " +
detail::type_name<variant>()));
},
u);
},
u);
return *this;
}
/// Return size of axis.
index_type size() const {
return visit([](const auto& a) -> index_type { return a.size(); }, *this);
}
/// Return options of axis or option::none_t if axis has no options.
unsigned options() const {
return visit([](const auto& a) { return traits::options(a); }, *this);
}
/// Returns true if the axis is inclusive or false.
bool inclusive() const {
return visit([](const auto& a) { return traits::inclusive(a); }, *this);
}
/// Returns true if the axis is ordered or false.
bool ordered() const {
return visit([](const auto& a) { return traits::ordered(a); }, *this);
}
/// Returns true if the axis is continuous or false.
bool continuous() const {
return visit([](const auto& a) { return traits::continuous(a); }, *this);
}
/// Return reference to const metadata or instance of null_type if axis has no
/// metadata.
metadata_type& metadata() const {
return visit(
[](const auto& a) -> metadata_type& {
using M = decltype(traits::metadata(a));
return detail::static_if<std::is_same<M, metadata_type&>>(
[](const auto& a) -> metadata_type& { return traits::metadata(a); },
[](const auto&) -> metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(
"cannot return metadata of type " + detail::type_name<M>() +
" through axis::variant interface which uses type " +
detail::type_name<metadata_type>() +
"; use boost::histogram::axis::get to obtain a reference "
"of this axis type"));
},
a);
},
*this);
}
/// Return reference to metadata or instance of null_type if axis has no
/// metadata.
metadata_type& metadata() {
return visit(
[](auto& a) -> metadata_type& {
using M = decltype(traits::metadata(a));
return detail::static_if<std::is_same<M, metadata_type&>>(
[](auto& a) -> metadata_type& { return traits::metadata(a); },
[](auto&) -> metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(
"cannot return metadata of type " + detail::type_name<M>() +
" through axis::variant interface which uses type " +
detail::type_name<metadata_type>() +
"; use boost::histogram::axis::get to obtain a reference "
"of this axis type"));
},
a);
},
*this);
}
/** Return index for value argument.
Throws std::invalid_argument if axis has incompatible call signature.
*/
template <class U>
index_type index(const U& u) const {
return visit([&u](const auto& a) { return traits::index(a, u); }, *this);
}
/** Return value for index argument.
Only works for axes with value method that returns something convertible
to double and will throw a runtime_error otherwise, see
axis::traits::value().
*/
double value(real_index_type idx) const {
return visit([idx](const auto& a) { return traits::value_as<double>(a, idx); },
*this);
}
/** Return bin for index argument.
Only works for axes with value method that returns something convertible
to double and will throw a runtime_error otherwise, see
axis::traits::value().
*/
auto bin(index_type idx) const {
return visit(
[idx](const auto& a) {
return detail::value_method_switch(
[idx](const auto& a) { // axis is discrete
const double x = traits::value_as<double>(a, idx);
return polymorphic_bin<double>(x, x);
},
[idx](const auto& a) { // axis is continuous
const double x1 = traits::value_as<double>(a, idx);
const double x2 = traits::value_as<double>(a, idx + 1);
return polymorphic_bin<double>(x1, x2);
},
a, detail::priority<1>{});
},
*this);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
detail::variant_proxy<variant> p{*this};
ar& make_nvp("variant", p);
}
private:
impl_type impl;
friend struct detail::variant_access;
friend struct boost::histogram::unsafe_access;
};
// specialization for empty argument list, useful for meta-programming
template <>
class variant<> {};
/// Apply visitor to variant (reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, variant<Us...>& var) {
return detail::variant_access::visit(vis, var);
}
/// Apply visitor to variant (movable reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, variant<Us...>&& var) {
return detail::variant_access::visit(vis, std::move(var));
}
/// Apply visitor to variant (const reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, const variant<Us...>& var) {
return detail::variant_access::visit(vis, var);
}
/// Returns pointer to T in variant or null pointer if type does not match.
template <class T, class... Us>
auto get_if(variant<Us...>* v) {
return detail::variant_access::template get_if<T>(v);
}
/// Returns pointer to const T in variant or null pointer if type does not match.
template <class T, class... Us>
auto get_if(const variant<Us...>* v) {
return detail::variant_access::template get_if<T>(v);
}
/// Return reference to T, throws std::runtime_error if type does not match.
template <class T, class... Us>
decltype(auto) get(variant<Us...>& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return *tp;
}
/// Return movable reference to T, throws unspecified exception if type does not match.
template <class T, class... Us>
decltype(auto) get(variant<Us...>&& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return std::move(*tp);
}
/// Return const reference to T, throws unspecified exception if type does not match.
template <class T, class... Us>
decltype(auto) get(const variant<Us...>& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return *tp;
}
// pass-through version of visit for generic programming
template <class Visitor, class T>
decltype(auto) visit(Visitor&& vis, T&& var) {
return std::forward<Visitor>(vis)(std::forward<T>(var));
}
// pass-through version of get for generic programming
template <class T, class U>
decltype(auto) get(U&& u) {
return std::forward<U>(u);
}
// pass-through version of get_if for generic programming
template <class T, class U>
auto get_if(U* u) {
return reinterpret_cast<T*>(std::is_same<T, std::decay_t<U>>::value ? u : nullptr);
}
// pass-through version of get_if for generic programming
template <class T, class U>
auto get_if(const U* u) {
return reinterpret_cast<const T*>(std::is_same<T, std::decay_t<U>>::value ? u
: nullptr);
}
/** Compare two variants.
Return true if the variants point to the same concrete axis type and the types compare
equal. Otherwise return false.
*/
template <class... Us, class... Vs>
bool operator==(const variant<Us...>& u, const variant<Vs...>& v) noexcept {
return visit([&](const auto& vi) { return u == vi; }, v);
}
/** Compare variant with a concrete axis type.
Return true if the variant point to the same concrete axis type and the types compare
equal. Otherwise return false.
*/
template <class... Us, class T>
bool operator==(const variant<Us...>& u, const T& t) noexcept {
using V = variant<Us...>;
return detail::static_if_c<(mp11::mp_contains<V, T>::value ||
mp11::mp_contains<V, T*>::value ||
mp11::mp_contains<V, const T*>::value)>(
[&](const auto& t) {
using U = std::decay_t<decltype(t)>;
const U* tp = detail::variant_access::template get_if<U>(&u);
return tp && detail::relaxed_equal{}(*tp, t);
},
[&](const auto&) { return false; }, t);
}
template <class T, class... Us>
bool operator==(const T& t, const variant<Us...>& u) noexcept {
return u == t;
}
/// The negation of operator==.
template <class... Us, class... Ts>
bool operator!=(const variant<Us...>& u, const variant<Ts...>& t) noexcept {
return !(u == t);
}
/// The negation of operator==.
template <class... Us, class T>
bool operator!=(const variant<Us...>& u, const T& t) noexcept {
return !(u == t);
}
/// The negation of operator==.
template <class T, class... Us>
bool operator!=(const T& t, const variant<Us...>& u) noexcept {
return u != t;
}
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+87
View File
@@ -0,0 +1,87 @@
// 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_DETAIL_ACCUMULATOR_TRAITS_HPP
#define BOOST_HISTOGRAM_DETAIL_ACCUMULATOR_TRAITS_HPP
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/fwd.hpp>
#include <tuple>
#include <type_traits>
namespace boost {
// forward declare accumulator_set so that it can be matched below
namespace accumulators {
template <class, class, class>
struct accumulator_set;
}
namespace histogram {
namespace detail {
template <bool WeightSupport, class... Ts>
struct accumulator_traits_holder {
static constexpr bool weight_support = WeightSupport;
using args = std::tuple<Ts...>;
};
// member function pointer with weight_type as first argument is better match
template <class R, class T, class U, class... Ts>
accumulator_traits_holder<true, Ts...> accumulator_traits_impl_call_op(
R (T::*)(boost::histogram::weight_type<U>, Ts...));
template <class R, class T, class U, class... Ts>
accumulator_traits_holder<true, Ts...> accumulator_traits_impl_call_op(
R (T::*)(boost::histogram::weight_type<U>&, Ts...));
template <class R, class T, class U, class... Ts>
accumulator_traits_holder<true, Ts...> accumulator_traits_impl_call_op(
R (T::*)(boost::histogram::weight_type<U>&&, Ts...));
template <class R, class T, class U, class... Ts>
accumulator_traits_holder<true, Ts...> accumulator_traits_impl_call_op(
R (T::*)(const boost::histogram::weight_type<U>&, Ts...));
// member function pointer only considered if all specializations above fail
template <class R, class T, class... Ts>
accumulator_traits_holder<false, Ts...> accumulator_traits_impl_call_op(R (T::*)(Ts...));
template <class T>
auto accumulator_traits_impl(T&, priority<2>)
-> decltype(accumulator_traits_impl_call_op(&T::operator()));
template <class T>
auto accumulator_traits_impl(T&, priority<2>)
-> decltype(std::declval<T&>() += std::declval<boost::histogram::weight_type<int>>(),
accumulator_traits_holder<true>{});
// fallback for simple arithmetic types that do not implement adding a weight_type
template <class T>
auto accumulator_traits_impl(T&, priority<1>)
-> decltype(std::declval<T&>() += 0, accumulator_traits_holder<true>{});
template <class T>
auto accumulator_traits_impl(T&, priority<0>) -> accumulator_traits_holder<false>;
// for boost.accumulators compatibility
template <class S, class F, class W>
accumulator_traits_holder<false, S> accumulator_traits_impl(
boost::accumulators::accumulator_set<S, F, W>&, priority<2>) {
static_assert(std::is_same<W, void>::value,
"accumulator_set with weights is not directly supported, please use "
"a wrapper class that implements the Accumulator concept");
}
template <class T>
using accumulator_traits =
decltype(accumulator_traits_impl(std::declval<T&>(), priority<2>{}));
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2015-2018 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_DETAIL_ARGS_TYPE_HPP
#define BOOST_HISTOGRAM_DETAIL_ARGS_TYPE_HPP
#include <tuple>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
struct args_type_impl {
using T::ERROR_this_should_never_be_instantiated_please_write_an_issue;
};
template <class R, class T, class... Ts>
struct args_type_impl<R (T::*)(Ts...)> {
using type = std::tuple<Ts...>;
};
template <class R, class T, class... Ts>
struct args_type_impl<R (T ::*)(Ts...) const> {
using type = std::tuple<Ts...>;
};
template <class R, class... Ts>
struct args_type_impl<R (*)(Ts...)> {
using type = std::tuple<Ts...>;
};
#if __cpp_noexcept_function_type >= 201510
template <class R, class T, class... Ts>
struct args_type_impl<R (T::*)(Ts...) noexcept> {
using type = std::tuple<Ts...>;
};
template <class R, class T, class... Ts>
struct args_type_impl<R (T ::*)(Ts...) const noexcept> {
using type = std::tuple<Ts...>;
};
template <class R, class... Ts>
struct args_type_impl<R (*)(Ts...) noexcept> {
using type = std::tuple<Ts...>;
};
#endif
template <class FunctionPointer>
using args_type = typename args_type_impl<FunctionPointer>::type;
template <class T, std::size_t N = 0>
using arg_type = std::tuple_element_t<N, args_type<T>>;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+83
View File
@@ -0,0 +1,83 @@
// 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_DETAIL_ARGUMENT_TRAITS_HPP
#define BOOST_HISTOGRAM_DETAIL_ARGUMENT_TRAITS_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/mp11/list.hpp>
#include <tuple>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
struct is_weight_impl : mp11::mp_false {};
template <class T>
struct is_weight_impl<weight_type<T>> : mp11::mp_true {};
template <class T>
using is_weight = is_weight_impl<T>;
template <class T>
struct is_sample_impl : mp11::mp_false {};
template <class T>
struct is_sample_impl<sample_type<T>> : mp11::mp_true {};
template <class T>
using is_sample = is_sample_impl<T>;
template <int Idx, class L>
struct sample_args_impl {
using type = mp11::mp_first<std::decay_t<mp11::mp_at_c<L, (Idx >= 0 ? Idx : 0)>>>;
};
template <class L>
struct sample_args_impl<-1, L> {
using type = std::tuple<>;
};
template <std::size_t NArgs, std::size_t Start, int WeightPos, int SamplePos,
class SampleArgs>
struct argument_traits_holder {
using nargs = mp11::mp_size_t<NArgs>;
using start = mp11::mp_size_t<Start>;
using wpos = mp11::mp_int<WeightPos>;
using spos = mp11::mp_int<SamplePos>;
using sargs = SampleArgs;
};
template <class... Ts>
struct argument_traits_impl {
using list_ = mp11::mp_list<Ts...>;
static constexpr std::size_t size_ = sizeof...(Ts);
static constexpr std::size_t weight_ = mp11::mp_find_if<list_, is_weight>::value;
static constexpr std::size_t sample_ = mp11::mp_find_if<list_, is_sample>::value;
static constexpr int spos_ = (sample_ < size_ ? static_cast<int>(sample_) : -1);
static constexpr int wpos_ = (weight_ < size_ ? static_cast<int>(weight_) : -1);
using type =
argument_traits_holder<(size_ - (weight_ < size_) - (sample_ < size_)),
(weight_ < size_ && sample_ < size_ &&
(weight_ + sample_ < 2)
? 2
: ((weight_ == 0 || sample_ == 0) ? 1 : 0)),
wpos_, spos_, typename sample_args_impl<spos_, list_>::type>;
};
template <class... Ts>
using argument_traits = typename argument_traits_impl<Ts...>::type;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+66
View File
@@ -0,0 +1,66 @@
// 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_DETAIL_ARRAY_WRAPPER_HPP
#define BOOST_HISTOGRAM_DETAIL_ARRAY_WRAPPER_HPP
#include <boost/core/make_span.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/mp11/function.hpp>
#include <boost/mp11/utility.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class = decltype(&T::template save_array<int>)>
struct has_save_array_impl;
template <class T, class = decltype(&T::template load_array<int>)>
struct has_load_array_impl;
template <class T>
using has_array_optimization = mp11::mp_or<mp11::mp_valid<has_save_array_impl, T>,
mp11::mp_valid<has_load_array_impl, T>>;
template <class T>
struct array_wrapper {
using pointer = T*;
pointer ptr;
std::size_t size;
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
static_if_c<(has_array_optimization<Archive>::value &&
std::is_trivially_copyable<T>::value)>(
[this](auto& ar) {
// cannot use and therefore bypass save_array / load_array interface, because
// it requires exact type boost::serialization::array_wrapper<T>
static_if_c<Archive::is_loading::value>(
[this](auto& ar) { ar.load_binary(this->ptr, sizeof(T) * this->size); },
[this](auto& ar) { ar.save_binary(this->ptr, sizeof(T) * this->size); },
ar);
},
[this](auto& ar) {
for (auto&& x : make_span(this->ptr, this->size)) ar& make_nvp("item", x);
},
ar);
}
};
template <class T>
auto make_array_wrapper(T* t, std::size_t s) {
return array_wrapper<T>{t, s};
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+92
View File
@@ -0,0 +1,92 @@
// 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_DETAIL_ATOMIC_NUMBER_HPP
#define BOOST_HISTOGRAM_DETAIL_ATOMIC_NUMBER_HPP
#include <atomic>
#include <boost/histogram/detail/priority.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
// copyable arithmetic type with thread-safe operator++ and operator+=
template <class T>
struct atomic_number : std::atomic<T> {
static_assert(std::is_arithmetic<T>(), "");
using base_t = std::atomic<T>;
using std::atomic<T>::atomic;
atomic_number() noexcept = default;
atomic_number(const atomic_number& o) noexcept : std::atomic<T>{o.load()} {}
atomic_number& operator=(const atomic_number& o) noexcept {
this->store(o.load());
return *this;
}
// not defined for float
atomic_number& operator++() noexcept {
increment_impl(static_cast<base_t&>(*this), priority<1>{});
return *this;
}
// operator is not defined for floating point before C++20
atomic_number& operator+=(const T& x) noexcept {
add_impl(static_cast<base_t&>(*this), x, priority<1>{});
return *this;
}
// not thread-safe
atomic_number& operator*=(const T& x) noexcept {
this->store(this->load() * x);
return *this;
}
// not thread-safe
atomic_number& operator/=(const T& x) noexcept {
this->store(this->load() / x);
return *this;
}
private:
// for integral types
template <class U = T>
auto increment_impl(std::atomic<U>& a, priority<1>) noexcept -> decltype(++a) {
return ++a;
}
// fallback implementation for floating point types
template <class U = T>
void increment_impl(std::atomic<U>&, priority<0>) noexcept {
this->operator+=(static_cast<U>(1));
}
// always available for integral types, in C++20 also available for float
template <class U = T>
static auto add_impl(std::atomic<U>& a, const U& x, priority<1>) noexcept
-> decltype(a += x) {
return a += x;
}
// pre-C++20 fallback implementation for floating point
template <class U = T>
static void add_impl(std::atomic<U>& a, const U& x, priority<0>) noexcept {
U expected = a.load();
// if another tread changed `expected` in the meantime, compare_exchange returns
// false and updates `expected`; we then loop and try to update again;
// see https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
while (!a.compare_exchange_weak(expected, expected + x))
;
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+454
View File
@@ -0,0 +1,454 @@
// Copyright 2015-2018 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_DETAIL_AXES_HPP
#define BOOST_HISTOGRAM_DETAIL_AXES_HPP
#include <array>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/axis/variant.hpp>
#include <boost/histogram/detail/make_default.hpp>
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <boost/histogram/detail/optional_index.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/relaxed_tuple_size.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/sub_array.hpp>
#include <boost/histogram/detail/try_cast.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/integer_sequence.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/tuple.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class Unary>
void for_each_axis_impl(dynamic_size, T& t, Unary& p) {
for (auto& a : t) axis::visit(p, a);
}
template <class N, class T, class Unary>
void for_each_axis_impl(N, T& t, Unary& p) {
mp11::tuple_for_each(t, p);
}
// also matches const T and const Unary
template <class T, class Unary>
void for_each_axis(T&& t, Unary&& p) {
for_each_axis_impl(relaxed_tuple_size(t), t, p);
}
// merge if a and b are discrete and growing
struct axis_merger {
template <class T, class U>
T operator()(const T& a, const U& u) {
const T* bp = ptr_cast<T>(&u);
if (!bp) BOOST_THROW_EXCEPTION(std::invalid_argument("axes not mergable"));
using O = axis::traits::get_options<T>;
constexpr bool discrete_and_growing =
axis::traits::is_continuous<T>::value == false && O::test(axis::option::growth);
return impl(mp11::mp_bool<discrete_and_growing>{}, a, *bp);
}
template <class T>
T impl(std::false_type, const T& a, const T& b) {
if (!relaxed_equal{}(a, b))
BOOST_THROW_EXCEPTION(std::invalid_argument("axes not mergable"));
return a;
}
template <class T>
T impl(std::true_type, const T& a, const T& b) {
if (relaxed_equal{}(axis::traits::metadata(a), axis::traits::metadata(b))) {
auto r = a;
if (axis::traits::is_ordered<T>::value) {
r.update(b.value(0));
r.update(b.value(b.size() - 1));
} else
for (auto&& v : b) r.update(v);
return r;
}
return impl(std::false_type{}, a, b);
}
};
// create empty dynamic axis which can store any axes types from the argument
template <class T>
auto make_empty_dynamic_axes(const T& axes) {
return make_default(axes);
}
template <class... Ts>
auto make_empty_dynamic_axes(const std::tuple<Ts...>&) {
using namespace ::boost::mp11;
using L = mp_unique<axis::variant<Ts...>>;
// return std::vector<axis::variant<Axis0, Axis1, ...>> or std::vector<Axis0>
return std::vector<mp_if_c<(mp_size<L>::value == 1), mp_first<L>, L>>{};
}
template <class T, class Functor, std::size_t... Is>
auto axes_transform_impl(const T& t, Functor&& f, mp11::index_sequence<Is...>) {
return std::make_tuple(f(Is, std::get<Is>(t))...);
}
// warning: sequential order of functor execution is platform-dependent!
template <class... Ts, class Functor>
auto axes_transform(const std::tuple<Ts...>& old_axes, Functor&& f) {
return axes_transform_impl(old_axes, std::forward<Functor>(f),
mp11::make_index_sequence<sizeof...(Ts)>{});
}
// changing axes type is not supported
template <class T, class Functor>
T axes_transform(const T& old_axes, Functor&& f) {
T axes = make_default(old_axes);
axes.reserve(old_axes.size());
for_each_axis(old_axes, [&](const auto& a) { axes.emplace_back(f(axes.size(), a)); });
return axes;
}
template <class... Ts, class Binary, std::size_t... Is>
std::tuple<Ts...> axes_transform_impl(const std::tuple<Ts...>& lhs,
const std::tuple<Ts...>& rhs, Binary&& bin,
mp11::index_sequence<Is...>) {
return std::make_tuple(bin(std::get<Is>(lhs), std::get<Is>(rhs))...);
}
template <class... Ts, class Binary>
std::tuple<Ts...> axes_transform(const std::tuple<Ts...>& lhs,
const std::tuple<Ts...>& rhs, Binary&& bin) {
return axes_transform_impl(lhs, rhs, bin, mp11::make_index_sequence<sizeof...(Ts)>{});
}
template <class T, class Binary>
T axes_transform(const T& lhs, const T& rhs, Binary&& bin) {
T ax = make_default(lhs);
ax.reserve(lhs.size());
using std::begin;
auto ir = begin(rhs);
for (auto&& li : lhs) {
axis::visit(
[&](const auto& li) {
axis::visit([&](const auto& ri) { ax.emplace_back(bin(li, ri)); }, *ir);
},
li);
++ir;
}
return ax;
}
template <class T>
unsigned axes_rank(const T& axes) {
using std::begin;
using std::end;
return static_cast<unsigned>(std::distance(begin(axes), end(axes)));
}
template <class... Ts>
constexpr unsigned axes_rank(const std::tuple<Ts...>&) {
return static_cast<unsigned>(sizeof...(Ts));
}
template <class T>
void throw_if_axes_is_too_large(const T& axes) {
if (axes_rank(axes) > BOOST_HISTOGRAM_DETAIL_AXES_LIMIT)
BOOST_THROW_EXCEPTION(
std::invalid_argument("length of axis vector exceeds internal buffers, "
"recompile with "
"-DBOOST_HISTOGRAM_DETAIL_AXES_LIMIT=<new max size> "
"to increase internal buffers"));
}
// tuple is never too large because internal buffers adapt to size of tuple
template <class... Ts>
void throw_if_axes_is_too_large(const std::tuple<Ts...>&) {}
template <unsigned N, class... Ts>
decltype(auto) axis_get(std::tuple<Ts...>& axes) {
return std::get<N>(axes);
}
template <unsigned N, class... Ts>
decltype(auto) axis_get(const std::tuple<Ts...>& axes) {
return std::get<N>(axes);
}
template <unsigned N, class T>
decltype(auto) axis_get(T& axes) {
return axes[N];
}
template <unsigned N, class T>
decltype(auto) axis_get(const T& axes) {
return axes[N];
}
template <class... Ts>
auto axis_get(std::tuple<Ts...>& axes, const unsigned i) {
constexpr auto S = sizeof...(Ts);
using V = mp11::mp_unique<axis::variant<Ts*...>>;
return mp11::mp_with_index<S>(i, [&axes](auto i) { return V(&std::get<i>(axes)); });
}
template <class... Ts>
auto axis_get(const std::tuple<Ts...>& axes, const unsigned i) {
constexpr auto S = sizeof...(Ts);
using V = mp11::mp_unique<axis::variant<const Ts*...>>;
return mp11::mp_with_index<S>(i, [&axes](auto i) { return V(&std::get<i>(axes)); });
}
template <class T>
decltype(auto) axis_get(T& axes, const unsigned i) {
return axes[i];
}
template <class T>
decltype(auto) axis_get(const T& axes, const unsigned i) {
return axes[i];
}
template <class T, class U, std::size_t... Is>
bool axes_equal_impl(const T& t, const U& u, mp11::index_sequence<Is...>) noexcept {
bool result = true;
// operator folding emulation
(void)std::initializer_list<bool>{
(result &= relaxed_equal{}(std::get<Is>(t), std::get<Is>(u)))...};
return result;
}
template <class... Ts, class... Us>
bool axes_equal_impl(const std::tuple<Ts...>& t, const std::tuple<Us...>& u) noexcept {
return axes_equal_impl(
t, u, mp11::make_index_sequence<(std::min)(sizeof...(Ts), sizeof...(Us))>{});
}
template <class... Ts, class U>
bool axes_equal_impl(const std::tuple<Ts...>& t, const U& u) noexcept {
using std::begin;
auto iu = begin(u);
bool result = true;
mp11::tuple_for_each(t, [&](const auto& ti) {
axis::visit([&](const auto& ui) { result &= relaxed_equal{}(ti, ui); }, *iu);
++iu;
});
return result;
}
template <class T, class... Us>
bool axes_equal_impl(const T& t, const std::tuple<Us...>& u) noexcept {
return axes_equal_impl(u, t);
}
template <class T, class U>
bool axes_equal_impl(const T& t, const U& u) noexcept {
using std::begin;
auto iu = begin(u);
bool result = true;
for (auto&& ti : t) {
axis::visit(
[&](const auto& ti) {
axis::visit([&](const auto& ui) { result &= relaxed_equal{}(ti, ui); }, *iu);
},
ti);
++iu;
}
return result;
}
template <class T, class U>
bool axes_equal(const T& t, const U& u) noexcept {
return axes_rank(t) == axes_rank(u) && axes_equal_impl(t, u);
}
// enable_if_t needed by msvc :(
template <class... Ts, class... Us>
std::enable_if_t<!(std::is_same<std::tuple<Ts...>, std::tuple<Us...>>::value)>
axes_assign(std::tuple<Ts...>&, const std::tuple<Us...>&) {
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot assign axes, types do not match"));
}
template <class... Ts>
void axes_assign(std::tuple<Ts...>& t, const std::tuple<Ts...>& u) {
t = u;
}
template <class... Ts, class U>
void axes_assign(std::tuple<Ts...>& t, const U& u) {
if (sizeof...(Ts) == detail::size(u)) {
using std::begin;
auto iu = begin(u);
mp11::tuple_for_each(t, [&](auto& ti) {
using T = std::decay_t<decltype(ti)>;
ti = axis::get<T>(*iu);
++iu;
});
return;
}
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot assign axes, sizes do not match"));
}
template <class T, class... Us>
void axes_assign(T& t, const std::tuple<Us...>& u) {
// resize instead of reserve, because t may not be empty and we want exact capacity
t.resize(sizeof...(Us));
using std::begin;
auto it = begin(t);
mp11::tuple_for_each(u, [&](const auto& ui) { *it++ = ui; });
}
template <class T, class U>
void axes_assign(T& t, const U& u) {
t.assign(u.begin(), u.end());
}
template <class Archive, class T>
void axes_serialize(Archive& ar, T& axes) {
ar& make_nvp("axes", axes);
}
template <class Archive, class... Ts>
void axes_serialize(Archive& ar, std::tuple<Ts...>& axes) {
// needed to keep serialization format backward compatible
struct proxy {
std::tuple<Ts...>& t;
void serialize(Archive& ar, unsigned /* version */) {
mp11::tuple_for_each(t, [&ar](auto& x) { ar& make_nvp("item", x); });
}
};
proxy p{axes};
ar& make_nvp("axes", p);
}
// total number of bins including *flow bins
template <class T>
std::size_t bincount(const T& axes) {
std::size_t n = 1;
for_each_axis(axes, [&n](const auto& a) {
const auto old = n;
const auto s = axis::traits::extent(a);
n *= s;
if (s > 0 && n < old) BOOST_THROW_EXCEPTION(std::overflow_error("bincount overflow"));
});
return n;
}
/** Initial offset for the linear index.
This precomputes an offset for the global index so that axis index = -1 addresses the
first entry in the storage. Example: one-dim. histogram. The offset is 1, stride is 1,
and global_index = offset + axis_index * stride == 0 addresses the first element of
the storage.
Using the offset makes the hot inner loop that computes the global_index simpler and
thus faster, because we do not have to branch for each axis to check whether it has
an underflow bin.
The offset is set to an invalid value when the histogram contains at least one growing
axis, because this optimization then cannot be used. See detail/linearize.hpp, in this
case linearize_growth is called.
*/
template <class T>
std::size_t offset(const T& axes) {
std::size_t n = 0;
auto stride = static_cast<std::size_t>(1);
for_each_axis(axes, [&](const auto& a) {
if (axis::traits::options(a) & axis::option::growth)
n = invalid_index;
else if (n != invalid_index && axis::traits::options(a) & axis::option::underflow)
n += stride;
stride *= axis::traits::extent(a);
});
return n;
}
// make default-constructed buffer (no initialization for POD types)
template <class T, class A>
auto make_stack_buffer(const A& a) {
return sub_array<T, buffer_size<A>::value>(axes_rank(a));
}
// make buffer with elements initialized to v
template <class T, class A>
auto make_stack_buffer(const A& a, const T& t) {
return sub_array<T, buffer_size<A>::value>(axes_rank(a), t);
}
template <class T>
using has_underflow =
decltype(axis::traits::get_options<T>::test(axis::option::underflow));
template <class T>
using is_growing = decltype(axis::traits::get_options<T>::test(axis::option::growth));
template <class T>
using is_not_inclusive = mp11::mp_not<axis::traits::is_inclusive<T>>;
// for vector<T>
template <class T>
struct axis_types_impl {
using type = mp11::mp_list<std::decay_t<T>>;
};
// for vector<variant<Ts...>>
template <class... Ts>
struct axis_types_impl<axis::variant<Ts...>> {
using type = mp11::mp_list<std::decay_t<Ts>...>;
};
// for tuple<Ts...>
template <class... Ts>
struct axis_types_impl<std::tuple<Ts...>> {
using type = mp11::mp_list<std::decay_t<Ts>...>;
};
template <class T>
using axis_types =
typename axis_types_impl<mp11::mp_if<is_vector_like<T>, mp11::mp_first<T>, T>>::type;
template <template <class> class Trait, class Axes>
using has_special_axis = mp11::mp_any_of<axis_types<Axes>, Trait>;
template <class Axes>
using has_growing_axis = mp11::mp_any_of<axis_types<Axes>, is_growing>;
template <class Axes>
using has_non_inclusive_axis = mp11::mp_any_of<axis_types<Axes>, is_not_inclusive>;
template <class T>
constexpr std::size_t type_score() {
return sizeof(T) * (std::is_integral<T>::value ? 1
: std::is_floating_point<T>::value ? 10
: 100);
}
// arbitrary ordering of types
template <class T, class U>
using type_less = mp11::mp_bool<(type_score<T>() < type_score<U>())>;
template <class Axes>
using value_types = mp11::mp_sort<
mp11::mp_unique<mp11::mp_transform<axis::traits::value_type, axis_types<Axes>>>,
type_less>;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2015-2018 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_DETAIL_COMMON_TYPE_HPP
#define BOOST_HISTOGRAM_DETAIL_COMMON_TYPE_HPP
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <tuple>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
// clang-format off
template <class T, class U>
using common_axes = mp11::mp_cond<
is_tuple<T>, T,
is_tuple<U>, U,
is_sequence_of_axis<T>, T,
is_sequence_of_axis<U>, U,
std::true_type, T
>;
// clang-format on
// Non-PODs rank highest, then floats, than integers; types with more capacity are higher
template <class Storage>
constexpr std::size_t type_rank() {
using T = typename Storage::value_type;
return !std::is_arithmetic<T>::value * 10000 + std::is_floating_point<T>::value * 100 +
10 * sizeof(T) + 2 * is_array_like<Storage>::value +
is_vector_like<Storage>::value;
;
}
template <class T, class U>
using common_storage = mp11::mp_if_c<(type_rank<T>() >= type_rank<U>()), T, U>;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2018-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_DETAIL_CONVERT_INTEGER_HPP
#define BOOST_HISTOGRAM_DETAIL_CONVERT_INTEGER_HPP
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class U>
using convert_integer =
std::conditional_t<std::is_integral<std::decay_t<T>>::value, U, T>;
}
} // namespace histogram
} // namespace boost
#endif
+77
View File
@@ -0,0 +1,77 @@
// 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_DETAIL_COUNTING_STREAMBUF_HPP
#define BOOST_HISTOGRAM_DETAIL_COUNTING_STREAMBUF_HPP
#include <boost/core/exchange.hpp>
#include <ostream>
#include <streambuf>
namespace boost {
namespace histogram {
namespace detail {
// detect how many characters will be printed by formatted output
template <class CharT, class Traits = std::char_traits<CharT>>
struct counting_streambuf : std::basic_streambuf<CharT, Traits> {
using base_t = std::basic_streambuf<CharT, Traits>;
using typename base_t::char_type;
using typename base_t::int_type;
std::streamsize* p_count;
counting_streambuf(std::streamsize& c) : p_count(&c) {}
std::streamsize xsputn(const char_type* /* s */, std::streamsize n) override {
*p_count += n;
return n;
}
int_type overflow(int_type ch) override {
++*p_count;
return ch;
}
};
template <class C, class T>
struct count_guard {
using bos = std::basic_ostream<C, T>;
using bsb = std::basic_streambuf<C, T>;
counting_streambuf<C, T> csb;
bos* p_os;
bsb* p_rdbuf;
count_guard(bos& os, std::streamsize& s) : csb(s), p_os(&os), p_rdbuf(os.rdbuf(&csb)) {}
count_guard(count_guard&& o)
: csb(o.csb), p_os(boost::exchange(o.p_os, nullptr)), p_rdbuf(o.p_rdbuf) {}
count_guard& operator=(count_guard&& o) {
if (this != &o) {
csb = std::move(o.csb);
p_os = boost::exchange(o.p_os, nullptr);
p_rdbuf = o.p_rdbuf;
}
return *this;
}
~count_guard() {
if (p_os) p_os->rdbuf(p_rdbuf);
}
};
template <class C, class T>
count_guard<C, T> make_count_guard(std::basic_ostream<C, T>& os, std::streamsize& s) {
return {os, s};
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+22
View File
@@ -0,0 +1,22 @@
// 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_DETAIL_DEBUG_HPP
#define BOOST_HISTOGRAM_DETAIL_DEBUG_HPP
#include <boost/config/pragma_message.hpp>
BOOST_PRAGMA_MESSAGE("debug.hpp included")
#include <boost/histogram/detail/type_name.hpp>
#include <iostream>
#define DEBUG(x) \
std::cout << __FILE__ << ":" << __LINE__ << " [" \
<< boost::histogram::detail::type_name<decltype(x)>() << "] " #x "=" << x \
<< std::endl;
#endif
+217
View File
@@ -0,0 +1,217 @@
// Copyright 2015-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_DETAIL_DETECT_HPP
#define BOOST_HISTOGRAM_DETAIL_DETECT_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/function.hpp> // mp_and, mp_or
#include <boost/mp11/integral.hpp> // mp_not
#include <boost/mp11/list.hpp> // mp_first
#include <iterator>
#include <tuple>
#include <type_traits>
// forward declaration
namespace boost {
namespace variant2 {
template <class...>
class variant;
} // namespace variant2
} // namespace boost
namespace boost {
namespace histogram {
namespace detail {
template <class...>
using void_t = void;
struct detect_base {
template <class T>
static T&& val();
template <class T>
static T& ref();
template <class T>
static T const& cref();
};
#define BOOST_HISTOGRAM_DETAIL_DETECT(name, cond) \
template <class U> \
struct name##_impl : detect_base { \
template <class T> \
static mp11::mp_true test(T& t, decltype(cond, 0)); \
template <class T> \
static mp11::mp_false test(T&, float); \
using type = decltype(test<U>(ref<U>(), 0)); \
}; \
template <class T> \
using name = typename name##_impl<T>::type
#define BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(name, cond) \
template <class V, class W> \
struct name##_impl : detect_base { \
template <class T, class U> \
static mp11::mp_true test(T& t, U& u, decltype(cond, 0)); \
template <class T, class U> \
static mp11::mp_false test(T&, U&, float); \
using type = decltype(test<V, W>(ref<V>(), ref<W>(), 0)); \
}; \
template <class T, class U = T> \
using name = typename name##_impl<T, U>::type
// reset has overloads, trying to get pmf in this case always fails
BOOST_HISTOGRAM_DETAIL_DETECT(has_method_reset, t.reset(0));
BOOST_HISTOGRAM_DETAIL_DETECT(is_indexable, t[0]);
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(is_transform, (t.inverse(t.forward(u))));
BOOST_HISTOGRAM_DETAIL_DETECT(is_indexable_container,
(t[0], t.size(), std::begin(t), std::end(t)));
BOOST_HISTOGRAM_DETAIL_DETECT(is_vector_like,
(t[0], t.size(), t.resize(0), std::begin(t), std::end(t)));
BOOST_HISTOGRAM_DETAIL_DETECT(is_array_like, (t[0], t.size(), std::tuple_size<T>::value,
std::begin(t), std::end(t)));
BOOST_HISTOGRAM_DETAIL_DETECT(is_map_like, ((typename T::key_type*)nullptr,
(typename T::mapped_type*)nullptr,
std::begin(t), std::end(t)));
// ok: is_axis is false for axis::variant, because T::index is templated
BOOST_HISTOGRAM_DETAIL_DETECT(is_axis, (t.size(), &T::index));
BOOST_HISTOGRAM_DETAIL_DETECT(is_iterable, (std::begin(t), std::end(t)));
BOOST_HISTOGRAM_DETAIL_DETECT(is_iterator,
(typename std::iterator_traits<T>::iterator_category{}));
BOOST_HISTOGRAM_DETAIL_DETECT(is_streamable, (std::declval<std::ostream&>() << t));
BOOST_HISTOGRAM_DETAIL_DETECT(is_allocator, (&T::allocate, &T::deallocate));
BOOST_HISTOGRAM_DETAIL_DETECT(has_operator_preincrement, ++t);
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_equal, (cref<T>() == u));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_radd, (t += u));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rsub, (t -= u));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rmul, (t *= u));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rdiv, (t /= u));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_method_eq, (cref<T>().operator==(u)));
BOOST_HISTOGRAM_DETAIL_DETECT(has_threading_support, (T::has_threading_support));
// stronger form of std::is_convertible that works with explicit operator T and ctors
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(is_explicitly_convertible, static_cast<U>(t));
BOOST_HISTOGRAM_DETAIL_DETECT(is_complete, sizeof(T));
template <class T>
using is_storage = mp11::mp_and<is_indexable_container<T>, has_method_reset<T>,
has_threading_support<T>>;
template <class T>
using is_adaptible =
mp11::mp_and<mp11::mp_not<is_storage<T>>,
mp11::mp_or<is_vector_like<T>, is_array_like<T>, is_map_like<T>>>;
template <class T>
struct is_tuple_impl : std::false_type {};
template <class... Ts>
struct is_tuple_impl<std::tuple<Ts...>> : std::true_type {};
template <class T>
using is_tuple = typename is_tuple_impl<T>::type;
template <class T>
struct is_variant_impl : std::false_type {};
template <class... Ts>
struct is_variant_impl<boost::variant2::variant<Ts...>> : std::true_type {};
template <class T>
using is_variant = typename is_variant_impl<T>::type;
template <class T>
struct is_axis_variant_impl : std::false_type {};
template <class... Ts>
struct is_axis_variant_impl<axis::variant<Ts...>> : std::true_type {};
template <class T>
using is_axis_variant = typename is_axis_variant_impl<T>::type;
template <class T>
using is_any_axis = mp11::mp_or<is_axis<T>, is_axis_variant<T>>;
template <class T>
using is_sequence_of_axis = mp11::mp_and<is_iterable<T>, is_axis<mp11::mp_first<T>>>;
template <class T>
using is_sequence_of_axis_variant =
mp11::mp_and<is_iterable<T>, is_axis_variant<mp11::mp_first<T>>>;
template <class T>
using is_sequence_of_any_axis =
mp11::mp_and<is_iterable<T>, is_any_axis<mp11::mp_first<T>>>;
// Poor-mans concept checks.
// These must be structs not aliases, so their names pop up in compiler errors.
template <class T, class = std::enable_if_t<is_storage<std::decay_t<T>>::value>>
struct requires_storage {};
template <class T, class _ = std::decay_t<T>,
class = std::enable_if_t<(is_storage<_>::value || is_adaptible<_>::value)>>
struct requires_storage_or_adaptible {};
template <class T, class = std::enable_if_t<is_iterator<std::decay_t<T>>::value>>
struct requires_iterator {};
template <class T, class = std::enable_if_t<
is_iterable<std::remove_cv_t<std::remove_reference_t<T>>>::value>>
struct requires_iterable {};
template <class T, class = std::enable_if_t<is_axis<std::decay_t<T>>::value>>
struct requires_axis {};
template <class T, class = std::enable_if_t<is_any_axis<std::decay_t<T>>::value>>
struct requires_any_axis {};
template <class T, class = std::enable_if_t<is_sequence_of_axis<std::decay_t<T>>::value>>
struct requires_sequence_of_axis {};
template <class T,
class = std::enable_if_t<is_sequence_of_axis_variant<std::decay_t<T>>::value>>
struct requires_sequence_of_axis_variant {};
template <class T,
class = std::enable_if_t<is_sequence_of_any_axis<std::decay_t<T>>::value>>
struct requires_sequence_of_any_axis {};
template <class T,
class = std::enable_if_t<is_any_axis<mp11::mp_first<std::decay_t<T>>>::value>>
struct requires_axes {};
template <class T, class U,
class = std::enable_if_t<is_transform<std::decay_t<T>, U>::value>>
struct requires_transform {};
template <class T, class = std::enable_if_t<is_allocator<std::decay_t<T>>::value>>
struct requires_allocator {};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2022 Hans Dembinski, Jay Gohil
//
// 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_DETAIL_ERF_INF_HPP
#define BOOST_HISTOGRAM_DETAIL_ERF_INF_HPP
#include <cmath>
namespace boost {
namespace histogram {
namespace detail {
// Simple implementation of erf_inv so that we do not depend on boost::math.
// If you happen to discover this, prefer the boost::math implementation,
// it is more accurate for x very close to -1 or 1 and faster.
// The only virtue of this implementation is its simplicity.
template <int Iterate = 3>
double erf_inv(double x) noexcept {
// Strategy: solve f(y) = x - erf(y) = 0 for given x with Newton's method.
// f'(y) = -erf'(y) = -2/sqrt(pi) e^(-y^2)
// Has quadratic convergence. Since erf_inv<0> is accurate to 1e-3,
// we should have machine precision after three iterations.
const double x0 = erf_inv<Iterate - 1>(x); // recursion
const double fx0 = x - std::erf(x0);
const double pi = std::acos(-1);
double fpx0 = -2.0 / std::sqrt(pi) * std::exp(-x0 * x0);
return x0 - fx0 / fpx0; // = x1
}
template <>
inline double erf_inv<0>(double x) noexcept {
// Specialization to get initial estimate.
// This formula is accurate to about 1e-3.
// Based on https://stackoverflow.com/questions/27229371/inverse-error-function-in-c
const double a = std::log((1 - x) * (1 + x));
const double b = std::fma(0.5, a, 4.120666747961526);
const double c = 6.47272819164 * a;
return std::copysign(std::sqrt(-b + std::sqrt(std::fma(b, b, -c))), x);
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+348
View File
@@ -0,0 +1,348 @@
// Copyright 2015-2018 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_DETAIL_FILL_HPP
#define BOOST_HISTOGRAM_DETAIL_FILL_HPP
#include <algorithm>
#include <boost/config/workaround.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/axis/variant.hpp>
#include <boost/histogram/detail/argument_traits.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/linearize.hpp>
#include <boost/histogram/detail/make_default.hpp>
#include <boost/histogram/detail/optional_index.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/tuple_slice.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/mp11/tuple.hpp>
#include <boost/mp11/utility.hpp>
#include <cassert>
#include <mutex>
#include <tuple>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class U>
struct sample_args_passed_vs_expected;
template <class... Passed, class... Expected>
struct sample_args_passed_vs_expected<std::tuple<Passed...>, std::tuple<Expected...>> {
static_assert(!(sizeof...(Expected) > 0 && sizeof...(Passed) == 0),
"error: accumulator requires samples, but sample argument is missing");
static_assert(
!(sizeof...(Passed) > 0 && sizeof...(Expected) == 0),
"error: accumulator does not accept samples, but sample argument is passed");
static_assert(sizeof...(Passed) == sizeof...(Expected),
"error: numbers of passed and expected sample arguments differ");
static_assert(
std::is_convertible<std::tuple<Passed...>, std::tuple<Expected...>>::value,
"error: sample argument(s) not convertible to accumulator argument(s)");
};
template <class A>
struct storage_grower {
const A& axes_;
struct {
axis::index_type idx, old_extent;
std::size_t new_stride;
} data_[buffer_size<A>::value];
std::size_t new_size_;
storage_grower(const A& axes) noexcept : axes_(axes) {}
void from_shifts(const axis::index_type* shifts) noexcept {
auto dit = data_;
std::size_t s = 1;
for_each_axis(axes_, [&](const auto& a) {
const auto n = axis::traits::extent(a);
*dit++ = {0, n - std::abs(*shifts++), s};
s *= n;
});
new_size_ = s;
}
// must be extents before any shifts were applied
void from_extents(const axis::index_type* old_extents) noexcept {
auto dit = data_;
std::size_t s = 1;
for_each_axis(axes_, [&](const auto& a) {
const auto n = axis::traits::extent(a);
*dit++ = {0, *old_extents++, s};
s *= n;
});
new_size_ = s;
}
template <class S>
void apply(S& storage, const axis::index_type* shifts) {
auto new_storage = make_default(storage);
new_storage.reset(new_size_);
const auto dlast = data_ + axes_rank(axes_) - 1;
for (auto&& x : storage) {
auto ns = new_storage.begin();
auto sit = shifts;
auto dit = data_;
for_each_axis(axes_, [&](const auto& a) {
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
if (opt::test(axis::option::underflow)) {
if (dit->idx == 0) {
// axis has underflow and we are in the underflow bin:
// keep storage pointer unchanged
++dit;
++sit;
return;
}
}
if (opt::test(axis::option::overflow)) {
if (dit->idx == dit->old_extent - 1) {
// axis has overflow and we are in the overflow bin:
// move storage pointer to corresponding overflow bin position
ns += (axis::traits::extent(a) - 1) * dit->new_stride;
++dit;
++sit;
return;
}
}
// we are in a normal bin:
// move storage pointer to index position; apply positive shifts if any
ns += (dit->idx + (*sit >= 0 ? *sit : 0)) * dit->new_stride;
++dit;
++sit;
});
// assign old value to new location
*ns = x;
// advance multi-dimensional index
dit = data_;
++dit->idx;
while (dit != dlast && dit->idx == dit->old_extent) {
dit->idx = 0;
++(++dit)->idx;
}
}
storage = std::move(new_storage);
}
};
template <class T, class... Us>
auto fill_storage_element_impl(priority<2>, T&& t, const Us&... args) noexcept
-> decltype(t(args...), void()) {
t(args...);
}
template <class T, class U>
auto fill_storage_element_impl(priority<1>, T&& t, const weight_type<U>& w) noexcept
-> decltype(t += w, void()) {
t += w;
}
// fallback for arithmetic types and accumulators that do not handle the weight
template <class T, class U>
auto fill_storage_element_impl(priority<0>, T&& t, const weight_type<U>& w) noexcept
-> decltype(t += w.value, void()) {
t += w.value;
}
template <class T>
auto fill_storage_element_impl(priority<1>, T&& t) noexcept -> decltype(++t, void()) {
++t;
}
template <class T, class... Us>
void fill_storage_element(T&& t, const Us&... args) noexcept {
fill_storage_element_impl(priority<2>{}, std::forward<T>(t), args...);
}
// t may be a proxy and then it is an rvalue reference, not an lvalue reference
template <class IW, class IS, class T, class U>
void fill_storage_2(IW, IS, T&& t, U&& u) noexcept {
mp11::tuple_apply(
[&](const auto&... args) {
fill_storage_element(std::forward<T>(t), std::get<IW::value>(u), args...);
},
std::get<IS::value>(u).value);
}
// t may be a proxy and then it is an rvalue reference, not an lvalue reference
template <class IS, class T, class U>
void fill_storage_2(mp11::mp_int<-1>, IS, T&& t, const U& u) noexcept {
mp11::tuple_apply(
[&](const auto&... args) { fill_storage_element(std::forward<T>(t), args...); },
std::get<IS::value>(u).value);
}
// t may be a proxy and then it is an rvalue reference, not an lvalue reference
template <class IW, class T, class U>
void fill_storage_2(IW, mp11::mp_int<-1>, T&& t, const U& u) noexcept {
fill_storage_element(std::forward<T>(t), std::get<IW::value>(u));
}
// t may be a proxy and then it is an rvalue reference, not an lvalue reference
template <class T, class U>
void fill_storage_2(mp11::mp_int<-1>, mp11::mp_int<-1>, T&& t, const U&) noexcept {
fill_storage_element(std::forward<T>(t));
}
template <class IW, class IS, class Storage, class Index, class Args>
auto fill_storage(IW, IS, Storage& s, const Index idx, const Args& a) noexcept {
if (is_valid(idx)) {
assert(idx < s.size());
fill_storage_2(IW{}, IS{}, s[idx], a);
return s.begin() + idx;
}
return s.end();
}
template <int S, int N>
struct linearize_args {
template <class Index, class A, class Args>
static void impl(mp11::mp_int<N>, Index&, const std::size_t, A&, const Args&) {}
template <int I, class Index, class A, class Args>
static void impl(mp11::mp_int<I>, Index& o, const std::size_t s, A& ax,
const Args& args) {
const auto e = linearize(o, s, axis_get<I>(ax), std::get<(S + I)>(args));
impl(mp11::mp_int<(I + 1)>{}, o, s * e, ax, args);
}
template <class Index, class A, class Args>
static void apply(Index& o, A& ax, const Args& args) {
impl(mp11::mp_int<0>{}, o, 1, ax, args);
}
};
template <int S>
struct linearize_args<S, 1> {
template <class Index, class A, class Args>
static void apply(Index& o, A& ax, const Args& args) {
linearize(o, 1, axis_get<0>(ax), std::get<S>(args));
}
};
template <class A>
constexpr unsigned(min)(const unsigned n) noexcept {
constexpr unsigned a = buffer_size<A>::value;
return a < n ? a : n;
}
// not growing
template <class ArgTraits, class Storage, class Axes, class Args>
auto fill_2(ArgTraits, mp11::mp_false, const std::size_t offset, Storage& st,
const Axes& axes, const Args& args) {
mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{offset};
linearize_args<ArgTraits::start::value, min<Axes>(ArgTraits::nargs::value)>::apply(
idx, axes, args);
return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx,
args);
}
// at least one axis is growing
template <class ArgTraits, class Storage, class Axes, class Args>
auto fill_2(ArgTraits, mp11::mp_true, const std::size_t, Storage& st, Axes& axes,
const Args& args) {
std::array<axis::index_type, ArgTraits::nargs::value> shifts;
// offset must be zero for linearize_growth (value of offset argument is ignored)
mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{0};
std::size_t stride = 1;
bool update_needed = false;
mp11::mp_for_each<mp11::mp_iota_c<min<Axes>(ArgTraits::nargs::value)>>([&](auto i) {
auto& ax = axis_get<i>(axes);
const auto extent = linearize_growth(idx, shifts[i], stride, ax,
std::get<(ArgTraits::start::value + i)>(args));
update_needed |= shifts[i] != 0;
stride *= extent;
});
if (update_needed) {
storage_grower<Axes> g(axes);
g.from_shifts(shifts.data());
g.apply(st, shifts.data());
}
return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx,
args);
}
// pack original args tuple into another tuple (which is unpacked later)
template <int Start, int Size, class IW, class IS, class Args>
decltype(auto) pack_args(IW, IS, const Args& args) noexcept {
return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args),
std::get<IS::value>(args));
}
template <int Start, int Size, class IW, class Args>
decltype(auto) pack_args(IW, mp11::mp_int<-1>, const Args& args) noexcept {
return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args));
}
template <int Start, int Size, class IS, class Args>
decltype(auto) pack_args(mp11::mp_int<-1>, IS, const Args& args) noexcept {
return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IS::value>(args));
}
template <int Start, int Size, class Args>
decltype(auto) pack_args(mp11::mp_int<-1>, mp11::mp_int<-1>, const Args& args) noexcept {
return std::make_tuple(args);
}
#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
#pragma warning(disable : 4702) // fixing warning would reduce code readability a lot
#endif
template <class ArgTraits, class S, class A, class Args>
auto fill(std::true_type, ArgTraits, const std::size_t offset, S& storage, A& axes,
const Args& args) -> typename S::iterator {
using growing = has_growing_axis<A>;
// Sometimes we need to pack the tuple into another tuple:
// - histogram contains one axis which accepts tuple
// - user passes tuple to fill(...)
// Tuple is normally unpacked and arguments are processed, this causes pos::nargs > 1.
// Now we pack tuple into another tuple so that original tuple is send to axis.
// Notes:
// - has nice side-effect of making histogram::operator(1, 2) work as well
// - cannot detect call signature of axis at compile-time in all configurations
// (axis::variant provides generic call interface and hides concrete
// interface), so we throw at runtime if incompatible argument is passed (e.g.
// 3d tuple)
if (axes_rank(axes) == ArgTraits::nargs::value)
return fill_2(ArgTraits{}, growing{}, offset, storage, axes, args);
else if (axes_rank(axes) == 1 &&
axis::traits::rank(axis_get<0>(axes)) == ArgTraits::nargs::value)
return fill_2(
argument_traits_holder<
1, 0, (ArgTraits::wpos::value >= 0 ? 1 : -1),
(ArgTraits::spos::value >= 0 ? (ArgTraits::wpos::value >= 0 ? 2 : 1) : -1),
typename ArgTraits::sargs>{},
growing{}, offset, storage, axes,
pack_args<ArgTraits::start::value, ArgTraits::nargs::value>(
typename ArgTraits::wpos{}, typename ArgTraits::spos{}, args));
return BOOST_THROW_EXCEPTION(
std::invalid_argument("number of arguments != histogram rank")),
storage.end();
}
#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
#pragma warning(default : 4702)
#endif
// empty implementation for bad arguments to stop compiler from showing internals
template <class ArgTraits, class S, class A, class Args>
auto fill(std::false_type, ArgTraits, const std::size_t, S& storage, A&, const Args&) ->
typename S::iterator {
return storage.end();
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+349
View File
@@ -0,0 +1,349 @@
// 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_DETAIL_FILL_N_HPP
#define BOOST_HISTOGRAM_DETAIL_FILL_N_HPP
#include <algorithm>
#include <boost/core/make_span.hpp>
#include <boost/core/span.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/fill.hpp>
#include <boost/histogram/detail/linearize.hpp>
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <boost/histogram/detail/optional_index.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/bind.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <cassert>
#include <initializer_list>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
namespace dtl = boost::histogram::detail;
template <class Axes, class T>
using is_convertible_to_any_value_type =
mp11::mp_any_of_q<value_types<Axes>, mp11::mp_bind_front<std::is_convertible, T>>;
template <class T>
auto to_ptr_size(const T& x) {
return static_if<std::is_scalar<T>>(
[](const auto& x) { return std::make_pair(&x, static_cast<std::size_t>(0)); },
[](const auto& x) { return std::make_pair(dtl::data(x), dtl::size(x)); }, x);
}
template <class F, class V>
decltype(auto) maybe_visit(F&& f, V&& v) {
return static_if<is_variant<std::decay_t<V>>>(
[](auto&& f, auto&& v) {
return variant2::visit(std::forward<F>(f), std::forward<V>(v));
},
[](auto&& f, auto&& v) { return std::forward<F>(f)(std::forward<V>(v)); },
std::forward<F>(f), std::forward<V>(v));
}
template <class Index, class Axis, class IsGrowing>
struct index_visitor {
using index_type = Index;
using pointer = index_type*;
using value_type = axis::traits::value_type<Axis>;
using Opt = axis::traits::get_options<Axis>;
Axis& axis_;
const std::size_t stride_, start_, size_; // start and size of value collection
const pointer begin_;
axis::index_type* shift_;
index_visitor(Axis& a, std::size_t& str, const std::size_t& sta, const std::size_t& si,
const pointer it, axis::index_type* shift)
: axis_(a), stride_(str), start_(sta), size_(si), begin_(it), shift_(shift) {}
template <class T>
void call_2(std::true_type, pointer it, const T& x) const {
// must use this code for all axes if one of them is growing
axis::index_type shift;
linearize_growth(*it, shift, stride_, axis_,
try_cast<value_type, std::invalid_argument>(x));
if (shift > 0) { // shift previous indices, because axis zero-point has changed
while (it != begin_) *--it += static_cast<std::size_t>(shift) * stride_;
*shift_ += shift;
}
}
template <class T>
void call_2(std::false_type, pointer it, const T& x) const {
// no axis is growing
linearize(*it, stride_, axis_, try_cast<value_type, std::invalid_argument>(x));
}
template <class T>
void call_1(std::false_type, const T& iterable) const {
// T is iterable; fill N values
const auto* tp = dtl::data(iterable) + start_;
for (auto it = begin_; it != begin_ + size_; ++it) call_2(IsGrowing{}, it, *tp++);
}
template <class T>
void call_1(std::true_type, const T& value) const {
// T is compatible value; fill single value N times
// Optimization: We call call_2 only once and then add the index shift onto the
// whole array of indices, because it is always the same. This also works if the
// axis grows during this operation. There are no shifts to apply if the zero-point
// changes.
const auto before = *begin_;
call_2(IsGrowing{}, begin_, value);
if (is_valid(*begin_)) {
// since index can be std::size_t or optional_index, must do conversion here
const auto delta =
static_cast<std::intptr_t>(*begin_) - static_cast<std::intptr_t>(before);
for (auto it = begin_ + 1; it != begin_ + size_; ++it) *it += delta;
} else
std::fill(begin_, begin_ + size_, invalid_index);
}
template <class T>
void operator()(const T& iterable_or_value) const {
call_1(mp11::mp_bool<(std::is_convertible<T, value_type>::value ||
!is_iterable<T>::value)>{},
iterable_or_value);
}
};
template <class Index, class S, class Axes, class T>
void fill_n_indices(Index* indices, const std::size_t start, const std::size_t size,
const std::size_t offset, S& storage, Axes& axes, const T* viter) {
axis::index_type extents[buffer_size<Axes>::value];
axis::index_type shifts[buffer_size<Axes>::value];
for_each_axis(axes, [eit = extents, sit = shifts](const auto& a) mutable {
*sit++ = 0;
*eit++ = axis::traits::extent(a);
}); // LCOV_EXCL_LINE: gcc-8 is missing this line for no reason
// TODO this seems to always take the path for growing axes, even if Axes is vector
// of variant and types actually held are not growing axes?
// index offset must be zero for growing axes
using IsGrowing = has_growing_axis<Axes>;
std::fill(indices, indices + size, IsGrowing::value ? 0 : offset);
for_each_axis(axes, [&, stride = static_cast<std::size_t>(1),
pshift = shifts](auto& axis) mutable {
using Axis = std::decay_t<decltype(axis)>;
maybe_visit(
index_visitor<Index, Axis, IsGrowing>{axis, stride, start, size, indices, pshift},
*viter++);
stride *= static_cast<std::size_t>(axis::traits::extent(axis));
++pshift;
});
bool update_needed = false;
for_each_axis(axes, [&update_needed, eit = extents](const auto& a) mutable {
update_needed |= *eit++ != axis::traits::extent(a);
});
if (update_needed) {
storage_grower<Axes> g(axes);
g.from_extents(extents);
g.apply(storage, shifts);
}
}
template <class S, class Index, class... Ts>
void fill_n_storage(S& s, const Index idx, Ts&&... p) noexcept {
if (is_valid(idx)) {
assert(idx < s.size());
fill_storage_element(s[idx], *p.first...);
}
// operator folding emulation
(void)std::initializer_list<int>{(p.second ? (++p.first, 0) : 0)...};
}
template <class S, class Index, class T, class... Ts>
void fill_n_storage(S& s, const Index idx, weight_type<T>&& w, Ts&&... ps) noexcept {
if (is_valid(idx)) {
assert(idx < s.size());
fill_storage_element(s[idx], weight(*w.value.first), *ps.first...);
}
if (w.value.second) ++w.value.first;
// operator folding emulation
(void)std::initializer_list<int>{(ps.second ? (++ps.first, 0) : 0)...};
}
// general Nd treatment
template <class Index, class S, class A, class T, class... Ts>
void fill_n_nd(const std::size_t offset, S& storage, A& axes, const std::size_t vsize,
const T* values, Ts&&... ts) {
constexpr std::size_t buffer_size = 1ul << 14;
Index indices[buffer_size];
/*
Parallelization options.
A) Run the whole fill2 method in parallel, each thread fills its own buffer of
indices, synchronization (atomics) are needed to synchronize the incrementing of
the storage cells. This leads to a lot of congestion for small histograms.
B) Run only fill_n_indices in parallel, subsections of the indices buffer
can be filled by different threads. The final loop that fills the storage runs
in the main thread, this requires no synchronization for the storage, cells do
not need to support atomic operations.
C) Like B), then sort the indices in the main thread and fill the
storage in parallel, where each thread uses a disjunct set of indices. This
should create less congestion and requires no synchronization for the storage.
Note on C): Let's say we have an axis with 5 bins (with *flow to simplify).
Then after filling 10 values, converting to indices and sorting, the index
buffer may look like this: 0 0 0 1 2 2 2 4 4 5. Let's use two threads to fill
the storage. Still in the main thread, we compute an iterator to the middle of
the index buffer and move it to the right until the pointee changes. Now we have
two ranges which contain disjunct sets of indices. We pass these ranges to the
threads which then fill the storage. Since the threads by construction do not
compete to increment the same cell, no further synchronization is required.
In all cases, growing axes cannot be parallelized.
*/
for (std::size_t start = 0; start < vsize; start += buffer_size) {
const std::size_t n = (std::min)(buffer_size, vsize - start);
// fill buffer of indices...
fill_n_indices(indices, start, n, offset, storage, axes, values);
// ...and fill corresponding storage cells
for (auto&& idx : make_span(indices, n))
fill_n_storage(storage, idx, std::forward<Ts>(ts)...);
}
}
template <class S, class... As, class T, class... Us>
void fill_n_1(const std::size_t offset, S& storage, std::tuple<As...>& axes,
const std::size_t vsize, const T* values, Us&&... us) {
using index_type =
mp11::mp_if<has_non_inclusive_axis<std::tuple<As...>>, optional_index, std::size_t>;
fill_n_nd<index_type>(offset, storage, axes, vsize, values, std::forward<Us>(us)...);
}
template <class S, class A, class T, class... Us>
void fill_n_1(const std::size_t offset, S& storage, A& axes, const std::size_t vsize,
const T* values, Us&&... us) {
bool all_inclusive = true;
for_each_axis(axes,
[&](const auto& ax) { all_inclusive &= axis::traits::inclusive(ax); });
if (axes_rank(axes) == 1) {
// Optimization: benchmark shows that this makes filling dynamic 1D histogram faster
axis::visit(
[&](auto& ax) {
std::tuple<decltype(ax)> axes{ax};
fill_n_1(offset, storage, axes, vsize, values, std::forward<Us>(us)...);
},
axes[0]);
} else {
if (all_inclusive)
fill_n_nd<std::size_t>(offset, storage, axes, vsize, values,
std::forward<Us>(us)...);
else
fill_n_nd<optional_index>(offset, storage, axes, vsize, values,
std::forward<Us>(us)...);
}
}
template <class A, class T, std::size_t N>
std::size_t get_total_size(const A& axes, const span<const T, N>& values) {
// supported cases (T = value type; CT = containter of T; V<T, CT, ...> = variant):
// - span<CT, N>: for any histogram, N == rank
// - span<V<T, CT>, N>: for any histogram, N == rank
assert(axes_rank(axes) == values.size());
constexpr auto unset = static_cast<std::size_t>(-1);
std::size_t size = unset;
for_each_axis(axes, [&size, vit = values.begin()](const auto& ax) mutable {
using AV = axis::traits::value_type<std::decay_t<decltype(ax)>>;
maybe_visit(
[&size](const auto& v) {
// v is either convertible to value or a sequence of values
using V = std::remove_const_t<std::remove_reference_t<decltype(v)>>;
static_if_c<(std::is_convertible<decltype(v), AV>::value ||
!is_iterable<V>::value)>(
[](const auto&) {},
[&size](const auto& v) {
const auto n = dtl::size(v);
// must repeat this here for msvc :(
constexpr auto unset = static_cast<std::size_t>(-1);
if (size == unset)
size = dtl::size(v);
else if (size != n)
BOOST_THROW_EXCEPTION(
std::invalid_argument("spans must have compatible lengths"));
},
v);
},
*vit++);
});
// if all arguments are not iterables, return size of 1
return size == unset ? 1 : size;
}
inline void fill_n_check_extra_args(std::size_t) noexcept {}
template <class T, class... Ts>
void fill_n_check_extra_args(std::size_t size, T&& x, Ts&&... ts) {
// sequences must have same lengths, but sequences of length 0 are broadcast
if (x.second != 0 && x.second != size)
BOOST_THROW_EXCEPTION(std::invalid_argument("spans must have compatible lengths"));
fill_n_check_extra_args(size, std::forward<Ts>(ts)...);
}
template <class T, class... Ts>
void fill_n_check_extra_args(std::size_t size, weight_type<T>&& w, Ts&&... ts) {
fill_n_check_extra_args(size, w.value, std::forward<Ts>(ts)...);
}
template <class S, class A, class T, std::size_t N, class... Us>
void fill_n(std::true_type, const std::size_t offset, S& storage, A& axes,
const span<const T, N> values, Us&&... us) {
// supported cases (T = value type; CT = containter of T; V<T, CT, ...> = variant):
// - span<T, N>: only valid for 1D histogram, N > 1 allowed
// - span<CT, N>: for any histogram, N == rank
// - span<V<T, CT>, N>: for any histogram, N == rank
static_if<is_convertible_to_any_value_type<A, T>>(
[&](const auto& values, auto&&... us) {
// T matches one of the axis value types, must be 1D special case
if (axes_rank(axes) != 1)
BOOST_THROW_EXCEPTION(
std::invalid_argument("number of arguments must match histogram rank"));
fill_n_check_extra_args(values.size(), std::forward<Us>(us)...);
fill_n_1(offset, storage, axes, values.size(), &values, std::forward<Us>(us)...);
},
[&](const auto& values, auto&&... us) {
// generic ND case
if (axes_rank(axes) != values.size())
BOOST_THROW_EXCEPTION(
std::invalid_argument("number of arguments must match histogram rank"));
const auto vsize = get_total_size(axes, values);
fill_n_check_extra_args(vsize, std::forward<Us>(us)...);
fill_n_1(offset, storage, axes, vsize, values.data(), std::forward<Us>(us)...);
},
values, std::forward<Us>(us)...);
}
// empty implementation for bad arguments to stop compiler from showing internals
template <class... Ts>
void fill_n(std::false_type, Ts...) {}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DETAIL_FILL_N_HPP
@@ -0,0 +1,8 @@
#if (defined(__GNUC__) || defined(__clang__))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning(push) // preserve warning settings
#pragma warning(disable : 4996) // disable depricated localtime/gmtime warning on vc8
#endif
@@ -0,0 +1,6 @@
#if (defined(__GNUC__) || defined(__clang__))
#pragma GCC diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif
+113
View File
@@ -0,0 +1,113 @@
// 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_DETAIL_INDEX_TRANSLATOR_HPP
#define BOOST_HISTOGRAM_DETAIL_INDEX_TRANSLATOR_HPP
#include <algorithm>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/axis/variant.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/relaxed_tuple_size.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/multi_index.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/integer_sequence.hpp>
#include <cassert>
#include <initializer_list>
#include <tuple>
#include <vector>
namespace boost {
namespace histogram {
namespace detail {
template <class A>
struct index_translator {
using index_type = axis::index_type;
using multi_index_type = multi_index<relaxed_tuple_size_t<A>::value>;
using cref = const A&;
cref dst, src;
bool pass_through[buffer_size<A>::value];
index_translator(cref d, cref s) : dst{d}, src{s} { init(dst, src); }
template <class T>
void init(const T& a, const T& b) {
std::transform(a.begin(), a.end(), b.begin(), pass_through,
[](const auto& a, const auto& b) {
return axis::visit(
[&](const auto& a) {
using U = std::decay_t<decltype(a)>;
return relaxed_equal{}(a, axis::get<U>(b));
},
a);
});
}
template <class... Ts>
void init(const std::tuple<Ts...>& a, const std::tuple<Ts...>& b) {
using Seq = mp11::mp_iota_c<sizeof...(Ts)>;
mp11::mp_for_each<Seq>([&](auto I) {
pass_through[I] = relaxed_equal{}(std::get<I>(a), std::get<I>(b));
});
}
template <class T>
static index_type translate(const T& dst, const T& src, index_type i) noexcept {
assert(axis::traits::is_continuous<T>::value == false); // LCOV_EXCL_LINE: unreachable
return dst.index(src.value(i));
}
template <class... Ts, class It>
void impl(const std::tuple<Ts...>& a, const std::tuple<Ts...>& b, It i,
index_type* j) const noexcept {
using Seq = mp11::mp_iota_c<sizeof...(Ts)>;
mp11::mp_for_each<Seq>([&](auto I) {
if (pass_through[I])
*(j + I) = *(i + I);
else
*(j + I) = this->translate(std::get<I>(a), std::get<I>(b), *(i + I));
});
}
template <class T, class It>
void impl(const T& a, const T& b, It i, index_type* j) const noexcept {
const bool* p = pass_through;
for (unsigned k = 0; k < a.size(); ++k, ++i, ++j, ++p) {
if (*p)
*j = *i;
else {
const auto& bk = b[k];
axis::visit(
[&](const auto& ak) {
using U = std::decay_t<decltype(ak)>;
*j = this->translate(ak, axis::get<U>(bk), *i);
},
a[k]);
}
}
}
template <class Indices>
auto operator()(const Indices& seq) const noexcept {
auto mi = multi_index_type::create(seq.size());
impl(dst, src, seq.begin(), mi.begin());
return mi;
}
};
template <class Axes>
auto make_index_translator(const Axes& dst, const Axes& src) noexcept {
return index_translator<Axes>{dst, src};
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+175
View File
@@ -0,0 +1,175 @@
// 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)
//
// Uses code segments from boost/iterator/iterator_adaptor.hpp
// and boost/iterator/iterator_fascade.hpp
#ifndef BOOST_HISTOGRAM_DETAIL_ITERATOR_ADAPTOR_HPP
#define BOOST_HISTOGRAM_DETAIL_ITERATOR_ADAPTOR_HPP
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
// operator->() needs special support for input iterators to strictly meet the
// standard's requirements. If *i is not a reference type, we must still
// produce an lvalue to which a pointer can be formed. We do that by
// returning a proxy object containing an instance of the reference object.
template <class Reference>
struct operator_arrow_dispatch_t {
struct pointer {
explicit pointer(Reference const& x) noexcept : m_ref(x) {}
Reference* operator->() noexcept { return std::addressof(m_ref); }
Reference m_ref;
};
using result_type = pointer;
static result_type apply(Reference const& x) noexcept { return pointer(x); }
};
// specialization for "real" references
template <class T>
struct operator_arrow_dispatch_t<T&> {
using result_type = T*;
static result_type apply(T& x) noexcept { return std::addressof(x); }
};
// it is ok if void_t is already defined in another header
template <class...>
using void_t = void;
template <class T, class = void>
struct get_difference_type_impl : std::make_signed<T> {};
template <class T>
struct get_difference_type_impl<
T, void_t<typename std::iterator_traits<T>::difference_type>> {
using type = typename std::iterator_traits<T>::difference_type;
};
template <class T>
using get_difference_type = typename get_difference_type_impl<T>::type;
// adaptor supports only random access Base
// Base: underlying base type of the iterator; can be iterator, pointer, integer
// Reference: type returned when pointer is dereferenced
template <class Derived, class Base, class Reference = std::remove_pointer_t<Base>&,
class Value = std::decay_t<Reference>>
class iterator_adaptor {
using operator_arrow_dispatch = operator_arrow_dispatch_t<Reference>;
public:
using base_type = Base;
using reference = Reference;
using value_type = Value;
using pointer = typename operator_arrow_dispatch::result_type;
using difference_type = get_difference_type<base_type>;
using iterator_category = std::random_access_iterator_tag;
iterator_adaptor() = default;
explicit iterator_adaptor(base_type const& iter) : iter_(iter) {}
// you can override this in derived
decltype(auto) operator*() const noexcept { return *iter_; }
// you can override this in derived
Derived& operator+=(difference_type n) {
iter_ += n;
return this->derived();
}
// you should override this in derived if there is an override for operator+=
template <class... Ts>
difference_type operator-(const iterator_adaptor<Ts...>& x) const noexcept {
return iter_ - x.iter_;
}
// you can override this in derived
template <class... Ts>
bool operator==(const iterator_adaptor<Ts...>& x) const noexcept {
return iter_ == x.iter_;
}
reference operator[](difference_type n) const { return *(this->derived() + n); }
pointer operator->() const noexcept {
return operator_arrow_dispatch::apply(this->derived().operator*());
}
Derived& operator-=(difference_type n) { return this->derived().operator+=(-n); }
Derived& operator++() { return this->derived().operator+=(1); }
Derived& operator--() { return this->derived().operator+=(-1); }
Derived operator++(int) {
Derived tmp(this->derived());
operator++();
return tmp;
}
Derived operator--(int) {
Derived tmp(this->derived());
operator--();
return tmp;
}
Derived operator+(difference_type n) const { return Derived(this->derived()) += n; }
Derived operator-(difference_type n) const { return Derived(this->derived()) -= n; }
template <class... Ts>
bool operator!=(const iterator_adaptor<Ts...>& x) const noexcept {
return !this->derived().operator==(x);
}
template <class... Ts>
bool operator<(const iterator_adaptor<Ts...>& x) const noexcept {
return iter_ < x.iter_;
}
template <class... Ts>
bool operator>=(const iterator_adaptor<Ts...>& x) const noexcept {
return iter_ >= x.iter_;
}
template <class... Ts>
bool operator>(const iterator_adaptor<Ts...>& x) const noexcept {
return iter_ > x.iter_;
}
template <class... Ts>
bool operator<=(const iterator_adaptor<Ts...>& x) const noexcept {
return iter_ <= x.iter_;
}
friend Derived operator+(difference_type n, const Derived& x) { return x + n; }
Base const& base() const noexcept { return iter_; }
protected:
// for convenience: refer to base class in derived class
using iterator_adaptor_ = iterator_adaptor;
private:
Derived& derived() noexcept { return *static_cast<Derived*>(this); }
const Derived& derived() const noexcept { return *static_cast<Derived const*>(this); }
base_type iter_;
template <class, class, class, class>
friend class iterator_adaptor;
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+255
View File
@@ -0,0 +1,255 @@
// Copyright 2018-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_DETAIL_LARGE_INT_HPP
#define BOOST_HISTOGRAM_DETAIL_LARGE_INT_HPP
#include <boost/histogram/detail/operators.hpp>
#include <boost/histogram/detail/safe_comparison.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/function.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
using is_unsigned_integral = mp11::mp_and<std::is_integral<T>, std::is_unsigned<T>>;
template <class T>
bool safe_increment(T& t) {
if (t < (std::numeric_limits<T>::max)()) {
++t;
return true;
}
return false;
}
template <class T, class U>
bool safe_radd(T& t, const U& u) {
static_assert(is_unsigned_integral<T>::value, "T must be unsigned integral type");
static_assert(is_unsigned_integral<U>::value, "T must be unsigned integral type");
if (static_cast<T>((std::numeric_limits<T>::max)() - t) >= u) {
t += static_cast<T>(u); // static_cast to suppress conversion warning
return true;
}
return false;
}
// An integer type which can grow arbitrarily large (until memory is exhausted).
// Use boost.multiprecision.cpp_int in your own code, it is much more sophisticated.
// We use it only to reduce coupling between boost libs.
template <class Allocator>
struct large_int : totally_ordered<large_int<Allocator>, large_int<Allocator>>,
partially_ordered<large_int<Allocator>, void> {
explicit large_int(const Allocator& a = {}) : data(1, 0, a) {}
explicit large_int(std::uint64_t v, const Allocator& a = {}) : data(1, v, a) {}
large_int(const large_int&) = default;
large_int& operator=(const large_int&) = default;
large_int(large_int&&) = default;
large_int& operator=(large_int&&) = default;
large_int& operator=(std::uint64_t o) {
data = decltype(data)(1, o);
return *this;
}
large_int& operator++() {
assert(data.size() > 0u);
std::size_t i = 0;
while (!safe_increment(data[i])) {
data[i] = 0;
++i;
if (i == data.size()) {
data.push_back(1);
break;
}
}
return *this;
}
large_int& operator+=(const large_int& o) {
if (this == &o) {
auto tmp{o};
return operator+=(tmp);
}
bool carry = false;
std::size_t i = 0;
for (std::uint64_t oi : o.data) {
auto& di = maybe_extend(i);
if (carry) {
if (safe_increment(oi))
carry = false;
else {
++i;
continue;
}
}
if (!safe_radd(di, oi)) {
add_remainder(di, oi);
carry = true;
}
++i;
}
while (carry) {
auto& di = maybe_extend(i);
if (safe_increment(di)) break;
di = 0;
++i;
}
return *this;
}
large_int& operator+=(std::uint64_t o) {
assert(data.size() > 0u);
if (safe_radd(data[0], o)) return *this;
add_remainder(data[0], o);
// carry the one, data may grow several times
std::size_t i = 1;
while (true) {
auto& di = maybe_extend(i);
if (safe_increment(di)) break;
di = 0;
++i;
}
return *this;
}
explicit operator double() const noexcept {
assert(data.size() > 0u);
double result = static_cast<double>(data[0]);
std::size_t i = 0;
while (++i < data.size())
result += static_cast<double>(data[i]) * std::pow(2.0, i * 64);
return result;
}
bool operator<(const large_int& o) const noexcept {
assert(data.size() > 0u);
assert(o.data.size() > 0u);
// no leading zeros allowed
assert(data.size() == 1 || data.back() > 0u);
assert(o.data.size() == 1 || o.data.back() > 0u);
if (data.size() < o.data.size()) return true;
if (data.size() > o.data.size()) return false;
auto s = data.size();
while (s > 0u) {
--s;
if (data[s] < o.data[s]) return true;
if (data[s] > o.data[s]) return false;
}
return false; // args are equal
}
bool operator==(const large_int& o) const noexcept {
assert(data.size() > 0u);
assert(o.data.size() > 0u);
// no leading zeros allowed
assert(data.size() == 1 || data.back() > 0u);
assert(o.data.size() == 1 || o.data.back() > 0u);
if (data.size() != o.data.size()) return false;
return std::equal(data.begin(), data.end(), o.data.begin());
}
template <class U>
std::enable_if_t<std::is_integral<U>::value, bool> operator<(
const U& o) const noexcept {
assert(data.size() > 0u);
return data.size() == 1 && safe_less()(data[0], o);
}
template <class U>
std::enable_if_t<std::is_integral<U>::value, bool> operator>(
const U& o) const noexcept {
assert(data.size() > 0u);
assert(data.size() == 1 || data.back() > 0u); // no leading zeros allowed
return data.size() > 1 || safe_less()(o, data[0]);
}
template <class U>
std::enable_if_t<std::is_integral<U>::value, bool> operator==(
const U& o) const noexcept {
assert(data.size() > 0u);
return data.size() == 1 && safe_equal()(data[0], o);
}
template <class U>
std::enable_if_t<std::is_floating_point<U>::value, bool> operator<(
const U& o) const noexcept {
return operator double() < o;
}
template <class U>
std::enable_if_t<std::is_floating_point<U>::value, bool> operator>(
const U& o) const noexcept {
return operator double() > o;
}
template <class U>
std::enable_if_t<std::is_floating_point<U>::value, bool> operator==(
const U& o) const noexcept {
return operator double() == o;
}
template <class U>
std::enable_if_t<
(!std::is_arithmetic<U>::value && std::is_convertible<U, double>::value), bool>
operator<(const U& o) const noexcept {
return operator double() < o;
}
template <class U>
std::enable_if_t<
(!std::is_arithmetic<U>::value && std::is_convertible<U, double>::value), bool>
operator>(const U& o) const noexcept {
return operator double() > o;
}
template <class U>
std::enable_if_t<
(!std::is_arithmetic<U>::value && std::is_convertible<U, double>::value), bool>
operator==(const U& o) const noexcept {
return operator double() == o;
}
std::uint64_t& maybe_extend(std::size_t i) {
while (i >= data.size()) data.push_back(0);
return data[i];
}
static void add_remainder(std::uint64_t& d, const std::uint64_t o) noexcept {
assert(d > 0u);
// in decimal system it would look like this:
// 8 + 8 = 6 = 8 - (9 - 8) - 1
// 9 + 1 = 0 = 9 - (9 - 1) - 1
auto tmp = (std::numeric_limits<std::uint64_t>::max)();
tmp -= o;
--d -= tmp;
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("data", data);
}
std::vector<std::uint64_t, Allocator> data;
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2015-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_DETAIL_LIMITS_HPP
#define BOOST_HISTOGRAM_DETAIL_LIMITS_HPP
#include <limits>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
constexpr T lowest() {
return std::numeric_limits<T>::lowest();
}
template <>
constexpr double lowest() {
return -std::numeric_limits<double>::infinity();
}
template <>
constexpr float lowest() {
return -std::numeric_limits<float>::infinity();
}
template <class T>
constexpr T highest() {
return (std::numeric_limits<T>::max)();
}
template <>
constexpr double highest() {
return std::numeric_limits<double>::infinity();
}
template <>
constexpr float highest() {
return std::numeric_limits<float>::infinity();
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+132
View File
@@ -0,0 +1,132 @@
// 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_DETAIL_LINEARIZE_HPP
#define BOOST_HISTOGRAM_DETAIL_LINEARIZE_HPP
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/axis/variant.hpp>
#include <boost/histogram/detail/optional_index.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/multi_index.hpp>
#include <cassert>
namespace boost {
namespace histogram {
namespace detail {
// initial offset to out must be set;
// this faster code can be used if all axes are inclusive
template <class Opts>
std::size_t linearize(Opts, std::size_t& out, const std::size_t stride,
const axis::index_type size, const axis::index_type idx) {
constexpr bool u = Opts::test(axis::option::underflow);
constexpr bool o = Opts::test(axis::option::overflow);
assert(idx >= (u ? -1 : 0));
assert(idx < (o ? size + 1 : size));
assert(idx >= 0 || static_cast<std::size_t>(-idx * stride) <= out);
out += idx * stride;
return size + u + o;
}
// initial offset to out must be set
// this slower code must be used if not all axes are inclusive
template <class Opts>
std::size_t linearize(Opts, optional_index& out, const std::size_t stride,
const axis::index_type size, const axis::index_type idx) {
constexpr bool u = Opts::test(axis::option::underflow);
constexpr bool o = Opts::test(axis::option::overflow);
assert(idx >= -1);
assert(idx < size + 1);
const bool is_valid = (u || idx >= 0) && (o || idx < size);
if (is_valid)
out += idx * stride;
else
out = invalid_index;
return size + u + o;
}
template <class Index, class Axis, class Value>
std::size_t linearize(Index& out, const std::size_t stride, const Axis& ax,
const Value& v) {
// mask options to reduce no. of template instantiations
constexpr auto opts = axis::traits::get_options<Axis>{} &
(axis::option::underflow | axis::option::overflow);
return linearize(opts, out, stride, ax.size(), axis::traits::index(ax, v));
}
/**
Must be used when axis is potentially growing. Also works for non-growing axis.
Initial offset of `out` must be zero. We cannot assert on this, because we do not
know if this is the first call of `linearize_growth`.
*/
template <class Index, class Axis, class Value>
std::size_t linearize_growth(Index& out, axis::index_type& shift,
const std::size_t stride, Axis& a, const Value& v) {
axis::index_type idx;
std::tie(idx, shift) = axis::traits::update(a, v);
constexpr bool u = axis::traits::get_options<Axis>::test(axis::option::underflow);
if (u) ++idx;
if (std::is_same<Index, std::size_t>::value) {
assert(idx < axis::traits::extent(a));
out += idx * stride;
} else {
if (0 <= idx && idx < axis::traits::extent(a))
out += idx * stride;
else
out = invalid_index;
}
return axis::traits::extent(a);
}
// initial offset of out must be zero
template <class A>
std::size_t linearize_index(optional_index& out, const std::size_t stride, const A& ax,
const axis::index_type idx) noexcept {
const auto opt = axis::traits::get_options<A>();
const axis::index_type begin = opt & axis::option::underflow ? -1 : 0;
const axis::index_type end = opt & axis::option::overflow ? ax.size() + 1 : ax.size();
const axis::index_type extent = end - begin;
// i may be arbitrarily out of range
if (begin <= idx && idx < end)
out += (idx - begin) * stride;
else
out = invalid_index;
return extent;
}
template <class A, std::size_t N>
optional_index linearize_indices(const A& axes, const multi_index<N>& indices) noexcept {
assert(axes_rank(axes) == detail::size(indices));
optional_index idx{0}; // offset not used by linearize_index
auto stride = static_cast<std::size_t>(1);
using std::begin;
auto i = begin(indices);
for_each_axis(axes,
[&](const auto& a) { stride *= linearize_index(idx, stride, a, *i++); });
return idx;
}
template <class Index, class... Ts, class Value>
std::size_t linearize(Index& o, const std::size_t s, const axis::variant<Ts...>& a,
const Value& v) {
return axis::visit([&o, &s, &v](const auto& a) { return linearize(o, s, a, v); }, a);
}
template <class Index, class... Ts, class Value>
std::size_t linearize_growth(Index& o, axis::index_type& sh, const std::size_t st,
axis::variant<Ts...>& a, const Value& v) {
return axis::visit([&](auto& a) { return linearize_growth(o, sh, st, a, v); }, a);
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DETAIL_LINEARIZE_HPP
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2015-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_DETAIL_MAKE_DEFAULT_HPP
#define BOOST_HISTOGRAM_DETAIL_MAKE_DEFAULT_HPP
namespace boost {
namespace histogram {
namespace detail {
template <class T>
T make_default_impl(const T& t, decltype(t.get_allocator(), 0)) {
return T(t.get_allocator());
}
template <class T>
T make_default_impl(const T&, float) {
return T{};
}
template <class T>
T make_default(const T& t) {
return make_default_impl(t, 0);
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+41
View File
@@ -0,0 +1,41 @@
// 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_DETAIL_NOOP_MUTEX_HPP
#define BOOST_HISTOGRAM_DETAIL_NOOP_MUTEX_HPP
#include <boost/core/empty_value.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/mp11/utility.hpp> // mp_if
#include <mutex>
namespace boost {
namespace histogram {
namespace detail {
struct null_mutex {
bool try_lock() noexcept { return true; }
void lock() noexcept {}
void unlock() noexcept {}
};
template <class Axes, class Storage,
class DetailMutex = mp11::mp_if_c<(Storage::has_threading_support &&
detail::has_growing_axis<Axes>::value),
std::mutex, detail::null_mutex>>
struct mutex_base : empty_value<DetailMutex> {
mutex_base() = default;
// do not copy or move mutex
mutex_base(const mutex_base&) : empty_value<DetailMutex>() {}
// do not copy or move mutex
mutex_base& operator=(const mutex_base&) { return *this; }
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
@@ -0,0 +1,62 @@
// 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_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP
#define BOOST_HISTOGRAM_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP
#include <initializer_list>
#include <type_traits>
#include <valarray>
namespace boost {
namespace histogram {
namespace detail {
template <class C>
constexpr auto data(C& c) -> decltype(c.data()) {
return c.data();
}
template <class C>
constexpr auto data(const C& c) -> decltype(c.data()) {
return c.data();
}
template <class T, std::size_t N>
constexpr T* data(T (&array)[N]) noexcept {
return array;
}
template <class E>
constexpr const E* data(std::initializer_list<E> il) noexcept {
return il.begin();
}
template <class E>
constexpr const E* data(const std::valarray<E>& v) noexcept {
return std::begin(v);
}
template <class E>
constexpr E* data(std::valarray<E>& v) noexcept {
return std::begin(v);
}
template <class C>
constexpr auto size(const C& c) -> decltype(c.size()) {
return c.size();
}
template <class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept {
return N;
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2022 Hans Dembinski, Jay Gohil
//
// 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_DETAIL_NORMAL_HPP
#define BOOST_HISTOGRAM_DETAIL_NORMAL_HPP
#include <boost/histogram/detail/erf_inv.hpp>
#include <cmath>
namespace boost {
namespace histogram {
namespace detail {
inline double normal_cdf(double x) noexcept {
return std::fma(0.5, std::erf(x / std::sqrt(2)), 0.5);
}
inline double normal_ppf(double p) noexcept {
return std::sqrt(2) * erf_inv(2 * (p - 0.5));
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+141
View File
@@ -0,0 +1,141 @@
// 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_DETAIL_OPERATORS_HPP
#define BOOST_HISTOGRAM_DETAIL_OPERATORS_HPP
#include <boost/histogram/detail/detect.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class U>
using if_not_same = std::enable_if_t<(!std::is_same<T, U>::value), bool>;
// template <class T, class U>
// using if_not_same_and_has_eq =
// std::enable_if_t<(!std::is_same<T, U>::value && !has_method_eq<T, U>::value),
// bool>;
// totally_ordered is for types with a <= b == !(a > b) [floats with NaN violate this]
// Derived must implement <,== for symmetric form and <,>,== for non-symmetric.
// partially_ordered is for types with a <= b == a < b || a == b [for floats with NaN]
// Derived must implement <,== for symmetric form and <,>,== for non-symmetric.
template <class T, class U>
struct mirrored {
friend bool operator<(const U& a, const T& b) noexcept { return b > a; }
friend bool operator>(const U& a, const T& b) noexcept { return b < a; }
friend bool operator==(const U& a, const T& b) noexcept { return b == a; }
friend bool operator<=(const U& a, const T& b) noexcept { return b >= a; }
friend bool operator>=(const U& a, const T& b) noexcept { return b <= a; }
friend bool operator!=(const U& a, const T& b) noexcept { return b != a; }
}; // namespace histogram
template <class T>
struct mirrored<T, void> {
template <class U>
friend if_not_same<T, U> operator<(const U& a, const T& b) noexcept {
return b > a;
}
template <class U>
friend if_not_same<T, U> operator>(const U& a, const T& b) noexcept {
return b < a;
}
template <class U>
friend std::enable_if_t<(!has_method_eq<U, T>::value), bool> operator==(
const U& a, const T& b) noexcept {
return b.operator==(a);
}
template <class U>
friend if_not_same<T, U> operator<=(const U& a, const T& b) noexcept {
return b >= a;
}
template <class U>
friend if_not_same<T, U> operator>=(const U& a, const T& b) noexcept {
return b <= a;
}
template <class U>
friend if_not_same<T, U> operator!=(const U& a, const T& b) noexcept {
return b != a;
}
};
template <class T>
struct mirrored<T, T> {
friend bool operator>(const T& a, const T& b) noexcept { return b.operator<(a); }
};
template <class T, class U>
struct equality {
friend bool operator!=(const T& a, const U& b) noexcept { return !a.operator==(b); }
};
template <class T>
struct equality<T, void> {
template <class U>
friend if_not_same<T, U> operator!=(const T& a, const U& b) noexcept {
return !(a == b);
}
};
template <class T, class U>
struct totally_ordered_impl : equality<T, U>, mirrored<T, U> {
friend bool operator<=(const T& a, const U& b) noexcept { return !(a > b); }
friend bool operator>=(const T& a, const U& b) noexcept { return !(a < b); }
};
template <class T>
struct totally_ordered_impl<T, void> : equality<T, void>, mirrored<T, void> {
template <class U>
friend if_not_same<T, U> operator<=(const T& a, const U& b) noexcept {
return !(a > b);
}
template <class U>
friend if_not_same<T, U> operator>=(const T& a, const U& b) noexcept {
return !(a < b);
}
};
template <class T, class... Ts>
using totally_ordered = mp11::mp_rename<
mp11::mp_product<totally_ordered_impl, mp11::mp_list<T>, mp11::mp_list<Ts...> >,
mp11::mp_inherit>;
template <class T, class U>
struct partially_ordered_impl : equality<T, U>, mirrored<T, U> {
friend bool operator<=(const T& a, const U& b) noexcept { return a < b || a == b; }
friend bool operator>=(const T& a, const U& b) noexcept { return a > b || a == b; }
};
template <class T>
struct partially_ordered_impl<T, void> : equality<T, void>, mirrored<T, void> {
template <class U>
friend if_not_same<T, U> operator<=(const T& a, const U& b) noexcept {
return a < b || a == b;
}
template <class U>
friend if_not_same<T, U> operator>=(const T& a, const U& b) noexcept {
return a > b || a == b;
}
};
template <class T, class... Ts>
using partially_ordered = mp11::mp_rename<
mp11::mp_product<partially_ordered_impl, mp11::mp_list<T>, mp11::mp_list<Ts...> >,
mp11::mp_inherit>;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DETAIL_OPERATORS_HPP
+55
View File
@@ -0,0 +1,55 @@
// 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_DETAIL_OPTIONAL_INDEX_HPP
#define BOOST_HISTOGRAM_DETAIL_OPTIONAL_INDEX_HPP
#include <cassert>
#include <cstdint>
namespace boost {
namespace histogram {
namespace detail {
constexpr auto invalid_index = ~static_cast<std::size_t>(0);
// integer with a persistent invalid state, similar to NaN
struct optional_index {
std::size_t value;
optional_index& operator=(std::size_t x) noexcept {
value = x;
return *this;
}
optional_index& operator+=(std::intptr_t x) noexcept {
assert(x >= 0 || static_cast<std::size_t>(-x) <= value);
if (value != invalid_index) { value += x; }
return *this;
}
optional_index& operator+=(const optional_index& x) noexcept {
if (value != invalid_index) return operator+=(x.value);
value = invalid_index;
return *this;
}
operator std::size_t() const noexcept { return value; }
friend bool operator<=(std::size_t x, optional_index idx) noexcept {
return x <= idx.value;
}
};
constexpr inline bool is_valid(const std::size_t) noexcept { return true; }
inline bool is_valid(const optional_index x) noexcept { return x.value != invalid_index; }
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+28
View File
@@ -0,0 +1,28 @@
// 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_DETAIL_PRIORITY_HPP
#define BOOST_HISTOGRAM_DETAIL_PRIORITY_HPP
#include <cstdint>
namespace boost {
namespace histogram {
namespace detail {
// priority is used to priorise ambiguous overloads
template <std::size_t N>
struct priority : priority<(N - 1)> {};
template <>
struct priority<0> {};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2020 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_DETAIL_REDUCE_COMMAND_HPP
#define BOOST_HISTOGRAM_DETAIL_REDUCE_COMMAND_HPP
#include <boost/core/span.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <stdexcept>
#include <string>
namespace boost {
namespace histogram {
namespace detail {
struct reduce_command {
static constexpr unsigned unset = static_cast<unsigned>(-1);
unsigned iaxis = unset;
enum class range_t : char {
none,
indices,
values,
} range = range_t::none;
union {
axis::index_type index;
double value;
} begin{0}, end{0};
unsigned merge = 0; // default value indicates unset option
bool crop = false;
// for internal use by the reduce algorithm
bool is_ordered = true;
bool use_underflow_bin = true;
bool use_overflow_bin = true;
};
// - puts commands in correct axis order
// - sets iaxis for positional commands
// - detects and fails on invalid settings
// - fuses merge commands with non-merge commands
inline void normalize_reduce_commands(span<reduce_command> out,
span<const reduce_command> in) {
unsigned iaxis = 0;
for (const auto& o_in : in) {
assert(o_in.merge > 0);
if (o_in.iaxis != reduce_command::unset && o_in.iaxis >= out.size())
BOOST_THROW_EXCEPTION(std::invalid_argument("invalid axis index"));
auto& o_out = out.begin()[o_in.iaxis == reduce_command::unset ? iaxis : o_in.iaxis];
if (o_out.merge == 0) {
o_out = o_in;
} else {
// Some command was already set for this axis, try to fuse commands.
if (!((o_in.range == reduce_command::range_t::none) ^
(o_out.range == reduce_command::range_t::none)) ||
(o_out.merge > 1 && o_in.merge > 1))
BOOST_THROW_EXCEPTION(std::invalid_argument(
"multiple conflicting reduce commands for axis " +
std::to_string(o_in.iaxis == reduce_command::unset ? iaxis : o_in.iaxis)));
if (o_in.range != reduce_command::range_t::none) {
o_out.range = o_in.range;
o_out.begin = o_in.begin;
o_out.end = o_in.end;
} else {
o_out.merge = o_in.merge;
}
}
++iaxis;
}
iaxis = 0;
for (auto& o : out) o.iaxis = iaxis++;
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2015-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_DETAIL_RELAXED_EQUAL_HPP
#define BOOST_HISTOGRAM_DETAIL_RELAXED_EQUAL_HPP
#include <boost/histogram/detail/priority.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
struct relaxed_equal {
template <class T, class U>
constexpr auto impl(const T& t, const U& u, priority<1>) const noexcept
-> decltype(t == u) const {
return t == u;
}
// consider T and U not equal, if there is no operator== defined for them
template <class T, class U>
constexpr bool impl(const T&, const U&, priority<0>) const noexcept {
return false;
}
// consider two T equal if they are stateless
template <class T>
constexpr bool impl(const T&, const T&, priority<0>) const noexcept {
return std::is_empty<T>::value;
}
template <class T, class U>
constexpr bool operator()(const T& t, const U& u) const noexcept {
return impl(t, u, priority<1>{});
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+37
View File
@@ -0,0 +1,37 @@
// 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_DETAIL_RELAXED_TUPLE_SIZE_HPP
#define BOOST_HISTOGRAM_DETAIL_RELAXED_TUPLE_SIZE_HPP
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
using dynamic_size = std::integral_constant<std::size_t, static_cast<std::size_t>(-1)>;
// Returns static size of tuple or dynamic_size
template <class T>
constexpr dynamic_size relaxed_tuple_size(const T&) noexcept {
return {};
}
template <class... Ts>
constexpr std::integral_constant<std::size_t, sizeof...(Ts)> relaxed_tuple_size(
const std::tuple<Ts...>&) noexcept {
return {};
}
template <class T>
using relaxed_tuple_size_t = decltype(relaxed_tuple_size(std::declval<T>()));
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+31
View File
@@ -0,0 +1,31 @@
// 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_DETAIL_REPLACE_TYPE_HPP
#define BOOST_HISTOGRAM_DETAIL_REPLACE_TYPE_HPP
#include <boost/core/use_default.hpp>
#include <string>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class From, class To>
using replace_type = std::conditional_t<std::is_same<T, From>::value, To, T>;
template <class T, class Default>
using replace_default = replace_type<T, boost::use_default, Default>;
template <class T>
using replace_cstring = replace_type<T, const char*, std::string>;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+87
View File
@@ -0,0 +1,87 @@
// 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_DETAIL_SAFE_COMPARISON_HPP
#define BOOST_HISTOGRAM_DETAIL_SAFE_COMPARISON_HPP
#include <boost/mp11/utility.hpp>
#include <boost/type.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
auto make_unsigned(const T& t) noexcept {
static_assert(std::is_integral<T>::value, "");
return static_cast<std::make_unsigned_t<T>>(t);
}
template <class T>
using number_category =
mp11::mp_if<std::is_integral<T>,
mp11::mp_if<std::is_signed<T>, type<int>, type<unsigned>>, type<void>>;
// version of std::equal_to<> which handles signed and unsigned integers correctly
struct safe_equal {
template <class T, class U>
bool operator()(const T& t, const U& u) const noexcept {
return impl(number_category<T>{}, number_category<U>{}, t, u);
}
template <class C1, class C2, class T, class U>
bool impl(C1, C2, const T& t, const U& u) const noexcept {
return t == u;
}
template <class T, class U>
bool impl(type<int>, type<unsigned>, const T& t, const U& u) const noexcept {
return t >= 0 && make_unsigned(t) == u;
}
template <class T, class U>
bool impl(type<unsigned>, type<int>, const T& t, const U& u) const noexcept {
return impl(type<int>{}, type<unsigned>{}, u, t);
}
};
// version of std::less<> which handles signed and unsigned integers correctly
struct safe_less {
template <class T, class U>
bool operator()(const T& t, const U& u) const noexcept {
return impl(number_category<T>{}, number_category<U>{}, t, u);
}
template <class C1, class C2, class T, class U>
bool impl(C1, C2, const T& t, const U& u) const noexcept {
return t < u;
}
template <class T, class U>
bool impl(type<int>, type<unsigned>, const T& t, const U& u) const noexcept {
return t < 0 || make_unsigned(t) < u;
}
template <class T, class U>
bool impl(type<unsigned>, type<int>, const T& t, const U& u) const noexcept {
return 0 < u && t < make_unsigned(u);
}
};
// version of std::greater<> which handles signed and unsigned integers correctly
struct safe_greater {
template <class T, class U>
bool operator()(const T& t, const U& u) const noexcept {
return safe_less()(u, t);
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2018-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_DETAIL_SQUARE_HPP
#define BOOST_HISTOGRAM_DETAIL_SQUARE_HPP
namespace boost {
namespace histogram {
namespace detail {
template <class T>
T square(T t) {
return t * t;
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2018-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_DETAIL_STATIC_IF_HPP
#define BOOST_HISTOGRAM_DETAIL_STATIC_IF_HPP
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class F, class... Args>
constexpr decltype(auto) static_if_impl(
std::true_type, T&& t, F&&,
Args&&... args) noexcept(noexcept(std::declval<T>()(std::declval<Args>()...))) {
return std::forward<T>(t)(std::forward<Args>(args)...);
}
template <class T, class F, class... Args>
constexpr decltype(auto) static_if_impl(
std::false_type, T&&, F&& f,
Args&&... args) noexcept(noexcept(std::declval<F>()(std::declval<Args>()...))) {
return std::forward<F>(f)(std::forward<Args>(args)...);
}
template <bool B, class... Ts>
constexpr decltype(auto) static_if_c(Ts&&... ts) noexcept(
noexcept(static_if_impl(std::integral_constant<bool, B>{}, std::declval<Ts>()...))) {
return static_if_impl(std::integral_constant<bool, B>{}, std::forward<Ts>(ts)...);
}
template <class Bool, class... Ts>
constexpr decltype(auto) static_if(Ts&&... ts) noexcept(
noexcept(static_if_impl(Bool{}, std::declval<Ts>()...))) {
return static_if_impl(Bool{}, std::forward<Ts>(ts)...);
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+128
View File
@@ -0,0 +1,128 @@
// 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_DETAIL_SUB_ARRAY_HPP
#define BOOST_HISTOGRAM_DETAIL_SUB_ARRAY_HPP
#include <algorithm>
#include <boost/throw_exception.hpp>
#include <stdexcept>
namespace boost {
namespace histogram {
namespace detail {
// Like std::array, but allows to use less than maximum capacity.
// Cannot inherit from std::array, since this confuses span.
template <class T, std::size_t N>
class sub_array {
static constexpr bool swap_element_is_noexcept() noexcept {
using std::swap;
return noexcept(swap(std::declval<T&>(), std::declval<T&>()));
}
public:
using element_type = T;
using size_type = std::size_t;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using iterator = pointer;
using const_iterator = const_pointer;
sub_array() = default;
explicit sub_array(std::size_t s) noexcept : size_(s) { assert(size_ <= N); }
sub_array(std::size_t s, const T& value) noexcept(
std::is_nothrow_assignable<T, const_reference>::value)
: sub_array(s) {
fill(value);
}
sub_array(std::initializer_list<T> il) noexcept(
std::is_nothrow_assignable<T, const_reference>::value)
: sub_array(il.size()) {
std::copy(il.begin(), il.end(), data_);
}
reference at(size_type pos) noexcept {
if (pos >= size()) BOOST_THROW_EXCEPTION(std::out_of_range{"pos is out of range"});
return data_[pos];
}
const_reference at(size_type pos) const noexcept {
if (pos >= size()) BOOST_THROW_EXCEPTION(std::out_of_range{"pos is out of range"});
return data_[pos];
}
reference operator[](size_type pos) noexcept { return data_[pos]; }
const_reference operator[](size_type pos) const noexcept { return data_[pos]; }
reference front() noexcept { return data_[0]; }
const_reference front() const noexcept { return data_[0]; }
reference back() noexcept { return data_[size_ - 1]; }
const_reference back() const noexcept { return data_[size_ - 1]; }
pointer data() noexcept { return static_cast<pointer>(data_); }
const_pointer data() const noexcept { return static_cast<const_pointer>(data_); }
iterator begin() noexcept { return data_; }
const_iterator begin() const noexcept { return data_; }
iterator end() noexcept { return begin() + size_; }
const_iterator end() const noexcept { return begin() + size_; }
const_iterator cbegin() const noexcept { return data_; }
const_iterator cend() const noexcept { return cbegin() + size_; }
constexpr size_type max_size() const noexcept { return N; }
size_type size() const noexcept { return size_; }
bool empty() const noexcept { return size_ == 0; }
void fill(const_reference value) noexcept(
std::is_nothrow_assignable<T, const_reference>::value) {
std::fill(begin(), end(), value);
}
void swap(sub_array& other) noexcept(swap_element_is_noexcept()) {
using std::swap;
const size_type s = (std::max)(size(), other.size());
for (auto i = begin(), j = other.begin(), end = begin() + s; i != end; ++i, ++j)
swap(*i, *j);
swap(size_, other.size_);
}
private:
size_type size_ = 0;
element_type data_[N];
};
template <class T, std::size_t N>
bool operator==(const sub_array<T, N>& a, const sub_array<T, N>& b) noexcept {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
template <class T, std::size_t N>
bool operator!=(const sub_array<T, N>& a, const sub_array<T, N>& b) noexcept {
return !(a == b);
}
} // namespace detail
} // namespace histogram
} // namespace boost
namespace std {
template <class T, std::size_t N>
void swap(::boost::histogram::detail::sub_array<T, N>& a,
::boost::histogram::detail::sub_array<T, N>& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
}
} // namespace std
#endif
+87
View File
@@ -0,0 +1,87 @@
// 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_DETAIL_TERM_INFO_HPP
#define BOOST_HISTOGRAM_DETAIL_TERM_INFO_HPP
#include <algorithm>
#if defined __has_include
#if __has_include(<sys/ioctl.h>) && __has_include(<unistd.h>)
#include <sys/ioctl.h>
#include <unistd.h>
#endif
#endif
#include <boost/config.hpp>
#include <cstdlib>
#include <cstring>
namespace boost {
namespace histogram {
namespace detail {
namespace term_info {
class env_t {
public:
env_t(const char* key) {
#if defined(BOOST_MSVC) // msvc complains about using std::getenv
_dupenv_s(&data_, &size_, key);
#else
data_ = std::getenv(key);
if (data_) size_ = std::strlen(data_);
#endif
}
~env_t() {
#if defined(BOOST_MSVC)
std::free(data_);
#endif
}
bool contains(const char* s) {
const std::size_t n = std::strlen(s);
if (size_ < n) return false;
return std::strstr(data_, s);
}
operator bool() { return size_ > 0; }
explicit operator int() { return size_ ? std::atoi(data_) : 0; }
const char* data() const { return data_; }
private:
char* data_;
std::size_t size_ = 0;
};
inline bool utf8() {
// return false only if LANG exists and does not contain the string UTF
env_t env("LANG");
bool b = true;
if (env) b = env.contains("UTF") || env.contains("utf");
return b;
}
inline int width() {
int w = 0;
#if defined TIOCGWINSZ
struct winsize ws;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
w = (std::max)(static_cast<int>(ws.ws_col), 0); // not sure if ws_col can be less than 0
#endif
env_t env("COLUMNS");
const int col = (std::max)(static_cast<int>(env), 0);
// if both t and w are set, COLUMNS may be used to restrict width
return w == 0 ? col : (std::min)(col, w);
}
} // namespace term_info
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+61
View File
@@ -0,0 +1,61 @@
// 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_DETAIL_TRY_CAST_HPP
#define BOOST_HISTOGRAM_DETAIL_TRY_CAST_HPP
#include <boost/config.hpp> // BOOST_NORETURN
#include <boost/throw_exception.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T, class U>
constexpr T* ptr_cast(U*) noexcept {
return nullptr;
}
template <class T>
constexpr T* ptr_cast(T* p) noexcept {
return p;
}
template <class T>
constexpr const T* ptr_cast(const T* p) noexcept {
return p;
}
template <class T, class E, class U>
BOOST_NORETURN T try_cast_impl(std::false_type, std::false_type, U&&) {
BOOST_THROW_EXCEPTION(E("type cast error"));
}
// converting cast
template <class T, class E, class U>
T try_cast_impl(std::false_type, std::true_type, U&& u) noexcept {
return static_cast<T>(u); // cast to avoid warnings
}
// pass-through cast
template <class T, class E>
T&& try_cast_impl(std::true_type, std::true_type, T&& t) noexcept {
return std::forward<T>(t);
}
// cast fails at runtime with exception E instead of compile-time, T must be a value
template <class T, class E, class U>
T try_cast(U&& u) noexcept(std::is_convertible<U, T>::value) {
return try_cast_impl<T, E>(std::is_same<U, T>{}, std::is_convertible<U, T>{},
std::forward<U>(u));
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2015-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_DETAIL_TUPLE_SLICE_HPP
#define BOOST_HISTOGRAM_DETAIL_TUPLE_SLICE_HPP
#include <boost/mp11/integer_sequence.hpp>
#include <tuple>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <std::size_t I, class T, std::size_t... K>
decltype(auto) tuple_slice_impl(T&& t, mp11::index_sequence<K...>) {
return std::forward_as_tuple(std::get<(I + K)>(std::forward<T>(t))...);
}
template <std::size_t I, std::size_t N, class Tuple>
decltype(auto) tuple_slice(Tuple&& t) {
constexpr auto S = std::tuple_size<std::decay_t<Tuple>>::value;
static_assert(I + N <= S, "I, N must be a valid subset");
return tuple_slice_impl<I>(std::forward<Tuple>(t), mp11::make_index_sequence<N>{});
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+47
View File
@@ -0,0 +1,47 @@
// 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_DETAIL_TYPE_NAME_HPP
#define BOOST_HISTOGRAM_DETAIL_TYPE_NAME_HPP
#include <boost/core/typeinfo.hpp>
#include <boost/type.hpp>
#include <string>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
std::string type_name_impl(boost::type<T>) {
return boost::core::demangled_name(BOOST_CORE_TYPEID(T));
}
template <class T>
std::string type_name_impl(boost::type<T const>) {
return type_name_impl(boost::type<T>{}) + " const";
}
template <class T>
std::string type_name_impl(boost::type<T&>) {
return type_name_impl(boost::type<T>{}) + " &";
}
template <class T>
std::string type_name_impl(boost::type<T&&>) {
return type_name_impl(boost::type<T>{}) + " &&";
}
template <class T>
std::string type_name() {
return type_name_impl(boost::type<T>{});
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+67
View File
@@ -0,0 +1,67 @@
// 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_DETAIL_VARIANT_PROXY_HPP
#define BOOST_HISTOGRAM_DETAIL_VARIANT_PROXY_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/traits.hpp> // variant_access
#include <boost/histogram/detail/static_if.hpp>
#include <boost/mp11/algorithm.hpp> // mp_with_index, mp_find, mp_at
#include <boost/mp11/list.hpp> // mp_size
#include <boost/throw_exception.hpp>
#include <stdexcept>
namespace boost {
namespace histogram {
namespace detail {
// This is a workaround to remain backward compatible in the serialization format. The
// proxy uses only the public interface of axis::variant for serialization and works
// independently of the underlying variant implementation.
template <class Variant>
struct variant_proxy {
Variant& variant;
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
detail::static_if_c<Archive::is_loading::value>(
[this](auto& ar) { // loading
int which = 0;
ar >> make_nvp("which", which);
constexpr unsigned N = mp11::mp_size<Variant>::value;
if (which < 0 || static_cast<unsigned>(which) >= N)
// throw if which >= N, can happen if type was removed from variant
BOOST_THROW_EXCEPTION(
std::runtime_error("variant has fewer types than stored version"));
mp11::mp_with_index<N>(static_cast<unsigned>(which), [&ar, this](auto i) {
using T = mp11::mp_at_c<Variant, i>;
T value;
ar >> make_nvp("value", value);
this->variant = std::move(value);
T* new_address = variant_access::template get_if<T>(&this->variant);
ar.reset_object_address(new_address, &value);
});
},
[this](auto& ar) { // saving
visit(
[&ar](const auto& value) {
using T = std::decay_t<decltype(value)>;
const int which = static_cast<int>(mp11::mp_find<Variant, T>::value);
ar << make_nvp("which", which);
ar << make_nvp("value", value);
},
this->variant);
},
ar);
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2015-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_FWD_HPP
#define BOOST_HISTOGRAM_FWD_HPP
/**
\file boost/histogram/fwd.hpp
Forward declarations, tag types and type aliases.
*/
#include <boost/config.hpp> // BOOST_ATTRIBUTE_NODISCARD
#include <boost/core/use_default.hpp>
#include <tuple>
#include <type_traits>
#include <vector>
namespace boost {
namespace histogram {
/// Tag type to indicate use of a default type
using boost::use_default;
namespace axis {
/// Integral type for axis indices
using index_type = int;
/// Real type for axis indices
using real_index_type = double;
/// Empty metadata type
struct null_type {
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Another alias for an empty metadata type
using empty_type = null_type;
// some forward declarations must be hidden from doxygen to fix the reference docu :(
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace transform {
struct id;
struct log;
struct sqrt;
struct pow;
} // namespace transform
template <class Value = double, class Transform = use_default,
class MetaData = use_default, class Options = use_default>
class regular;
template <class Value = int, class MetaData = use_default, class Options = use_default>
class integer;
template <class Value = double, class MetaData = use_default, class Options = use_default,
class Allocator = std::allocator<Value>>
class variable;
template <class Value = int, class MetaData = use_default, class Options = use_default,
class Allocator = std::allocator<Value>>
class category;
template <class MetaData = use_default>
class boolean;
template <class... Ts>
class variant;
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
} // namespace axis
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
template <class T>
struct weight_type;
template <class T>
struct sample_type;
namespace accumulators {
template <class ValueType = double, bool ThreadSafe = false>
class count;
template <class ValueType = double>
class fraction;
template <class ValueType = double>
class sum;
template <class ValueType = double>
class weighted_sum;
template <class ValueType = double>
class mean;
template <class ValueType = double>
class weighted_mean;
} // namespace accumulators
struct unsafe_access;
template <class Allocator = std::allocator<char>>
class unlimited_storage;
template <class T>
class storage_adaptor;
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
/// Vector-like storage for fast zero-overhead access to cells.
template <class T, class A = std::allocator<T>>
using dense_storage = storage_adaptor<std::vector<T, A>>;
/// Default storage, optimized for unweighted histograms
using default_storage = unlimited_storage<>;
/// Dense storage which tracks sums of weights and a variance estimate.
using weight_storage = dense_storage<accumulators::weighted_sum<>>;
/// Dense storage which tracks means of samples in each cell.
using profile_storage = dense_storage<accumulators::mean<>>;
/// Dense storage which tracks means of weighted samples in each cell.
using weighted_profile_storage = dense_storage<accumulators::weighted_mean<>>;
// some forward declarations must be hidden from doxygen to fix the reference docu :(
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
template <class Axes, class Storage = default_storage>
class BOOST_ATTRIBUTE_NODISCARD histogram;
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace utility {
template <class ValueType = double>
class clopper_pearson_interval;
template <class ValueType = double>
class jeffreys_interval;
template <class ValueType = double>
class wald_interval;
template <class ValueType = double>
class wilson_interval;
} // namespace utility
namespace detail {
/*
Most of the histogram code is generic and works for any number of axes. Buffers with a
fixed maximum capacity are used in some places, which have a size equal to the rank of
a histogram. The buffers are allocated from the stack to improve performance, which
means in C++ that they need a preset maximum capacity. 32 seems like a safe upper limit
for the rank. You can nevertheless increase it with the compile-time flag
BOOST_HISTOGRAM_DETAIL_AXES_LIMIT, if necessary.
*/
#ifndef BOOST_HISTOGRAM_DETAIL_AXES_LIMIT
#define BOOST_HISTOGRAM_DETAIL_AXES_LIMIT 32
#endif
template <class T>
struct buffer_size_impl
: std::integral_constant<std::size_t, BOOST_HISTOGRAM_DETAIL_AXES_LIMIT> {};
template <class... Ts>
struct buffer_size_impl<std::tuple<Ts...>>
: std::integral_constant<std::size_t, sizeof...(Ts)> {};
template <class T>
using buffer_size = typename buffer_size_impl<T>::type;
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
+718
View File
@@ -0,0 +1,718 @@
// Copyright 2015-2018 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_HISTOGRAM_HPP
#define BOOST_HISTOGRAM_HISTOGRAM_HPP
#include <boost/core/make_span.hpp>
#include <boost/histogram/detail/accumulator_traits.hpp>
#include <boost/histogram/detail/argument_traits.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/common_type.hpp>
#include <boost/histogram/detail/fill.hpp>
#include <boost/histogram/detail/fill_n.hpp>
#include <boost/histogram/detail/index_translator.hpp>
#include <boost/histogram/detail/mutex_base.hpp>
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/indexed.hpp>
#include <boost/histogram/multi_index.hpp>
#include <boost/histogram/sample.hpp>
#include <boost/histogram/storage_adaptor.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <boost/histogram/weight.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/tuple.hpp>
#include <boost/throw_exception.hpp>
#include <mutex>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace histogram {
/** Central class of the histogram library.
Histogram uses the call operator to insert data, like the
[Boost.Accumulators](https://www.boost.org/doc/libs/develop/doc/html/accumulators.html).
Use factory functions (see
[make_histogram.hpp](histogram/reference.html#header.boost.histogram.make_histogram_hpp)
and
[make_profile.hpp](histogram/reference.html#header.boost.histogram.make_profile_hpp)) to
conveniently create histograms rather than calling the ctors directly.
Use the [indexed](boost/histogram/indexed.html) range generator to iterate over filled
histograms, which is convenient and faster than hand-written loops for multi-dimensional
histograms.
@tparam Axes std::tuple of axis types OR std::vector of an axis type or axis::variant
@tparam Storage class that implements the storage interface
*/
template <class Axes, class Storage>
class histogram : detail::mutex_base<Axes, Storage> {
static_assert(std::is_same<std::decay_t<Storage>, Storage>::value,
"Storage may not be a reference or const or volatile");
static_assert(mp11::mp_size<Axes>::value > 0, "at least one axis required");
public:
using axes_type = Axes;
using storage_type = Storage;
using value_type = typename storage_type::value_type;
// typedefs for boost::range_iterator
using iterator = typename storage_type::iterator;
using const_iterator = typename storage_type::const_iterator;
using multi_index_type = multi_index<detail::relaxed_tuple_size_t<axes_type>::value>;
private:
using mutex_base = typename detail::mutex_base<axes_type, storage_type>;
public:
histogram() = default;
template <class A, class S>
explicit histogram(histogram<A, S>&& rhs)
: storage_(std::move(unsafe_access::storage(rhs)))
, offset_(unsafe_access::offset(rhs)) {
detail::axes_assign(axes_, std::move(unsafe_access::axes(rhs)));
detail::throw_if_axes_is_too_large(axes_);
}
template <class A, class S>
explicit histogram(const histogram<A, S>& rhs)
: storage_(unsafe_access::storage(rhs)), offset_(unsafe_access::offset(rhs)) {
detail::axes_assign(axes_, unsafe_access::axes(rhs));
detail::throw_if_axes_is_too_large(axes_);
}
template <class A, class S>
histogram& operator=(histogram<A, S>&& rhs) {
detail::axes_assign(axes_, std::move(unsafe_access::axes(rhs)));
detail::throw_if_axes_is_too_large(axes_);
storage_ = std::move(unsafe_access::storage(rhs));
offset_ = unsafe_access::offset(rhs);
return *this;
}
template <class A, class S>
histogram& operator=(const histogram<A, S>& rhs) {
detail::axes_assign(axes_, unsafe_access::axes(rhs));
detail::throw_if_axes_is_too_large(axes_);
storage_ = unsafe_access::storage(rhs);
offset_ = unsafe_access::offset(rhs);
return *this;
}
template <class A, class = detail::requires_axes<A>>
histogram(A&& a, Storage s)
: axes_(std::forward<A>(a))
, storage_(std::move(s))
, offset_(detail::offset(axes_)) {
detail::throw_if_axes_is_too_large(axes_);
storage_.reset(detail::bincount(axes_));
}
explicit histogram(Axes axes) : histogram(axes, storage_type()) {}
template <class... As, class = detail::requires_axes<std::tuple<std::decay_t<As>...>>>
explicit histogram(As&&... as)
: histogram(std::tuple<std::decay_t<As>...>(std::forward<As>(as)...),
storage_type()) {}
/// Number of axes (dimensions).
constexpr unsigned rank() const noexcept { return detail::axes_rank(axes_); }
/// Total number of bins (including underflow/overflow).
std::size_t size() const noexcept { return storage_.size(); }
/// Reset all bins to default initialized values.
void reset() { storage_.reset(size()); }
/// Get N-th axis using a compile-time number.
/// This version is more efficient than the one accepting a run-time number.
template <unsigned N = 0>
decltype(auto) axis(std::integral_constant<unsigned, N> = {}) const {
assert(N < rank());
return detail::axis_get<N>(axes_);
}
/// Get N-th axis with run-time number.
/// Prefer the version that accepts a compile-time number, if you can use it.
decltype(auto) axis(unsigned i) const {
assert(i < rank());
return detail::axis_get(axes_, i);
}
/// Apply unary functor/function to each axis.
template <class Unary>
auto for_each_axis(Unary&& unary) const {
return detail::for_each_axis(axes_, std::forward<Unary>(unary));
}
/** Fill histogram with values, an optional weight, and/or a sample.
Returns iterator to located cell.
Arguments are passed in order to the axis objects. Passing an argument type that is
not convertible to the value type accepted by the axis or passing the wrong number
of arguments causes a throw of `std::invalid_argument`.
__Optional weight__
An optional weight can be passed as the first or last argument
with the [weight](boost/histogram/weight.html) helper function. Compilation fails if
the storage elements do not support weights.
__Samples__
If the storage elements accept samples, pass them with the sample helper function
in addition to the axis arguments, which can be the first or last argument. The
[sample](boost/histogram/sample.html) helper function can pass one or more arguments
to the storage element. If samples and weights are used together, they can be passed
in any order at the beginning or end of the argument list.
__Axis with multiple arguments__
If the histogram contains an axis which accepts a `std::tuple` of arguments, the
arguments for that axis need to be passed as a `std::tuple`, for example,
`std::make_tuple(1.2, 2.3)`. If the histogram contains only this axis and no other,
the arguments can be passed directly.
*/
template <class T0, class... Ts,
class = std::enable_if_t<(detail::is_tuple<T0>::value == false ||
sizeof...(Ts) > 0)>>
iterator operator()(const T0& arg0, const Ts&... args) {
return operator()(std::forward_as_tuple(arg0, args...));
}
/// Fill histogram with values, an optional weight, and/or a sample from a `std::tuple`.
template <class... Ts>
iterator operator()(const std::tuple<Ts...>& args) {
using arg_traits = detail::argument_traits<std::decay_t<Ts>...>;
using acc_traits = detail::accumulator_traits<value_type>;
constexpr bool weight_valid =
arg_traits::wpos::value == -1 || acc_traits::weight_support;
static_assert(weight_valid, "error: accumulator does not support weights");
detail::sample_args_passed_vs_expected<typename arg_traits::sargs,
typename acc_traits::args>();
constexpr bool sample_valid =
std::is_convertible<typename arg_traits::sargs, typename acc_traits::args>::value;
std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
return detail::fill(mp11::mp_bool<(weight_valid && sample_valid)>{}, arg_traits{},
offset_, storage_, axes_, args);
}
/** Fill histogram with several values at once.
The argument must be an iterable with a size that matches the
rank of the histogram. The element of an iterable may be 1) a value or 2) an iterable
over a contiguous sequence of values or 3) a variant of 1) and 2). Sub-iterables must
have the same length.
Warning: `std::vector<bool>` is not a contiguous sequence over boolean values because
of the infamous vector specialization for booleans. It cannot be used as an
argument, but any truely contiguous sequence of boolean values can (`std::array<bool,
N>` or `std::valarray<bool>`, for example).
Values are passed to the corresponding histogram axis in order. If a single value is
passed together with an iterable of values, the single value is treated like an
iterable with matching length of copies of this value.
If the histogram has only one axis, an iterable of values may be passed directly.
@param args iterable as explained in the long description.
*/
template <class Iterable, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args) {
using acc_traits = detail::accumulator_traits<value_type>;
constexpr unsigned n_sample_args_expected =
std::tuple_size<typename acc_traits::args>::value;
static_assert(n_sample_args_expected == 0,
"sample argument is missing but required by accumulator");
std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
detail::fill_n(mp11::mp_bool<(n_sample_args_expected == 0)>{}, offset_, storage_,
axes_, make_span(args));
}
/** Fill histogram with several values and weights at once.
@param args iterable of values.
@param weights single weight or an iterable of weights.
*/
template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args, const weight_type<T>& weights) {
using acc_traits = detail::accumulator_traits<value_type>;
constexpr bool weight_valid = acc_traits::weight_support;
static_assert(weight_valid, "error: accumulator does not support weights");
detail::sample_args_passed_vs_expected<std::tuple<>, typename acc_traits::args>();
constexpr bool sample_valid =
std::is_convertible<std::tuple<>, typename acc_traits::args>::value;
std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
detail::fill_n(mp11::mp_bool<(weight_valid && sample_valid)>{}, offset_, storage_,
axes_, make_span(args), weight(detail::to_ptr_size(weights.value)));
}
/** Fill histogram with several values and weights at once.
@param weights single weight or an iterable of weights.
@param args iterable of values.
*/
template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
void fill(const weight_type<T>& weights, const Iterable& args) {
fill(args, weights);
}
/** Fill histogram with several values and samples at once.
@param args iterable of values.
@param samples single sample or an iterable of samples.
*/
template <class Iterable, class... Ts, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args, const sample_type<std::tuple<Ts...>>& samples) {
using acc_traits = detail::accumulator_traits<value_type>;
using sample_args_passed =
std::tuple<decltype(*detail::to_ptr_size(std::declval<Ts>()).first)...>;
detail::sample_args_passed_vs_expected<sample_args_passed,
typename acc_traits::args>();
std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
mp11::tuple_apply( // LCOV_EXCL_LINE: gcc-11 is missing this line for no reason
[&](const auto&... sargs) {
constexpr bool sample_valid =
std::is_convertible<sample_args_passed, typename acc_traits::args>::value;
detail::fill_n(mp11::mp_bool<(sample_valid)>{}, offset_, storage_, axes_,
make_span(args), detail::to_ptr_size(sargs)...);
},
samples.value);
}
/** Fill histogram with several values and samples at once.
@param samples single sample or an iterable of samples.
@param args iterable of values.
*/
template <class Iterable, class T, class = detail::requires_iterable<Iterable>>
void fill(const sample_type<T>& samples, const Iterable& args) {
fill(args, samples);
}
template <class Iterable, class T, class... Ts,
class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args, const weight_type<T>& weights,
const sample_type<std::tuple<Ts...>>& samples) {
using acc_traits = detail::accumulator_traits<value_type>;
using sample_args_passed =
std::tuple<decltype(*detail::to_ptr_size(std::declval<Ts>()).first)...>;
detail::sample_args_passed_vs_expected<sample_args_passed,
typename acc_traits::args>();
std::lock_guard<typename mutex_base::type> guard{mutex_base::get()};
mp11::tuple_apply( // LCOV_EXCL_LINE: gcc-11 is missing this line for no reason
[&](const auto&... sargs) {
constexpr bool weight_valid = acc_traits::weight_support;
static_assert(weight_valid, "error: accumulator does not support weights");
constexpr bool sample_valid =
std::is_convertible<sample_args_passed, typename acc_traits::args>::value;
detail::fill_n(mp11::mp_bool<(weight_valid && sample_valid)>{}, offset_,
storage_, axes_, make_span(args),
weight(detail::to_ptr_size(weights.value)),
detail::to_ptr_size(sargs)...);
},
samples.value);
}
template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
void fill(const sample_type<T>& samples, const weight_type<U>& weights,
const Iterable& args) {
fill(args, weights, samples);
}
template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
void fill(const weight_type<T>& weights, const sample_type<U>& samples,
const Iterable& args) {
fill(args, weights, samples);
}
template <class Iterable, class T, class U, class = detail::requires_iterable<Iterable>>
void fill(const Iterable& args, const sample_type<T>& samples,
const weight_type<U>& weights) {
fill(args, weights, samples);
}
/** Access cell value at integral indices.
You can pass indices as individual arguments, as a std::tuple of integers, or as an
interable range of integers. Passing the wrong number of arguments causes a throw of
std::invalid_argument. Passing an index which is out of bounds causes a throw of
std::out_of_range.
@param i index of first axis.
@param is indices of second, third, ... axes.
@returns reference to cell value.
*/
template <class... Is>
decltype(auto) at(axis::index_type i, Is... is) {
return at(multi_index_type{i, static_cast<axis::index_type>(is)...});
}
/// Access cell value at integral indices (read-only).
template <class... Is>
decltype(auto) at(axis::index_type i, Is... is) const {
return at(multi_index_type{i, static_cast<axis::index_type>(is)...});
}
/// Access cell value at integral indices stored in iterable.
decltype(auto) at(const multi_index_type& is) {
if (rank() != is.size())
BOOST_THROW_EXCEPTION(
std::invalid_argument("number of arguments != histogram rank"));
const auto idx = detail::linearize_indices(axes_, is);
if (!is_valid(idx))
BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
assert(idx < storage_.size());
return storage_[idx];
}
/// Access cell value at integral indices stored in iterable (read-only).
decltype(auto) at(const multi_index_type& is) const {
if (rank() != is.size())
BOOST_THROW_EXCEPTION(
std::invalid_argument("number of arguments != histogram rank"));
const auto idx = detail::linearize_indices(axes_, is);
if (!is_valid(idx))
BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
assert(idx < storage_.size());
return storage_[idx];
}
/// Access value at index (for rank = 1).
decltype(auto) operator[](axis::index_type i) {
const axis::index_type shift =
axis::traits::options(axis()) & axis::option::underflow ? 1 : 0;
return storage_[static_cast<std::size_t>(i + shift)];
}
/// Access value at index (for rank = 1, read-only).
decltype(auto) operator[](axis::index_type i) const {
const axis::index_type shift =
axis::traits::options(axis()) & axis::option::underflow ? 1 : 0;
return storage_[static_cast<std::size_t>(i + shift)];
}
/// Access value at index tuple.
decltype(auto) operator[](const multi_index_type& is) {
return storage_[detail::linearize_indices(axes_, is)];
}
/// Access value at index tuple (read-only).
decltype(auto) operator[](const multi_index_type& is) const {
return storage_[detail::linearize_indices(axes_, is)];
}
/// Equality operator, tests equality for all axes and the storage.
template <class A, class S>
bool operator==(const histogram<A, S>& rhs) const noexcept {
// testing offset is redundant, but offers fast return if it fails
return offset_ == unsafe_access::offset(rhs) &&
detail::axes_equal(axes_, unsafe_access::axes(rhs)) &&
storage_ == unsafe_access::storage(rhs);
}
/// Negation of the equality operator.
template <class A, class S>
bool operator!=(const histogram<A, S>& rhs) const noexcept {
return !operator==(rhs);
}
/** Add values of another histogram.
This operator is only available if the value_type supports operator+=.
Both histograms must be compatible to be addable. The histograms are compatible, if
the axes are either all identical. If the axes only differ in the states of their
discrete growing axis types, then they are also compatible. The discrete growing
axes are merged in this case.
*/
template <class A, class S>
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
std::enable_if_t<
detail::has_operator_radd<value_type, typename histogram<A, S>::value_type>::value,
histogram&>
#endif
operator+=(const histogram<A, S>& rhs) {
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
auto rit = unsafe_access::storage(rhs).begin();
for (auto&& x : storage_) x += *rit++;
return *this;
}
// specialization that allows axes merging
template <class S>
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
std::enable_if_t<detail::has_operator_radd<
value_type, typename histogram<axes_type, S>::value_type>::value,
histogram&>
#endif
operator+=(const histogram<axes_type, S>& rhs) {
const auto& raxes = unsafe_access::axes(rhs);
if (detail::axes_equal(axes_, unsafe_access::axes(rhs))) {
auto rit = unsafe_access::storage(rhs).begin();
for (auto&& x : storage_) x += *rit++;
return *this;
}
if (rank() != detail::axes_rank(raxes))
BOOST_THROW_EXCEPTION(std::invalid_argument("axes have different length"));
auto h = histogram<axes_type, storage_type>(
detail::axes_transform(axes_, raxes, detail::axis_merger{}),
detail::make_default(storage_));
const auto& axes = unsafe_access::axes(h);
const auto tr1 = detail::make_index_translator(axes, axes_);
for (auto&& x : indexed(*this, coverage::all)) h[tr1(x.indices())] += *x;
const auto tr2 = detail::make_index_translator(axes, raxes);
for (auto&& x : indexed(rhs, coverage::all)) h[tr2(x.indices())] += *x;
*this = std::move(h);
return *this;
}
/** Subtract values of another histogram.
This operator is only available if the value_type supports operator-=.
*/
template <class A, class S>
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
std::enable_if_t<
detail::has_operator_rsub<value_type, typename histogram<A, S>::value_type>::value,
histogram&>
#endif
operator-=(const histogram<A, S>& rhs) {
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
auto rit = unsafe_access::storage(rhs).begin();
for (auto&& x : storage_) x -= *rit++;
return *this;
}
/** Multiply by values of another histogram.
This operator is only available if the value_type supports operator*=.
*/
template <class A, class S>
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
std::enable_if_t<
detail::has_operator_rmul<value_type, typename histogram<A, S>::value_type>::value,
histogram&>
#endif
operator*=(const histogram<A, S>& rhs) {
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
auto rit = unsafe_access::storage(rhs).begin();
for (auto&& x : storage_) x *= *rit++;
return *this;
}
/** Divide by values of another histogram.
This operator is only available if the value_type supports operator/=.
*/
template <class A, class S>
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
std::enable_if_t<
detail::has_operator_rdiv<value_type, typename histogram<A, S>::value_type>::value,
histogram&>
#endif
operator/=(const histogram<A, S>& rhs) {
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
auto rit = unsafe_access::storage(rhs).begin();
for (auto&& x : storage_) x /= *rit++;
return *this;
}
/** Multiply all values with a scalar.
This operator is only available if the value_type supports operator*=.
*/
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
template <class V = value_type>
std::enable_if_t<(detail::has_operator_rmul<V, double>::value), histogram&>
#endif
operator*=(const double x) {
// use special storage implementation of scaling if available,
// else fallback to scaling item by item
detail::static_if<detail::has_operator_rmul<storage_type, double>>(
[x](auto& s) { s *= x; },
[x](auto& s) {
for (auto&& si : s) si *= x;
},
storage_);
return *this;
}
/** Divide all values by a scalar.
This operator is only available if operator*= is available.
*/
#ifdef BOOST_HISTOGRAM_DOXYGEN_INVOKED
histogram&
#else
template <class H = histogram>
std::enable_if_t<(detail::has_operator_rmul<H, double>::value), histogram&>
#endif
operator/=(const double x) {
return operator*=(1.0 / x);
}
/// Return value iterator to the beginning of the histogram.
iterator begin() noexcept { return storage_.begin(); }
/// Return value iterator to the end in the histogram.
iterator end() noexcept { return storage_.end(); }
/// Return value iterator to the beginning of the histogram (read-only).
const_iterator begin() const noexcept { return storage_.begin(); }
/// Return value iterator to the end in the histogram (read-only).
const_iterator end() const noexcept { return storage_.end(); }
/// Return value iterator to the beginning of the histogram (read-only).
const_iterator cbegin() const noexcept { return begin(); }
/// Return value iterator to the end in the histogram (read-only).
const_iterator cend() const noexcept { return end(); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
detail::axes_serialize(ar, axes_);
ar& make_nvp("storage", storage_);
if (Archive::is_loading::value) {
offset_ = detail::offset(axes_);
detail::throw_if_axes_is_too_large(axes_);
}
}
private:
axes_type axes_;
storage_type storage_;
std::size_t offset_ = 0;
friend struct unsafe_access;
};
/**
Pairwise add cells of two histograms and return histogram with the sum.
The returned histogram type is the most efficient and safest one constructible from the
inputs, if they are not the same type. If one histogram has a tuple axis, the result has
a tuple axis. The chosen storage is the one with the larger dynamic range.
*/
template <class A1, class S1, class A2, class S2>
auto operator+(const histogram<A1, S1>& a, const histogram<A2, S2>& b) {
auto r = histogram<detail::common_axes<A1, A2>, detail::common_storage<S1, S2>>(a);
return r += b;
}
/** Pairwise multiply cells of two histograms and return histogram with the product.
For notes on the returned histogram type, see operator+.
*/
template <class A1, class S1, class A2, class S2>
auto operator*(const histogram<A1, S1>& a, const histogram<A2, S2>& b) {
auto r = histogram<detail::common_axes<A1, A2>, detail::common_storage<S1, S2>>(a);
return r *= b;
}
/** Pairwise subtract cells of two histograms and return histogram with the difference.
For notes on the returned histogram type, see operator+.
*/
template <class A1, class S1, class A2, class S2>
auto operator-(const histogram<A1, S1>& a, const histogram<A2, S2>& b) {
auto r = histogram<detail::common_axes<A1, A2>, detail::common_storage<S1, S2>>(a);
return r -= b;
}
/** Pairwise divide cells of two histograms and return histogram with the quotient.
For notes on the returned histogram type, see operator+.
*/
template <class A1, class S1, class A2, class S2>
auto operator/(const histogram<A1, S1>& a, const histogram<A2, S2>& b) {
auto r = histogram<detail::common_axes<A1, A2>, detail::common_storage<S1, S2>>(a);
return r /= b;
}
/** Multiply all cells of the histogram by a number and return a new histogram.
If the original histogram has integer cells, the result has double cells.
*/
template <class A, class S>
auto operator*(const histogram<A, S>& h, double x) {
auto r = histogram<A, detail::common_storage<S, dense_storage<double>>>(h);
return r *= x;
}
/** Multiply all cells of the histogram by a number and return a new histogram.
If the original histogram has integer cells, the result has double cells.
*/
template <class A, class S>
auto operator*(double x, const histogram<A, S>& h) {
return h * x;
}
/** Divide all cells of the histogram by a number and return a new histogram.
If the original histogram has integer cells, the result has double cells.
*/
template <class A, class S>
auto operator/(const histogram<A, S>& h, double x) {
return h * (1.0 / x);
}
#if __cpp_deduction_guides >= 201606
template <class... Axes, class = detail::requires_axes<std::tuple<std::decay_t<Axes>...>>>
histogram(Axes...) -> histogram<std::tuple<std::decay_t<Axes>...>>;
template <class... Axes, class S, class = detail::requires_storage_or_adaptible<S>>
histogram(std::tuple<Axes...>, S)
-> histogram<std::tuple<Axes...>, std::conditional_t<detail::is_adaptible<S>::value,
storage_adaptor<S>, S>>;
template <class Iterable, class = detail::requires_iterable<Iterable>,
class = detail::requires_any_axis<typename Iterable::value_type>>
histogram(Iterable) -> histogram<std::vector<typename Iterable::value_type>>;
template <class Iterable, class S, class = detail::requires_iterable<Iterable>,
class = detail::requires_any_axis<typename Iterable::value_type>,
class = detail::requires_storage_or_adaptible<S>>
histogram(Iterable, S) -> histogram<
std::vector<typename Iterable::value_type>,
std::conditional_t<detail::is_adaptible<S>::value, storage_adaptor<S>, S>>;
#endif
} // namespace histogram
} // namespace boost
#endif
+442
View File
@@ -0,0 +1,442 @@
// Copyright 2015-2016 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_INDEXED_HPP
#define BOOST_HISTOGRAM_INDEXED_HPP
#include <array>
#include <boost/config.hpp> // BOOST_ATTRIBUTE_NODISCARD
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <boost/histogram/detail/operators.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <iterator>
#include <type_traits>
#include <utility> // std::get
namespace boost {
namespace histogram {
namespace detail {
using std::get;
template <std::size_t I, class T>
auto get(T&& t) -> decltype(t[0]) {
return t[I];
}
} // namespace detail
/** Coverage mode of the indexed range generator.
Defines options for the iteration strategy.
*/
enum class coverage {
inner, /*!< iterate over inner bins, exclude underflow and overflow */
all, /*!< iterate over all bins, including underflow and overflow */
};
/** Input iterator range over histogram bins with multi-dimensional index.
The iterator returned by begin() can only be incremented. If several copies of the
input iterators exist, the other copies become invalid if one of them is incremented.
*/
template <class Histogram>
class BOOST_ATTRIBUTE_NODISCARD indexed_range {
private:
using histogram_type = Histogram;
static constexpr unsigned buffer_size =
detail::buffer_size<typename std::decay_t<histogram_type>::axes_type>::value;
public:
/// implementation detail
using value_iterator = std::conditional_t<std::is_const<histogram_type>::value,
typename histogram_type::const_iterator,
typename histogram_type::iterator>;
/// implementation detail
using value_reference = typename std::iterator_traits<value_iterator>::reference;
/// implementation detail
using value_type = typename std::iterator_traits<value_iterator>::value_type;
class iterator;
/** Lightweight view to access value and index of current cell.
The methods provide access to the current cell indices and bins. It acts like a
pointer to the cell value, and in a limited way also like a reference. To interoperate
with the algorithms of the standard library, the accessor is implicitly convertible to
a cell value. Assignments and comparisons are passed through to the cell. An accessor
is coupled to its parent indexed_range::iterator. Moving the parent iterator
forward also updates the linked accessor. Accessors are not copyable. They cannot be
stored in containers, but indexed_range::iterator can be stored.
*/
class BOOST_ATTRIBUTE_NODISCARD accessor : detail::mirrored<accessor, void> {
public:
/// Array-like view into the current multi-dimensional index.
class index_view {
using index_pointer = const typename iterator::index_data*;
public:
using const_reference = const axis::index_type&;
/// implementation detail
class const_iterator
: public detail::iterator_adaptor<const_iterator, index_pointer,
const_reference> {
public:
const_reference operator*() const noexcept { return const_iterator::base()->idx; }
private:
explicit const_iterator(index_pointer i) noexcept
: const_iterator::iterator_adaptor_(i) {}
friend class index_view;
};
const_iterator begin() const noexcept { return const_iterator{begin_}; }
const_iterator end() const noexcept { return const_iterator{end_}; }
std::size_t size() const noexcept {
return static_cast<std::size_t>(end_ - begin_);
}
const_reference operator[](unsigned d) const noexcept { return begin_[d].idx; }
const_reference at(unsigned d) const { return begin_[d].idx; }
private:
/// implementation detail
index_view(index_pointer b, index_pointer e) : begin_(b), end_(e) {}
index_pointer begin_, end_;
friend class accessor;
};
// assignment is pass-through
accessor& operator=(const accessor& o) {
get() = o.get();
return *this;
}
// assignment is pass-through
template <class T>
accessor& operator=(const T& x) {
get() = x;
return *this;
}
/// Returns the cell reference.
value_reference get() const noexcept { return *(iter_.iter_); }
/// @copydoc get()
value_reference operator*() const noexcept { return get(); }
/// Access fields and methods of the cell object.
value_iterator operator->() const noexcept { return iter_.iter_; }
/// Access current index.
/// @param d axis dimension.
axis::index_type index(unsigned d = 0) const noexcept {
return iter_.indices_[d].idx;
}
/// Access indices as an iterable range.
index_view indices() const noexcept {
assert(iter_.indices_.hist_);
return {iter_.indices_.begin(), iter_.indices_.end()};
}
/// Access current bin.
/// @tparam N axis dimension.
template <unsigned N = 0>
decltype(auto) bin(std::integral_constant<unsigned, N> = {}) const {
assert(iter_.indices_.hist_);
return iter_.indices_.hist_->axis(std::integral_constant<unsigned, N>())
.bin(index(N));
}
/// Access current bin.
/// @param d axis dimension.
decltype(auto) bin(unsigned d) const {
assert(iter_.indices_.hist_);
return iter_.indices_.hist_->axis(d).bin(index(d));
}
/** Computes density in current cell.
The density is computed as the cell value divided by the product of bin widths. Axes
without bin widths, like axis::category, are treated as having unit bin with.
*/
double density() const {
assert(iter_.indices_.hist_);
double x = 1;
unsigned d = 0;
iter_.indices_.hist_->for_each_axis([&](const auto& a) {
const auto w = axis::traits::width_as<double>(a, this->index(d++));
x *= w != 0 ? w : 1;
});
return get() / x;
}
// forward all comparison operators to the value
bool operator<(const accessor& o) noexcept { return get() < o.get(); }
bool operator>(const accessor& o) noexcept { return get() > o.get(); }
bool operator==(const accessor& o) noexcept { return get() == o.get(); }
bool operator!=(const accessor& o) noexcept { return get() != o.get(); }
bool operator<=(const accessor& o) noexcept { return get() <= o.get(); }
bool operator>=(const accessor& o) noexcept { return get() >= o.get(); }
template <class U>
bool operator<(const U& o) const noexcept {
return get() < o;
}
template <class U>
bool operator>(const U& o) const noexcept {
return get() > o;
}
template <class U>
bool operator==(const U& o) const noexcept {
return get() == o;
}
template <class U>
bool operator!=(const U& o) const noexcept {
return get() != o;
}
template <class U>
bool operator<=(const U& o) const noexcept {
return get() <= o;
}
template <class U>
bool operator>=(const U& o) const noexcept {
return get() >= o;
}
operator value_type() const noexcept { return get(); }
private:
accessor(iterator& i) noexcept : iter_(i) {}
accessor(const accessor&) = default; // only callable by indexed_range::iterator
iterator& iter_;
friend class iterator;
};
/// implementation detail
class iterator {
public:
using value_type = typename indexed_range::value_type;
using reference = accessor;
private:
struct pointer_proxy {
reference* operator->() noexcept { return std::addressof(ref_); }
reference ref_;
};
public:
using pointer = pointer_proxy;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
reference operator*() noexcept { return *this; }
pointer operator->() noexcept { return pointer_proxy{operator*()}; }
iterator& operator++() {
assert(iter_ < indices_.hist_->end());
const auto cbeg = indices_.begin();
auto c = cbeg;
++iter_;
++c->idx;
if (c->idx < c->end) return *this;
while (c->idx == c->end) {
iter_ += c->end_skip;
if (++c == indices_.end()) return *this;
++c->idx;
}
while (c-- != cbeg) {
c->idx = c->begin;
iter_ += c->begin_skip;
}
return *this;
}
iterator operator++(int) {
auto prev = *this;
operator++();
return prev;
}
bool operator==(const iterator& x) const noexcept { return iter_ == x.iter_; }
bool operator!=(const iterator& x) const noexcept { return !operator==(x); }
// make iterator ready for C++17 sentinels
bool operator==(const value_iterator& x) const noexcept { return iter_ == x; }
bool operator!=(const value_iterator& x) const noexcept { return !operator==(x); }
// useful for iterator debugging
std::size_t offset() const noexcept { return iter_ - indices_.hist_->begin(); }
private:
iterator(value_iterator i, histogram_type& h) : iter_(i), indices_(&h) {}
value_iterator iter_; // original histogram iterator
struct index_data {
axis::index_type idx, begin, end;
std::size_t begin_skip, end_skip;
};
struct indices_t : private std::array<index_data, buffer_size> {
using base_type = std::array<index_data, buffer_size>;
using pointer = index_data*;
using const_pointer = const index_data*;
indices_t(histogram_type* h) noexcept : hist_{h} {}
using base_type::operator[];
unsigned size() const noexcept { return hist_->rank(); }
pointer begin() noexcept { return base_type::data(); }
const_pointer begin() const noexcept { return base_type::data(); }
pointer end() noexcept { return begin() + size(); }
const_pointer end() const noexcept { return begin() + size(); }
histogram_type* hist_;
} indices_;
friend class indexed_range;
};
indexed_range(histogram_type& hist, coverage cov)
: indexed_range(hist, make_range(hist, cov)) {}
template <class Iterable, class = detail::requires_iterable<Iterable>>
indexed_range(histogram_type& hist, Iterable&& range)
: begin_(hist.begin(), hist), end_(hist.end(), hist) {
// if histogram is empty, incrementing begin_.iter_ may be undefined behavior
if (begin_ == end_) return;
auto r_begin = std::begin(range);
assert(std::distance(r_begin, std::end(range)) == static_cast<int>(hist.rank()));
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), r_begin,
stride = std::size_t{1},
this](const auto& a) mutable {
const auto size = a.size();
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
constexpr axis::index_type start = opt::test(axis::option::underflow) ? -1 : 0;
const auto stop = size + (opt::test(axis::option::overflow) ? 1 : 0);
ca->begin = (std::max)(start, detail::get<0>(*r_begin));
ca->end = (std::min)(stop, detail::get<1>(*r_begin));
assert(ca->begin <= ca->end);
ca->idx = ca->begin;
ca->begin_skip = static_cast<std::size_t>(ca->begin - start) * stride;
ca->end_skip = static_cast<std::size_t>(stop - ca->end) * stride;
begin_.iter_ += ca->begin_skip;
end_.iter_ -= ca->end_skip;
stride *= stop - start;
++ca;
++r_begin;
});
// check if selected range is empty
if (end_.iter_ < begin_.iter_) {
begin_ = end_;
} else {
// reset end_ to hist.end(), since end_skips are done in operator++
end_.iter_ = hist.end();
}
}
iterator begin() noexcept { return begin_; }
iterator end() noexcept { return end_; }
private:
auto make_range(histogram_type& hist, coverage cov) {
using range_item = std::array<axis::index_type, 2>;
auto b = detail::make_stack_buffer<range_item>(unsafe_access::axes(hist));
hist.for_each_axis([cov, it = std::begin(b)](const auto& a) mutable {
(*it)[0] = 0;
(*it)[1] = a.size();
if (cov == coverage::all) {
(*it)[0] -= 1; // making this wider than actual range is safe
(*it)[1] += 1; // making this wider than actual range is safe
} else
assert(cov == coverage::inner);
++it;
});
return b;
}
iterator begin_, end_;
};
/** Generates an indexed range of <a
href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
over the histogram cells.
Use this in a range-based for loop:
```
for (auto&& x : indexed(hist)) { ... }
```
This generates an optimized loop which is nearly always faster than a hand-written loop
over the histogram cells. The iterators dereference to an indexed_range::accessor, which
has methods to query the current indices and bins and acts like a pointer to the cell
value. The returned iterators are forward iterators. They can be stored in a container,
but may not be used after the life-time of the histogram ends.
@returns indexed_range
@param hist Reference to the histogram.
@param cov Iterate over all or only inner bins (optional, default: inner).
*/
template <class Histogram>
auto indexed(Histogram&& hist, coverage cov = coverage::inner) {
return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
cov};
}
/** Generates and indexed range <a
href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
over a rectangular region of histogram cells.
Use this in a range-based for loop. Example:
```
auto hist = make_histogram(axis::integer<>(0, 4), axis::integer<>(2, 6));
axis::index_type range[2] = {{1, 3}, {0, 2}};
for (auto&& x : indexed(hist, range)) { ... }
```
This skips the first and last index of the first axis, and the last two indices of the
second.
@returns indexed_range
@param hist Reference to the histogram.
@param range Iterable over items with two axis::index_type values, which mark the
begin and end index of each axis. The length of the iterable must be
equal to the rank of the histogram. The begin index must be smaller than
the end index. Index ranges wider than the actual range are reduced to
the actual range including underflow and overflow indices.
*/
template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
auto indexed(Histogram&& hist, Iterable&& range) {
return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
std::forward<Iterable>(range)};
}
} // namespace histogram
} // namespace boost
#endif
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2015-2017 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_LITERALS_HPP
#define BOOST_HISTOGRAM_LITERALS_HPP
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
constexpr unsigned parse_number(unsigned n) { return n; }
template <class... Rest>
constexpr unsigned parse_number(unsigned n, char f, Rest... rest) {
return parse_number(10u * n + static_cast<unsigned>(f - '0'), rest...);
}
} // namespace detail
namespace literals {
/// Suffix operator to generate literal compile-time numbers, 0_c, 12_c, etc.
template <char... digits>
auto operator"" _c() {
return std::integral_constant<unsigned, detail::parse_number(0, digits...)>();
}
} // namespace literals
} // namespace histogram
} // namespace boost
#endif
+138
View File
@@ -0,0 +1,138 @@
// Copyright 2015-2018 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_MAKE_HISTOGRAM_HPP
#define BOOST_HISTOGRAM_MAKE_HISTOGRAM_HPP
/**
\file boost/histogram/make_histogram.hpp
Collection of factory functions to conveniently create histograms.
*/
#include <boost/histogram/accumulators/weighted_sum.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/histogram.hpp>
#include <boost/histogram/storage_adaptor.hpp>
#include <boost/histogram/unlimited_storage.hpp> // = default_storage
#include <boost/mp11/utility.hpp>
#include <tuple>
#include <vector>
namespace boost {
namespace histogram {
/**
Make histogram from compile-time axis configuration and custom storage.
@param storage Storage or container with standard interface (any vector, array, or map).
@param axis First axis instance.
@param axes Other axis instances.
*/
template <class Storage, class Axis, class... Axes,
class = detail::requires_storage_or_adaptible<Storage>,
class = detail::requires_axis<Axis>>
auto make_histogram_with(Storage&& storage, Axis&& axis, Axes&&... axes) {
auto a = std::make_tuple(std::forward<Axis>(axis), std::forward<Axes>(axes)...);
using U = std::decay_t<Storage>;
using S = mp11::mp_if<detail::is_storage<U>, U, storage_adaptor<U>>;
return histogram<decltype(a), S>(std::move(a), S(std::forward<Storage>(storage)));
}
/**
Make histogram from compile-time axis configuration and default storage.
@param axis First axis instance.
@param axes Other axis instances.
*/
template <class Axis, class... Axes, class = detail::requires_axis<Axis>>
auto make_histogram(Axis&& axis, Axes&&... axes) {
return make_histogram_with(default_storage(), std::forward<Axis>(axis),
std::forward<Axes>(axes)...);
}
/**
Make histogram from compile-time axis configuration and weight-counting storage.
@param axis First axis instance.
@param axes Other axis instances.
*/
template <class Axis, class... Axes, class = detail::requires_axis<Axis>>
auto make_weighted_histogram(Axis&& axis, Axes&&... axes) {
return make_histogram_with(weight_storage(), std::forward<Axis>(axis),
std::forward<Axes>(axes)...);
}
/**
Make histogram from iterable range and custom storage.
@param storage Storage or container with standard interface (any vector, array, or map).
@param iterable Iterable range of axis objects.
*/
template <class Storage, class Iterable,
class = detail::requires_storage_or_adaptible<Storage>,
class = detail::requires_sequence_of_any_axis<Iterable>>
auto make_histogram_with(Storage&& storage, Iterable&& iterable) {
using U = std::decay_t<Storage>;
using S = mp11::mp_if<detail::is_storage<U>, U, storage_adaptor<U>>;
using It = std::decay_t<Iterable>;
using A = mp11::mp_if<detail::is_indexable_container<It>, It,
std::vector<mp11::mp_first<It>>>;
return histogram<A, S>(std::forward<Iterable>(iterable),
S(std::forward<Storage>(storage)));
}
/**
Make histogram from iterable range and default storage.
@param iterable Iterable range of axis objects.
*/
template <class Iterable, class = detail::requires_sequence_of_any_axis<Iterable>>
auto make_histogram(Iterable&& iterable) {
return make_histogram_with(default_storage(), std::forward<Iterable>(iterable));
}
/**
Make histogram from iterable range and weight-counting storage.
@param iterable Iterable range of axis objects.
*/
template <class Iterable, class = detail::requires_sequence_of_any_axis<Iterable>>
auto make_weighted_histogram(Iterable&& iterable) {
return make_histogram_with(weight_storage(), std::forward<Iterable>(iterable));
}
/**
Make histogram from iterator interval and custom storage.
@param storage Storage or container with standard interface (any vector, array, or map).
@param begin Iterator to range of axis objects.
@param end Iterator to range of axis objects.
*/
template <class Storage, class Iterator,
class = detail::requires_storage_or_adaptible<Storage>,
class = detail::requires_iterator<Iterator>>
auto make_histogram_with(Storage&& storage, Iterator begin, Iterator end) {
using T = std::decay_t<decltype(*begin)>;
return make_histogram_with(std::forward<Storage>(storage), std::vector<T>(begin, end));
}
/**
Make histogram from iterator interval and default storage.
@param begin Iterator to range of axis objects.
@param end Iterator to range of axis objects.
*/
template <class Iterator, class = detail::requires_iterator<Iterator>>
auto make_histogram(Iterator begin, Iterator end) {
return make_histogram_with(default_storage(), begin, end);
}
/**
Make histogram from iterator interval and weight-counting storage.
@param begin Iterator to range of axis objects.
@param end Iterator to range of axis objects.
*/
template <class Iterator, class = detail::requires_iterator<Iterator>>
auto make_weighted_histogram(Iterator begin, Iterator end) {
return make_histogram_with(weight_storage(), begin, end);
}
} // namespace histogram
} // namespace boost
#endif
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2018 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_MAKE_PROFILE_HPP
#define BOOST_HISTOGRAM_MAKE_PROFILE_HPP
#include <boost/histogram/accumulators/mean.hpp>
#include <boost/histogram/accumulators/weighted_mean.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/make_histogram.hpp>
/**
\file boost/histogram/make_profile.hpp
Collection of factory functions to conveniently create profiles.
Profiles are histograms which accept an additional sample and compute the mean of the
sample in each cell.
*/
namespace boost {
namespace histogram {
/**
Make profle from compile-time axis configuration.
@param axis First axis instance.
@param axes Other axis instances.
*/
template <class Axis, class... Axes, class = detail::requires_axis<Axis>>
auto make_profile(Axis&& axis, Axes&&... axes) {
return make_histogram_with(profile_storage(), std::forward<Axis>(axis),
std::forward<Axes>(axes)...);
}
/**
Make profle from compile-time axis configuration which accepts weights.
@param axis First axis instance.
@param axes Other axis instances.
*/
template <class Axis, class... Axes, class = detail::requires_axis<Axis>>
auto make_weighted_profile(Axis&& axis, Axes&&... axes) {
return make_histogram_with(weighted_profile_storage(), std::forward<Axis>(axis),
std::forward<Axes>(axes)...);
}
/**
Make profile from iterable range.
@param iterable Iterable range of axis objects.
*/
template <class Iterable, class = detail::requires_sequence_of_any_axis<Iterable>>
auto make_profile(Iterable&& iterable) {
return make_histogram_with(profile_storage(), std::forward<Iterable>(iterable));
}
/**
Make profile from iterable range which accepts weights.
@param iterable Iterable range of axis objects.
*/
template <class Iterable, class = detail::requires_sequence_of_any_axis<Iterable>>
auto make_weighted_profile(Iterable&& iterable) {
return make_histogram_with(weighted_profile_storage(),
std::forward<Iterable>(iterable));
}
/**
Make profile from iterator interval.
@param begin Iterator to range of axis objects.
@param end Iterator to range of axis objects.
*/
template <class Iterator, class = detail::requires_iterator<Iterator>>
auto make_profile(Iterator begin, Iterator end) {
return make_histogram_with(profile_storage(), begin, end);
}
/**
Make profile from iterator interval which accepts weights.
@param begin Iterator to range of axis objects.
@param end Iterator to range of axis objects.
*/
template <class Iterator, class = detail::requires_iterator<Iterator>>
auto make_weighted_profile(Iterator begin, Iterator end) {
return make_histogram_with(weighted_profile_storage(), begin, end);
}
} // namespace histogram
} // namespace boost
#endif
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2020 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_MULTI_INDEX_HPP
#define BOOST_HISTOGRAM_MULTI_INDEX_HPP
#include <algorithm>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/integer_sequence.hpp>
#include <boost/throw_exception.hpp>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <tuple>
namespace boost {
namespace histogram {
/** Holder for multiple axis indices.
Adapts external iterable, tuple, or explicit list of indices to the same representation.
*/
template <std::size_t Size>
struct multi_index {
using value_type = axis::index_type;
using iterator = value_type*;
using const_iterator = const value_type*;
static multi_index create(std::size_t s) {
if (s != size())
BOOST_THROW_EXCEPTION(std::invalid_argument("size does not match static size"));
return multi_index(priv_tag{});
}
template <class... Is>
multi_index(axis::index_type i, Is... is)
: multi_index(std::initializer_list<axis::index_type>{
i, static_cast<axis::index_type>(is)...}) {}
template <class... Is>
multi_index(const std::tuple<axis::index_type, Is...>& is)
: multi_index(is, mp11::make_index_sequence<(1 + sizeof...(Is))>{}) {}
template <class Iterable, class = detail::requires_iterable<Iterable>>
multi_index(const Iterable& is) {
if (detail::size(is) != size())
BOOST_THROW_EXCEPTION(std::invalid_argument("no. of axes != no. of indices"));
using std::begin;
using std::end;
std::copy(begin(is), end(is), data_);
}
iterator begin() noexcept { return data_; }
iterator end() noexcept { return data_ + size(); }
const_iterator begin() const noexcept { return data_; }
const_iterator end() const noexcept { return data_ + size(); }
static constexpr std::size_t size() noexcept { return Size; }
private:
struct priv_tag {};
multi_index(priv_tag) {}
template <class T, std::size_t... Is>
multi_index(const T& is, mp11::index_sequence<Is...>)
: multi_index(static_cast<axis::index_type>(std::get<Is>(is))...) {}
axis::index_type data_[size()];
};
template <>
struct multi_index<static_cast<std::size_t>(-1)> {
using value_type = axis::index_type;
using iterator = value_type*;
using const_iterator = const value_type*;
static multi_index create(std::size_t s) { return multi_index(priv_tag{}, s); }
template <class... Is>
multi_index(axis::index_type i, Is... is)
: multi_index(std::initializer_list<axis::index_type>{
i, static_cast<axis::index_type>(is)...}) {}
template <class... Is>
multi_index(const std::tuple<axis::index_type, Is...>& is)
: multi_index(is, mp11::make_index_sequence<(1 + sizeof...(Is))>{}) {}
template <class Iterable, class = detail::requires_iterable<Iterable>>
multi_index(const Iterable& is) : size_(detail::size(is)) {
using std::begin;
using std::end;
std::copy(begin(is), end(is), data_);
}
iterator begin() noexcept { return data_; }
iterator end() noexcept { return data_ + size_; }
const_iterator begin() const noexcept { return data_; }
const_iterator end() const noexcept { return data_ + size_; }
std::size_t size() const noexcept { return size_; }
private:
struct priv_tag {};
multi_index(priv_tag, std::size_t s) : size_(s) {}
template <class T, std::size_t... Ns>
multi_index(const T& is, mp11::index_sequence<Ns...>)
: multi_index(static_cast<axis::index_type>(std::get<Ns>(is))...) {}
std::size_t size_ = 0;
static constexpr std::size_t max_size_ = BOOST_HISTOGRAM_DETAIL_AXES_LIMIT;
axis::index_type data_[max_size_];
};
} // namespace histogram
} // namespace boost
#endif
+360
View File
@@ -0,0 +1,360 @@
// Copyright 2015-2019 Hans Dembinski
// Copyright 2019 Przemyslaw Bartosik
//
// 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_OSTREAM_HPP
#define BOOST_HISTOGRAM_OSTREAM_HPP
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/axis/ostream.hpp>
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/term_info.hpp>
#include <boost/histogram/indexed.hpp>
#include <cmath>
#include <iomanip>
#include <ios>
#include <limits>
#include <numeric>
#include <ostream>
#include <streambuf>
#include <type_traits>
/**
\file boost/histogram/ostream.hpp
A simple streaming operator for the histogram type. The text representation is
rudimentary and not guaranteed to be stable between versions of Boost.Histogram. This
header is not included by any other header and must be explicitly included to use the
streaming operator.
To use your own, simply include your own implementation instead of this header.
*/
namespace boost {
namespace histogram {
namespace detail {
template <class OStream, unsigned N>
class tabular_ostream_wrapper : public std::array<int, N> {
using base_t = std::array<int, N>;
using char_type = typename OStream::char_type;
using traits_type = typename OStream::traits_type;
public:
template <class T>
tabular_ostream_wrapper& operator<<(const T& t) {
if (collect_) {
if (static_cast<unsigned>(iter_ - base_t::begin()) == size_) {
++size_;
assert(size_ <= N);
assert(iter_ != end());
*iter_ = 0;
}
count_ = 0;
os_ << t;
*iter_ = (std::max)(*iter_, static_cast<int>(count_));
} else {
assert(iter_ != end());
os_ << std::setw(*iter_) << t;
}
++iter_;
return *this;
}
tabular_ostream_wrapper& operator<<(decltype(std::setprecision(0)) t) {
os_ << t;
return *this;
}
tabular_ostream_wrapper& operator<<(decltype(std::fixed) t) {
os_ << t;
return *this;
}
tabular_ostream_wrapper& row() {
iter_ = base_t::begin();
return *this;
}
explicit tabular_ostream_wrapper(OStream& os)
: os_(os), cbuf_(count_), orig_(os_.rdbuf(&cbuf_)) {}
auto end() { return base_t::begin() + size_; }
auto end() const { return base_t::begin() + size_; }
auto cend() const { return base_t::cbegin() + size_; }
void complete() {
assert(collect_); // only call this once
collect_ = false;
os_.rdbuf(orig_);
}
private:
typename base_t::iterator iter_ = base_t::begin();
unsigned size_ = 0;
std::streamsize count_ = 0;
bool collect_ = true;
OStream& os_;
counting_streambuf<char_type, traits_type> cbuf_;
std::basic_streambuf<char_type, traits_type>* orig_;
};
template <class OStream, class T>
void ostream_value_impl(OStream& os, const T& t,
decltype(static_cast<double>(t), priority<1>{})) {
// a value from histogram cell
const auto d = static_cast<double>(t);
if ((std::numeric_limits<int>::min)() <= d && d <= (std::numeric_limits<int>::max)()) {
const auto i = static_cast<int>(d);
if (i == d) {
os << i;
return;
}
}
os << std::defaultfloat << std::setprecision(4) << d;
}
template <class OStream, class T>
void ostream_value_impl(OStream& os, const T& t, priority<0>) {
os << t;
}
template <class OStream, class T>
void ostream_value(OStream& os, const T& t) {
ostream_value_impl(os << std::left, t, priority<1>{});
}
template <class OStream, class Axis>
auto ostream_bin(OStream& os, const Axis& ax, axis::index_type i, std::true_type,
priority<1>) -> decltype((void)ax.value(i)) {
auto a = ax.value(i), b = ax.value(i + 1);
os << std::right << std::defaultfloat << std::setprecision(4);
// round edges to zero if deviation from zero is small
const auto eps = 1e-8 * std::abs(b - a);
if (std::abs(a) < 1e-14 && std::abs(a) < eps) a = 0;
if (std::abs(b) < 1e-14 && std::abs(b) < eps) b = 0;
os << "[" << a << ", " << b << ")";
}
template <class OStream, class Axis>
auto ostream_bin(OStream& os, const Axis& ax, axis::index_type i, std::false_type,
priority<1>) -> decltype((void)ax.value(i)) {
os << std::right;
os << ax.value(i);
}
template <class OStream, class... Ts>
void ostream_bin(OStream& os, const axis::category<Ts...>& ax, axis::index_type i,
std::false_type, priority<1>) {
os << std::right;
if (i < ax.size())
os << ax.value(i);
else
os << "other";
}
template <class OStream, class Axis, class B>
void ostream_bin(OStream& os, const Axis&, axis::index_type i, B, priority<0>) {
os << std::right;
os << i;
}
struct line {
const char* ch;
const int size;
line(const char* a, int b) : ch{a}, size{(std::max)(b, 0)} {}
};
template <class T>
std::basic_ostream<char, T>& operator<<(std::basic_ostream<char, T>& os, line&& l) {
for (int i = 0; i < l.size; ++i) os << l.ch;
return os;
}
template <class OStream, class Axis>
void ostream_head(OStream& os, const Axis& ax, int index, double val) {
axis::visit(
[&](const auto& ax) {
using A = std::decay_t<decltype(ax)>;
ostream_bin(os, ax, index, axis::traits::is_continuous<A>{}, priority<1>{});
os << ' ';
ostream_value(os, val);
},
ax);
}
template <class OStream>
void ostream_bar(OStream& os, int zero_offset, double z, int width, bool utf8) {
int k = static_cast<int>(std::lround(z * width));
if (utf8) {
os << "";
if (z > 0) {
const char* scale[8] = {" ", "", "", "", "", "", "", ""};
int j = static_cast<int>(std::lround(8 * (z * width - k)));
if (j < 0) {
--k;
j += 8;
}
os << line(" ", zero_offset) << line("", k);
os << scale[j];
os << line(" ", width - zero_offset - k);
} else if (z < 0) {
os << line(" ", zero_offset + k) << line("", -k)
<< line(" ", width - zero_offset + 1);
} else {
os << line(" ", width + 1);
}
os << "\n";
} else {
os << " |";
if (z >= 0) {
os << line(" ", zero_offset) << line("=", k) << line(" ", width - zero_offset - k);
} else {
os << line(" ", zero_offset + k) << line("=", -k) << line(" ", width - zero_offset);
}
os << " |\n";
}
}
// cannot display generalized histograms yet; line not reachable by coverage tests
template <class OStream, class Histogram>
void plot(OStream&, const Histogram&, int, std::false_type) {} // LCOV_EXCL_LINE
template <class OStream, class Histogram>
void plot(OStream& os, const Histogram& h, int w_total, std::true_type) {
if (w_total == 0) {
w_total = term_info::width();
if (w_total == 0 || w_total > 78) w_total = 78;
}
bool utf8 = term_info::utf8();
const auto& ax = h.axis();
// value range; can be integer or float, positive or negative
double vmin = 0;
double vmax = 0;
tabular_ostream_wrapper<OStream, 7> tos(os);
// first pass to get widths
for (auto&& v : indexed(h, coverage::all)) {
auto w = static_cast<double>(*v);
ostream_head(tos.row(), ax, v.index(), w);
vmin = (std::min)(vmin, w);
vmax = (std::max)(vmax, w);
}
tos.complete();
if (vmax == 0) vmax = 1;
// calculate width useable by bar (notice extra space at top)
// <-- head --> |<--- bar ---> |
// w_head + 2 + 2
const int w_head = std::accumulate(tos.begin(), tos.end(), 0);
const int w_bar = w_total - 4 - w_head;
if (w_bar < 0) return;
// draw upper line
os << '\n' << line(" ", w_head + 1);
if (utf8)
os << "" << line("", w_bar + 1) << "\n";
else
os << '+' << line("-", w_bar + 1) << "+\n";
const int zero_offset = static_cast<int>(std::lround((-vmin) / (vmax - vmin) * w_bar));
for (auto&& v : indexed(h, coverage::all)) {
auto w = static_cast<double>(*v);
ostream_head(tos.row(), ax, v.index(), w);
// rest uses os, not tos
ostream_bar(os, zero_offset, w / (vmax - vmin), w_bar, utf8);
}
// draw lower line
os << line(" ", w_head + 1);
if (utf8)
os << "" << line("", w_bar + 1) << "\n";
else
os << '+' << line("-", w_bar + 1) << "+\n";
}
template <class OStream, class Histogram>
void ostream(OStream& os, const Histogram& h, const bool show_values = true) {
os << "histogram(";
unsigned iaxis = 0;
const auto rank = h.rank();
h.for_each_axis([&](const auto& ax) {
if ((show_values && rank > 0) || rank > 1) os << "\n ";
ostream_any(os, ax);
});
if (show_values && rank > 0) {
tabular_ostream_wrapper<OStream, (BOOST_HISTOGRAM_DETAIL_AXES_LIMIT + 1)> tos(os);
for (auto&& v : indexed(h, coverage::all)) {
tos.row();
for (auto i : v.indices()) tos << std::right << i;
ostream_value(tos, *v);
}
tos.complete();
const int w_item = std::accumulate(tos.begin(), tos.end(), 0) + 4 + h.rank();
const int nrow = (std::max)(1, 65 / w_item);
int irow = 0;
for (auto&& v : indexed(h, coverage::all)) {
os << (irow == 0 ? "\n (" : " (");
tos.row();
iaxis = 0;
for (auto i : v.indices()) {
tos << std::right << i;
os << (++iaxis == h.rank() ? "):" : " ");
}
os << ' ';
ostream_value(tos, *v);
++irow;
if (nrow > 0 && irow == nrow) irow = 0;
}
os << '\n';
}
os << ')';
}
} // namespace detail
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
template <class CharT, class Traits, class A, class S>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const histogram<A, S>& h) {
// save fmt
const auto flags = os.flags();
os.flags(std::ios::dec | std::ios::left);
const auto w = static_cast<int>(os.width());
os.width(0);
using value_type = typename histogram<A, S>::value_type;
using convertible = detail::is_explicitly_convertible<value_type, double>;
// must be non-const to avoid a msvc warning about possible use of if constexpr
bool show_plot = convertible::value && h.rank() == 1;
if (show_plot) {
detail::ostream(os, h, false);
detail::plot(os, h, w, convertible{});
} else {
detail::ostream(os, h);
}
// restore fmt
os.flags(flags);
return os;
}
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
} // namespace histogram
} // namespace boost
#endif
+39
View File
@@ -0,0 +1,39 @@
// 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_SAMPLE_HPP
#define BOOST_HISTOGRAM_SAMPLE_HPP
#include <tuple>
#include <utility>
namespace boost {
namespace histogram {
/** Sample holder and type envelope.
You should not construct these directly, use the sample() helper function.
@tparam Underlying type.
*/
template <class T>
struct sample_type {
T value;
};
/** Helper function to mark arguments as sample.
@param ts arguments to be forwarded to the accumulator.
*/
template <class... Ts>
auto sample(Ts&&... ts) noexcept {
return sample_type<std::tuple<Ts...>>{std::forward_as_tuple(std::forward<Ts>(ts)...)};
}
} // namespace histogram
} // namespace boost
#endif
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2015-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_SERIALIZATION_HPP
#define BOOST_HISTOGRAM_SERIALIZATION_HPP
#include <boost/serialization/array.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
/**
\file boost/histogram/serialization.hpp
Headers from
[Boost.Serialization](https://www.boost.org/doc/libs/develop/libs/serialization/doc/index.html)
needed to serialize STL types that are used internally by the Boost.Histogram classes.
*/
#endif
+397
View File
@@ -0,0 +1,397 @@
// Copyright 2018-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_STORAGE_ADAPTOR_HPP
#define BOOST_HISTOGRAM_STORAGE_ADAPTOR_HPP
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/accumulators/is_thread_safe.hpp>
#include <boost/histogram/detail/array_wrapper.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <boost/histogram/detail/safe_comparison.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
struct vector_impl : T {
using allocator_type = typename T::allocator_type;
static constexpr bool has_threading_support =
accumulators::is_thread_safe<typename T::value_type>::value;
vector_impl(const allocator_type& a = {}) : T(a) {}
vector_impl(const vector_impl&) = default;
vector_impl& operator=(const vector_impl&) = default;
vector_impl(vector_impl&&) = default;
vector_impl& operator=(vector_impl&&) = default;
explicit vector_impl(T&& t) : T(std::move(t)) {}
explicit vector_impl(const T& t) : T(t) {}
template <class U, class = requires_iterable<U>>
explicit vector_impl(const U& u, const allocator_type& a = {})
: T(std::begin(u), std::end(u), a) {}
template <class U, class = requires_iterable<U>>
vector_impl& operator=(const U& u) {
T::resize(u.size());
auto it = T::begin();
for (auto&& x : u) *it++ = x;
return *this;
}
void reset(std::size_t n) {
using value_type = typename T::value_type;
const auto old_size = T::size();
T::resize(n, value_type());
std::fill_n(T::begin(), (std::min)(n, old_size), value_type());
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("vector", static_cast<T&>(*this));
}
};
template <class T>
struct array_impl : T {
static constexpr bool has_threading_support =
accumulators::is_thread_safe<typename T::value_type>::value;
array_impl() = default;
array_impl(const array_impl& t) : T(t), size_(t.size_) {}
array_impl& operator=(const array_impl& t) {
T::operator=(t);
size_ = t.size_;
return *this;
}
explicit array_impl(T&& t) : T(std::move(t)) {}
explicit array_impl(const T& t) : T(t) {}
template <class U, class = requires_iterable<U>>
explicit array_impl(const U& u) : size_(u.size()) {
using std::begin;
using std::end;
std::copy(begin(u), end(u), this->begin());
}
template <class U, class = requires_iterable<U>>
array_impl& operator=(const U& u) {
if (u.size() > T::max_size()) // for std::arra
BOOST_THROW_EXCEPTION(std::length_error("argument size exceeds maximum capacity"));
size_ = u.size();
using std::begin;
using std::end;
std::copy(begin(u), end(u), T::begin());
return *this;
}
void reset(std::size_t n) {
using value_type = typename T::value_type;
if (n > T::max_size()) // for std::array
BOOST_THROW_EXCEPTION(std::length_error("argument size exceeds maximum capacity"));
std::fill_n(T::begin(), n, value_type());
size_ = n;
}
typename T::iterator end() noexcept { return T::begin() + size_; }
typename T::const_iterator end() const noexcept { return T::begin() + size_; }
std::size_t size() const noexcept { return size_; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("size", size_);
auto w = detail::make_array_wrapper(T::data(), size_);
ar& make_nvp("array", w);
}
std::size_t size_ = 0;
};
template <class T>
struct map_impl : T {
static_assert(std::is_same<typename T::key_type, std::size_t>::value,
"requires std::size_t as key_type");
using value_type = typename T::mapped_type;
using const_reference = const value_type&;
static constexpr bool has_threading_support = false;
static_assert(
!accumulators::is_thread_safe<value_type>::value,
"std::map and std::unordered_map do not support thread-safe element access. "
"If you have a map with thread-safe element access, please file an issue and"
"support will be added.");
struct reference {
reference(map_impl* m, std::size_t i) noexcept : map(m), idx(i) {}
reference(const reference&) noexcept = default;
reference& operator=(const reference& o) {
if (this != &o) operator=(static_cast<const_reference>(o));
return *this;
}
operator const_reference() const noexcept {
return static_cast<const map_impl*>(map)->operator[](idx);
}
reference& operator=(const_reference u) {
auto it = map->find(idx);
if (u == value_type{}) {
if (it != static_cast<T*>(map)->end()) { map->erase(it); }
} else {
if (it != static_cast<T*>(map)->end()) {
it->second = u;
} else {
map->emplace(idx, u);
}
}
return *this;
}
template <class U, class V = value_type,
class = std::enable_if_t<has_operator_radd<V, U>::value>>
reference& operator+=(const U& u) {
auto it = map->find(idx);
if (it != static_cast<T*>(map)->end()) {
it->second += u;
} else {
map->emplace(idx, u);
}
return *this;
}
template <class U, class V = value_type,
class = std::enable_if_t<has_operator_rsub<V, U>::value>>
reference& operator-=(const U& u) {
auto it = map->find(idx);
if (it != static_cast<T*>(map)->end()) {
it->second -= u;
} else {
map->emplace(idx, -u);
}
return *this;
}
template <class U, class V = value_type,
class = std::enable_if_t<has_operator_rmul<V, U>::value>>
reference& operator*=(const U& u) {
auto it = map->find(idx);
if (it != static_cast<T*>(map)->end()) it->second *= u;
return *this;
}
template <class U, class V = value_type,
class = std::enable_if_t<has_operator_rdiv<V, U>::value>>
reference& operator/=(const U& u) {
auto it = map->find(idx);
if (it != static_cast<T*>(map)->end()) {
it->second /= u;
} else if (!(value_type{} / u == value_type{})) {
map->emplace(idx, value_type{} / u);
}
return *this;
}
template <class V = value_type,
class = std::enable_if_t<has_operator_preincrement<V>::value>>
reference operator++() {
auto it = map->find(idx);
if (it != static_cast<T*>(map)->end()) {
++it->second;
} else {
value_type tmp{};
++tmp;
map->emplace(idx, tmp);
}
return *this;
}
template <class V = value_type,
class = std::enable_if_t<has_operator_preincrement<V>::value>>
value_type operator++(int) {
const value_type tmp = *this;
operator++();
return tmp;
}
template <class U, class = std::enable_if_t<has_operator_equal<value_type, U>::value>>
bool operator==(const U& rhs) const {
return operator const_reference() == rhs;
}
template <class U, class = std::enable_if_t<has_operator_equal<value_type, U>::value>>
bool operator!=(const U& rhs) const {
return !operator==(rhs);
}
template <class CharT, class Traits>
friend std::basic_ostream<CharT, Traits>& operator<<(
std::basic_ostream<CharT, Traits>& os, reference x) {
os << static_cast<const_reference>(x);
return os;
}
template <class... Ts>
auto operator()(const Ts&... args) -> decltype(std::declval<value_type>()(args...)) {
return (*map)[idx](args...);
}
map_impl* map;
std::size_t idx;
};
template <class Value, class Reference, class MapPtr>
struct iterator_t
: iterator_adaptor<iterator_t<Value, Reference, MapPtr>, std::size_t, Reference> {
iterator_t() = default;
template <class V, class R, class M,
class = std::enable_if_t<std::is_convertible<M, MapPtr>::value>>
iterator_t(const iterator_t<V, R, M>& it) noexcept : iterator_t(it.map_, it.base()) {}
iterator_t(MapPtr m, std::size_t i) noexcept
: iterator_t::iterator_adaptor_(i), map_(m) {}
template <class V, class R, class M>
bool equal(const iterator_t<V, R, M>& rhs) const noexcept {
return map_ == rhs.map_ && iterator_t::base() == rhs.base();
}
Reference operator*() const { return (*map_)[iterator_t::base()]; }
MapPtr map_ = nullptr;
};
using iterator = iterator_t<value_type, reference, map_impl*>;
using const_iterator = iterator_t<const value_type, const_reference, const map_impl*>;
using allocator_type = typename T::allocator_type;
map_impl(const allocator_type& a = {}) : T(a) {}
map_impl(const map_impl&) = default;
map_impl& operator=(const map_impl&) = default;
map_impl(map_impl&&) = default;
map_impl& operator=(map_impl&&) = default;
map_impl(const T& t) : T(t), size_(t.size()) {}
map_impl(T&& t) : T(std::move(t)), size_(t.size()) {}
template <class U, class = requires_iterable<U>>
explicit map_impl(const U& u, const allocator_type& a = {}) : T(a), size_(u.size()) {
using std::begin;
using std::end;
std::copy(begin(u), end(u), this->begin());
}
template <class U, class = requires_iterable<U>>
map_impl& operator=(const U& u) {
if (u.size() < size_)
reset(u.size());
else
size_ = u.size();
using std::begin;
using std::end;
std::copy(begin(u), end(u), this->begin());
return *this;
}
void reset(std::size_t n) {
T::clear();
size_ = n;
}
reference operator[](std::size_t i) noexcept { return {this, i}; }
const_reference operator[](std::size_t i) const noexcept {
auto it = T::find(i);
static const value_type null = value_type{};
if (it == T::end()) return null;
return it->second;
}
iterator begin() noexcept { return {this, 0}; }
iterator end() noexcept { return {this, size_}; }
const_iterator begin() const noexcept { return {this, 0}; }
const_iterator end() const noexcept { return {this, size_}; }
std::size_t size() const noexcept { return size_; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("size", size_);
ar& make_nvp("map", static_cast<T&>(*this));
}
std::size_t size_ = 0;
};
template <class T>
struct ERROR_type_passed_to_storage_adaptor_not_recognized;
// clang-format off
template <class T>
using storage_adaptor_impl =
mp11::mp_cond<
is_vector_like<T>, vector_impl<T>,
is_array_like<T>, array_impl<T>,
is_map_like<T>, map_impl<T>,
std::true_type, ERROR_type_passed_to_storage_adaptor_not_recognized<T>
>;
// clang-format on
} // namespace detail
/// Turns any vector-like, array-like, and map-like container into a storage type.
template <class T>
class storage_adaptor : public detail::storage_adaptor_impl<T> {
using impl_type = detail::storage_adaptor_impl<T>;
public:
// standard copy, move, assign
storage_adaptor(storage_adaptor&&) = default;
storage_adaptor(const storage_adaptor&) = default;
storage_adaptor& operator=(storage_adaptor&&) = default;
storage_adaptor& operator=(const storage_adaptor&) = default;
// forwarding constructor
template <class... Ts>
storage_adaptor(Ts&&... ts) : impl_type(std::forward<Ts>(ts)...) {}
// forwarding assign
template <class U>
storage_adaptor& operator=(U&& u) {
impl_type::operator=(std::forward<U>(u));
return *this;
}
template <class U, class = detail::requires_iterable<U>>
bool operator==(const U& u) const {
using std::begin;
using std::end;
return std::equal(this->begin(), this->end(), begin(u), end(u), detail::safe_equal{});
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("impl", static_cast<impl_type&>(*this));
}
private:
friend struct unsafe_access;
};
} // namespace histogram
} // namespace boost
#endif
+636
View File
@@ -0,0 +1,636 @@
// Copyright 2015-2019 Hans Dembinski
// Copyright 2019 Glen Joseph Fernandes (glenjofe@gmail.com)
//
// 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_UNLIMTED_STORAGE_HPP
#define BOOST_HISTOGRAM_UNLIMTED_STORAGE_HPP
#include <algorithm>
#include <boost/core/alloc_construct.hpp>
#include <boost/core/exchange.hpp>
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/array_wrapper.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <boost/histogram/detail/large_int.hpp>
#include <boost/histogram/detail/operators.hpp>
#include <boost/histogram/detail/safe_comparison.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
struct is_large_int : std::false_type {};
template <class A>
struct is_large_int<large_int<A>> : std::true_type {};
template <class T, class ReturnType>
using if_arithmetic_or_large_int =
std::enable_if_t<(std::is_arithmetic<T>::value || is_large_int<T>::value),
ReturnType>;
template <class L, class T>
using next_type = mp11::mp_at_c<L, (mp11::mp_find<L, T>::value + 1)>;
template <class Allocator>
class construct_guard {
public:
using pointer = typename std::allocator_traits<Allocator>::pointer;
construct_guard(Allocator& a, pointer p, std::size_t n) noexcept
: a_(a), p_(p), n_(n) {}
~construct_guard() {
if (p_) { a_.deallocate(p_, n_); }
}
void release() { p_ = pointer(); }
construct_guard(const construct_guard&) = delete;
construct_guard& operator=(const construct_guard&) = delete;
private:
Allocator& a_;
pointer p_;
std::size_t n_;
};
template <class Allocator>
void* buffer_create(Allocator& a, std::size_t n) {
auto ptr = a.allocate(n); // may throw
static_assert(std::is_trivially_copyable<decltype(ptr)>::value,
"ptr must be trivially copyable");
construct_guard<Allocator> guard(a, ptr, n);
boost::alloc_construct_n(a, ptr, n);
guard.release();
return static_cast<void*>(ptr);
}
template <class Allocator, class Iterator>
auto buffer_create(Allocator& a, std::size_t n, Iterator iter) {
assert(n > 0u);
auto ptr = a.allocate(n); // may throw
static_assert(std::is_trivially_copyable<decltype(ptr)>::value,
"ptr must be trivially copyable");
construct_guard<Allocator> guard(a, ptr, n);
using T = typename std::allocator_traits<Allocator>::value_type;
struct casting_iterator {
void operator++() noexcept { ++iter_; }
T operator*() noexcept {
return static_cast<T>(*iter_);
} // silence conversion warnings
Iterator iter_;
};
boost::alloc_construct_n(a, ptr, n, casting_iterator{iter});
guard.release();
return ptr;
}
template <class Allocator>
void buffer_destroy(Allocator& a, typename std::allocator_traits<Allocator>::pointer p,
std::size_t n) {
assert(p);
assert(n > 0u);
boost::alloc_destroy_n(a, p, n);
a.deallocate(p, n);
}
} // namespace detail
/**
Memory-efficient storage for integral counters which cannot overflow.
This storage provides a no-overflow-guarantee if the counters are incremented with
integer weights. It maintains a contiguous array of elemental counters, one for each
cell. If an operation is requested which would overflow a counter, the array is
replaced with another of a wider integral type, then the operation is executed. The
storage uses integers of 8, 16, 32, 64 bits, and then switches to a multiprecision
integral type, similar to those in
[Boost.Multiprecision](https://www.boost.org/doc/libs/develop/libs/multiprecision/doc/html/index.html).
A scaling operation or adding a floating point number triggers a conversion of the
elemental counters into doubles, which voids the no-overflow-guarantee.
*/
template <class Allocator>
class unlimited_storage {
static_assert(
std::is_same<typename std::allocator_traits<Allocator>::pointer,
typename std::allocator_traits<Allocator>::value_type*>::value,
"unlimited_storage requires allocator with trivial pointer type");
using U8 = std::uint8_t;
using U16 = std::uint16_t;
using U32 = std::uint32_t;
using U64 = std::uint64_t;
public:
static constexpr bool has_threading_support = false;
using allocator_type = Allocator;
using value_type = double;
using large_int = detail::large_int<
typename std::allocator_traits<allocator_type>::template rebind_alloc<U64>>;
struct buffer_type {
// cannot be moved outside of scope of unlimited_storage, large_int is dependent type
using types = mp11::mp_list<U8, U16, U32, U64, large_int, double>;
template <class T>
static constexpr unsigned type_index() noexcept {
return static_cast<unsigned>(mp11::mp_find<types, T>::value);
}
template <class F, class... Ts>
decltype(auto) visit(F&& f, Ts&&... ts) const {
// this is intentionally not a switch, the if-chain is faster in benchmarks
if (type == type_index<U8>())
return f(static_cast<U8*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<U16>())
return f(static_cast<U16*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<U32>())
return f(static_cast<U32*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<U64>())
return f(static_cast<U64*>(ptr), std::forward<Ts>(ts)...);
if (type == type_index<large_int>())
return f(static_cast<large_int*>(ptr), std::forward<Ts>(ts)...);
return f(static_cast<double*>(ptr), std::forward<Ts>(ts)...);
}
buffer_type(const allocator_type& a = {}) : alloc(a) {}
buffer_type(buffer_type&& o) noexcept
: alloc(std::move(o.alloc))
, size(boost::exchange(o.size, 0))
, type(boost::exchange(o.type, 0))
, ptr(boost::exchange(o.ptr, nullptr)) {}
buffer_type& operator=(buffer_type&& o) noexcept {
using std::swap;
swap(alloc, o.alloc);
swap(size, o.size);
swap(type, o.type);
swap(ptr, o.ptr);
return *this;
}
buffer_type(const buffer_type& x) : alloc(x.alloc) {
x.visit([this, n = x.size](const auto* xp) {
using T = std::decay_t<decltype(*xp)>;
this->template make<T>(n, xp);
});
}
buffer_type& operator=(const buffer_type& o) {
*this = buffer_type(o);
return *this;
}
~buffer_type() noexcept { destroy(); }
void destroy() noexcept {
assert((ptr == nullptr) == (size == 0));
if (ptr == nullptr) return;
visit([this](auto* p) {
using T = std::decay_t<decltype(*p)>;
using alloc_type =
typename std::allocator_traits<allocator_type>::template rebind_alloc<T>;
alloc_type a(alloc); // rebind allocator
detail::buffer_destroy(a, p, this->size);
});
size = 0;
type = 0;
ptr = nullptr;
}
template <class T>
void make(std::size_t n) {
// note: order of commands is to not leave buffer in invalid state upon throw
destroy();
if (n > 0) {
// rebind allocator
using alloc_type =
typename std::allocator_traits<allocator_type>::template rebind_alloc<T>;
alloc_type a(alloc);
ptr = detail::buffer_create(a, n); // may throw
}
size = n;
type = type_index<T>();
}
template <class T, class U>
void make(std::size_t n, U iter) {
// note: iter may be current ptr, so create new buffer before deleting old buffer
void* new_ptr = nullptr;
const auto new_type = type_index<T>();
if (n > 0) {
// rebind allocator
using alloc_type =
typename std::allocator_traits<allocator_type>::template rebind_alloc<T>;
alloc_type a(alloc);
new_ptr = detail::buffer_create(a, n, iter); // may throw
}
destroy();
size = n;
type = new_type;
ptr = new_ptr;
}
allocator_type alloc;
std::size_t size = 0;
unsigned type = 0;
mutable void* ptr = nullptr;
};
class reference; // forward declare to make friend of const_reference
/// implementation detail
class const_reference
: detail::partially_ordered<const_reference, const_reference, void> {
public:
const_reference(buffer_type& b, std::size_t i) noexcept : bref_(b), idx_(i) {
assert(idx_ < bref_.size);
}
const_reference(const const_reference&) noexcept = default;
// no assignment for const_references
const_reference& operator=(const const_reference&) = delete;
const_reference& operator=(const_reference&&) = delete;
operator double() const noexcept {
return bref_.visit(
[this](const auto* p) { return static_cast<double>(p[this->idx_]); });
}
bool operator<(const const_reference& o) const noexcept {
return apply_binary<detail::safe_less>(o);
}
bool operator==(const const_reference& o) const noexcept {
return apply_binary<detail::safe_equal>(o);
}
template <class U>
detail::if_arithmetic_or_large_int<U, bool> operator<(const U& o) const noexcept {
return apply_binary<detail::safe_less>(o);
}
template <class U>
detail::if_arithmetic_or_large_int<U, bool> operator>(const U& o) const noexcept {
return apply_binary<detail::safe_greater>(o);
}
template <class U>
detail::if_arithmetic_or_large_int<U, bool> operator==(const U& o) const noexcept {
return apply_binary<detail::safe_equal>(o);
}
private:
template <class Binary>
bool apply_binary(const const_reference& x) const noexcept {
return x.bref_.visit([this, ix = x.idx_](const auto* xp) {
return this->apply_binary<Binary>(xp[ix]);
});
}
template <class Binary, class U>
bool apply_binary(const U& x) const noexcept {
return bref_.visit([i = idx_, &x](const auto* p) { return Binary()(p[i], x); });
}
protected:
buffer_type& bref_;
std::size_t idx_;
friend class reference;
};
/// implementation detail
class reference : public const_reference,
public detail::partially_ordered<reference, reference, void> {
public:
reference(buffer_type& b, std::size_t i) noexcept : const_reference(b, i) {}
// references do copy-construct
reference(const reference& x) noexcept = default;
// references do not rebind, assign through
reference& operator=(const reference& x) {
return operator=(static_cast<const_reference>(x));
}
// references do not rebind, assign through
reference& operator=(const const_reference& x) {
// safe for self-assignment, assigning matching type doesn't invalide buffer
x.bref_.visit([this, ix = x.idx_](const auto* xp) { this->operator=(xp[ix]); });
return *this;
}
template <class U>
detail::if_arithmetic_or_large_int<U, reference&> operator=(const U& x) {
this->bref_.visit([this, &x](auto* p) {
// gcc-8 optimizes the expression `p[this->idx_] = 0` away even at -O0,
// so we merge it into the next line which is properly counted
adder()((p[this->idx_] = 0, p), this->bref_, this->idx_, x);
});
return *this;
}
bool operator<(const reference& o) const noexcept {
return const_reference::operator<(o);
}
bool operator==(const reference& o) const noexcept {
return const_reference::operator==(o);
}
template <class U>
detail::if_arithmetic_or_large_int<U, bool> operator<(const U& o) const noexcept {
return const_reference::operator<(o);
}
template <class U>
detail::if_arithmetic_or_large_int<U, bool> operator>(const U& o) const noexcept {
return const_reference::operator>(o);
}
template <class U>
detail::if_arithmetic_or_large_int<U, bool> operator==(const U& o) const noexcept {
return const_reference::operator==(o);
}
reference& operator+=(const const_reference& x) {
x.bref_.visit([this, ix = x.idx_](const auto* xp) { this->operator+=(xp[ix]); });
return *this;
}
template <class U>
detail::if_arithmetic_or_large_int<U, reference&> operator+=(const U& x) {
this->bref_.visit(adder(), this->bref_, this->idx_, x);
return *this;
}
reference& operator-=(const double x) { return operator+=(-x); }
reference& operator*=(const double x) {
this->bref_.visit(multiplier(), this->bref_, this->idx_, x);
return *this;
}
reference& operator/=(const double x) { return operator*=(1.0 / x); }
reference& operator++() {
this->bref_.visit(incrementor(), this->bref_, this->idx_);
return *this;
}
};
private:
template <class Value, class Reference>
class iterator_impl : public detail::iterator_adaptor<iterator_impl<Value, Reference>,
std::size_t, Reference, Value> {
public:
iterator_impl() = default;
template <class V, class R>
iterator_impl(const iterator_impl<V, R>& it)
: iterator_impl::iterator_adaptor_(it.base()), buffer_(it.buffer_) {}
iterator_impl(buffer_type* b, std::size_t i) noexcept
: iterator_impl::iterator_adaptor_(i), buffer_(b) {}
Reference operator*() const noexcept { return {*buffer_, this->base()}; }
template <class V, class R>
friend class iterator_impl;
private:
mutable buffer_type* buffer_ = nullptr;
};
public:
using const_iterator = iterator_impl<const value_type, const_reference>;
using iterator = iterator_impl<value_type, reference>;
explicit unlimited_storage(const allocator_type& a = {}) : buffer_(a) {}
unlimited_storage(const unlimited_storage&) = default;
unlimited_storage& operator=(const unlimited_storage&) = default;
unlimited_storage(unlimited_storage&&) = default;
unlimited_storage& operator=(unlimited_storage&&) = default;
// TODO
// template <class Allocator>
// unlimited_storage(const unlimited_storage<Allocator>& s)
template <class Iterable, class = detail::requires_iterable<Iterable>>
explicit unlimited_storage(const Iterable& s) {
using std::begin;
using std::end;
auto s_begin = begin(s);
auto s_end = end(s);
using V = typename std::iterator_traits<decltype(begin(s))>::value_type;
// must be non-const to avoid msvc warning about if constexpr
auto ti = buffer_type::template type_index<V>();
auto nt = mp11::mp_size<typename buffer_type::types>::value;
const std::size_t size = static_cast<std::size_t>(std::distance(s_begin, s_end));
if (ti < nt)
buffer_.template make<V>(size, s_begin);
else
buffer_.template make<double>(size, s_begin);
}
template <class Iterable, class = detail::requires_iterable<Iterable>>
unlimited_storage& operator=(const Iterable& s) {
*this = unlimited_storage(s);
return *this;
}
allocator_type get_allocator() const { return buffer_.alloc; }
void reset(std::size_t n) { buffer_.template make<U8>(n); }
std::size_t size() const noexcept { return buffer_.size; }
reference operator[](std::size_t i) noexcept { return {buffer_, i}; }
const_reference operator[](std::size_t i) const noexcept { return {buffer_, i}; }
bool operator==(const unlimited_storage& x) const noexcept {
if (size() != x.size()) return false;
return buffer_.visit([&x](const auto* p) {
return x.buffer_.visit([p, n = x.size()](const auto* xp) {
return std::equal(p, p + n, xp, detail::safe_equal{});
});
});
}
template <class Iterable>
bool operator==(const Iterable& iterable) const {
if (size() != iterable.size()) return false;
return buffer_.visit([&iterable](const auto* p) {
return std::equal(p, p + iterable.size(), std::begin(iterable),
detail::safe_equal{});
});
}
unlimited_storage& operator*=(const double x) {
buffer_.visit(multiplier(), buffer_, x);
return *this;
}
iterator begin() noexcept { return {&buffer_, 0}; }
iterator end() noexcept { return {&buffer_, size()}; }
const_iterator begin() const noexcept { return {&buffer_, 0}; }
const_iterator end() const noexcept { return {&buffer_, size()}; }
/// implementation detail; used by unit tests, not part of generic storage interface
template <class T>
unlimited_storage(std::size_t s, const T* p, const allocator_type& a = {})
: buffer_(std::move(a)) {
buffer_.template make<T>(s, p);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
if (Archive::is_loading::value) {
buffer_type tmp(buffer_.alloc);
std::size_t size;
ar& make_nvp("type", tmp.type);
ar& make_nvp("size", size);
tmp.visit([this, size](auto* tp) {
assert(tp == nullptr);
using T = std::decay_t<decltype(*tp)>;
buffer_.template make<T>(size);
});
} else {
ar& make_nvp("type", buffer_.type);
ar& make_nvp("size", buffer_.size);
}
buffer_.visit([this, &ar](auto* tp) {
auto w = detail::make_array_wrapper(tp, this->buffer_.size);
ar& make_nvp("buffer", w);
});
}
private:
struct incrementor {
template <class T>
void operator()(T* tp, buffer_type& b, std::size_t i) {
assert(tp && i < b.size);
if (!detail::safe_increment(tp[i])) {
using U = detail::next_type<typename buffer_type::types, T>;
b.template make<U>(b.size, tp);
++static_cast<U*>(b.ptr)[i];
}
}
void operator()(large_int* tp, buffer_type&, std::size_t i) { ++tp[i]; }
void operator()(double* tp, buffer_type&, std::size_t i) { ++tp[i]; }
};
struct adder {
template <class U>
void operator()(double* tp, buffer_type&, std::size_t i, const U& x) {
tp[i] += static_cast<double>(x);
}
void operator()(large_int* tp, buffer_type&, std::size_t i, const large_int& x) {
tp[i] += x; // potentially adding large_int to itself is safe
}
template <class T, class U>
void operator()(T* tp, buffer_type& b, std::size_t i, const U& x) {
is_x_integral(std::is_integral<U>{}, tp, b, i, x);
}
template <class T, class U>
void is_x_integral(std::false_type, T* tp, buffer_type& b, std::size_t i,
const U& x) {
// x could be reference to buffer we manipulate, make copy before changing buffer
const auto v = static_cast<double>(x);
b.template make<double>(b.size, tp);
operator()(static_cast<double*>(b.ptr), b, i, v);
}
template <class T>
void is_x_integral(std::false_type, T* tp, buffer_type& b, std::size_t i,
const large_int& x) {
// x could be reference to buffer we manipulate, make copy before changing buffer
const auto v = static_cast<large_int>(x);
b.template make<large_int>(b.size, tp);
operator()(static_cast<large_int*>(b.ptr), b, i, v);
}
template <class T, class U>
void is_x_integral(std::true_type, T* tp, buffer_type& b, std::size_t i, const U& x) {
is_x_unsigned(std::is_unsigned<U>{}, tp, b, i, x);
}
template <class T, class U>
void is_x_unsigned(std::false_type, T* tp, buffer_type& b, std::size_t i,
const U& x) {
if (x >= 0)
is_x_unsigned(std::true_type{}, tp, b, i, detail::make_unsigned(x));
else
is_x_integral(std::false_type{}, tp, b, i, static_cast<double>(x));
}
template <class T, class U>
void is_x_unsigned(std::true_type, T* tp, buffer_type& b, std::size_t i, const U& x) {
if (detail::safe_radd(tp[i], x)) return;
// x could be reference to buffer we manipulate, need to convert to value
const auto y = x;
using TN = detail::next_type<typename buffer_type::types, T>;
b.template make<TN>(b.size, tp);
is_x_unsigned(std::true_type{}, static_cast<TN*>(b.ptr), b, i, y);
}
template <class U>
void is_x_unsigned(std::true_type, large_int* tp, buffer_type&, std::size_t i,
const U& x) {
tp[i] += x;
}
};
struct multiplier {
template <class T>
void operator()(T* tp, buffer_type& b, const double x) {
// potential lossy conversion that cannot be avoided
b.template make<double>(b.size, tp);
operator()(static_cast<double*>(b.ptr), b, x);
}
void operator()(double* tp, buffer_type& b, const double x) {
for (auto end = tp + b.size; tp != end; ++tp) *tp *= x;
}
template <class T>
void operator()(T* tp, buffer_type& b, std::size_t i, const double x) {
b.template make<double>(b.size, tp);
operator()(static_cast<double*>(b.ptr), b, i, x);
}
void operator()(double* tp, buffer_type&, std::size_t i, const double x) {
tp[i] *= static_cast<double>(x);
}
};
mutable buffer_type buffer_;
friend struct unsafe_access;
};
} // namespace histogram
} // namespace boost
#endif
+118
View File
@@ -0,0 +1,118 @@
// Copyright 2018 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_UNSAFE_ACCESS_HPP
#define BOOST_HISTOGRAM_UNSAFE_ACCESS_HPP
#include <boost/histogram/detail/axes.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
/** Unsafe read/write access to private data that potentially breaks consistency.
This struct enables access to private data of some classes. It is intended for library
developers who need this to implement algorithms efficiently, for example,
serialization. Users should not use this. If you are a user who absolutely needs this to
get a specific effect, please submit an issue on Github. Perhaps the public
interface is insufficient and should be extended for your use case.
Unlike the normal interface, the unsafe_access interface may change between versions.
If your code relies on unsafe_access, it may or may not break when you update Boost.
This is another reason to not use it unless you are ok with these conditions.
*/
struct unsafe_access {
/**
Get axes.
@param hist histogram.
*/
template <class Histogram>
static auto& axes(Histogram& hist) {
return hist.axes_;
}
/// @copydoc axes()
template <class Histogram>
static const auto& axes(const Histogram& hist) {
return hist.axes_;
}
/**
Get mutable axis reference with compile-time number.
@param hist histogram.
@tparam I axis index (optional, default: 0).
*/
template <class Histogram, unsigned I = 0>
static decltype(auto) axis(Histogram& hist, std::integral_constant<unsigned, I> = {}) {
assert(I < hist.rank());
return detail::axis_get<I>(hist.axes_);
}
/**
Get mutable axis reference with run-time number.
@param hist histogram.
@param i axis index.
*/
template <class Histogram>
static decltype(auto) axis(Histogram& hist, unsigned i) {
assert(i < hist.rank());
return detail::axis_get(hist.axes_, i);
}
/**
Get storage.
@param hist histogram.
*/
template <class Histogram>
static auto& storage(Histogram& hist) {
return hist.storage_;
}
/// @copydoc storage()
template <class Histogram>
static const auto& storage(const Histogram& hist) {
return hist.storage_;
}
/**
Get index offset.
@param hist histogram
*/
template <class Histogram>
static auto& offset(Histogram& hist) {
return hist.offset_;
}
/// @copydoc offset()
template <class Histogram>
static const auto& offset(const Histogram& hist) {
return hist.offset_;
}
/**
Get buffer of unlimited_storage.
@param storage instance of unlimited_storage.
*/
template <class Allocator>
static constexpr auto& unlimited_storage_buffer(unlimited_storage<Allocator>& storage) {
return storage.buffer_;
}
/**
Get implementation of storage_adaptor.
@param storage instance of storage_adaptor.
*/
template <class T>
static constexpr auto& storage_adaptor_impl(storage_adaptor<T>& storage) {
return static_cast<typename storage_adaptor<T>::impl_type&>(storage);
}
};
} // namespace histogram
} // namespace boost
#endif
@@ -0,0 +1,132 @@
// 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_UTILITY_BINOMIAL_PROPORTION_INTERVAL_HPP
#define BOOST_HISTOGRAM_UTILITY_BINOMIAL_PROPORTION_INTERVAL_HPP
#include <boost/histogram/detail/normal.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cmath>
#include <stdexcept>
#include <type_traits>
namespace boost {
namespace histogram {
namespace utility {
/**
Common base class for interval calculators.
*/
template <class ValueType>
class binomial_proportion_interval {
static_assert(std::is_floating_point<ValueType>::value,
"Value must be a floating point!");
public:
using value_type = ValueType;
using interval_type = std::pair<value_type, value_type>;
/** Compute interval for given number of successes and failures.
@param successes Number of successful trials.
@param failures Number of failed trials.
*/
virtual interval_type operator()(value_type successes,
value_type failures) const noexcept = 0;
/** Compute interval for a fraction accumulator.
@param fraction Fraction accumulator.
*/
template <class T>
interval_type operator()(const accumulators::fraction<T>& fraction) const noexcept {
return operator()(fraction.successes(), fraction.failures());
}
};
class deviation;
class confidence_level;
/** Confidence level in units of deviations for intervals.
Intervals become wider as the deviation value increases. The standard deviation
corresponds to a value of 1 and corresponds to 68.3 % confidence level. The conversion
between confidence level and deviations is based on a two-sided interval on the normal
distribution.
*/
class deviation {
public:
/// constructor from units of standard deviations
explicit deviation(double d) : d_{d} {
if (d <= 0)
BOOST_THROW_EXCEPTION(std::invalid_argument("scaling factor must be positive"));
}
/// explicit conversion to units of standard deviations
template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>>
explicit operator T() const noexcept {
return static_cast<T>(d_);
}
/// implicit conversion to confidence level
operator confidence_level() const noexcept; // need to implement confidence_level first
friend deviation operator*(deviation d, double z) noexcept {
return deviation(d.d_ * z);
}
friend deviation operator*(double z, deviation d) noexcept { return d * z; }
friend bool operator==(deviation a, deviation b) noexcept { return a.d_ == b.d_; }
friend bool operator!=(deviation a, deviation b) noexcept { return !(a == b); }
private:
double d_;
};
/** Confidence level for intervals.
Intervals become wider as the deviation value increases.
*/
class confidence_level {
public:
/// constructor from confidence level (a probability)
explicit confidence_level(double cl) : cl_{cl} {
if (cl <= 0 || cl >= 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("0 < cl < 1 is required"));
}
/// explicit conversion to numerical confidence level
template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>>
explicit operator T() const noexcept {
return static_cast<T>(cl_);
}
/// implicit conversion to units of standard deviation
operator deviation() const noexcept {
return deviation{detail::normal_ppf(std::fma(0.5, cl_, 0.5))};
}
friend bool operator==(confidence_level a, confidence_level b) noexcept {
return a.cl_ == b.cl_;
}
friend bool operator!=(confidence_level a, confidence_level b) noexcept {
return !(a == b);
}
private:
double cl_;
};
inline deviation::operator confidence_level() const noexcept {
// solve normal cdf(z) - cdf(-z) = 2 (cdf(z) - 0.5)
return confidence_level{std::fma(2.0, detail::normal_cdf(d_), -1.0)};
}
} // namespace utility
} // namespace histogram
} // namespace boost
#endif
+83
View File
@@ -0,0 +1,83 @@
// 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_UTILITY_CLOPPER_PEARSON_INTERVAL_HPP
#define BOOST_HISTOGRAM_UTILITY_CLOPPER_PEARSON_INTERVAL_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/utility/binomial_proportion_interval.hpp>
#include <boost/math/distributions/beta.hpp>
#include <cmath>
namespace boost {
namespace histogram {
namespace utility {
/**
Clopper-Pearson interval.
This is the classic frequentist interval obtained with the Neyman construction.
It is therefore often called the 'exact' interval. It is guaranteed to have at least the
requested confidence level for all values of the fraction.
The interval is wider than others that produce coverage closer to the expected
confidence level over a random ensemble of factions. The Clopper-Pearson interval
essentially always overcovers for such a random ensemble, which is undesirable in
practice. The Clopper-Pearson interval is recommended when it is important to be
conservative, but the Wilson interval should be preferred in most applications.
C. Clopper, E.S. Pearson (1934), Biometrika 26 (4): 404-413.
doi:10.1093/biomet/26.4.404.
*/
template <class ValueType>
class clopper_pearson_interval : public binomial_proportion_interval<ValueType> {
public:
using value_type = typename clopper_pearson_interval::value_type;
using interval_type = typename clopper_pearson_interval::interval_type;
/** Construct Clopper-Pearson interval computer.
@param cl Confidence level for the interval. The default value produces a
confidence level of 68 % equivalent to one standard deviation. Both `deviation` and
`confidence_level` objects can be used to initialize the interval.
*/
explicit clopper_pearson_interval(confidence_level cl = deviation{1}) noexcept
: alpha_half_{static_cast<value_type>(0.5 - 0.5 * static_cast<double>(cl))} {}
using binomial_proportion_interval<ValueType>::operator();
/** Compute interval for given number of successes and failures.
@param successes Number of successful trials.
@param failures Number of failed trials.
*/
interval_type operator()(value_type successes, value_type failures) const noexcept {
// analytical solution when successes or failures are zero
// T. Mans (2014), Electronic Journal of Statistics. 8 (1): 817-840.
// arXiv:1303.1288. doi:10.1214/14-EJS909.
const value_type total = successes + failures;
if (successes == 0) return {0, 1 - std::pow(alpha_half_, 1 / total)};
if (failures == 0) return {std::pow(alpha_half_, 1 / total), 1};
// Source:
// https://en.wikipedia.org/wiki/
// Binomial_proportion_confidence_interval#Clopper%E2%80%93Pearson_interval
math::beta_distribution<value_type> beta_a(successes, failures + 1);
const value_type a = math::quantile(beta_a, alpha_half_);
math::beta_distribution<value_type> beta_b(successes + 1, failures);
const value_type b = math::quantile(beta_b, 1 - alpha_half_);
return {a, b};
}
private:
value_type alpha_half_;
};
} // namespace utility
} // namespace histogram
} // namespace boost
#endif
+78
View File
@@ -0,0 +1,78 @@
// 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_UTILITY_JEFFREYS_INTERVAL_HPP
#define BOOST_HISTOGRAM_UTILITY_JEFFREYS_INTERVAL_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/utility/binomial_proportion_interval.hpp>
#include <boost/math/distributions/beta.hpp>
#include <cmath>
namespace boost {
namespace histogram {
namespace utility {
/**
Jeffreys interval.
This is the Bayesian credible interval with a Jeffreys prior. Although it has a
Bayesian derivation, it has good coverage. The interval boundaries are close to the
Wilson interval. A special property of this interval is that it is equal-tailed; the
probability of the true value to be above or below the interval is approximately equal.
To avoid coverage probability tending to zero when the fraction approaches 0 or 1,
this implementation uses a modification described in section 4.1.2 of the
paper by L.D. Brown, T.T. Cai, A. DasGupta, Statistical Science 16 (2001) 101-133,
doi:10.1214/ss/1009213286.
*/
template <class ValueType>
class jeffreys_interval : public binomial_proportion_interval<ValueType> {
public:
using value_type = typename jeffreys_interval::value_type;
using interval_type = typename jeffreys_interval::interval_type;
/** Construct Jeffreys interval computer.
@param cl Confidence level for the interval. The default value produces a
confidence level of 68 % equivalent to one standard deviation. Both `deviation` and
`confidence_level` objects can be used to initialize the interval.
*/
explicit jeffreys_interval(confidence_level cl = deviation{1}) noexcept
: alpha_half_{static_cast<value_type>(0.5 - 0.5 * static_cast<double>(cl))} {}
using binomial_proportion_interval<ValueType>::operator();
/** Compute interval for given number of successes and failures.
@param successes Number of successful trials.
@param failures Number of failed trials.
*/
interval_type operator()(value_type successes, value_type failures) const noexcept {
// See L.D. Brown, T.T. Cai, A. DasGupta, Statistical Science 16 (2001) 101-133,
// doi:10.1214/ss/1009213286, section 4.1.2.
const value_type half{0.5};
const value_type total = successes + failures;
// if successes or failures are 0, modified interval is equal to Clopper-Pearson
if (successes == 0) return {0, 1 - std::pow(alpha_half_, 1 / total)};
if (failures == 0) return {std::pow(alpha_half_, 1 / total), 1};
math::beta_distribution<value_type> beta(successes + half, failures + half);
const value_type a = successes == 1 ? 0 : math::quantile(beta, alpha_half_);
const value_type b = failures == 1 ? 1 : math::quantile(beta, 1 - alpha_half_);
return {a, b};
}
private:
value_type alpha_half_;
};
} // namespace utility
} // namespace histogram
} // namespace boost
#endif
+80
View File
@@ -0,0 +1,80 @@
// 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_UTILITY_WALD_INTERVAL_HPP
#define BOOST_HISTOGRAM_UTILITY_WALD_INTERVAL_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/utility/binomial_proportion_interval.hpp>
#include <cmath>
#include <utility>
namespace boost {
namespace histogram {
namespace utility {
/**
Wald interval or normal approximation interval.
The Wald interval is a symmetric interval. It is simple to compute, but has poor
statistical properties and is universally rejected by statisticians. It should always be
replaced by another iternal, for example, the Wilson interval.
The Wald interval can be derived easily using the plug-in estimate of the variance for
the binomial distribution, which is likely a reason for its omnipresence. Without
further insight into statistical theory, it is not obvious that this derivation is
flawed and that better alternatives exist.
The Wald interval undercovers on average. It is unsuitable when the sample size is small
or when the fraction is close to 0 or 1. e. Its limits are not naturally bounded by 0
or 1. It produces empty intervals if the number of successes or failures is zero.
For a critique of the Wald interval, see (a selection):
L.D. Brown, T.T. Cai, A. DasGupta, Statistical Science 16 (2001) 101-133.
R. D. Cousins, K. E. Hymes, J. Tucker, Nucl. Instrum. Meth. A 612 (2010) 388-398.
*/
template <class ValueType>
class wald_interval : public binomial_proportion_interval<ValueType> {
public:
using value_type = typename wald_interval::value_type;
using interval_type = typename wald_interval::interval_type;
/** Construct Wald interval computer.
@param d Number of standard deviations for the interval. The default value 1
corresponds to a confidence level of 68 %. Both `deviation` and `confidence_level`
objects can be used to initialize the interval.
*/
explicit wald_interval(deviation d = deviation{1.0}) noexcept
: z_{static_cast<value_type>(d)} {}
using binomial_proportion_interval<ValueType>::operator();
/** Compute interval for given number of successes and failures.
@param successes Number of successful trials.
@param failures Number of failed trials.
*/
interval_type operator()(value_type successes, value_type failures) const noexcept {
// See https://en.wikipedia.org/wiki/
// Binomial_proportion_confidence_interval
// #Normal_approximation_interval_or_Wald_interval
const value_type total_inv = 1 / (successes + failures);
const value_type a = successes * total_inv;
const value_type b = (z_ * total_inv) * std::sqrt(successes * failures * total_inv);
return {a - b, a + b};
}
private:
value_type z_;
};
} // namespace utility
} // namespace histogram
} // namespace boost
#endif
+86
View File
@@ -0,0 +1,86 @@
// 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_UTILITY_WILSON_INTERVAL_HPP
#define BOOST_HISTOGRAM_UTILITY_WILSON_INTERVAL_HPP
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/utility/binomial_proportion_interval.hpp>
#include <cmath>
#include <utility>
namespace boost {
namespace histogram {
namespace utility {
/**
Wilson interval.
The Wilson score interval is simple to compute, has good coverage. Intervals are
automatically bounded between 0 and 1 and never empty. The interval is asymmetric.
Wilson, E. B. (1927). "Probable inference, the law of succession, and statistical
inference". Journal of the American Statistical Association. 22 (158): 209-212.
doi:10.1080/01621459.1927.10502953. JSTOR 2276774.
The coverage probability for a random ensemble of fractions is close to the nominal
value. Unlike the Clopper-Pearson interval, the Wilson score interval is not
conservative. For some values of the fractions, the interval undercovers and overcovers
for neighboring values. This is a shared property of all alternatives to the
Clopper-Pearson interval.
The Wilson score intervals is widely recommended for general use in the literature. For
a review of the literature, see R. D. Cousins, K. E. Hymes, J. Tucker, Nucl. Instrum.
Meth. A 612 (2010) 388-398.
*/
template <class ValueType>
class wilson_interval : public binomial_proportion_interval<ValueType> {
public:
using value_type = typename wilson_interval::value_type;
using interval_type = typename wilson_interval::interval_type;
/** Construct Wilson interval computer.
@param d Number of standard deviations for the interval. The default value 1
corresponds to a confidence level of 68 %. Both `deviation` and `confidence_level`
objects can be used to initialize the interval.
*/
explicit wilson_interval(deviation d = deviation{1.0}) noexcept
: z_{static_cast<value_type>(d)} {}
using binomial_proportion_interval<ValueType>::operator();
/** Compute interval for given number of successes and failures.
@param successes Number of successful trials.
@param failures Number of failed trials.
*/
interval_type operator()(value_type successes, value_type failures) const noexcept {
// See https://en.wikipedia.org/wiki/
// Binomial_proportion_confidence_interval
// #Wilson_score_interval
// We make sure calculation is done in single precision if value_type is float
// by converting all literals to value_type. Double literals in the equation
// would turn intermediate values to double.
const value_type half{0.5}, quarter{0.25}, zsq{z_ * z_};
const value_type total = successes + failures;
const value_type minv = 1 / (total + zsq);
const value_type t1 = (successes + half * zsq) * minv;
const value_type t2 =
z_ * minv * std::sqrt(successes * failures / total + quarter * zsq);
return {t1 - t2, t1 + t2};
}
private:
value_type z_;
};
} // namespace utility
} // namespace histogram
} // namespace boost
#endif
+45
View File
@@ -0,0 +1,45 @@
// 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_WEIGHT_HPP
#define BOOST_HISTOGRAM_WEIGHT_HPP
#include <utility>
namespace boost {
namespace histogram {
/** Weight holder and type envelope.
You should not construct these directly, use the weight() helper function.
@tparam Underlying arithmetic type.
*/
template <class T>
struct weight_type {
/// Access underlying value.
T value;
/// Allow implicit conversions of types when the underlying value type allows them.
template <class U>
operator weight_type<U>() const {
return weight_type<U>{static_cast<U>(value)};
}
};
/** Helper function to mark argument as weight.
@param t argument to be forward to the histogram.
*/
template <class T>
auto weight(T&& t) noexcept {
return weight_type<T>{std::forward<T>(t)};
}
} // namespace histogram
} // namespace boost
#endif