feat():initial version

This commit is contained in:
2026-06-09 20:16:47 +08:00
commit 85fbb3188c
14809 changed files with 3044607 additions and 0 deletions

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/mean.hpp>
#include <boost/histogram/accumulators/sum.hpp>
#include <boost/histogram/accumulators/thread_safe.hpp>
#include <boost/histogram/accumulators/weighted_mean.hpp>
#include <boost/histogram/accumulators/weighted_sum.hpp>
#endif

View File

@@ -0,0 +1,137 @@
// 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_NUMBER_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_NUMBER_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/fwd.hpp> // for count<>
#include <type_traits> // for std::common_type
namespace boost {
namespace histogram {
namespace accumulators {
/**
Uses a C++ builtin arithmetic type to accumulate a count.
This wrapper class may be used as a base class by users who want to add custom metadata
to each bin of a histogram. Otherwise, arithmetic types should be used directly as
accumulators in storages for simplicity. In other words, prefer `dense_storage<double>`
over `dense_storage<count<double>>`, both are functionally equivalent.
When weighted data is accumulated and high precision is required, use
`accumulators::sum` instead. 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`.
*/
template <class ValueType>
class count {
public:
using value_type = ValueType;
using const_reference = const value_type&;
count() = 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>
count(const count<T>& 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
const_reference 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 */) {
ar& make_nvp("value", value_);
}
// 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_; }
// end: extra operators
private:
value_type value_{};
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
struct common_type<boost::histogram::accumulators::count<T>,
boost::histogram::accumulators::count<U>> {
using type = boost::histogram::accumulators::count<common_type_t<T, U>>;
};
} // namespace std
#endif
#endif

View File

@@ -0,0 +1,153 @@
// 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/fwd.hpp> // for mean<>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <stdexcept>
#include <type_traits>
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 (sum_ != 0 || rhs.sum_ != 0) {
const auto tmp = mean_ * sum_ + rhs.mean_ * rhs.sum_;
sum_ += rhs.sum_;
mean_ = tmp / sum_;
}
sum_of_deltas_squared_ += rhs.sum_of_deltas_squared_;
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
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

View File

@@ -0,0 +1,107 @@
// 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>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const count<U>& 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() << " + " << x.small() << ")";
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 T>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const thread_safe<T>& x) {
os << x.load();
return os;
}
} // namespace accumulators
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
#endif

View File

@@ -0,0 +1,178 @@
// 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(), s.small()) {}
/// Initialize sum explicitly with large and small parts
sum(const_reference large, const_reference small) noexcept
: large_(large), small_(small) {}
/// 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() const noexcept { return large_; }
/// Return small part of the sum.
const_reference small() const noexcept { return 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

View File

@@ -0,0 +1,76 @@
// 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_THREAD_SAFE_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_THREAD_SAFE_HPP
#include <atomic>
#include <boost/core/nvp.hpp>
#include <boost/mp11/utility.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace accumulators {
/** Thread-safe adaptor for builtin integral and floating point numbers.
This adaptor uses std::atomic to make concurrent increments and additions safe for the
stored value.
On common computing platforms, the adapted integer has the same size and
alignment as underlying type. The atomicity is implemented with a special CPU
instruction. On exotic platforms the size of the adapted number may be larger and/or the
type may have different alignment, which means it cannot be tightly packed into arrays.
@tparam T type to adapt, must be supported by std::atomic.
*/
template <class T>
class thread_safe : public std::atomic<T> {
public:
using value_type = T;
using super_t = std::atomic<T>;
thread_safe() noexcept : super_t(static_cast<T>(0)) {}
// non-atomic copy and assign is allowed, because storage is locked in this case
thread_safe(const thread_safe& o) noexcept : super_t(o.load()) {}
thread_safe& operator=(const thread_safe& o) noexcept {
super_t::store(o.load());
return *this;
}
thread_safe(value_type arg) : super_t(arg) {}
thread_safe& operator=(value_type arg) {
super_t::store(arg);
return *this;
}
thread_safe& operator+=(const thread_safe& arg) {
operator+=(arg.load());
return *this;
}
thread_safe& operator+=(value_type arg) {
super_t::fetch_add(arg, std::memory_order_relaxed);
return *this;
}
thread_safe& operator++() {
operator+=(static_cast<value_type>(1));
return *this;
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
auto value = super_t::load();
ar& make_nvp("value", value);
super_t::store(value);
}
};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#endif

View File

@@ -0,0 +1,149 @@
// 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/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 (sum_of_weights_ != 0 || rhs.sum_of_weights_ != 0) {
const auto tmp =
weighted_mean_ * sum_of_weights_ + rhs.weighted_mean_ * rhs.sum_of_weights_;
sum_of_weights_ += rhs.sum_of_weights_;
sum_of_weights_squared_ += rhs.sum_of_weights_squared_;
weighted_mean_ = tmp / sum_of_weights_;
}
sum_of_weighted_deltas_squared_ += rhs.sum_of_weighted_deltas_squared_;
return *this;
}
/** Scale by value
This acts as if all samples were scaled by the value.
*/
weighted_mean& operator*=(const_reference s) {
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 mean value of accumulated weighted samples.
The result is undefined, if `sum_of_weights() == 0`.
*/
const_reference value() const noexcept { return weighted_mean_; }
/** Return variance of accumulated weighted samples
The result is undefined, if `sum_of_weights() == 0` or
`sum_of_weights() == sum_of_weights_squared()`.
*/
value_type variance() const {
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

View File

@@ -0,0 +1,119 @@
// 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/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.
template <class T>
weighted_sum& operator+=(const weight_type<T>& w) {
sum_of_weights_ += w.value;
sum_of_weights_squared_ += w.value * 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;
}
/// 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

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

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

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

View File

@@ -0,0 +1,453 @@
// 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;
using reduce_option [[deprecated("use reduce_command instead")]] =
reduce_command; ///< deprecated
/** 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.
@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.
@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 = !o.crop && AO::test(axis::option::underflow);
o.use_overflow_bin = !o.crop && 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) {
o.begin.index = 0;
o.end.index = a_in.size();
} else {
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;
}
// 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.
@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

View File

@@ -0,0 +1,67 @@
// 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;
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

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

View File

@@ -0,0 +1,98 @@
// 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.
*/
explicit boolean(metadata_type meta = {}) : 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

View File

@@ -0,0 +1,205 @@
// 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>;
static_assert(!options_type::test(option::underflow),
"category axis cannot have underflow");
static_assert(!options_type::test(option::circular),
"category axis cannot be circular");
static_assert(!(options_type::test(option::growth) &&
options_type::test(option::overflow)),
"growing category axis cannot have entries in overflow bin");
public:
constexpr category() = default;
explicit category(allocator_type alloc) : vec_(alloc) {}
/** Construct from 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.
* \param alloc allocator instance to use.
*/
template <class It, class = detail::requires_iterator<It>>
category(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(alloc) {
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++);
}
/** Construct axis from iterable sequence of unique values.
*
* \param iterable sequence of unique values.
* \param meta description of the axis.
* \param alloc allocator instance to use.
*/
template <class C, class = detail::requires_iterable<C>>
category(const C& iterable, metadata_type meta = {}, allocator_type 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 alloc allocator instance to use.
*/
template <class U>
category(std::initializer_list<U> list, metadata_type meta = {},
allocator_type 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>>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif

View File

@@ -0,0 +1,221 @@
// 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 faster than a regular axis.
@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 (all values allowed).
*/
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)>;
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_type::test(option::circular | option::growth) ||
(options_type::test(option::circular) ^
options_type::test(option::growth)),
"circular and growth options are mutually exclusive");
static_assert(std::is_floating_point<value_type>::value ||
(!options_type::test(option::circular) &&
!options_type::test(option::growth)) ||
(!options_type::test(option::overflow) &&
!options_type::test(option::underflow)),
"circular or growing integer axis with integral type "
"cannot have entries in underflow or overflow bins");
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.
*/
integer(value_type start, value_type stop, metadata_type meta = {})
: metadata_base(std::move(meta))
, size_(static_cast<index_type>(stop - start))
, min_(start) {
if (!(stop >= start))
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 {
return (options() & option::underflow || options() & option::overflow) ||
(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>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif

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

View File

@@ -0,0 +1,66 @@
// 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:
/// Make iterator from axis and index.
iterator(const Axis& axis, index_type idx)
: iterator::iterator_adaptor_(idx), axis_(axis) {}
/// Return current bin object.
decltype(auto) 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

View File

@@ -0,0 +1,93 @@
// 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;
// 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;
}
private:
mutable metadata_type data_;
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_; }
};
#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

View File

@@ -0,0 +1,84 @@
// 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>
struct bit : bitset<(1 << Pos)> {};
/// 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

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

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

View File

@@ -0,0 +1,431 @@
// 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.
@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 (all values allowed).
*/
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)>;
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");
using unit_type = detail::get_unit_type<value_type>;
using internal_value_type = detail::get_scale_type<value_type>;
static_assert(std::is_floating_point<internal_value_type>::value,
"regular axis requires floating point type");
static_assert(
(!options_type::test(option::circular) && !options_type::test(option::growth)) ||
(options_type::test(option::circular) ^ options_type::test(option::growth)),
"circular and growth options are mutually exclusive");
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).
*/
regular(transform_type trans, unsigned n, value_type start, value_type stop,
metadata_type meta = {})
: 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_) {
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).
*/
regular(unsigned n, value_type start, value_type stop, metadata_type meta = {})
: regular({}, n, start, stop, std::move(meta)) {}
/** 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).
*
* 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>
regular(transform_type trans, step_type<T> step, value_type start, value_type stop,
metadata_type meta = {})
: 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)) {}
/** 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).
*
* 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>
regular(step_type<T> step, value_type start, value_type stop, metadata_type meta = {})
: regular({}, step, start, stop, std::move(meta)) {}
/// 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;
}
}
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 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>>>;
#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

View File

@@ -0,0 +1,498 @@
// 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>{}));
template <class Axis>
using static_options [[deprecated("use get_options instead")]] = get_options<Axis>;
#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 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.
An inclusive axis has a bin for every possible input value. A histogram which consists
only of inclusive axes can be filled more efficiently, since 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.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_inclusive = decltype(detail::traits_is_inclusive<Axis>(detail::priority<1>{}));
template <class Axis>
using static_is_inclusive [[deprecated("use is_inclusive instead")]] = is_inclusive<Axis>;
#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

View File

@@ -0,0 +1,251 @@
// 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.
@tparam Value input value type, must be floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option (all values allowed).
@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>;
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_type::test(option::circular) && !options_type::test(option::growth)) ||
(options_type::test(option::circular) ^ options_type::test(option::growth)),
"circular and growth options are mutually exclusive");
public:
constexpr variable() = default;
explicit variable(allocator_type alloc) : vec_(alloc) {}
/** Construct from iterator range of bin edges.
*
* \param begin begin of edge sequence.
* \param end end of edge sequence.
* \param meta description of the axis.
* \param alloc allocator instance to use.
*/
template <class It, class = detail::requires_iterator<It>>
variable(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(std::move(alloc)) {
if (std::distance(begin, end) < 2)
BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
vec_.reserve(std::distance(begin, end));
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"));
}
/** Construct variable axis from iterable range of bin edges.
*
* \param iterable iterable range of bin edges.
* \param meta description of the axis.
* \param alloc allocator instance to use.
*/
template <class U, class = detail::requires_iterable<U>>
variable(const U& iterable, metadata_type meta = {}, allocator_type 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.
* @param alloc allocator instance to use.
*/
template <class U>
variable(std::initializer_list<U> list, metadata_type meta = {},
allocator_type 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);
}
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 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>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif

View File

@@ -0,0 +1,347 @@
// 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

View File

@@ -0,0 +1,81 @@
// 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<1>)
-> decltype(accumulator_traits_impl_call_op(&T::operator()));
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<1>) {
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<1>{}));
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

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

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

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_ARRAY_WRAPPER_HPP
#define BOOST_HISTOGRAM_DETAIL_ARRAY_WRAPPER_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/span.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 : boost::histogram::detail::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

