Added thirdparty: boost library

This commit is contained in:
Viacheslav Demydiuk
2024-01-06 19:55:56 +02:00
parent bf49f439e1
commit bccd1e7051
15683 changed files with 3239840 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
// Copyright Hans Dembinski 2020
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
#define BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <string>
namespace boost {
namespace histogram {
namespace axis {
/**
Discrete axis for boolean data.
Binning is a pass-though operation with zero cost, making this the
fastest possible axis. The axis has no internal state apart from the
optional metadata state. The axis has no under- and overflow bins.
It cannot grow and cannot be reduced.
@tparam MetaData type to store meta data.
*/
template <class MetaData>
class boolean : public iterator_mixin<boolean<MetaData>>,
public metadata_base_t<MetaData> {
using value_type = bool;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
public:
/** Construct a boolean axis.
@param meta description of the axis.
The constructor is nothrow if meta is nothrow move constructible.
*/
explicit boolean(metadata_type meta = {}) noexcept(
std::is_nothrow_move_constructible<metadata_type>::value)
: metadata_base(std::move(meta)) {}
/// Return index for value argument.
index_type index(value_type x) const noexcept { return static_cast<index_type>(x); }
/// Return value for index argument.
value_type value(index_type i) const noexcept { return static_cast<value_type>(i); }
/// Return bin for index argument.
value_type bin(index_type i) const noexcept { return value(i); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return 2; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept { return true; }
/// Returns the options.
static constexpr unsigned options() noexcept { return option::none_t::value; }
template <class M>
bool operator==(const boolean<M>& o) const noexcept {
return detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class M>
bool operator!=(const boolean<M>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("meta", this->metadata());
}
private:
template <class M>
friend class boolean;
};
#if __cpp_deduction_guides >= 201606
boolean()->boolean<null_type>;
template <class M>
boolean(M) -> boolean<detail::replace_type<std::decay_t<M>, const char*, std::string>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_AXIS_BOOLEAN_HPP
+240
View File
@@ -0,0 +1,240 @@
// Copyright 2015-2018 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
#define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace histogram {
namespace axis {
/** Maps at a set of unique values to bin indices.
The axis maps a set of values to bins, following the order of arguments in the
constructor. The optional overflow bin for this axis counts input values that
are not part of the set. Binning has O(N) complexity, but with a very small
factor. For small N (the typical use case) it beats other kinds of lookup.
@tparam Value input value type, must be equal-comparable.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
The options `underflow` and `circular` are not allowed. The options `growth`
and `overflow` are mutually exclusive.
*/
template <class Value, class MetaData, class Options, class Allocator>
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type = detail::replace_default<Options, option::overflow_t>;
using allocator_type = Allocator;
using vector_type = std::vector<value_type, allocator_type>;
public:
constexpr category() = default;
explicit category(allocator_type alloc) : vec_(alloc) {}
/** Construct from forward iterator range of unique values.
@param begin begin of category range of unique values.
@param end end of category range of unique values.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
The constructor throws `std::invalid_argument` if iterator range is invalid. If the
range contains duplicated values, the behavior of the axis is undefined.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
template <class It, class = detail::requires_iterator<It>>
category(It begin, It end, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(alloc) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(!options.test(option::underflow),
"category axis cannot have underflow");
static_assert(!options.test(option::circular), "category axis cannot be circular");
static_assert(!(options.test(option::growth) && options.test(option::overflow)),
"growing category axis cannot have entries in overflow bin");
if (std::distance(begin, end) < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));
vec_.reserve(std::distance(begin, end));
while (begin != end) vec_.emplace_back(*begin++);
}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class It, class A, class = detail::requires_iterator<It>,
class = detail::requires_allocator<A>>
category(It begin, It end, metadata_type meta, A alloc)
: category(begin, end, std::move(meta), {}, std::move(alloc)) {}
/** Construct axis from iterable sequence of unique values.
@param iterable sequence of unique values.
@param meta description of the axis.
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use.
*/
template <class C, class = detail::requires_iterable<C>>
category(const C& iterable, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: category(std::begin(iterable), std::end(iterable), std::move(meta), options,
std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class C, class A, class = detail::requires_iterable<C>,
class = detail::requires_allocator<A>>
category(const C& iterable, metadata_type meta, A alloc)
: category(std::begin(iterable), std::end(iterable), std::move(meta), {},
std::move(alloc)) {}
/** Construct axis from an initializer list of unique values.
@param list `std::initializer_list` of unique values.
@param meta description of the axis.
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use.
*/
template <class U>
category(std::initializer_list<U> list, metadata_type meta = {},
options_type options = {}, allocator_type alloc = {})
: category(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_allocator<A>>
category(std::initializer_list<U> list, metadata_type meta, A alloc)
: category(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
category(const category& src, index_type begin, index_type end, unsigned merge)
// LCOV_EXCL_START: gcc-8 is missing the delegated ctor for no reason
: category(src.vec_.begin() + begin, src.vec_.begin() + end, src.metadata(), {},
src.get_allocator())
// LCOV_EXCL_STOP
{
if (merge > 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for category axis"));
}
/// Return index for value argument.
index_type index(const value_type& x) const noexcept {
const auto beg = vec_.begin();
const auto end = vec_.end();
return static_cast<index_type>(std::distance(beg, std::find(beg, end, x)));
}
/// Returns index and shift (if axis has grown) for the passed argument.
std::pair<index_type, index_type> update(const value_type& x) {
const auto i = index(x);
if (i < size()) return {i, 0};
vec_.emplace_back(x);
return {i, -1};
}
/// Return value for index argument.
/// Throws `std::out_of_range` if the index is out of bounds.
auto value(index_type idx) const
-> std::conditional_t<std::is_scalar<value_type>::value, value_type,
const value_type&> {
if (idx < 0 || idx >= size())
BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
return vec_[idx];
}
/// Return value for index argument; alias for value(...).
decltype(auto) bin(index_type idx) const { return value(idx); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
return options() & (option::overflow | option::growth);
}
/// Indicate that the axis is not ordered.
static constexpr bool ordered() noexcept { return false; }
template <class V, class M, class O, class A>
bool operator==(const category<V, M, O, A>& o) const noexcept {
const auto& a = vec_;
const auto& b = o.vec_;
return std::equal(a.begin(), a.end(), b.begin(), b.end(), detail::relaxed_equal{}) &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O, class A>
bool operator!=(const category<V, M, O, A>& o) const noexcept {
return !operator==(o);
}
allocator_type get_allocator() const { return vec_.get_allocator(); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("seq", vec_);
ar& make_nvp("meta", this->metadata());
}
private:
vector_type vec_;
template <class V, class M, class O, class A>
friend class category;
};
#if __cpp_deduction_guides >= 201606
template <class T>
category(std::initializer_list<T>)
-> category<detail::replace_cstring<std::decay_t<T>>, null_type>;
template <class T, class M>
category(std::initializer_list<T>, M)
-> category<detail::replace_cstring<std::decay_t<T>>,
detail::replace_cstring<std::decay_t<M>>>;
template <class T, class M, unsigned B>
category(std::initializer_list<T>, M, const option::bitset<B>&)
-> category<detail::replace_cstring<std::decay_t<T>>,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+239
View File
@@ -0,0 +1,239 @@
// Copyright 2015-2018 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_INTEGER_HPP
#define BOOST_HISTOGRAM_AXIS_INTEGER_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/limits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace axis {
/** Axis for an interval of integer values with unit steps.
Binning is a O(1) operation. This axis bins even faster than a regular axis.
The options `growth` and `circular` are mutually exclusive. If the axis uses
integers and either `growth` or `circular` are set, the axis cannot have
the options `underflow` or `overflow` set.
@tparam Value input value type. Must be integer or floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
*/
template <class Value, class MetaData, class Options>
class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
index_type, real_index_type>;
public:
constexpr integer() = default;
/** Construct over semi-open integer interval [start, stop).
@param start first integer of covered range.
@param stop one past last integer of covered range.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The constructor throws `std::invalid_argument` if start is not less than stop.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
integer(value_type start, value_type stop, metadata_type meta = {},
options_type options = {})
: metadata_base(std::move(meta))
, size_(static_cast<index_type>(stop - start))
, min_(start) {
static_assert(
std::is_integral<value_type>::value || std::is_floating_point<value_type>::value,
"integer axis requires floating point or integral type");
static_assert(!(options.test(option::circular) && options.test(option::growth)),
"circular and growth options are mutually exclusive");
static_assert(
std::is_floating_point<value_type>::value ||
!((options.test(option::growth) || options.test(option::circular)) &&
(options.test(option::overflow) || options.test(option::underflow))),
"circular or growing integer axis with integral type "
"cannot have entries in underflow or overflow bins");
if (!(stop >= start)) // double negation so it works with NaN
BOOST_THROW_EXCEPTION(std::invalid_argument("stop >= start required"));
}
/// Constructor used by algorithm::reduce to shrink and rebin.
integer(const integer& src, index_type begin, index_type end, unsigned merge)
: integer(src.value(begin), src.value(end), src.metadata()) {
if (merge > 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for integer axis"));
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
}
/// Return index for value argument.
index_type index(value_type x) const noexcept {
return index_impl(options_type::test(axis::option::circular),
std::is_floating_point<value_type>{},
static_cast<double>(x - min_));
}
/// Returns index and shift (if axis has grown) for the passed argument.
auto update(value_type x) noexcept {
auto impl = [this](long x) -> std::pair<index_type, index_type> {
const auto i = x - min_;
if (i >= 0) {
const auto k = static_cast<axis::index_type>(i);
if (k < size()) return {k, 0};
const auto n = k - size() + 1;
size_ += n;
return {k, -n};
}
const auto k = static_cast<axis::index_type>(
detail::static_if<std::is_floating_point<value_type>>(
[](auto x) { return std::floor(x); }, [](auto x) { return x; }, i));
min_ += k;
size_ -= k;
return {0, -k};
};
return detail::static_if<std::is_floating_point<value_type>>(
[this, impl](auto x) -> std::pair<index_type, index_type> {
if (std::isfinite(x)) return impl(static_cast<long>(std::floor(x)));
return {x < 0 ? -1 : this->size(), 0};
},
impl, x);
}
/// Return value for index argument.
value_type value(local_index_type i) const noexcept {
if (!options_type::test(option::circular) &&
std::is_floating_point<value_type>::value) {
if (i < 0) return detail::lowest<value_type>();
if (i > size()) return detail::highest<value_type>();
}
return min_ + i;
}
/// Return bin for index argument.
decltype(auto) bin(index_type idx) const noexcept {
return detail::static_if<std::is_floating_point<value_type>>(
[this](auto idx) { return interval_view<integer>(*this, idx); },
[this](auto idx) { return this->value(idx); }, idx);
}
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return size_; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
/// Whether the axis is inclusive (see axis::traits::is_inclusive).
static constexpr bool inclusive() noexcept {
// If axis has underflow and overflow, it is inclusive.
// If axis is growing or circular:
// - it is inclusive if value_type is an integer.
// - it is not inclusive if value_type is floating point, because of nan and inf.
constexpr bool full_flow = options_type().test(option::underflow | option::overflow);
return full_flow || (std::is_integral<value_type>::value &&
(options() & (option::growth | option::circular)));
}
template <class V, class M, class O>
bool operator==(const integer<V, M, O>& o) const noexcept {
return size() == o.size() && min_ == o.min_ &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O>
bool operator!=(const integer<V, M, O>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("size", size_);
ar& make_nvp("meta", this->metadata());
ar& make_nvp("min", min_);
}
private:
// axis not circular
template <class B>
index_type index_impl(std::false_type, B, double z) const noexcept {
if (z < size()) return z >= 0 ? static_cast<index_type>(z) : -1;
return size();
}
// value_type is integer, axis circular
index_type index_impl(std::true_type, std::false_type, double z) const noexcept {
return static_cast<index_type>(z - std::floor(z / size()) * size());
}
// value_type is floating point, must handle +/-infinite or nan, axis circular
index_type index_impl(std::true_type, std::true_type, double z) const noexcept {
if (std::isfinite(z)) return index_impl(std::true_type{}, std::false_type{}, z);
return z < size() ? -1 : size();
}
index_type size_{0};
value_type min_{0};
template <class V, class M, class O>
friend class integer;
};
#if __cpp_deduction_guides >= 201606
template <class T>
integer(T, T) -> integer<detail::convert_integer<T, index_type>, null_type>;
template <class T, class M>
integer(T, T, M)
-> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class T, class M, unsigned B>
integer(T, T, M, const option::bitset<B>&)
-> integer<detail::convert_integer<T, index_type>,
detail::replace_type<std::decay_t<M>, const char*, std::string>,
option::bitset<B>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2015-2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP
#define BOOST_HISTOGRAM_AXIS_INTERVAL_VIEW_HPP
#include <boost/histogram/fwd.hpp>
namespace boost {
namespace histogram {
namespace axis {
/**
Lightweight bin view.
Represents the current bin interval.
*/
template <class Axis>
class interval_view {
public:
interval_view(const Axis& axis, index_type idx) : axis_(axis), idx_(idx) {}
// avoid viewing a temporary that goes out of scope
interval_view(Axis&& axis, index_type idx) = delete;
/// Return lower edge of bin.
decltype(auto) lower() const noexcept { return axis_.value(idx_); }
/// Return upper edge of bin.
decltype(auto) upper() const noexcept { return axis_.value(idx_ + 1); }
/// Return center of bin.
decltype(auto) center() const noexcept { return axis_.value(idx_ + 0.5); }
/// Return width of bin.
decltype(auto) width() const noexcept { return upper() - lower(); }
template <class BinType>
bool operator==(const BinType& rhs) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <class BinType>
bool operator!=(const BinType& rhs) const noexcept {
return !operator==(rhs);
}
private:
const Axis& axis_;
const index_type idx_;
};
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2015-2017 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_ITERATOR_HPP
#define BOOST_HISTOGRAM_AXIS_ITERATOR_HPP
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <iterator>
namespace boost {
namespace histogram {
namespace axis {
template <class Axis>
class iterator : public detail::iterator_adaptor<iterator<Axis>, index_type,
decltype(std::declval<Axis>().bin(0))> {
public:
using reference = typename iterator::iterator_adaptor_::reference;
/// Make iterator from axis and index.
iterator(const Axis& axis, index_type idx)
: iterator::iterator_adaptor_(idx), axis_(axis) {}
/// Return current bin object.
reference operator*() const { return axis_.bin(this->base()); }
private:
const Axis& axis_;
};
/// Uses CRTP to inject iterator logic into Derived.
template <class Derived>
class iterator_mixin {
public:
using const_iterator = iterator<Derived>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/// Bin iterator to beginning of the axis (read-only).
const_iterator begin() const noexcept {
return const_iterator(*static_cast<const Derived*>(this), 0);
}
/// Bin iterator to the end of the axis (read-only).
const_iterator end() const noexcept {
return const_iterator(*static_cast<const Derived*>(this),
static_cast<const Derived*>(this)->size());
}
/// Reverse bin iterator to the last entry of the axis (read-only).
const_reverse_iterator rbegin() const noexcept {
return std::make_reverse_iterator(end());
}
/// Reverse bin iterator to the end (read-only).
const_reverse_iterator rend() const noexcept {
return std::make_reverse_iterator(begin());
}
};
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
#define BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <string>
#include <type_traits>
namespace boost {
namespace histogram {
namespace axis {
/** Meta data holder with space optimization for empty meta data types.
Allows write-access to metadata even if const.
@tparam Metadata Wrapped meta data type.
*/
template <class Metadata, bool Detail>
class metadata_base {
protected:
using metadata_type = Metadata;
static_assert(std::is_default_constructible<metadata_type>::value,
"metadata must be default constructible");
static_assert(std::is_copy_constructible<metadata_type>::value,
"metadata must be copy constructible");
static_assert(std::is_copy_assignable<metadata_type>::value,
"metadata must be copy assignable");
// std::string explicitly guarantees nothrow only in C++17
static_assert(std::is_same<metadata_type, std::string>::value ||
std::is_nothrow_move_constructible<metadata_type>::value,
"metadata must be nothrow move constructible");
metadata_base() = default;
metadata_base(const metadata_base&) = default;
metadata_base& operator=(const metadata_base&) = default;
// make noexcept because std::string is nothrow move constructible only in C++17
metadata_base(metadata_base&& o) noexcept : data_(std::move(o.data_)) {}
metadata_base(metadata_type&& o) noexcept : data_(std::move(o)) {}
// make noexcept because std::string is nothrow move constructible only in C++17
metadata_base& operator=(metadata_base&& o) noexcept {
data_ = std::move(o.data_);
return *this;
}
public:
/// Returns reference to metadata.
metadata_type& metadata() noexcept { return data_; }
/// Returns reference to mutable metadata from const axis.
metadata_type& metadata() const noexcept { return data_; }
private:
mutable metadata_type data_;
};
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// specialization for empty metadata
template <class Metadata>
class metadata_base<Metadata, true> {
protected:
using metadata_type = Metadata;
metadata_base() = default;
metadata_base(metadata_type&&) {}
metadata_base& operator=(metadata_type&&) { return *this; }
public:
metadata_type& metadata() noexcept {
return static_cast<const metadata_base&>(*this).metadata();
}
metadata_type& metadata() const noexcept {
static metadata_type data;
return data;
}
};
template <class Metadata, class Detail = detail::replace_default<Metadata, std::string>>
using metadata_base_t =
metadata_base<Detail, (std::is_empty<Detail>::value && std::is_final<Detail>::value)>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2015-2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_OPTION_HPP
#define BOOST_HISTOGRAM_AXIS_OPTION_HPP
#include <type_traits>
/**
\file option.hpp Options for builtin axis types.
Options `circular` and `growth` are mutually exclusive.
Options `circular` and `underflow` are mutually exclusive.
*/
namespace boost {
namespace histogram {
namespace axis {
namespace option {
/// Holder of axis options.
template <unsigned Bits>
struct bitset : std::integral_constant<unsigned, Bits> {
/// Returns true if all option flags in the argument are set and false otherwise.
template <unsigned B>
static constexpr auto test(bitset<B>) {
// B + 0 needed to avoid false positive -Wtautological-compare in gcc-6
return std::integral_constant<bool, static_cast<bool>((Bits & B) == (B + 0))>{};
}
};
/// Set union of the axis option arguments.
template <unsigned B1, unsigned B2>
constexpr auto operator|(bitset<B1>, bitset<B2>) {
return bitset<(B1 | B2)>{};
}
/// Set intersection of the option arguments.
template <unsigned B1, unsigned B2>
constexpr auto operator&(bitset<B1>, bitset<B2>) {
return bitset<(B1 & B2)>{};
}
/// Set difference of the option arguments.
template <unsigned B1, unsigned B2>
constexpr auto operator-(bitset<B1>, bitset<B2>) {
return bitset<(B1 & ~B2)>{};
}
/**
Single option flag.
@tparam Pos position of the bit in the set.
*/
template <unsigned Pos>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using bit = bitset<(1 << Pos)>;
#else
struct bit;
#endif
/// All options off.
using none_t = bitset<0>;
/// Axis has an underflow bin. Mutually exclusive with `circular`.
using underflow_t = bit<0>;
/// Axis has overflow bin.
using overflow_t = bit<1>;
/// Axis is circular. Mutually exclusive with `growth` and `underflow`.
using circular_t = bit<2>;
/// Axis can grow. Mutually exclusive with `circular`.
using growth_t = bit<3>;
constexpr none_t none{}; ///< Instance of `none_t`.
constexpr underflow_t underflow{}; ///< Instance of `underflow_t`.
constexpr overflow_t overflow{}; ///< Instance of `overflow_t`.
constexpr circular_t circular{}; ///< Instance of `circular_t`.
constexpr growth_t growth{}; ///< Instance of `growth_t`.
} // namespace option
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+220
View File
@@ -0,0 +1,220 @@
// Copyright 2015-2017 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// String representations here evaluate correctly in Python.
#ifndef BOOST_HISTOGRAM_AXIS_OSTREAM_HPP
#define BOOST_HISTOGRAM_AXIS_OSTREAM_HPP
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <iomanip>
#include <iosfwd>
#include <sstream>
#include <stdexcept>
#include <type_traits>
/**
\file boost/histogram/axis/ostream.hpp
Simple streaming operators for the builtin axis types.
The text representation is not guaranteed to be stable between versions of
Boost.Histogram. This header is only included by
[boost/histogram/ostream.hpp](histogram/reference.html#header.boost.histogram.ostream_hpp).
To use your own, include your own implementation instead of this header and do not
include
[boost/histogram/ostream.hpp](histogram/reference.html#header.boost.histogram.ostream_hpp).
*/
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace boost {
namespace histogram {
namespace detail {
template <class OStream, class T>
auto ostream_any_impl(OStream& os, const T& t, priority<1>) -> decltype(os << t) {
return os << t;
}
template <class OStream, class T>
OStream& ostream_any_impl(OStream& os, const T&, priority<0>) {
return os << type_name<T>();
}
template <class OStream, class T>
OStream& ostream_any(OStream& os, const T& t) {
return ostream_any_impl(os, t, priority<1>{});
}
template <class OStream, class... Ts>
OStream& ostream_any_quoted(OStream& os, const std::basic_string<Ts...>& s) {
return os << std::quoted(s);
}
template <class OStream, class T>
OStream& ostream_any_quoted(OStream& os, const T& t) {
return ostream_any(os, t);
}
template <class... Ts, class T>
std::basic_ostream<Ts...>& ostream_metadata(std::basic_ostream<Ts...>& os, const T& t,
const char* prefix = ", ") {
std::streamsize count = 0;
{
auto g = make_count_guard(os, count);
ostream_any(os, t);
}
if (!count) return os;
os << prefix << "metadata=";
return ostream_any_quoted(os, t);
}
template <class OStream>
void ostream_options(OStream& os, const unsigned bits) {
bool first = true;
os << ", options=";
#define BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(x) \
if (bits & axis::option::x) { \
if (first) \
first = false; \
else { \
os << " | "; \
} \
os << #x; \
}
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(underflow);
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(overflow);
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(circular);
BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM(growth);
#undef BOOST_HISTOGRAM_AXIS_OPTION_OSTREAM
if (first) os << "none";
}
} // namespace detail
namespace axis {
template <class T>
class polymorphic_bin;
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const null_type&) {
return os; // do nothing
}
template <class... Ts, class U>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const interval_view<U>& i) {
return os << "[" << i.lower() << ", " << i.upper() << ")";
}
template <class... Ts, class U>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const polymorphic_bin<U>& i) {
if (i.is_discrete()) return os << static_cast<double>(i);
return os << "[" << i.lower() << ", " << i.upper() << ")";
}
namespace transform {
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const id&) {
return os;
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const log&) {
return os << "transform::log{}";
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const sqrt&) {
return os << "transform::sqrt{}";
}
template <class... Ts>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os, const pow& p) {
return os << "transform::pow{" << p.power << "}";
}
} // namespace transform
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const regular<Us...>& a) {
os << "regular(";
const auto pos = os.tellp();
os << a.transform();
if (os.tellp() > pos) os << ", ";
os << a.size() << ", " << a.value(0) << ", " << a.value(a.size());
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const integer<Us...>& a) {
os << "integer(" << a.value(0) << ", " << a.value(a.size());
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const variable<Us...>& a) {
os << "variable(" << a.value(0);
for (index_type i = 1, n = a.size(); i <= n; ++i) { os << ", " << a.value(i); }
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const category<Us...>& a) {
os << "category(";
for (index_type i = 0, n = a.size(); i < n; ++i) {
detail::ostream_any_quoted(os, a.value(i));
os << (i == (a.size() - 1) ? "" : ", ");
}
detail::ostream_metadata(os, a.metadata());
detail::ostream_options(os, a.options());
return os << ")";
}
template <class... Ts, class M>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const boolean<M>& a) {
os << "boolean(";
detail::ostream_metadata(os, a.metadata(), "");
return os << ")";
}
template <class... Ts, class... Us>
std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
const variant<Us...>& v) {
visit([&os](const auto& x) { detail::ostream_any(os, x); }, v);
return os;
}
} // namespace axis
} // namespace histogram
} // namespace boost
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
#endif
+86
View File
@@ -0,0 +1,86 @@
// Copyright 2018 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_HPP
#define BOOST_HISTOGRAM_AXIS_POLYMORPHIC_BIN_HPP
namespace boost {
namespace histogram {
namespace axis {
/**
Holds the bin data of an axis::variant.
The interface is a superset of the axis::interval_view
class. In addition, the object is implicitly convertible to the value type,
returning the equivalent of a call to lower(). For discrete axes, lower() ==
upper(), and width() returns zero.
This is not a view like axis::interval_view for two reasons.
- Sequential calls to lower() and upper() would have to each loop through
the variant types. This is likely to be slower than filling all the data in
one loop.
- polymorphic_bin may be created from a temporary instance of axis::variant,
like in the call histogram::axis(0). Storing a reference to the axis would
result in a dangling reference. Rather than specialing the code to handle
this, it seems easier to just use a value instead of a view.
*/
template <class RealType>
class polymorphic_bin {
using value_type = RealType;
public:
polymorphic_bin(value_type lower, value_type upper)
: lower_or_value_(lower), upper_(upper) {}
/// Implicitly convert to bin value (for axis with discrete values).
operator const value_type&() const noexcept { return lower_or_value_; }
/// Return lower edge of bin.
value_type lower() const noexcept { return lower_or_value_; }
/// Return upper edge of bin.
value_type upper() const noexcept { return upper_; }
/// Return center of bin.
value_type center() const noexcept { return 0.5 * (lower() + upper()); }
/// Return width of bin.
value_type width() const noexcept { return upper() - lower(); }
template <class BinType>
bool operator==(const BinType& rhs) const noexcept {
return equal_impl(rhs, 0);
}
template <class BinType>
bool operator!=(const BinType& rhs) const noexcept {
return !operator==(rhs);
}
/// Return true if bin is discrete.
bool is_discrete() const noexcept { return lower_or_value_ == upper_; }
private:
bool equal_impl(const polymorphic_bin& rhs, int) const noexcept {
return lower_or_value_ == rhs.lower_or_value_ && upper_ == rhs.upper_;
}
template <class BinType>
auto equal_impl(const BinType& rhs, decltype(rhs.lower(), 0)) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <class BinType>
bool equal_impl(const BinType& rhs, float) const noexcept {
return is_discrete() && static_cast<value_type>(*this) == rhs;
}
const value_type lower_or_value_, upper_;
};
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+457
View File
@@ -0,0 +1,457 @@
// Copyright 2015-2018 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_REGULAR_HPP
#define BOOST_HISTOGRAM_AXIS_REGULAR_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
using get_scale_type_helper = typename T::value_type;
template <class T>
using get_scale_type = mp11::mp_eval_or<T, detail::get_scale_type_helper, T>;
struct one_unit {};
template <class T>
T operator*(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <class T>
T operator/(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <class T>
using get_unit_type_helper = typename T::unit_type;
template <class T>
using get_unit_type = mp11::mp_eval_or<one_unit, detail::get_unit_type_helper, T>;
template <class T, class R = get_scale_type<T>>
R get_scale(const T& t) {
return t / get_unit_type<T>();
}
} // namespace detail
namespace axis {
namespace transform {
/// Identity transform for equidistant bins.
struct id {
/// Pass-through.
template <class T>
static T forward(T&& x) noexcept {
return std::forward<T>(x);
}
/// Pass-through.
template <class T>
static T inverse(T&& x) noexcept {
return std::forward<T>(x);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Log transform for equidistant bins in log-space.
struct log {
/// Returns log(x) of external value x.
template <class T>
static T forward(T x) {
return std::log(x);
}
/// Returns exp(x) for internal value x.
template <class T>
static T inverse(T x) {
return std::exp(x);
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Sqrt transform for equidistant bins in sqrt-space.
struct sqrt {
/// Returns sqrt(x) of external value x.
template <class T>
static T forward(T x) {
return std::sqrt(x);
}
/// Returns x^2 of internal value x.
template <class T>
static T inverse(T x) {
return x * x;
}
template <class Archive>
void serialize(Archive&, unsigned /* version */) {}
};
/// Pow transform for equidistant bins in pow-space.
struct pow {
double power = 1; /**< power index */
/// Make transform with index p.
explicit pow(double p) : power(p) {}
pow() = default;
/// Returns pow(x, power) of external value x.
template <class T>
auto forward(T x) const {
return std::pow(x, power);
}
/// Returns pow(x, 1/power) of external value x.
template <class T>
auto inverse(T x) const {
return std::pow(x, 1.0 / power);
}
bool operator==(const pow& o) const noexcept { return power == o.power; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("power", power);
}
};
} // namespace transform
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
// Type envelope to mark value as step size
template <class T>
struct step_type {
T value;
};
#endif
/**
Helper function to mark argument as step size.
*/
template <class T>
step_type<T> step(T t) {
return step_type<T>{t};
}
/** Axis for equidistant intervals on the real line.
The most common binning strategy. Very fast. Binning is a O(1) operation.
If the axis has an overflow bin (the default), a value on the upper edge of the last
bin is put in the overflow bin. The axis range represents a semi-open interval.
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
still counted towards the last bin. The axis range represents a closed interval.
The options `growth` and `circular` are mutually exclusive.
@tparam Value input value type, must be floating point.
@tparam Transform builtin or user-defined transform type.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
*/
template <class Value, class Transform, class MetaData, class Options>
class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
protected detail::replace_default<Transform, transform::id>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using transform_type = detail::replace_default<Transform, transform::id>;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using unit_type = detail::get_unit_type<value_type>;
using internal_value_type = detail::get_scale_type<value_type>;
public:
constexpr regular() = default;
/** Construct n bins over real transformed range [start, stop).
@param trans transform instance to use.
@param n number of bins.
@param start low edge of first bin.
@param stop high edge of last bin.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The constructor throws `std::invalid_argument` if n is zero, or if start and stop
produce an invalid range after transformation.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
regular(transform_type trans, unsigned n, value_type start, value_type stop,
metadata_type meta = {}, options_type options = {})
: transform_type(std::move(trans))
, metadata_base(std::move(meta))
, size_(static_cast<index_type>(n))
, min_(this->forward(detail::get_scale(start)))
, delta_(this->forward(detail::get_scale(stop)) - min_) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(std::is_nothrow_move_constructible<transform_type>::value,
"transform must be no-throw move constructible");
static_assert(std::is_nothrow_move_assignable<transform_type>::value,
"transform must be no-throw move assignable");
static_assert(std::is_floating_point<internal_value_type>::value,
"regular axis requires floating point type");
static_assert(!(options.test(option::circular) && options.test(option::growth)),
"circular and growth options are mutually exclusive");
if (size() <= 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
if (!std::isfinite(min_) || !std::isfinite(delta_))
BOOST_THROW_EXCEPTION(
std::invalid_argument("forward transform of start or stop invalid"));
if (delta_ == 0)
BOOST_THROW_EXCEPTION(std::invalid_argument("range of axis is zero"));
}
/** Construct n bins over real range [start, stop).
@param n number of bins.
@param start low edge of first bin.
@param stop high edge of last bin.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
*/
explicit regular(unsigned n, value_type start, value_type stop, metadata_type meta = {},
options_type options = {})
: regular({}, n, start, stop, std::move(meta), options) {}
/** Construct bins with the given step size over real transformed range
[start, stop).
@param trans transform instance to use.
@param step width of a single bin.
@param start low edge of first bin.
@param stop upper limit of high edge of last bin (see below).
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The axis computes the number of bins as n = abs(stop - start) / step,
rounded down. This means that stop is an upper limit to the actual value
(start + n * step).
*/
template <class T>
explicit regular(transform_type trans, step_type<T> step, value_type start,
value_type stop, metadata_type meta = {}, options_type options = {})
: regular(trans, static_cast<index_type>(std::abs(stop - start) / step.value),
start,
start + static_cast<index_type>(std::abs(stop - start) / step.value) *
step.value,
std::move(meta), options) {}
/** Construct bins with the given step size over real range [start, stop).
@param step width of a single bin.
@param start low edge of first bin.
@param stop upper limit of high edge of last bin (see below).
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
The axis computes the number of bins as n = abs(stop - start) / step,
rounded down. This means that stop is an upper limit to the actual value
(start + n * step).
*/
template <class T>
explicit regular(step_type<T> step, value_type start, value_type stop,
metadata_type meta = {}, options_type options = {})
: regular({}, step, start, stop, std::move(meta), options) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
regular(const regular& src, index_type begin, index_type end, unsigned merge)
: regular(src.transform(), (end - begin) / merge, src.value(begin), src.value(end),
src.metadata()) {
assert((end - begin) % merge == 0);
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
}
/// Return instance of the transform type.
const transform_type& transform() const noexcept { return *this; }
/// Return index for value argument.
index_type index(value_type x) const noexcept {
// Runs in hot loop, please measure impact of changes
auto z = (this->forward(x / unit_type{}) - min_) / delta_;
if (options_type::test(option::circular)) {
if (std::isfinite(z)) {
z -= std::floor(z);
return static_cast<index_type>(z * size());
}
} else {
if (z < 1) {
if (z >= 0)
return static_cast<index_type>(z * size());
else
return -1;
}
// upper edge of last bin is inclusive if overflow bin is not present
if (!options_type::test(option::overflow) && z == 1) return size() - 1;
}
return size(); // also returned if x is NaN
}
/// Returns index and shift (if axis has grown) for the passed argument.
std::pair<index_type, index_type> update(value_type x) noexcept {
assert(options_type::test(option::growth));
const auto z = (this->forward(x / unit_type{}) - min_) / delta_;
if (z < 1) { // don't use i here!
if (z >= 0) {
const auto i = static_cast<axis::index_type>(z * size());
return {i, 0};
}
if (z != -std::numeric_limits<internal_value_type>::infinity()) {
const auto stop = min_ + delta_;
const auto i = static_cast<axis::index_type>(std::floor(z * size()));
min_ += i * (delta_ / size());
delta_ = stop - min_;
size_ -= i;
return {0, -i};
}
// z is -infinity
return {-1, 0};
}
// z either beyond range, infinite, or NaN
if (z < std::numeric_limits<internal_value_type>::infinity()) {
const auto i = static_cast<axis::index_type>(z * size());
const auto n = i - size() + 1;
delta_ /= size();
delta_ *= size() + n;
size_ += n;
return {i, -n};
}
// z either infinite or NaN
return {size(), 0};
}
/// Return value for fractional index argument.
value_type value(real_index_type i) const noexcept {
auto z = i / size();
if (!options_type::test(option::circular) && z < 0.0)
z = -std::numeric_limits<internal_value_type>::infinity() * delta_;
else if (options_type::test(option::circular) || z <= 1.0)
z = (1.0 - z) * min_ + z * (min_ + delta_);
else {
z = std::numeric_limits<internal_value_type>::infinity() * delta_;
}
return static_cast<value_type>(this->inverse(z) * unit_type());
}
/// Return bin for index argument.
decltype(auto) bin(index_type idx) const noexcept {
return interval_view<regular>(*this, idx);
}
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return size_; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
template <class V, class T, class M, class O>
bool operator==(const regular<V, T, M, O>& o) const noexcept {
return detail::relaxed_equal{}(transform(), o.transform()) && size() == o.size() &&
min_ == o.min_ && delta_ == o.delta_ &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class T, class M, class O>
bool operator!=(const regular<V, T, M, O>& o) const noexcept {
return !operator==(o);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("transform", static_cast<transform_type&>(*this));
ar& make_nvp("size", size_);
ar& make_nvp("meta", this->metadata());
ar& make_nvp("min", min_);
ar& make_nvp("delta", delta_);
}
private:
index_type size_{0};
internal_value_type min_{0}, delta_{1};
template <class V, class T, class M, class O>
friend class regular;
};
#if __cpp_deduction_guides >= 201606
template <class T>
regular(unsigned, T, T)
-> regular<detail::convert_integer<T, double>, transform::id, null_type>;
template <class T, class M>
regular(unsigned, T, T, M) -> regular<detail::convert_integer<T, double>, transform::id,
detail::replace_cstring<std::decay_t<M>>>;
template <class T, class M, unsigned B>
regular(unsigned, T, T, M, const option::bitset<B>&)
-> regular<detail::convert_integer<T, double>, transform::id,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
template <class Tr, class T, class = detail::requires_transform<Tr, T>>
regular(Tr, unsigned, T, T) -> regular<detail::convert_integer<T, double>, Tr, null_type>;
template <class Tr, class T, class M>
regular(Tr, unsigned, T, T, M) -> regular<detail::convert_integer<T, double>, Tr,
detail::replace_cstring<std::decay_t<M>>>;
template <class Tr, class T, class M, unsigned B>
regular(Tr, unsigned, T, T, M, const option::bitset<B>&)
-> regular<detail::convert_integer<T, double>, Tr,
detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
#endif
/// Regular axis with circular option already set.
template <class Value = double, class MetaData = use_default, class Options = use_default>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using circular = regular<Value, transform::id, MetaData,
decltype(detail::replace_default<Options, option::overflow_t>{} |
option::circular)>;
#else
class circular;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+490
View File
@@ -0,0 +1,490 @@
// Copyright 2018 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/args_type.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/try_cast.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
#include <string>
#include <utility>
namespace boost {
namespace histogram {
namespace detail {
template <class Axis>
struct value_type_deducer {
using type =
std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
};
template <class Axis>
auto traits_options(priority<2>) -> axis::option::bitset<Axis::options()>;
template <class Axis>
auto traits_options(priority<1>) -> decltype(&Axis::update, axis::option::growth_t{});
template <class Axis>
auto traits_options(priority<0>) -> axis::option::none_t;
template <class Axis>
auto traits_is_inclusive(priority<1>) -> std::integral_constant<bool, Axis::inclusive()>;
template <class Axis>
auto traits_is_inclusive(priority<0>)
-> decltype(traits_options<Axis>(priority<2>{})
.test(axis::option::underflow | axis::option::overflow));
template <class Axis>
auto traits_is_ordered(priority<1>) -> std::integral_constant<bool, Axis::ordered()>;
template <class Axis, class ValueType = typename value_type_deducer<Axis>::type>
auto traits_is_ordered(priority<0>) -> typename std::is_arithmetic<ValueType>::type;
template <class I, class D, class A,
class J = std::decay_t<arg_type<decltype(&A::value)>>>
decltype(auto) value_method_switch(I&& i, D&& d, const A& a, priority<1>) {
return static_if<std::is_same<J, axis::index_type>>(std::forward<I>(i),
std::forward<D>(d), a);
}
template <class I, class D, class A>
double value_method_switch(I&&, D&&, const A&, priority<0>) {
// comma trick to make all compilers happy; some would complain about
// unreachable code after the throw, others about a missing return
return BOOST_THROW_EXCEPTION(
std::runtime_error(type_name<A>() + " has no value method")),
double{};
}
struct variant_access {
template <class T, class Variant>
static auto get_if(Variant* v) noexcept {
using T0 = mp11::mp_first<std::decay_t<Variant>>;
return static_if<std::is_pointer<T0>>(
[](auto* vptr) {
using TP = mp11::mp_if<std::is_const<std::remove_pointer_t<T0>>, const T*, T*>;
auto ptp = variant2::get_if<TP>(vptr);
return ptp ? *ptp : nullptr;
},
[](auto* vptr) { return variant2::get_if<T>(vptr); }, &(v->impl));
}
template <class T0, class Visitor, class Variant>
static decltype(auto) visit_impl(mp11::mp_identity<T0>, Visitor&& vis, Variant&& v) {
return variant2::visit(std::forward<Visitor>(vis), v.impl);
}
template <class T0, class Visitor, class Variant>
static decltype(auto) visit_impl(mp11::mp_identity<T0*>, Visitor&& vis, Variant&& v) {
return variant2::visit(
[&vis](auto&& x) -> decltype(auto) { return std::forward<Visitor>(vis)(*x); },
v.impl);
}
template <class Visitor, class Variant>
static decltype(auto) visit(Visitor&& vis, Variant&& v) {
using T0 = mp11::mp_first<std::decay_t<Variant>>;
return visit_impl(mp11::mp_identity<T0>{}, std::forward<Visitor>(vis),
std::forward<Variant>(v));
}
};
template <class A>
decltype(auto) metadata_impl(A&& a, decltype(a.metadata(), 0)) {
return std::forward<A>(a).metadata();
}
template <class A>
axis::null_type& metadata_impl(A&&, float) {
static axis::null_type null_value;
return null_value;
}
} // namespace detail
namespace axis {
namespace traits {
/** Value type for axis type.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns the value type.
The value type is deduced from the argument of the `Axis::index` method. Const
references are decayed to the their value types, for example, the type deduced for
`Axis::index(const int&)` is `int`.
The deduction always succeeds if the axis type models the Axis concept correctly. Errors
come from violations of the concept, in particular, an index method that is templated or
overloaded is not allowed.
@tparam Axis axis type.
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using value_type = typename detail::value_type_deducer<Axis>::type;
#else
struct value_type;
#endif
/** Whether axis is continuous or discrete.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns a compile-time boolean.
If the boolean is true, the axis is continuous (covers a continuous range of values).
Otherwise it is discrete (covers discrete values).
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_continuous = typename std::is_floating_point<traits::value_type<Axis>>::type;
#else
struct is_continuous;
#endif
/** Meta-function to detect whether an axis is reducible.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and represents compile-time boolean which is true or false, depending on
whether the axis can be reduced with boost::histogram::algorithm::reduce().
An axis can be made reducible by adding a special constructor, see Axis concept for
details.
@tparam Axis axis type.
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
axis::index_type, unsigned>;
#else
struct is_reducible;
#endif
/** Get axis options for axis type.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns the boost::histogram::axis::option::bitset.
If Axis::options() is valid and constexpr, get_options is the corresponding
option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
axis has a method `update`, else boost::histogram::axis::option::none_t.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using get_options = decltype(detail::traits_options<Axis>(detail::priority<2>{}));
#else
struct get_options;
#endif
/** Meta-function to detect whether an axis is inclusive.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and represents compile-time boolean which is true or false, depending on
whether the axis is inclusive or not.
An inclusive axis has a bin for every possible input value. In other words, all
possible input values always end up in a valid cell and there is no need to keep track
of input tuples that need to be discarded. A histogram which consists entirely of
inclusive axes can be filled more efficiently, which can be a factor 2 faster.
An axis with underflow and overflow bins is always inclusive, but an axis may be
inclusive under other conditions. The meta-function checks for the method `constexpr
static bool inclusive()`, and uses the result. If this method is not present, it uses
get_options<Axis> and checks whether the underflow and overflow bits are present.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_inclusive = decltype(detail::traits_is_inclusive<Axis>(detail::priority<1>{}));
#else
struct is_inclusive;
#endif
/** Meta-function to detect whether an axis is ordered.
Doxygen does not render this well. This is a meta-function (template alias), it accepts
an axis type and returns a compile-time boolean. If the boolean is true, the axis is
ordered.
The meta-function checks for the method `constexpr static bool ordered()`, and uses the
result. If this method is not present, it returns true if the value type of the Axis is
arithmetic and false otherwise.
An ordered axis has a value type that is ordered, which means that indices i <
j < k implies either value(i) < value(j) < value(k) or value(i) > value(j) > value(k)
for all i,j,k. For example, the integer axis is ordered, but the category axis is not.
Axis which are not ordered must not have underflow bins, because they only have an
"other" category, which is identified with the overflow bin if it is available.
@tparam Axis axis type
*/
template <class Axis>
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
using is_ordered = decltype(detail::traits_is_ordered<Axis>(detail::priority<1>{}));
#else
struct is_ordered;
#endif
/** Returns axis options as unsigned integer.
See get_options for details.
@param axis any axis instance
*/
template <class Axis>
constexpr unsigned options(const Axis& axis) noexcept {
(void)axis;
return get_options<Axis>::value;
}
// specialization for variant
template <class... Ts>
unsigned options(const variant<Ts...>& axis) noexcept {
return axis.options();
}
/** Returns true if axis is inclusive or false.
See is_inclusive for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool inclusive(const Axis& axis) noexcept {
(void)axis;
return is_inclusive<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool inclusive(const variant<Ts...>& axis) noexcept {
return axis.inclusive();
}
/** Returns true if axis is ordered or false.
See is_ordered for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool ordered(const Axis& axis) noexcept {
(void)axis;
return is_ordered<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool ordered(const variant<Ts...>& axis) noexcept {
return axis.ordered();
}
/** Returns true if axis is continuous or false.
See is_continuous for details.
@param axis any axis instance
*/
template <class Axis>
constexpr bool continuous(const Axis& axis) noexcept {
(void)axis;
return is_continuous<Axis>::value;
}
// specialization for variant
template <class... Ts>
bool continuous(const variant<Ts...>& axis) noexcept {
return axis.continuous();
}
/** Returns axis size plus any extra bins for under- and overflow.
@param axis any axis instance
*/
template <class Axis>
index_type extent(const Axis& axis) noexcept {
const auto opt = options(axis);
return axis.size() + (opt & option::underflow ? 1 : 0) +
(opt & option::overflow ? 1 : 0);
}
/** Returns reference to metadata of an axis.
If the expression x.metadata() for an axis instance `x` (maybe const) is valid, return
the result. Otherwise, return a reference to a static instance of
boost::histogram::axis::null_type.
@param axis any axis instance
*/
template <class Axis>
decltype(auto) metadata(Axis&& axis) noexcept {
return detail::metadata_impl(std::forward<Axis>(axis), 0);
}
/** Returns axis value for index.
If the axis has no `value` method, throw std::runtime_error. If the method exists and
accepts a floating point index, pass the index and return the result. If the method
exists but accepts only integer indices, cast the floating point index to int, pass this
index and return the result.
@param axis any axis instance
@param index floating point axis index
*/
template <class Axis>
decltype(auto) value(const Axis& axis, real_index_type index) {
return detail::value_method_switch(
[index](const auto& a) { return a.value(static_cast<index_type>(index)); },
[index](const auto& a) { return a.value(index); }, axis, detail::priority<1>{});
}
/** Returns axis value for index if it is convertible to target type or throws.
Like boost::histogram::axis::traits::value, but converts the result into the requested
return type. If the conversion is not possible, throws std::runtime_error.
@tparam Result requested return type
@tparam Axis axis type
@param axis any axis instance
@param index floating point axis index
*/
template <class Result, class Axis>
Result value_as(const Axis& axis, real_index_type index) {
return detail::try_cast<Result, std::runtime_error>(
axis::traits::value(axis, index)); // avoid conversion warning
}
/** Returns axis index for value.
Throws std::invalid_argument if the value argument is not implicitly convertible.
@param axis any axis instance
@param value argument to be passed to `index` method
*/
template <class Axis, class U>
axis::index_type index(const Axis& axis, const U& value) noexcept(
std::is_convertible<U, value_type<Axis>>::value) {
return axis.index(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
}
// specialization for variant
template <class... Ts, class U>
axis::index_type index(const variant<Ts...>& axis, const U& value) {
return axis.index(value);
}
/** Return axis rank (how many arguments it processes).
@param axis any axis instance
*/
// gcc workaround: must use unsigned int not unsigned as return type
template <class Axis>
constexpr unsigned int rank(const Axis& axis) {
(void)axis;
using T = value_type<Axis>;
// cannot use mp_eval_or since T could be a fixed-sized sequence
return mp11::mp_eval_if_not<detail::is_tuple<T>, mp11::mp_size_t<1>, mp11::mp_size,
T>::value;
}
// specialization for variant
// gcc workaround: must use unsigned int not unsigned as return type
template <class... Ts>
unsigned int rank(const axis::variant<Ts...>& axis) {
return detail::variant_access::visit(
[](const auto& a) { return axis::traits::rank(a); }, axis);
}
/** Returns pair of axis index and shift for the value argument.
Throws `std::invalid_argument` if the value argument is not implicitly convertible to
the argument expected by the `index` method. If the result of
boost::histogram::axis::traits::get_options<decltype(axis)> has the growth flag set,
call `update` method with the argument and return the result. Otherwise, call `index`
and return the pair of the result and a zero shift.
@param axis any axis instance
@param value argument to be passed to `update` or `index` method
*/
template <class Axis, class U>
std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
std::is_convertible<U, value_type<Axis>>::value) {
return detail::static_if_c<get_options<Axis>::test(option::growth)>(
[&value](auto& a) {
return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
},
[&value](auto& a) -> std::pair<index_type, index_type> {
return {axis::traits::index(a, value), 0};
},
axis);
}
// specialization for variant
template <class... Ts, class U>
std::pair<index_type, index_type> update(variant<Ts...>& axis, const U& value) {
return visit([&value](auto& a) { return a.update(value); }, axis);
}
/** Returns bin width at axis index.
If the axis has no `value` method, throw std::runtime_error. If the method exists and
accepts a floating point index, return the result of `axis.value(index + 1) -
axis.value(index)`. If the method exists but accepts only integer indices, return 0.
@param axis any axis instance
@param index bin index
*/
template <class Axis>
decltype(auto) width(const Axis& axis, index_type index) {
return detail::value_method_switch(
[](const auto&) { return 0; },
[index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis,
detail::priority<1>{});
}
/** Returns bin width at axis index.
Like boost::histogram::axis::traits::width, but converts the result into the requested
return type. If the conversion is not possible, throw std::runtime_error.
@param axis any axis instance
@param index bin index
*/
template <class Result, class Axis>
Result width_as(const Axis& axis, index_type index) {
return detail::value_method_switch(
[](const auto&) { return Result{}; },
[index](const auto& a) {
return detail::try_cast<Result, std::runtime_error>(a.value(index + 1) -
a.value(index));
},
axis, detail::priority<1>{});
}
} // namespace traits
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+307
View File
@@ -0,0 +1,307 @@
// Copyright 2015-2018 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_VARIABLE_HPP
#define BOOST_HISTOGRAM_AXIS_VARIABLE_HPP
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/interval_view.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/metadata_base.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/detail/convert_integer.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/limits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/replace_type.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/throw_exception.hpp>
#include <cassert>
#include <cmath>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace boost {
namespace histogram {
namespace axis {
/** Axis for non-equidistant bins on the real line.
Binning is a O(log(N)) operation. If speed matters and the problem domain
allows it, prefer a regular axis, possibly with a transform.
If the axis has an overflow bin (the default), a value on the upper edge of the last
bin is put in the overflow bin. The axis range represents a semi-open interval.
If the overflow bin is deactivated, then a value on the upper edge of the last bin is
still counted towards the last bin. The axis range represents a closed interval. This
is the desired behavior for random numbers drawn from a bounded interval, which is
usually closed.
@tparam Value input value type, must be floating point.
@tparam MetaData type to store meta data.
@tparam Options see boost::histogram::axis::option.
@tparam Allocator allocator to use for dynamic memory management.
*/
template <class Value, class MetaData, class Options, class Allocator>
class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>>,
public metadata_base_t<MetaData> {
// these must be private, so that they are not automatically inherited
using value_type = Value;
using metadata_base = metadata_base_t<MetaData>;
using metadata_type = typename metadata_base::metadata_type;
using options_type =
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
using allocator_type = Allocator;
using vector_type = std::vector<Value, allocator_type>;
public:
constexpr variable() = default;
explicit variable(allocator_type alloc) : vec_(alloc) {}
/** Construct from forward iterator range of bin edges.
@param begin begin of edge sequence.
@param end end of edge sequence.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
The constructor throws `std::invalid_argument` if iterator range is invalid, if less
than two edges are provided or if bin edges are not in ascending order.
The arguments meta and alloc are passed by value. If you move either of them into the
axis and the constructor throws, their values are lost. Do not move if you cannot
guarantee that the bin description is not valid.
*/
template <class It, class = detail::requires_iterator<It>>
variable(It begin, It end, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: metadata_base(std::move(meta)), vec_(std::move(alloc)) {
// static_asserts were moved here from class scope to satisfy deduction in gcc>=11
static_assert(
std::is_floating_point<value_type>::value,
"current version of variable axis requires floating point type; "
"if you need a variable axis with an integral type, please submit an issue");
static_assert((!options.test(option::circular) && !options.test(option::growth)) ||
(options.test(option::circular) ^ options.test(option::growth)),
"circular and growth options are mutually exclusive");
const auto n = std::distance(begin, end);
if (n < 0)
BOOST_THROW_EXCEPTION(
std::invalid_argument("end must be reachable by incrementing begin"));
if (n < 2) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 1 required"));
vec_.reserve(n);
vec_.emplace_back(*begin++);
bool strictly_ascending = true;
for (; begin != end; ++begin) {
strictly_ascending &= vec_.back() < *begin;
vec_.emplace_back(*begin);
}
if (!strictly_ascending)
BOOST_THROW_EXCEPTION(
std::invalid_argument("input sequence must be strictly ascending"));
}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class It, class A, class = detail::requires_iterator<It>,
class = detail::requires_allocator<A>>
variable(It begin, It end, metadata_type meta, A alloc)
: variable(begin, end, std::move(meta), {}, std::move(alloc)) {}
/** Construct variable axis from iterable range of bin edges.
@param iterable iterable range of bin edges.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
*/
template <class U, class = detail::requires_iterable<U>>
variable(const U& iterable, metadata_type meta = {}, options_type options = {},
allocator_type alloc = {})
: variable(std::begin(iterable), std::end(iterable), std::move(meta), options,
std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_iterable<U>,
class = detail::requires_allocator<A>>
variable(const U& iterable, metadata_type meta, A alloc)
: variable(std::begin(iterable), std::end(iterable), std::move(meta), {},
std::move(alloc)) {}
/** Construct variable axis from initializer list of bin edges.
@param list `std::initializer_list` of bin edges.
@param meta description of the axis (optional).
@param options see boost::histogram::axis::option (optional).
@param alloc allocator instance to use (optional).
*/
template <class U>
variable(std::initializer_list<U> list, metadata_type meta = {},
options_type options = {}, allocator_type alloc = {})
: variable(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
// kept for backward compatibility; requires_allocator is a workaround for deduction
// guides in gcc>=11
template <class U, class A, class = detail::requires_allocator<A>>
variable(std::initializer_list<U> list, metadata_type meta, A alloc)
: variable(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
variable(const variable& src, index_type begin, index_type end, unsigned merge)
: metadata_base(src), vec_(src.get_allocator()) {
assert((end - begin) % merge == 0);
if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
vec_.reserve((end - begin) / merge);
const auto beg = src.vec_.begin();
for (index_type i = begin; i <= end; i += merge) vec_.emplace_back(*(beg + i));
}
/// Return index for value argument.
index_type index(value_type x) const noexcept {
if (options_type::test(option::circular)) {
const auto a = vec_[0];
const auto b = vec_[size()];
x -= std::floor((x - a) / (b - a)) * (b - a);
}
// upper edge of last bin is inclusive if overflow bin is not present
if (!options_type::test(option::overflow) && x == vec_.back()) return size() - 1;
return static_cast<index_type>(std::upper_bound(vec_.begin(), vec_.end(), x) -
vec_.begin() - 1);
}
std::pair<index_type, index_type> update(value_type x) noexcept {
const auto i = index(x);
if (std::isfinite(x)) {
if (0 <= i) {
if (i < size()) return std::make_pair(i, 0);
const auto d = value(size()) - value(size() - 0.5);
x = std::nextafter(x, (std::numeric_limits<value_type>::max)());
x = (std::max)(x, vec_.back() + d);
vec_.push_back(x);
return {i, -1};
}
const auto d = value(0.5) - value(0);
x = (std::min)(x, value(0) - d);
vec_.insert(vec_.begin(), x);
return {0, -i};
}
return {x < 0 ? -1 : size(), 0};
}
/// Return value for fractional index argument.
value_type value(real_index_type i) const noexcept {
if (options_type::test(option::circular)) {
auto shift = std::floor(i / size());
i -= shift * size();
double z;
const auto k = static_cast<index_type>(std::modf(i, &z));
const auto a = vec_[0];
const auto b = vec_[size()];
return (1.0 - z) * vec_[k] + z * vec_[k + 1] + shift * (b - a);
}
if (i < 0) return detail::lowest<value_type>();
if (i == size()) return vec_.back();
if (i > size()) return detail::highest<value_type>();
const auto k = static_cast<index_type>(i); // precond: i >= 0
const real_index_type z = i - k;
// check z == 0 needed to avoid returning nan when vec_[k + 1] is infinity
return (1.0 - z) * vec_[k] + (z == 0 ? 0 : z * vec_[k + 1]);
}
/// Return bin for index argument.
auto bin(index_type idx) const noexcept { return interval_view<variable>(*this, idx); }
/// Returns the number of bins, without over- or underflow.
index_type size() const noexcept { return static_cast<index_type>(vec_.size()) - 1; }
/// Returns the options.
static constexpr unsigned options() noexcept { return options_type::value; }
template <class V, class M, class O, class A>
bool operator==(const variable<V, M, O, A>& o) const noexcept {
const auto& a = vec_;
const auto& b = o.vec_;
return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
detail::relaxed_equal{}(this->metadata(), o.metadata());
}
template <class V, class M, class O, class A>
bool operator!=(const variable<V, M, O, A>& o) const noexcept {
return !operator==(o);
}
/// Return allocator instance.
auto get_allocator() const { return vec_.get_allocator(); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("seq", vec_);
ar& make_nvp("meta", this->metadata());
}
private:
vector_type vec_;
template <class V, class M, class O, class A>
friend class variable;
};
#if __cpp_deduction_guides >= 201606
template <class T>
variable(std::initializer_list<T>)
-> variable<detail::convert_integer<T, double>, null_type>;
template <class T, class M>
variable(std::initializer_list<T>, M)
-> variable<detail::convert_integer<T, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class T, class M, unsigned B>
variable(std::initializer_list<T>, M, const option::bitset<B>&)
-> variable<detail::convert_integer<T, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>,
option::bitset<B>>;
template <class Iterable, class = detail::requires_iterable<Iterable>>
variable(Iterable) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
null_type>;
template <class Iterable, class M>
variable(Iterable, M) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>>;
template <class Iterable, class M, unsigned B>
variable(Iterable, M, const option::bitset<B>&) -> variable<
detail::convert_integer<
std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
detail::replace_type<std::decay_t<M>, const char*, std::string>, option::bitset<B>>;
#endif
} // namespace axis
} // namespace histogram
} // namespace boost
#endif
+348
View File
@@ -0,0 +1,348 @@
// Copyright 2015-2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_AXIS_VARIANT_HPP
#define BOOST_HISTOGRAM_AXIS_VARIANT_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/axis/iterator.hpp>
#include <boost/histogram/axis/polymorphic_bin.hpp>
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/relaxed_equal.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/detail/type_name.hpp>
#include <boost/histogram/detail/variant_proxy.hpp>
#include <boost/mp11/algorithm.hpp> // mp_contains
#include <boost/mp11/list.hpp> // mp_first
#include <boost/throw_exception.hpp>
#include <boost/variant2/variant.hpp>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace boost {
namespace histogram {
namespace axis {
/// Polymorphic axis type
template <class... Ts>
class variant : public iterator_mixin<variant<Ts...>> {
using impl_type = boost::variant2::variant<Ts...>;
template <class T>
using is_bounded_type = mp11::mp_contains<variant, std::decay_t<T>>;
template <class T>
using requires_bounded_type = std::enable_if_t<is_bounded_type<T>::value>;
using metadata_type =
std::remove_const_t<std::remove_reference_t<decltype(traits::metadata(
std::declval<std::remove_pointer_t<mp11::mp_first<variant>>>()))>>;
public:
// cannot import ctors with using directive, it breaks gcc and msvc
variant() = default;
variant(const variant&) = default;
variant& operator=(const variant&) = default;
variant(variant&&) = default;
variant& operator=(variant&&) = default;
template <class T, class = requires_bounded_type<T>>
variant(T&& t) : impl(std::forward<T>(t)) {}
template <class T, class = requires_bounded_type<T>>
variant& operator=(T&& t) {
impl = std::forward<T>(t);
return *this;
}
template <class... Us>
variant(const variant<Us...>& u) {
this->operator=(u);
}
template <class... Us>
variant& operator=(const variant<Us...>& u) {
visit(
[this](const auto& u) {
using U = std::decay_t<decltype(u)>;
detail::static_if<is_bounded_type<U>>(
[this](const auto& u) { this->operator=(u); },
[](const auto&) {
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::type_name<U>() + " is not convertible to a bounded type of " +
detail::type_name<variant>()));
},
u);
},
u);
return *this;
}
/// Return size of axis.
index_type size() const {
return visit([](const auto& a) -> index_type { return a.size(); }, *this);
}
/// Return options of axis or option::none_t if axis has no options.
unsigned options() const {
return visit([](const auto& a) { return traits::options(a); }, *this);
}
/// Returns true if the axis is inclusive or false.
bool inclusive() const {
return visit([](const auto& a) { return traits::inclusive(a); }, *this);
}
/// Returns true if the axis is ordered or false.
bool ordered() const {
return visit([](const auto& a) { return traits::ordered(a); }, *this);
}
/// Returns true if the axis is continuous or false.
bool continuous() const {
return visit([](const auto& a) { return traits::continuous(a); }, *this);
}
/// Return reference to const metadata or instance of null_type if axis has no
/// metadata.
metadata_type& metadata() const {
return visit(
[](const auto& a) -> metadata_type& {
using M = decltype(traits::metadata(a));
return detail::static_if<std::is_same<M, metadata_type&>>(
[](const auto& a) -> metadata_type& { return traits::metadata(a); },
[](const auto&) -> metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(
"cannot return metadata of type " + detail::type_name<M>() +
" through axis::variant interface which uses type " +
detail::type_name<metadata_type>() +
"; use boost::histogram::axis::get to obtain a reference "
"of this axis type"));
},
a);
},
*this);
}
/// Return reference to metadata or instance of null_type if axis has no
/// metadata.
metadata_type& metadata() {
return visit(
[](auto& a) -> metadata_type& {
using M = decltype(traits::metadata(a));
return detail::static_if<std::is_same<M, metadata_type&>>(
[](auto& a) -> metadata_type& { return traits::metadata(a); },
[](auto&) -> metadata_type& {
BOOST_THROW_EXCEPTION(std::runtime_error(
"cannot return metadata of type " + detail::type_name<M>() +
" through axis::variant interface which uses type " +
detail::type_name<metadata_type>() +
"; use boost::histogram::axis::get to obtain a reference "
"of this axis type"));
},
a);
},
*this);
}
/** Return index for value argument.
Throws std::invalid_argument if axis has incompatible call signature.
*/
template <class U>
index_type index(const U& u) const {
return visit([&u](const auto& a) { return traits::index(a, u); }, *this);
}
/** Return value for index argument.
Only works for axes with value method that returns something convertible
to double and will throw a runtime_error otherwise, see
axis::traits::value().
*/
double value(real_index_type idx) const {
return visit([idx](const auto& a) { return traits::value_as<double>(a, idx); },
*this);
}
/** Return bin for index argument.
Only works for axes with value method that returns something convertible
to double and will throw a runtime_error otherwise, see
axis::traits::value().
*/
auto bin(index_type idx) const {
return visit(
[idx](const auto& a) {
return detail::value_method_switch(
[idx](const auto& a) { // axis is discrete
const double x = traits::value_as<double>(a, idx);
return polymorphic_bin<double>(x, x);
},
[idx](const auto& a) { // axis is continuous
const double x1 = traits::value_as<double>(a, idx);
const double x2 = traits::value_as<double>(a, idx + 1);
return polymorphic_bin<double>(x1, x2);
},
a, detail::priority<1>{});
},
*this);
}
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
detail::variant_proxy<variant> p{*this};
ar& make_nvp("variant", p);
}
private:
impl_type impl;
friend struct detail::variant_access;
friend struct boost::histogram::unsafe_access;
};
// specialization for empty argument list, useful for meta-programming
template <>
class variant<> {};
/// Apply visitor to variant (reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, variant<Us...>& var) {
return detail::variant_access::visit(vis, var);
}
/// Apply visitor to variant (movable reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, variant<Us...>&& var) {
return detail::variant_access::visit(vis, std::move(var));
}
/// Apply visitor to variant (const reference).
template <class Visitor, class... Us>
decltype(auto) visit(Visitor&& vis, const variant<Us...>& var) {
return detail::variant_access::visit(vis, var);
}
/// Returns pointer to T in variant or null pointer if type does not match.
template <class T, class... Us>
auto get_if(variant<Us...>* v) {
return detail::variant_access::template get_if<T>(v);
}
/// Returns pointer to const T in variant or null pointer if type does not match.
template <class T, class... Us>
auto get_if(const variant<Us...>* v) {
return detail::variant_access::template get_if<T>(v);
}
/// Return reference to T, throws std::runtime_error if type does not match.
template <class T, class... Us>
decltype(auto) get(variant<Us...>& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return *tp;
}
/// Return movable reference to T, throws unspecified exception if type does not match.
template <class T, class... Us>
decltype(auto) get(variant<Us...>&& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return std::move(*tp);
}
/// Return const reference to T, throws unspecified exception if type does not match.
template <class T, class... Us>
decltype(auto) get(const variant<Us...>& v) {
auto tp = get_if<T>(&v);
if (!tp) BOOST_THROW_EXCEPTION(std::runtime_error("T is not the held type"));
return *tp;
}
// pass-through version of visit for generic programming
template <class Visitor, class T>
decltype(auto) visit(Visitor&& vis, T&& var) {
return std::forward<Visitor>(vis)(std::forward<T>(var));
}
// pass-through version of get for generic programming
template <class T, class U>
decltype(auto) get(U&& u) {
return std::forward<U>(u);
}
// pass-through version of get_if for generic programming
template <class T, class U>
auto get_if(U* u) {
return reinterpret_cast<T*>(std::is_same<T, std::decay_t<U>>::value ? u : nullptr);
}
// pass-through version of get_if for generic programming
template <class T, class U>
auto get_if(const U* u) {
return reinterpret_cast<const T*>(std::is_same<T, std::decay_t<U>>::value ? u
: nullptr);
}
/** Compare two variants.
Return true if the variants point to the same concrete axis type and the types compare
equal. Otherwise return false.
*/
template <class... Us, class... Vs>
bool operator==(const variant<Us...>& u, const variant<Vs...>& v) noexcept {
return visit([&](const auto& vi) { return u == vi; }, v);
}
/** Compare variant with a concrete axis type.
Return true if the variant point to the same concrete axis type and the types compare
equal. Otherwise return false.
*/
template <class... Us, class T>
bool operator==(const variant<Us...>& u, const T& t) noexcept {
using V = variant<Us...>;
return detail::static_if_c<(mp11::mp_contains<V, T>::value ||
mp11::mp_contains<V, T*>::value ||
mp11::mp_contains<V, const T*>::value)>(
[&](const auto& t) {
using U = std::decay_t<decltype(t)>;
const U* tp = detail::variant_access::template get_if<U>(&u);
return tp && detail::relaxed_equal{}(*tp, t);
},
[&](const auto&) { return false; }, t);
}
template <class T, class... Us>
bool operator==(const T& t, const variant<Us...>& u) noexcept {
return u == t;
}
/// The negation of operator==.
template <class... Us, class... Ts>
bool operator!=(const variant<Us...>& u, const variant<Ts...>& t) noexcept {
return !(u == t);
}
/// The negation of operator==.
template <class... Us, class T>
bool operator!=(const variant<Us...>& u, const T& t) noexcept {
return !(u == t);
}
/// The negation of operator==.
template <class T, class... Us>
bool operator!=(const T& t, const variant<Us...>& u) noexcept {
return u != t;
}
} // namespace axis
} // namespace histogram
} // namespace boost
#endif