View File

@@ -0,0 +1,439 @@
// 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
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

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

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

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

View File

@@ -0,0 +1,207 @@
// 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 {
#define BOOST_HISTOGRAM_DETAIL_DETECT(name, cond) \
template <class U> \
struct name##_impl { \
typedef char yes[1]; \
typedef char no[2]; \
template <class T> \
static yes& test(T& t, decltype(cond, 0)); \
template <class T> \
static no& test(T&, float); \
using type = \
std::integral_constant<bool, (sizeof(test(std::declval<U&>(), 0)) == 1)>; \
}; \
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 { \
typedef char yes[1]; \
typedef char no[2]; \
template <class T, class U> \
static yes& test(decltype(cond, 0)); \
template <class, class> \
static no& test(float); \
using type = std::integral_constant<bool, (sizeof(test<V, W>(0)) == 1)>; \
}; \
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,
(std::declval<T&>().inverse(std::declval<T&>().forward(std::declval<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(has_operator_preincrement, ++t);
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_equal, (std::declval<const T&>() ==
std::declval<const U&>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_radd,
(std::declval<T&>() += std::declval<U>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rsub,
(std::declval<T&>() -= std::declval<U>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rmul,
(std::declval<T&>() *= std::declval<U>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rdiv,
(std::declval<T&>() /= std::declval<U>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(
has_method_eq, (std::declval<const T&>().operator==(std::declval<const U&>())));
BOOST_HISTOGRAM_DETAIL_DETECT(has_threading_support, (T::has_threading_support));
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
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 {};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

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
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

View File

@@ -0,0 +1,339 @@
// 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/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/span.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
index_type idx{*begin_};
call_2(IsGrowing{}, &idx, value);
if (is_valid(idx)) {
const auto delta =
static_cast<std::intptr_t>(idx) - static_cast<std::intptr_t>(*begin_);
for (auto&& i : make_span(begin_, size_)) i += 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
// 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) {
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 dtl::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 dtl::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

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

View File

@@ -0,0 +1,162 @@
// 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 <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 // proxy references
{
struct proxy {
explicit proxy(Reference const& x) noexcept : m_ref(x) {}
Reference* operator->() noexcept { return std::addressof(m_ref); }
Reference m_ref;
};
using result_type = proxy;
static result_type apply(Reference const& x) noexcept { return proxy(x); }
};
template <class T>
struct operator_arrow_dispatch_t<T&> // "real" references
{
using result_type = T*;
static result_type apply(T& x) noexcept { return std::addressof(x); }
};
// only for random access Base
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 = std::remove_const_t<Value>;
using pointer = typename operator_arrow_dispatch::result_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
iterator_adaptor() = default;
explicit iterator_adaptor(base_type const& iter) : iter_(iter) {}
pointer operator->() const noexcept {
return operator_arrow_dispatch::apply(this->derived().operator*());
}
reference operator[](difference_type n) const { return *(this->derived() + n); }
Derived& operator++() {
++iter_;
return this->derived();
}
Derived& operator--() {
--iter_;
return this->derived();
}
Derived operator++(int) {
Derived tmp(this->derived());
++iter_;
return tmp;
}
Derived operator--(int) {
Derived tmp(this->derived());
--iter_;
return tmp;
}
Derived& operator+=(difference_type n) {
iter_ += n;
return this->derived();
}
Derived& operator-=(difference_type n) {
iter_ -= n;
return this->derived();
}
Derived operator+(difference_type n) const {
Derived tmp(this->derived());
tmp += n;
return tmp;
}
Derived operator-(difference_type n) const { return operator+(-n); }
template <class... Ts>
difference_type 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 !this->derived().operator==(x); // equal operator may be overridden in derived
}
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 in derived classes
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 iter_;
template <class, class, class, class>
friend class iterator_adaptor;
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

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

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

View File

@@ -0,0 +1,123 @@
// 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
template <class Index, class Opts>
std::size_t linearize(Opts, 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);
// must be non-const to avoid if constexpr warning from msvc
bool fast_track = std::is_same<Index, std::size_t>::value || (u && o);
if (fast_track) {
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;
} else {
assert(idx >= -1);
assert(idx < size + 1);
// must be non-const to avoid if constexpr warning from msvc
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));
}
// initial offset of out must be zero
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

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

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

View File

@@ -0,0 +1,70 @@
// 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
#if __cpp_lib_nonmember_container_access >= 201411
#include <iterator>
namespace boost {
namespace histogram {
namespace detail {
using std::data;
using std::size;
} // namespace detail
} // namespace histogram
} // namespace boost
#else // std implementations are not available
#include <initializer_list>
#include <type_traits>
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 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
#endif // BOOST_HISTOGRAM_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP

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

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

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

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/histogram/detail/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

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

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

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

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

View File

@@ -0,0 +1,277 @@
// 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_SPAN_HPP
#define BOOST_HISTOGRAM_DETAIL_SPAN_HPP
#ifdef __has_include
#if __has_include(<version>)
#include <version>
#ifdef __cpp_lib_span
#if __cpp_lib_span >= 201902
#define BOOST_HISTOGRAM_DETAIL_HAS_STD_SPAN
#endif
#endif
#endif
#endif
#ifdef BOOST_HISTOGRAM_DETAIL_HAS_STD_SPAN
#include <span>
namespace boost {
namespace histogram {
namespace detail {
using std::span;
} // namespace detail
} // namespace histogram
} // namespace boost
#else // C++17 span not available, so we use our implementation
// to be replaced by boost::span
#include <array>
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <cassert>
#include <initializer_list>
#include <iterator>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
namespace dtl = ::boost::histogram::detail;
static constexpr std::size_t dynamic_extent = ~static_cast<std::size_t>(0);
template <class T, std::size_t N>
class span_base {
public:
constexpr T* data() noexcept { return begin_; }
constexpr const T* data() const noexcept { return begin_; }
constexpr std::size_t size() const noexcept { return N; }
protected:
constexpr span_base(T* b, std::size_t s) noexcept : begin_(b) {
(void)s;
assert(N == s);
}
constexpr void set(T* b, std::size_t s) noexcept {
(void)s;
begin_ = b;
assert(N == s);
}
private:
T* begin_;
};
template <class T>
class span_base<T, dynamic_extent> {
public:
constexpr span_base() noexcept : begin_(nullptr), size_(0) {}
constexpr T* data() noexcept { return begin_; }
constexpr const T* data() const noexcept { return begin_; }
constexpr std::size_t size() const noexcept { return size_; }
protected:
constexpr span_base(T* b, std::size_t s) noexcept : begin_(b), size_(s) {}
constexpr void set(T* b, std::size_t s) noexcept {
begin_ = b;
size_ = s;
}
private:
T* begin_;
std::size_t size_;
};
template <class T, std::size_t Extent = dynamic_extent>
class span : public span_base<T, Extent> {
using base = span_base<T, Extent>;
public:
using element_type = T;
using value_type = std::remove_cv_t<T>;
using index_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
static constexpr std::size_t extent = Extent;
using base::base;
constexpr span(pointer first, pointer last)
: span(first, static_cast<std::size_t>(last - first)) {
assert(extent == dynamic_extent ||
static_cast<difference_type>(extent) == (last - first));
}
constexpr span(pointer ptr, index_type count) : base(ptr, count) {}
template <std::size_t N>
constexpr span(element_type (&arr)[N]) noexcept : span(dtl::data(arr), N) {
static_assert(extent == dynamic_extent || extent == N, "static sizes do not match");
}
template <std::size_t N,
class = std::enable_if_t<(extent == dynamic_extent || extent == N)> >
constexpr span(std::array<value_type, N>& arr) noexcept : span(dtl::data(arr), N) {}
template <std::size_t N,
class = std::enable_if_t<(extent == dynamic_extent || extent == N)> >
constexpr span(const std::array<value_type, N>& arr) noexcept
: span(dtl::data(arr), N) {}
template <class Container, class = std::enable_if_t<std::is_convertible<
decltype(dtl::size(std::declval<const Container&>()),
dtl::data(std::declval<const Container&>())),
pointer>::value> >
constexpr span(const Container& cont) : span(dtl::data(cont), dtl::size(cont)) {}
template <class Container, class = std::enable_if_t<std::is_convertible<
decltype(dtl::size(std::declval<Container&>()),
dtl::data(std::declval<Container&>())),
pointer>::value> >
constexpr span(Container& cont) : span(dtl::data(cont), dtl::size(cont)) {}
template <class U, std::size_t N,
class = std::enable_if_t<((extent == dynamic_extent || extent == N) &&
std::is_convertible<U, element_type>::value)> >
constexpr span(const span<U, N>& s) noexcept : span(s.data(), s.size()) {}
template <class U, std::size_t N,
class = std::enable_if_t<((extent == dynamic_extent || extent == N) &&
std::is_convertible<U, element_type>::value)> >
constexpr span(span<U, N>& s) noexcept : span(s.data(), s.size()) {}
constexpr span(const span& other) noexcept = default;
constexpr iterator begin() { return base::data(); }
constexpr const_iterator begin() const { return base::data(); }
constexpr const_iterator cbegin() const { return base::data(); }
constexpr iterator end() { return base::data() + base::size(); }
constexpr const_iterator end() const { return base::data() + base::size(); }
constexpr const_iterator cend() const { return base::data() + base::size(); }
reverse_iterator rbegin() { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const { return reverse_iterator(end()); }
const_reverse_iterator crbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rend() const { return reverse_iterator(begin()); }
const_reverse_iterator crend() { return reverse_iterator(begin()); }
constexpr reference front() { return *base::data(); }
constexpr reference back() { return *(base::data() + base::size() - 1); }
constexpr reference operator[](index_type idx) const { return base::data()[idx]; }
constexpr std::size_t size_bytes() const noexcept {
return base::size() * sizeof(element_type);
}
constexpr bool empty() const noexcept { return base::size() == 0; }
template <std::size_t Count>
constexpr span<element_type, Count> first() const {
assert(Count <= base::size());
return span<element_type, Count>(base::data(), Count);
}
constexpr span<element_type, dynamic_extent> first(std::size_t count) const {
assert(count <= base::size());
return span<element_type, dynamic_extent>(base::data(), count);
}
template <std::size_t Count>
constexpr span<element_type, Count> last() const {
assert(Count <= base::size());
return span<element_type, Count>(base::data() + base::size() - Count, Count);
}
constexpr span<element_type, dynamic_extent> last(std::size_t count) const {
assert(count <= base::size());
return span<element_type, dynamic_extent>(base::data() + base::size() - count, count);
}
template <std::size_t Offset, std::size_t Count = dynamic_extent>
constexpr span<element_type,
(Count != dynamic_extent
? Count
: (extent != dynamic_extent ? extent - Offset : dynamic_extent))>
subspan() const {
assert(Offset <= base::size());
constexpr std::size_t E =
(Count != dynamic_extent
? Count
: (extent != dynamic_extent ? extent - Offset : dynamic_extent));
assert(E == dynamic_extent || E <= base::size());
return span<element_type, E>(base::data() + Offset,
Count == dynamic_extent ? base::size() - Offset : Count);
}
constexpr span<element_type, dynamic_extent> subspan(
std::size_t offset, std::size_t count = dynamic_extent) const {
assert(offset <= base::size());
const std::size_t s = count == dynamic_extent ? base::size() - offset : count;
assert(s <= base::size());
return span<element_type, dynamic_extent>(base::data() + offset, s);
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
namespace dtl = ::boost::histogram::detail;
template <class T>
auto make_span(T* begin, T* end) {
return dtl::span<T>{begin, end};
}
template <class T>
auto make_span(T* begin, std::size_t size) {
return dtl::span<T>{begin, size};
}
template <class Container, class = decltype(dtl::size(std::declval<Container>()),
dtl::data(std::declval<Container>()))>
auto make_span(const Container& cont) {
return make_span(dtl::data(cont), dtl::size(cont));
}
template <class T, std::size_t N>
auto make_span(T (&arr)[N]) {
return dtl::span<T, N>(arr, N);
}
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

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

View File

@@ -0,0 +1,120 @@
// 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 {
template <class T, std::size_t N>
class sub_array {
constexpr bool swap_element_is_noexcept() noexcept {
using std::swap;
return noexcept(swap(std::declval<T&>(), std::declval<T&>()));
}
public:
using value_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);
}
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() noexcept { return data_; }
const_iterator cbegin() const noexcept { return data_; }
const_iterator cend() noexcept { return cbegin() + size_; }
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;
for (auto i = begin(), j = other.begin(); i != end(); ++i, ++j) swap(*i, *j);
}
private:
size_type size_ = 0;
value_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());
}
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

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

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

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

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

View File

@@ -0,0 +1,183 @@
// 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>
class count;
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;
template <class T>
class thread_safe;
template <class T>
struct is_thread_safe : std::false_type {};
template <class T>
struct is_thread_safe<thread_safe<T>> : std::true_type {};
} // 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 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 statically allocated to improve performance, which means
* that they need a preset maximum capacity. 32 seems like a safe upper limit for the rank
* (you can nevertheless increase it here if necessary): the simplest non-trivial axis has
* 2 bins; even if counters are used which need only a byte of storage per bin, 32 axes
* would generate of 4 GB.
*/
#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

View File

@@ -0,0 +1,714 @@
// 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/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/span.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
with contiguous storage over values or 3) a variant of 1) and 2). Sub-iterables must
have the same length.
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_, detail::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_, detail::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(
[&](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_,
detail::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(
[&](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_, detail::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

View File

@@ -0,0 +1,372 @@
// 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/iterator_adaptor.hpp>
#include <boost/histogram/detail/operators.hpp>
#include <boost/histogram/fwd.hpp>
#include <iterator>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
/** 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. begin() may only be called
once, calling it a second time returns the end() iterator. 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:
using value_iterator = std::conditional_t<std::is_const<histogram_type>::value,
typename histogram_type::const_iterator,
typename histogram_type::iterator>;
using value_reference = typename std::iterator_traits<value_iterator>::reference;
using value_type = typename std::iterator_traits<value_iterator>::value_type;
class iterator;
using range_iterator [[deprecated("use iterator instead")]] = iterator; ///< deprecated
/** 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&;
using reference [[deprecated("use const_reference instead")]] =
const_reference; ///< deprecated
/// 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_;
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)
: begin_(hist.begin(), hist), end_(hist.end(), hist) {
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), cov,
stride = std::size_t{1},
this](const auto& a) mutable {
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
constexpr axis::index_type under = opt::test(axis::option::underflow);
constexpr axis::index_type over = opt::test(axis::option::overflow);
const auto size = a.size();
// -1 if underflow and cover all, else 0
ca->begin = cov == coverage::all ? -under : 0;
// size + 1 if overflow and cover all, else size
ca->end = cov == coverage::all ? size + over : size;
ca->idx = ca->begin;
// if axis has *flow and coverage::all OR axis has no *flow:
// begin + under == 0, size + over - end == 0
// if axis has *flow and coverage::inner:
// begin + under == 1, size + over - end == 1
ca->begin_skip = static_cast<std::size_t>(ca->begin + under) * stride;
ca->end_skip = static_cast<std::size_t>(size + over - ca->end) * stride;
begin_.iter_ += ca->begin_skip;
stride *= size + under + over;
++ca;
});
}
iterator begin() noexcept { return begin_; }
iterator end() noexcept { return end_; }
private:
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};
}
} // namespace histogram
} // namespace boost
#endif

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

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

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

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

View File

@@ -0,0 +1,322 @@
// 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/priority.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;
}
template <class CharT>
struct line_t {
CharT ch;
int size;
};
template <class CharT>
auto line(CharT c, int n) {
return line_t<CharT>{c, n};
}
template <class C, class T>
std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& os, line_t<C>&& 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);
}
// cannot display generalized histograms yet; line not reachable by coverage tests
template <class OStream, class Histogram>
void ascii_plot(OStream&, const Histogram&, int, std::false_type) {} // LCOV_EXCL_LINE
template <class OStream, class Histogram>
void ascii_plot(OStream& os, const Histogram& h, int w_total, std::true_type) {
if (w_total == 0) w_total = 78; // TODO detect actual width of terminal
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)) {
ostream_head(tos.row(), ax, v.index(), *v);
vmin = std::min(vmin, static_cast<double>(*v));
vmax = std::max(vmax, static_cast<double>(*v));
}
tos.complete();
if (vmax == 0) vmax = 1;
// calculate width useable by bar (notice extra space at top)
// <-- head --> |<--- bar ---> |
// w_head + 2 + 2
const auto w_head = std::accumulate(tos.begin(), tos.end(), 0);
const auto w_bar = w_total - 4 - w_head;
if (w_bar < 0) return;
// draw upper line
os << '\n' << line(' ', w_head + 1) << '+' << 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)) {
ostream_head(tos.row(), ax, v.index(), *v);
// rest uses os, not tos
os << " |";
const int k = static_cast<int>(std::lround(*v / (vmax - vmin) * w_bar));
if (k < 0) {
os << line(' ', zero_offset + k) << line('=', -k) << line(' ', w_bar - zero_offset);
} else {
os << line(' ', zero_offset) << line('=', k) << line(' ', w_bar - zero_offset - k);
}
os << " |\n";
}
// draw lower line
os << line(' ', w_head + 1) << '+' << 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;
// must be non-const to avoid a msvc warning about possible use of if constexpr
bool show_ascii = std::is_convertible<value_type, double>::value && h.rank() == 1;
if (show_ascii) {
detail::ostream(os, h, false);
detail::ascii_plot(os, h, w, std::is_convertible<value_type, double>{});
} else {
detail::ostream(os, h);
}
// restore fmt
os.flags(flags);
return os;
}
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
#endif

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

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

View File

@@ -0,0 +1,396 @@
// 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/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

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

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

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