mirror of
https://github.com/boostorg/histogram.git
synced 2025-05-09 23:04:07 +00:00
Flexible category add (#118)
- added axis::traits::continuous to match axis::traits::is_continuous - axes now return mutable metadata even when constant - histograms with discrete growing axis can be added - use multi_index in histogram interface
This commit is contained in:
parent
455c43ca97
commit
e8b61bf483
@ -91,6 +91,7 @@ unspecified.text = "unspecified"
|
||||
for item in select(
|
||||
lambda x: x.get("name") == "" or x.get("name").startswith("Detail"),
|
||||
"template-type-parameter",
|
||||
"template-nontype-parameter",
|
||||
):
|
||||
parent = parent_map[item]
|
||||
assert parent.tag == "template"
|
||||
|
@ -75,7 +75,7 @@ auto project(const histogram<A, S>& h, const Iterable& c) {
|
||||
// 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<bool>(old_axes, false);
|
||||
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"));
|
||||
|
@ -333,105 +333,64 @@ 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 opts = detail::make_stack_buffer<reduce_command>(old_axes);
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
// check for invalid commands, merge commands, and set iaxis for positional commands
|
||||
unsigned iaxis = 0;
|
||||
for (const reduce_command& o_in : options) {
|
||||
BOOST_ASSERT(o_in.merge > 0);
|
||||
if (o_in.iaxis != reduce_command::unset && o_in.iaxis >= hist.rank())
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("invalid axis index"));
|
||||
auto& o_out = opts[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, see if we can combine commands.
|
||||
// We can combine a rebin and non-rebin command.
|
||||
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;
|
||||
}
|
||||
|
||||
// make new axes container with default-constructed axis instances
|
||||
auto axes = detail::make_default(old_axes);
|
||||
detail::static_if<detail::is_tuple<decltype(axes)>>(
|
||||
[](auto&, const auto&) {},
|
||||
[](auto& axes, const auto& old_axes) {
|
||||
axes.reserve(old_axes.size());
|
||||
detail::for_each_axis(old_axes, [&axes](const auto& a) {
|
||||
axes.emplace_back(detail::make_default(a));
|
||||
});
|
||||
},
|
||||
axes, old_axes);
|
||||
|
||||
// override default-constructed axis instances with modified instances
|
||||
iaxis = 0;
|
||||
hist.for_each_axis([&](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);
|
||||
detail::static_if_c<axis::traits::is_reducible<A>::value>(
|
||||
[&o](auto&& a_out, const auto& a_in) {
|
||||
using A = std::decay_t<decltype(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 is exactly 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);
|
||||
a_out = A(a_in, o.begin.index, o.end.index, o.merge);
|
||||
},
|
||||
[iaxis](auto&&, const auto&) {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("axis " + std::to_string(iaxis) +
|
||||
" is not reducible"));
|
||||
},
|
||||
axis::get<A>(detail::axis_get(axes, iaxis)), 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();
|
||||
axis::get<A>(detail::axis_get(axes, iaxis)) = a_in;
|
||||
}
|
||||
++iaxis;
|
||||
});
|
||||
|
||||
auto idx = detail::make_stack_buffer<index_type>(axes);
|
||||
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();
|
||||
|
@ -12,6 +12,8 @@
|
||||
#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>
|
||||
@ -42,10 +44,11 @@ namespace axis {
|
||||
*/
|
||||
template <class Value, class MetaData, class Options, class Allocator>
|
||||
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
|
||||
public metadata_base<MetaData> {
|
||||
public metadata_base_t<MetaData> {
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
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>;
|
||||
@ -71,7 +74,7 @@ public:
|
||||
*/
|
||||
template <class It, class = detail::requires_iterator<It>>
|
||||
category(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
|
||||
: metadata_base<MetaData>(std::move(meta)), vec_(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"));
|
||||
@ -158,8 +161,8 @@ public:
|
||||
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()) &&
|
||||
metadata_base<MetaData>::operator==(o);
|
||||
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>
|
||||
|
@ -13,6 +13,7 @@
|
||||
#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>
|
||||
@ -39,10 +40,11 @@ namespace axis {
|
||||
*/
|
||||
template <class Value, class MetaData, class Options>
|
||||
class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
|
||||
public metadata_base<MetaData> {
|
||||
public metadata_base_t<MetaData> {
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
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)>;
|
||||
|
||||
@ -76,7 +78,7 @@ public:
|
||||
* \param meta description of the axis.
|
||||
*/
|
||||
integer(value_type start, value_type stop, metadata_type meta = {})
|
||||
: metadata_base<MetaData>(std::move(meta))
|
||||
: metadata_base(std::move(meta))
|
||||
, size_(static_cast<index_type>(stop - start))
|
||||
, min_(start) {
|
||||
if (!(stop >= start))
|
||||
@ -174,7 +176,8 @@ public:
|
||||
|
||||
template <class V, class M, class O>
|
||||
bool operator==(const integer<V, M, O>& o) const noexcept {
|
||||
return size() == o.size() && min_ == o.min_ && metadata_base<MetaData>::operator==(o);
|
||||
return size() == o.size() && min_ == o.min_ &&
|
||||
detail::relaxed_equal{}(this->metadata(), o.metadata());
|
||||
}
|
||||
|
||||
template <class V, class M, class O>
|
||||
|
@ -7,8 +7,7 @@
|
||||
#ifndef BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
|
||||
#define BOOST_HISTOGRAM_AXIS_METADATA_BASE_HPP
|
||||
|
||||
#include <boost/core/empty_value.hpp>
|
||||
#include <boost/histogram/detail/relaxed_equal.hpp>
|
||||
#include <boost/histogram/axis/traits.hpp>
|
||||
#include <boost/histogram/detail/replace_type.hpp>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@ -17,14 +16,16 @@ namespace boost {
|
||||
namespace histogram {
|
||||
namespace axis {
|
||||
|
||||
/// Meta data holder with space optimization for empty meta data types.
|
||||
template <class Metadata,
|
||||
class DetailMetadata = detail::replace_default<Metadata, std::string>>
|
||||
class metadata_base : empty_value<DetailMetadata> {
|
||||
using base_t = empty_value<DetailMetadata>;
|
||||
/** 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 = DetailMetadata;
|
||||
using metadata_type = Metadata;
|
||||
|
||||
// std::string explicitly guarantees nothrow only in C++17
|
||||
static_assert(std::is_same<metadata_type, std::string>::value ||
|
||||
@ -36,30 +37,55 @@ protected:
|
||||
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 : base_t(std::move(o)) {}
|
||||
metadata_base(metadata_type&& o) noexcept : base_t(empty_init_t{}, std::move(o)) {}
|
||||
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 {
|
||||
base_t::operator=(o);
|
||||
data_ = std::move(o.data_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable metadata_type data_;
|
||||
|
||||
public:
|
||||
/// Returns reference to metadata.
|
||||
metadata_type& metadata() noexcept { return base_t::get(); }
|
||||
metadata_type& metadata() noexcept { return data_; }
|
||||
|
||||
/// Returns reference to const metadata.
|
||||
const metadata_type& metadata() const noexcept { return base_t::get(); }
|
||||
/// Returns reference to mutable metadata from const axis.
|
||||
metadata_type& metadata() const noexcept { return data_; }
|
||||
};
|
||||
|
||||
bool operator==(const metadata_base& o) const noexcept {
|
||||
return detail::relaxed_equal(metadata(), o.metadata());
|
||||
#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();
|
||||
}
|
||||
|
||||
bool operator!=(const metadata_base& o) const noexcept {
|
||||
return operator==(o.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
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/histogram/axis/regular.hpp>
|
||||
#include <boost/histogram/detail/detect.hpp>
|
||||
#include <boost/histogram/detail/static_if.hpp>
|
||||
#include <boost/histogram/detail/type_name.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
|
@ -178,11 +178,12 @@ step_type<T> step(T t) {
|
||||
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<MetaData> {
|
||||
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_type = typename metadata_base<MetaData>::metadata_type;
|
||||
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)>;
|
||||
|
||||
@ -216,7 +217,7 @@ public:
|
||||
regular(transform_type trans, unsigned n, value_type start, value_type stop,
|
||||
metadata_type meta = {})
|
||||
: transform_type(std::move(trans))
|
||||
, metadata_base<MetaData>(std::move(meta))
|
||||
, 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_) {
|
||||
@ -366,8 +367,9 @@ public:
|
||||
|
||||
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_ && metadata_base<MetaData>::operator==(o);
|
||||
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 {
|
||||
|
@ -15,12 +15,12 @@
|
||||
#include <boost/histogram/detail/static_if.hpp>
|
||||
#include <boost/histogram/detail/try_cast.hpp>
|
||||
#include <boost/histogram/detail/type_name.hpp>
|
||||
#include <boost/variant2/variant.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>
|
||||
@ -300,6 +300,24 @@ 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 {
|
||||
boost::ignore_unused(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
|
||||
@ -323,10 +341,7 @@ template <class Axis>
|
||||
decltype(auto) metadata(Axis&& axis) noexcept {
|
||||
return detail::static_if<detail::has_method_metadata<std::decay_t<Axis>>>(
|
||||
[](auto&& a) -> decltype(auto) { return a.metadata(); },
|
||||
[](auto &&) -> mp11::mp_if<std::is_const<std::remove_reference_t<Axis>>,
|
||||
axis::null_type const&, axis::null_type&> {
|
||||
return detail::null_value;
|
||||
},
|
||||
[](auto &&) -> axis::null_type& { return detail::null_value; },
|
||||
std::forward<Axis>(axis));
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#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>
|
||||
@ -46,10 +47,11 @@ namespace axis {
|
||||
*/
|
||||
template <class Value, class MetaData, class Options, class Allocator>
|
||||
class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>>,
|
||||
public metadata_base<MetaData> {
|
||||
public metadata_base_t<MetaData> {
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
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;
|
||||
@ -78,7 +80,7 @@ public:
|
||||
*/
|
||||
template <class It, class = detail::requires_iterator<It>>
|
||||
variable(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
|
||||
: metadata_base<MetaData>(std::move(meta)), vec_(std::move(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"));
|
||||
|
||||
@ -118,7 +120,7 @@ public:
|
||||
|
||||
/// 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<MetaData>(src), vec_(src.get_allocator()) {
|
||||
: metadata_base(src), vec_(src.get_allocator()) {
|
||||
BOOST_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"));
|
||||
@ -190,7 +192,7 @@ public:
|
||||
const auto& a = vec_;
|
||||
const auto& b = o.vec_;
|
||||
return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
|
||||
metadata_base<MetaData>::operator==(o);
|
||||
detail::relaxed_equal{}(this->metadata(), o.metadata());
|
||||
}
|
||||
|
||||
template <class V, class M, class O, class A>
|
||||
|
@ -14,11 +14,11 @@
|
||||
#include <boost/histogram/detail/relaxed_equal.hpp>
|
||||
#include <boost/histogram/detail/static_if.hpp>
|
||||
#include <boost/histogram/detail/type_name.hpp>
|
||||
#include <boost/variant2/variant.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>
|
||||
@ -38,9 +38,8 @@ class variant : public iterator_mixin<variant<Ts...>> {
|
||||
template <class T>
|
||||
using requires_bounded_type = std::enable_if_t<is_bounded_type<T>::value>;
|
||||
|
||||
// maybe metadata_type or const metadata_type, if bounded type is const
|
||||
using metadata_type = std::remove_reference_t<decltype(
|
||||
traits::metadata(std::declval<std::remove_pointer_t<mp11::mp_first<variant>>>()))>;
|
||||
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
|
||||
@ -102,15 +101,20 @@ public:
|
||||
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.
|
||||
const metadata_type& metadata() const {
|
||||
metadata_type& metadata() const {
|
||||
return visit(
|
||||
[](const auto& a) -> const metadata_type& {
|
||||
[](const auto& a) -> metadata_type& {
|
||||
using M = decltype(traits::metadata(a));
|
||||
return detail::static_if<std::is_same<M, const metadata_type&>>(
|
||||
[](const auto& a) -> const metadata_type& { return traits::metadata(a); },
|
||||
[](const auto&) -> const metadata_type& {
|
||||
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 " +
|
||||
@ -188,40 +192,6 @@ public:
|
||||
*this);
|
||||
}
|
||||
|
||||
/** 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>
|
||||
bool operator==(const variant<Us...>& u) const {
|
||||
return visit([&u](const auto& x) { return u == x; }, *this);
|
||||
}
|
||||
|
||||
/** 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 T>
|
||||
bool operator==(const T& t) const {
|
||||
return detail::static_if_c<(mp11::mp_contains<impl_type, T>::value ||
|
||||
mp11::mp_contains<impl_type, T*>::value ||
|
||||
mp11::mp_contains<impl_type, const T*>::value)>(
|
||||
[&](const auto& t) {
|
||||
using U = std::decay_t<decltype(t)>;
|
||||
const U* tp = detail::variant_access::template get_if<U>(this);
|
||||
return tp && detail::relaxed_equal(*tp, t);
|
||||
},
|
||||
[&](const auto&) { return false; }, t);
|
||||
}
|
||||
|
||||
/// The negation of operator==.
|
||||
template <class T>
|
||||
bool operator!=(const T& t) const {
|
||||
return !operator==(t);
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, unsigned /* version */) {
|
||||
detail::variant_proxy<variant> p{*this};
|
||||
@ -318,6 +288,58 @@ auto get_if(const U* 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
|
||||
|
@ -1,48 +0,0 @@
|
||||
// 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_AT_HPP
|
||||
#define BOOST_HISTOGRAM_DETAIL_AT_HPP
|
||||
|
||||
#include <boost/histogram/axis/option.hpp>
|
||||
#include <boost/histogram/axis/traits.hpp>
|
||||
#include <boost/histogram/detail/axes.hpp>
|
||||
#include <boost/histogram/detail/linearize.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/mp11/algorithm.hpp>
|
||||
#include <tuple>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace detail {
|
||||
|
||||
template <class A, class... Us>
|
||||
optional_index at(const A& axes, const std::tuple<Us...>& args) noexcept {
|
||||
optional_index idx{0}; // offset not used by linearize_index
|
||||
mp11::mp_for_each<mp11::mp_iota_c<sizeof...(Us)>>(
|
||||
[&, stride = static_cast<std::size_t>(1)](auto i) mutable {
|
||||
stride *= linearize_index(idx, stride, axis_get<i>(axes),
|
||||
static_cast<axis::index_type>(std::get<i>(args)));
|
||||
});
|
||||
return idx;
|
||||
}
|
||||
|
||||
template <class A, class U>
|
||||
optional_index at(const A& axes, const U& args) noexcept {
|
||||
optional_index idx{0};
|
||||
using std::begin;
|
||||
for_each_axis(axes, [&, it = begin(args),
|
||||
stride = static_cast<std::size_t>(1)](const auto& a) mutable {
|
||||
stride *= linearize_index(idx, stride, a, static_cast<axis::index_type>(*it++));
|
||||
});
|
||||
return idx;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -9,39 +9,160 @@
|
||||
|
||||
#include <array>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config/workaround.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#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/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 <initializer_list>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
/* 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, this still
|
||||
* corresponds to 4 GB of storage.
|
||||
*/
|
||||
#ifndef BOOST_HISTOGRAM_DETAIL_AXES_LIMIT
|
||||
#define BOOST_HISTOGRAM_DETAIL_AXES_LIMIT 32
|
||||
#endif
|
||||
|
||||
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>
|
||||
T operator()(const T& a, const T& b) {
|
||||
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, b);
|
||||
}
|
||||
|
||||
#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
|
||||
#pragma warning(disable : 4702) // fixing warning would reduce code readability a lot
|
||||
#endif
|
||||
template <class T, class U>
|
||||
T operator()(const T& a, const U&) {
|
||||
return BOOST_THROW_EXCEPTION(std::invalid_argument("axes not mergable")), a;
|
||||
}
|
||||
#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
|
||||
#pragma warning(default : 4702)
|
||||
#endif
|
||||
|
||||
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;
|
||||
@ -112,67 +233,93 @@ decltype(auto) axis_get(const T& axes, const unsigned i) {
|
||||
return axes[i];
|
||||
}
|
||||
|
||||
template <class... Ts, class... Us>
|
||||
bool axes_equal(const std::tuple<Ts...>& ts, const std::tuple<Us...>& us) {
|
||||
using namespace ::boost::mp11;
|
||||
return static_if<std::is_same<mp_list<Ts...>, mp_list<Us...>>>(
|
||||
[](const auto& ts, const auto& us) {
|
||||
using N = mp_size<std::decay_t<decltype(ts)>>;
|
||||
bool equal = true;
|
||||
mp_for_each<mp_iota<N>>(
|
||||
[&](auto I) { equal &= relaxed_equal(std::get<I>(ts), std::get<I>(us)); });
|
||||
return equal;
|
||||
},
|
||||
[](const auto&, const auto&) { return false; }, ts, us);
|
||||
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
|
||||
ignore_unused(std::initializer_list<bool>{
|
||||
(result &= relaxed_equal{}(std::get<Is>(t), std::get<Is>(u)))...});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T, class... Us>
|
||||
bool axes_equal(const T& t, const std::tuple<Us...>& u) {
|
||||
using namespace ::boost::mp11;
|
||||
if (t.size() != sizeof...(Us)) return false;
|
||||
bool equal = true;
|
||||
mp_for_each<mp_iota_c<sizeof...(Us)>>([&](auto I) { equal &= t[I] == std::get<I>(u); });
|
||||
return equal;
|
||||
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(const std::tuple<Ts...>& t, const U& u) {
|
||||
return axes_equal(u, t);
|
||||
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(const T& t, const U& u) {
|
||||
if (t.size() != u.size()) return false;
|
||||
return std::equal(t.begin(), t.end(), u.begin());
|
||||
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>
|
||||
void axes_assign(std::tuple<Ts...>& t, const std::tuple<Us...>& u) {
|
||||
using namespace ::boost::mp11;
|
||||
static_if<std::is_same<mp_list<Ts...>, mp_list<Us...>>>(
|
||||
[](auto& a, const auto& b) { a = b; },
|
||||
[](auto&, const auto&) {
|
||||
BOOST_THROW_EXCEPTION(
|
||||
std::invalid_argument("cannot assign axes, types do not match"));
|
||||
},
|
||||
t, u);
|
||||
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) {
|
||||
using namespace ::boost::mp11;
|
||||
mp_for_each<mp_iota_c<sizeof...(Ts)>>([&](auto I) {
|
||||
using T = mp_at_c<std::tuple<Ts...>, I>;
|
||||
std::get<I>(t) = axis::get<T>(u[I]);
|
||||
});
|
||||
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 namespace ::boost::mp11;
|
||||
mp_for_each<mp_iota_c<sizeof...(Us)>>([&](auto I) { t[I] = std::get<I>(u); });
|
||||
using std::begin;
|
||||
auto it = begin(t);
|
||||
mp11::tuple_for_each(u, [&](const auto& ui) { *it++ = ui; });
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
@ -198,52 +345,6 @@ void axes_serialize(Archive& ar, std::tuple<Ts...>& axes) {
|
||||
ar& make_nvp("axes", p);
|
||||
}
|
||||
|
||||
// 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>
|
||||
void axis_index_is_valid(const T& axes, const unsigned N) {
|
||||
BOOST_ASSERT_MSG(N < axes_rank(axes), "index out of range");
|
||||
}
|
||||
|
||||
template <class Axes, class V>
|
||||
void for_each_axis_impl(std::true_type, Axes&& axes, V&& v) {
|
||||
for (auto&& a : axes) { axis::visit(std::forward<V>(v), a); }
|
||||
}
|
||||
|
||||
template <class Axes, class V>
|
||||
void for_each_axis_impl(std::false_type, Axes&& axes, V&& v) {
|
||||
for (auto&& a : axes) std::forward<V>(v)(a);
|
||||
}
|
||||
|
||||
template <class Axes, class V>
|
||||
void for_each_axis(Axes&& a, V&& v) {
|
||||
using namespace ::boost::mp11;
|
||||
using T = mp_first<std::decay_t<Axes>>;
|
||||
for_each_axis_impl(is_axis_variant<T>(), std::forward<Axes>(a), std::forward<V>(v));
|
||||
}
|
||||
|
||||
template <class V, class... Axis>
|
||||
void for_each_axis(const std::tuple<Axis...>& a, V&& v) {
|
||||
mp11::tuple_for_each(a, std::forward<V>(v));
|
||||
}
|
||||
|
||||
template <class V, class... Axis>
|
||||
void for_each_axis(std::tuple<Axis...>& a, V&& v) {
|
||||
mp11::tuple_for_each(a, std::forward<V>(v));
|
||||
}
|
||||
|
||||
// total number of bins including *flow bins
|
||||
template <class T>
|
||||
std::size_t bincount(const T& axes) {
|
||||
@ -261,7 +362,8 @@ std::size_t bincount(const T& axes) {
|
||||
template <class T>
|
||||
std::size_t offset(const T& axes) {
|
||||
std::size_t n = 0;
|
||||
for_each_axis(axes, [&n, stride = static_cast<std::size_t>(1)](const auto& a) mutable {
|
||||
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)
|
||||
@ -272,54 +374,21 @@ std::size_t offset(const T& axes) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
using buffer_size_impl = typename std::tuple_size<T>::type;
|
||||
|
||||
template <class T>
|
||||
using buffer_size = mp11::mp_eval_or<
|
||||
std::integral_constant<std::size_t, BOOST_HISTOGRAM_DETAIL_AXES_LIMIT>,
|
||||
buffer_size_impl, T>;
|
||||
|
||||
template <class T, std::size_t N>
|
||||
class sub_array : public std::array<T, N> {
|
||||
using base_type = std::array<T, N>;
|
||||
|
||||
public:
|
||||
explicit sub_array(std::size_t s) noexcept(
|
||||
std::is_nothrow_default_constructible<T>::value)
|
||||
: size_(s) {
|
||||
BOOST_ASSERT_MSG(size_ <= N, "requested size exceeds size of static buffer");
|
||||
}
|
||||
|
||||
sub_array(std::size_t s,
|
||||
const T& value) noexcept(std::is_nothrow_copy_constructible<T>::value)
|
||||
: size_(s) {
|
||||
BOOST_ASSERT_MSG(size_ <= N, "requested size exceeds size of static buffer");
|
||||
std::array<T, N>::fill(value);
|
||||
}
|
||||
|
||||
// need to override both versions of std::array
|
||||
auto end() noexcept { return base_type::begin() + size_; }
|
||||
auto end() const noexcept { return base_type::begin() + size_; }
|
||||
|
||||
auto size() const noexcept { return size_; }
|
||||
|
||||
private:
|
||||
std::size_t size_;
|
||||
};
|
||||
|
||||
template <class U, class T>
|
||||
using stack_buffer = sub_array<U, buffer_size<T>::value>;
|
||||
using buffer_size =
|
||||
std::integral_constant<std::size_t, std::min(relaxed_tuple_size_t<T>::value,
|
||||
static_cast<std::size_t>(
|
||||
BOOST_HISTOGRAM_DETAIL_AXES_LIMIT))>;
|
||||
|
||||
// make default-constructed buffer (no initialization for POD types)
|
||||
template <class U, class T>
|
||||
auto make_stack_buffer(const T& t) {
|
||||
return stack_buffer<U, T>(axes_rank(t));
|
||||
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 U, class T, class V>
|
||||
auto make_stack_buffer(const T& t, V&& v) {
|
||||
return stack_buffer<U, T>(axes_rank(t), std::forward<V>(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>
|
||||
|
@ -9,22 +9,24 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/histogram/axis/option.hpp>
|
||||
#include <boost/histogram/axis/traits.hpp>
|
||||
#include <boost/histogram/detail/axes.hpp>
|
||||
#include <boost/histogram/detail/detect.hpp>
|
||||
#include <boost/histogram/detail/fill.hpp>
|
||||
#include <boost/histogram/detail/linearize.hpp>
|
||||
#include <boost/histogram/detail/non_member_container_access.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/variant2/variant.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 <initializer_list>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@ -39,9 +41,6 @@ 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... Ts>
|
||||
void fold(Ts&&...) noexcept {} // helper to enable operator folding
|
||||
|
||||
template <class T>
|
||||
auto to_ptr_size(const T& x) {
|
||||
return static_if<std::is_scalar<T>>(
|
||||
@ -161,7 +160,8 @@ void fill_n_storage(S& s, const Index idx, Ts&&... p) noexcept {
|
||||
BOOST_ASSERT(idx < s.size());
|
||||
fill_storage_element(s[idx], *p.first...);
|
||||
}
|
||||
fold((p.second ? ++p.first : 0)...);
|
||||
// operator folding emulation
|
||||
ignore_unused(std::initializer_list<int>{(p.second ? (++p.first, 0) : 0)...});
|
||||
}
|
||||
|
||||
template <class S, class Index, class T, class... Ts>
|
||||
@ -171,7 +171,8 @@ void fill_n_storage(S& s, const Index idx, weight_type<T>&& w, Ts&&... ps) noexc
|
||||
fill_storage_element(s[idx], weight(*w.value.first), *ps.first...);
|
||||
}
|
||||
if (w.value.second) ++w.value.first;
|
||||
fold((ps.second ? ++ps.first : 0)...);
|
||||
// operator folding emulation
|
||||
ignore_unused(std::initializer_list<int>{(ps.second ? (++ps.first, 0) : 0)...});
|
||||
}
|
||||
|
||||
// general Nd treatment
|
||||
|
82
include/boost/histogram/detail/index_translator.hpp
Normal file
82
include/boost/histogram/detail/index_translator.hpp
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 <boost/assert.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#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/multi_index.hpp>
|
||||
#include <boost/mp11/integer_sequence.hpp>
|
||||
#include <initializer_list>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace detail {
|
||||
|
||||
template <class A>
|
||||
struct index_translator {
|
||||
const A& dst;
|
||||
const A& src;
|
||||
using index_type = axis::index_type;
|
||||
using multi_index_type = multi_index<relaxed_tuple_size_t<A>::value>;
|
||||
|
||||
template <class T>
|
||||
index_type translate(const T& a, const T& b, const index_type& i) const noexcept {
|
||||
return axis::traits::index(a, axis::traits::value(b, i + 0.5));
|
||||
}
|
||||
|
||||
template <class T, class It, std::size_t... Is>
|
||||
void impl(const T& a, const T& b, It i, index_type* j,
|
||||
mp11::index_sequence<Is...>) const noexcept {
|
||||
// operator folding emulation
|
||||
ignore_unused(std::initializer_list<index_type>{
|
||||
(*j++ = translate(std::get<Is>(a), std::get<Is>(b), *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 {
|
||||
impl(a, b, i, j, mp11::make_index_sequence<sizeof...(Ts)>{});
|
||||
}
|
||||
|
||||
template <class T, class It>
|
||||
void impl(const T& a, const T& b, It i, index_type* j) const noexcept {
|
||||
for (unsigned k = 0; k < a.size(); ++k, ++i, ++j) {
|
||||
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
|
@ -14,6 +14,7 @@
|
||||
#include <boost/histogram/axis/variant.hpp>
|
||||
#include <boost/histogram/detail/optional_index.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/histogram/multi_index.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
@ -78,7 +79,7 @@ std::size_t linearize_growth(Index& out, axis::index_type& shift,
|
||||
// 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) {
|
||||
const axis::index_type idx) noexcept {
|
||||
// cannot use get_options here, since A may be variant
|
||||
const auto opt = axis::traits::options(ax);
|
||||
const axis::index_type begin = opt & axis::option::underflow ? -1 : 0;
|
||||
@ -92,6 +93,20 @@ std::size_t linearize_index(optional_index& out, const std::size_t stride, const
|
||||
return extent;
|
||||
}
|
||||
|
||||
template <class A, std::size_t N>
|
||||
optional_index linearize_index(const A& axes, const multi_index<N>& indices) noexcept {
|
||||
BOOST_ASSERT_MSG(axes_rank(axes) == detail::size(indices),
|
||||
"axes and indices must have equal lengths");
|
||||
|
||||
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) {
|
||||
|
@ -4,8 +4,8 @@
|
||||
// (See accompanying file LICENSE_1_0.txt
|
||||
// or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef BOOST_HISTOGRAM_DETAIL_NON_MEMBER_CONTAINER_ACCESS_HPP
|
||||
#define BOOST_HISTOGRAM_DETAIL_NON_MEMBER_CONTAINER_ACCESS_HPP
|
||||
#ifndef BOOST_HISTOGRAM_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP
|
||||
#define BOOST_HISTOGRAM_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP
|
||||
|
||||
#if __cpp_lib_nonmember_container_access >= 201411
|
||||
|
||||
@ -22,7 +22,7 @@ using std::size;
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
||||
#else
|
||||
#else // std implementations are not available
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
@ -67,4 +67,4 @@ constexpr std::size_t size(const T (&)[N]) noexcept {
|
||||
|
||||
#endif
|
||||
|
||||
#endif // BOOST_HISTOGRAM_DETAIL_NON_MEMBER_CONTAINER_ACCESS_HPP
|
||||
#endif // BOOST_HISTOGRAM_DETAIL_NONMEMBER_CONTAINER_ACCESS_HPP
|
@ -7,7 +7,12 @@
|
||||
#ifndef BOOST_HISTOGRAM_DETAIL_REDUCE_COMMAND_HPP
|
||||
#define BOOST_HISTOGRAM_DETAIL_REDUCE_COMMAND_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/histogram/detail/span.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
@ -15,7 +20,7 @@ namespace detail {
|
||||
|
||||
struct reduce_command {
|
||||
static constexpr unsigned unset = static_cast<unsigned>(-1);
|
||||
unsigned iaxis;
|
||||
unsigned iaxis = unset;
|
||||
enum class range_t : char {
|
||||
none,
|
||||
indices,
|
||||
@ -24,7 +29,7 @@ struct reduce_command {
|
||||
union {
|
||||
axis::index_type index;
|
||||
double value;
|
||||
} begin, end;
|
||||
} begin{0}, end{0};
|
||||
unsigned merge = 0; // default value indicates unset option
|
||||
bool crop = false;
|
||||
// for internal use by the reduce algorithm
|
||||
@ -33,6 +38,43 @@ struct reduce_command {
|
||||
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) {
|
||||
BOOST_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
|
||||
|
@ -7,19 +7,37 @@
|
||||
#ifndef BOOST_HISTOGRAM_DETAIL_RELAXED_EQUAL_HPP
|
||||
#define BOOST_HISTOGRAM_DETAIL_RELAXED_EQUAL_HPP
|
||||
|
||||
#include <boost/histogram/detail/detect.hpp>
|
||||
#include <boost/histogram/detail/static_if.hpp>
|
||||
#include <boost/histogram/detail/priority.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
constexpr bool relaxed_equal(const T& a, const T& b) noexcept {
|
||||
return static_if<has_operator_equal<T>>(
|
||||
[](const auto& a, const auto& b) { return a == b; },
|
||||
[](const auto&, const auto&) { return true; }, a, b);
|
||||
}
|
||||
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
|
||||
|
37
include/boost/histogram/detail/relaxed_tuple_size.hpp
Normal file
37
include/boost/histogram/detail/relaxed_tuple_size.hpp
Normal 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
|
@ -36,7 +36,7 @@ using std::span;
|
||||
|
||||
#include <array>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/histogram/detail/non_member_container_access.hpp>
|
||||
#include <boost/histogram/detail/nonmember_container_access.hpp>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
@ -70,13 +70,14 @@ private:
|
||||
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;
|
||||
@ -107,9 +108,7 @@ public:
|
||||
|
||||
static constexpr std::size_t extent = Extent;
|
||||
|
||||
template <std::size_t _ = extent,
|
||||
class = std::enable_if_t<(_ == 0 || _ == dynamic_extent)> >
|
||||
constexpr span() noexcept : base(nullptr, 0) {}
|
||||
using base::base;
|
||||
|
||||
constexpr span(pointer first, pointer last)
|
||||
: span(first, static_cast<std::size_t>(last - first)) {
|
||||
@ -134,14 +133,14 @@ public:
|
||||
: span(dtl::data(arr), N) {}
|
||||
|
||||
template <class Container, class = std::enable_if_t<std::is_convertible<
|
||||
decltype(dtl::size(std::declval<Container>()),
|
||||
dtl::data(std::declval<Container>())),
|
||||
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>())),
|
||||
decltype(dtl::size(std::declval<Container&>()),
|
||||
dtl::data(std::declval<Container&>())),
|
||||
pointer>::value> >
|
||||
constexpr span(Container& cont) : span(dtl::data(cont), dtl::size(cont)) {}
|
||||
|
||||
@ -173,8 +172,8 @@ public:
|
||||
const_reverse_iterator rend() const { return reverse_iterator(begin()); }
|
||||
const_reverse_iterator crend() { return reverse_iterator(begin()); }
|
||||
|
||||
constexpr reference front() { *base::data(); }
|
||||
constexpr reference back() { *(base::data() + base::size() - 1); }
|
||||
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]; }
|
||||
|
||||
@ -237,7 +236,7 @@ public:
|
||||
|
||||
#endif
|
||||
|
||||
#include <boost/histogram/detail/non_member_container_access.hpp>
|
||||
#include <boost/histogram/detail/nonmember_container_access.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace boost {
|
||||
|
121
include/boost/histogram/detail/sub_array.hpp
Normal file
121
include/boost/histogram/detail/sub_array.hpp
Normal file
@ -0,0 +1,121 @@
|
||||
// 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 <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) {
|
||||
BOOST_ASSERT_MSG(size_ <= N, "requested size exceeds size of static buffer");
|
||||
}
|
||||
|
||||
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()) throw std::out_of_range{"pos is out of range"};
|
||||
return data_[pos];
|
||||
}
|
||||
|
||||
const_reference at(size_type pos) const noexcept {
|
||||
if (pos >= size()) throw 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
|
@ -148,4 +148,16 @@ class BOOST_ATTRIBUTE_NODISCARD histogram;
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
||||
/* 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
|
||||
|
||||
#endif
|
||||
|
@ -9,16 +9,18 @@
|
||||
|
||||
#include <boost/histogram/detail/accumulator_traits.hpp>
|
||||
#include <boost/histogram/detail/argument_traits.hpp>
|
||||
#include <boost/histogram/detail/at.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/non_member_container_access.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>
|
||||
@ -68,6 +70,7 @@ public:
|
||||
// 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>;
|
||||
@ -137,14 +140,14 @@ public:
|
||||
/// 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 {
|
||||
detail::axis_index_is_valid(axes_, N);
|
||||
BOOST_ASSERT_MSG(N < rank(), "index out of range");
|
||||
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 {
|
||||
detail::axis_index_is_valid(axes_, i);
|
||||
BOOST_ASSERT_MSG(i < rank(), "index out of range");
|
||||
return detail::axis_get(axes_, i);
|
||||
}
|
||||
|
||||
@ -349,50 +352,23 @@ public:
|
||||
@param is indices of second, third, ... axes.
|
||||
@returns reference to cell value.
|
||||
*/
|
||||
template <class... Indices>
|
||||
decltype(auto) at(axis::index_type i, Indices... is) {
|
||||
return at(std::forward_as_tuple(i, is...));
|
||||
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... Indices>
|
||||
decltype(auto) at(axis::index_type i, Indices... is) const {
|
||||
return at(std::forward_as_tuple(i, is...));
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices stored in `std::tuple`.
|
||||
template <class... Indices>
|
||||
decltype(auto) at(const std::tuple<Indices...>& is) {
|
||||
if (rank() != sizeof...(Indices))
|
||||
BOOST_THROW_EXCEPTION(
|
||||
std::invalid_argument("number of arguments != histogram rank"));
|
||||
const auto idx = detail::at(axes_, is);
|
||||
if (!is_valid(idx))
|
||||
BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
|
||||
BOOST_ASSERT(idx < storage_.size());
|
||||
return storage_[idx];
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices stored in `std::tuple` (read-only).
|
||||
template <class... Indices>
|
||||
decltype(auto) at(const std::tuple<Indices...>& is) const {
|
||||
if (rank() != sizeof...(Indices))
|
||||
BOOST_THROW_EXCEPTION(
|
||||
std::invalid_argument("number of arguments != histogram rank"));
|
||||
const auto idx = detail::at(axes_, is);
|
||||
if (!is_valid(idx))
|
||||
BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
|
||||
BOOST_ASSERT(idx < storage_.size());
|
||||
return storage_[idx];
|
||||
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.
|
||||
template <class Iterable, class = detail::requires_iterable<Iterable>>
|
||||
decltype(auto) at(const Iterable& is) {
|
||||
if (rank() != detail::axes_rank(is))
|
||||
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::at(axes_, is);
|
||||
const auto idx = detail::linearize_index(axes_, is);
|
||||
if (!is_valid(idx))
|
||||
BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
|
||||
BOOST_ASSERT(idx < storage_.size());
|
||||
@ -400,28 +376,39 @@ public:
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices stored in iterable (read-only).
|
||||
template <class Iterable, class = detail::requires_iterable<Iterable>>
|
||||
decltype(auto) at(const Iterable& is) const {
|
||||
if (rank() != detail::axes_rank(is))
|
||||
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::at(axes_, is);
|
||||
const auto idx = detail::linearize_index(axes_, is);
|
||||
if (!is_valid(idx))
|
||||
BOOST_THROW_EXCEPTION(std::out_of_range("at least one index out of bounds"));
|
||||
BOOST_ASSERT(idx < storage_.size());
|
||||
return storage_[idx];
|
||||
}
|
||||
|
||||
/// Access value at index (number for rank = 1, else `std::tuple` or iterable).
|
||||
template <class Indices>
|
||||
decltype(auto) operator[](const Indices& is) {
|
||||
return at(is);
|
||||
/// 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 (read-only).
|
||||
template <class Indices>
|
||||
decltype(auto) operator[](const Indices& is) const {
|
||||
return at(is);
|
||||
/// 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_index(axes_, is)];
|
||||
}
|
||||
|
||||
/// Access value at index tuple (read-only).
|
||||
decltype(auto) operator[](const multi_index_type& is) const {
|
||||
return storage_[detail::linearize_index(axes_, is)];
|
||||
}
|
||||
|
||||
/// Equality operator, tests equality for all axes and the storage.
|
||||
@ -459,6 +446,31 @@ public:
|
||||
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 (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)) h[tr1(x.indices())] += *x;
|
||||
const auto tr2 = detail::make_index_translator(axes, raxes);
|
||||
for (auto&& x : indexed(rhs)) h[tr2(x.indices())] += *x;
|
||||
operator=(std::move(h));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Subtract values of another histogram.
|
||||
|
||||
This operator is only available if the value_type supports operator-=.
|
||||
|
@ -41,7 +41,7 @@ class BOOST_ATTRIBUTE_NODISCARD indexed_range {
|
||||
private:
|
||||
using histogram_type = Histogram;
|
||||
static constexpr std::size_t buffer_size =
|
||||
detail::buffer_size<typename std::remove_const_t<histogram_type>::axes_type>::value;
|
||||
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,
|
||||
|
123
include/boost/histogram/multi_index.hpp
Normal file
123
include/boost/histogram/multi_index.hpp
Normal 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<(sizeof...(Is) + 1)>{}) {}
|
||||
|
||||
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... Ns>
|
||||
multi_index(const T& is, mp11::index_sequence<Ns...>)
|
||||
: multi_index(static_cast<axis::index_type>(std::get<Ns>(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<(sizeof...(Is) + 1)>{}) {}
|
||||
|
||||
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
|
@ -10,10 +10,8 @@
|
||||
|
||||
#include <boost/histogram/accumulators/ostream.hpp>
|
||||
#include <boost/histogram/axis/ostream.hpp>
|
||||
#include <boost/histogram/axis/variant.hpp>
|
||||
#include <boost/histogram/detail/axes.hpp>
|
||||
#include <boost/histogram/detail/counting_streambuf.hpp>
|
||||
#include <boost/histogram/detail/detect.hpp>
|
||||
#include <boost/histogram/detail/detect.hpp> // is_streamable
|
||||
#include <boost/histogram/detail/static_if.hpp>
|
||||
#include <boost/histogram/indexed.hpp>
|
||||
#include <cmath>
|
||||
|
@ -48,7 +48,7 @@ struct unsafe_access {
|
||||
*/
|
||||
template <class Histogram, unsigned I = 0>
|
||||
static decltype(auto) axis(Histogram& hist, std::integral_constant<unsigned, I> = {}) {
|
||||
detail::axis_index_is_valid(hist.axes_, I);
|
||||
BOOST_ASSERT_MSG(I < hist.rank(), "index out of range");
|
||||
return detail::axis_get<I>(hist.axes_);
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ struct unsafe_access {
|
||||
*/
|
||||
template <class Histogram>
|
||||
static decltype(auto) axis(Histogram& hist, unsigned i) {
|
||||
detail::axis_index_is_valid(hist.axes_, i);
|
||||
BOOST_ASSERT_MSG(i < hist.rank(), "index out of range");
|
||||
return detail::axis_get(hist.axes_, i);
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,8 @@ template <typename Tag>
|
||||
void run_tests() {
|
||||
// limitations: shrink does not work with arguments not convertible to double
|
||||
|
||||
using R = axis::regular<double, axis::transform::id, axis::null_type>;
|
||||
using ID = axis::integer<double, axis::null_type>;
|
||||
using R = axis::regular<double>;
|
||||
using ID = axis::integer<double, axis::empty_type>;
|
||||
using V = axis::variable<double, axis::empty_type>;
|
||||
using CI = axis::category<int, axis::empty_type>;
|
||||
|
||||
@ -106,7 +106,7 @@ void run_tests() {
|
||||
}
|
||||
|
||||
{
|
||||
auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5), R(3, -1, 2));
|
||||
auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5, "1"), R(3, -1, 2, "2"));
|
||||
|
||||
/*
|
||||
matrix layout:
|
||||
@ -127,8 +127,8 @@ void run_tests() {
|
||||
auto hr = reduce(h, shrink(1, -1, 2), rebin(0, 1));
|
||||
BOOST_TEST_EQ(hr.rank(), 2);
|
||||
BOOST_TEST_EQ(sum(hr), 10);
|
||||
BOOST_TEST_EQ(hr.axis(0), R(4, 1, 5));
|
||||
BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2));
|
||||
BOOST_TEST_EQ(hr.axis(0), R(4, 1, 5, "1"));
|
||||
BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
|
||||
BOOST_TEST_EQ(hr, h);
|
||||
|
||||
// noop slice
|
||||
@ -139,8 +139,8 @@ void run_tests() {
|
||||
hr = reduce(h, shrink(0, 2, 4));
|
||||
BOOST_TEST_EQ(hr.rank(), 2);
|
||||
BOOST_TEST_EQ(sum(hr), 10);
|
||||
BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4));
|
||||
BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2));
|
||||
BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4, "1"));
|
||||
BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
|
||||
BOOST_TEST_EQ(hr.at(-1, 0), 1); // underflow
|
||||
BOOST_TEST_EQ(hr.at(0, 0), 0);
|
||||
BOOST_TEST_EQ(hr.at(1, 0), 1);
|
||||
@ -165,8 +165,8 @@ void run_tests() {
|
||||
hr = reduce(h, shrink_and_rebin(0, 2, 5, 2), rebin(1, 3));
|
||||
BOOST_TEST_EQ(hr.rank(), 2);
|
||||
BOOST_TEST_EQ(sum(hr), 10);
|
||||
BOOST_TEST_EQ(hr.axis(0), R(1, 2, 4));
|
||||
BOOST_TEST_EQ(hr.axis(1), R(1, -1, 2));
|
||||
BOOST_TEST_EQ(hr.axis(0), R(1, 2, 4, "1"));
|
||||
BOOST_TEST_EQ(hr.axis(1), R(1, -1, 2, "2"));
|
||||
BOOST_TEST_EQ(hr.at(-1, 0), 2); // underflow
|
||||
BOOST_TEST_EQ(hr.at(0, 0), 5);
|
||||
BOOST_TEST_EQ(hr.at(1, 0), 3); // overflow
|
||||
|
@ -49,9 +49,10 @@ int main() {
|
||||
{
|
||||
axis::regular<> a{4, -2, 2, "foo"};
|
||||
BOOST_TEST_EQ(a.metadata(), "foo");
|
||||
BOOST_TEST_EQ(static_cast<const axis::regular<>&>(a).metadata(), "foo");
|
||||
a.metadata() = "bar";
|
||||
BOOST_TEST_EQ(static_cast<const axis::regular<>&>(a).metadata(), "bar");
|
||||
const auto& cref = a;
|
||||
BOOST_TEST_EQ(cref.metadata(), "foo");
|
||||
cref.metadata() = "bar"; // this is allowed
|
||||
BOOST_TEST_EQ(cref.metadata(), "bar");
|
||||
BOOST_TEST_EQ(a.value(0), -2);
|
||||
BOOST_TEST_EQ(a.value(1), -1);
|
||||
BOOST_TEST_EQ(a.value(2), 0);
|
||||
|
@ -256,7 +256,7 @@ int main() {
|
||||
BOOST_TEST_TRAIT_SAME(decltype(traits::metadata(static_cast<None&>(none))),
|
||||
null_type&);
|
||||
BOOST_TEST_TRAIT_SAME(decltype(traits::metadata(static_cast<const None&>(none))),
|
||||
const null_type&);
|
||||
null_type&);
|
||||
|
||||
Const c;
|
||||
BOOST_TEST_EQ(traits::metadata(c), 0);
|
||||
|
@ -61,6 +61,7 @@ int main() {
|
||||
auto t3 = std::make_tuple(v, i);
|
||||
auto t4 = std::make_tuple(r, r);
|
||||
|
||||
BOOST_TEST(detail::axes_equal(t1, t1));
|
||||
BOOST_TEST(detail::axes_equal(t1, v1));
|
||||
BOOST_TEST(detail::axes_equal(t1, v2));
|
||||
BOOST_TEST(detail::axes_equal(t1, v4));
|
||||
@ -114,6 +115,51 @@ int main() {
|
||||
BOOST_TEST(detail::axes_equal(t2, t3));
|
||||
}
|
||||
|
||||
// axes_transform
|
||||
{
|
||||
using R = axis::regular<>;
|
||||
using I = axis::integer<double>;
|
||||
|
||||
{
|
||||
auto t = std::make_tuple(R(1, 0, 1), R(2, 0, 2), I(0, 3));
|
||||
auto t2 = detail::axes_transform(
|
||||
t, [](std::size_t, const auto& a) { return I(0, a.size()); });
|
||||
BOOST_TEST_EQ(t2, std::make_tuple(I(0, 1), I(0, 2), I(0, 3)));
|
||||
}
|
||||
{
|
||||
auto t = std::vector<I>{{I(0, 1), I(0, 2)}};
|
||||
auto t2 = detail::axes_transform(
|
||||
t, [](std::size_t, const auto& a) { return I(0, a.size() + 1); });
|
||||
auto t3 = std::vector<I>{{I(0, 2), I(0, 3)}};
|
||||
BOOST_TEST(detail::axes_equal(t2, t3));
|
||||
}
|
||||
{
|
||||
using V = axis::variant<R, I>;
|
||||
auto t = std::vector<V>{{V{I(0, 1)}, V{R(2, 0, 2)}}};
|
||||
auto t2 = detail::axes_transform(
|
||||
t, [](std::size_t, const auto& a) { return I(0, a.size() + 1); });
|
||||
auto t3 = std::vector<V>{{I(0, 2), I(0, 3)}};
|
||||
BOOST_TEST(detail::axes_equal(t2, t3));
|
||||
}
|
||||
|
||||
{
|
||||
using V = axis::variant<R, I>;
|
||||
auto t1 = std::vector<V>{{V{I(0, 1)}, V{R(2, 0, 2)}}};
|
||||
auto t2 = std::vector<V>{{V{I(0, 1)}, V{R(2, 0, 2)}}};
|
||||
auto t3 = detail::axes_transform(
|
||||
t1, t2, [](const auto& a, const auto& b) { return I(0, a.size() + b.size()); });
|
||||
auto t4 = std::vector<V>{{I(0, 2), I(0, 4)}};
|
||||
BOOST_TEST(detail::axes_equal(t3, t4));
|
||||
}
|
||||
|
||||
{
|
||||
// test otherwise unreachable code
|
||||
auto a = R(2, 0, 2);
|
||||
auto b = I(0, 2);
|
||||
BOOST_TEST_THROWS(detail::axis_merger{}(a, b), std::invalid_argument);
|
||||
}
|
||||
}
|
||||
|
||||
// axes_rank
|
||||
{
|
||||
std::tuple<int, int> a;
|
||||
|
@ -4,18 +4,48 @@
|
||||
// (See accompanying file LICENSE_1_0.txt
|
||||
// or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
#include <boost/core/lightweight_test_trait.hpp>
|
||||
#include <boost/histogram/accumulators/weighted_sum.hpp>
|
||||
#include <boost/histogram/axis/integer.hpp>
|
||||
#include <boost/histogram/detail/common_type.hpp>
|
||||
#include <boost/histogram/detail/counting_streambuf.hpp>
|
||||
#include <boost/histogram/detail/non_member_container_access.hpp>
|
||||
#include <boost/histogram/detail/index_translator.hpp>
|
||||
#include <boost/histogram/detail/nonmember_container_access.hpp>
|
||||
#include <boost/histogram/detail/span.hpp>
|
||||
#include <boost/histogram/detail/sub_array.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/histogram/literals.hpp>
|
||||
#include <boost/histogram/storage_adaptor.hpp>
|
||||
#include <boost/histogram/unlimited_storage.hpp>
|
||||
#include <ostream>
|
||||
#include "std_ostream.hpp"
|
||||
#include "throw_exception.hpp"
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
template <std::size_t N>
|
||||
std::ostream& operator<<(std::ostream& os, const multi_index<N>& mi) {
|
||||
os << "(";
|
||||
bool first = true;
|
||||
for (auto&& x : mi) {
|
||||
if (!first)
|
||||
os << " ";
|
||||
else
|
||||
first = false;
|
||||
os << x << ", ";
|
||||
}
|
||||
os << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <std::size_t N, std::size_t M>
|
||||
bool operator==(const multi_index<N>& a, const multi_index<M>& b) {
|
||||
return std::equal(a.begin(), a.end(), b.begin(), b.end());
|
||||
}
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
||||
using namespace boost::histogram;
|
||||
using namespace boost::histogram::literals;
|
||||
@ -84,5 +114,44 @@ int main() {
|
||||
BOOST_TEST_EQ(cbuf.count, 6);
|
||||
}
|
||||
|
||||
// sub_array and span
|
||||
{
|
||||
dtl::sub_array<int, 2> a(2, 1);
|
||||
a[1] = 2;
|
||||
auto sp = dtl::span<int>(a);
|
||||
BOOST_TEST_EQ(sp.size(), 2);
|
||||
BOOST_TEST_EQ(sp.front(), 1);
|
||||
BOOST_TEST_EQ(sp.back(), 2);
|
||||
|
||||
const auto& ca = a;
|
||||
auto csp = dtl::span<const int>(ca);
|
||||
BOOST_TEST_EQ(csp.size(), 2);
|
||||
BOOST_TEST_EQ(csp.front(), 1);
|
||||
BOOST_TEST_EQ(csp.back(), 2);
|
||||
}
|
||||
|
||||
// index_translator
|
||||
{
|
||||
using I = axis::integer<>;
|
||||
|
||||
{
|
||||
auto t = std::vector<I>{I{0, 1}, I{1, 3}};
|
||||
auto tr = dtl::make_index_translator(t, t);
|
||||
multi_index<static_cast<std::size_t>(-1)> mi{0, 1};
|
||||
BOOST_TEST_EQ(tr(mi), mi);
|
||||
multi_index<2> mi2{0, 1};
|
||||
BOOST_TEST_EQ(tr(mi2), mi);
|
||||
}
|
||||
|
||||
{
|
||||
auto t = std::make_tuple(I{0, 1});
|
||||
auto tr = dtl::make_index_translator(t, t);
|
||||
multi_index<static_cast<std::size_t>(-1)> mi{0};
|
||||
BOOST_TEST_EQ(tr(mi), mi);
|
||||
}
|
||||
|
||||
BOOST_TEST_THROWS(multi_index<1>::create(2), std::invalid_argument);
|
||||
}
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
@ -12,16 +12,23 @@
|
||||
using namespace boost::histogram::detail;
|
||||
|
||||
int main() {
|
||||
struct A {};
|
||||
A a, b;
|
||||
struct Stateless {
|
||||
} a, b;
|
||||
|
||||
struct B {
|
||||
bool operator==(const B&) const { return false; }
|
||||
};
|
||||
B c, d;
|
||||
struct Stateful {
|
||||
int state; // has state
|
||||
} c, d;
|
||||
|
||||
BOOST_TEST(relaxed_equal(a, b));
|
||||
BOOST_TEST_NOT(relaxed_equal(c, d));
|
||||
struct HasEqual {
|
||||
int state;
|
||||
bool operator==(const HasEqual& rhs) const { return state == rhs.state; }
|
||||
} e{1}, f{1}, g{2};
|
||||
|
||||
BOOST_TEST(relaxed_equal{}(a, b));
|
||||
BOOST_TEST_NOT(relaxed_equal{}(a, c));
|
||||
BOOST_TEST_NOT(relaxed_equal{}(c, d));
|
||||
BOOST_TEST(relaxed_equal{}(e, f));
|
||||
BOOST_TEST_NOT(relaxed_equal{}(e, g));
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
@ -79,5 +79,13 @@ int main() {
|
||||
run_tests<static_tag, dynamic_tag>();
|
||||
run_tests<dynamic_tag, static_tag>();
|
||||
|
||||
// copy assign
|
||||
{
|
||||
auto a = make(static_tag{}, axis::regular<>{3, 0, 3}, axis::integer<>{0, 2});
|
||||
auto b = make(dynamic_tag{}, axis::regular<>{3, 0, 3}, axis::regular<>{2, 0, 2},
|
||||
axis::integer<>{0, 2});
|
||||
BOOST_TEST_THROWS(a = b, std::invalid_argument);
|
||||
}
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
@ -167,14 +167,77 @@ void run_tests() {
|
||||
BOOST_TEST_EQ(d.at(1).variance(), 11);
|
||||
}
|
||||
|
||||
// merging add
|
||||
{
|
||||
using C = axis::category<int, use_default, axis::option::growth_t>;
|
||||
using I = axis::integer<int, axis::null_type, axis::option::growth_t>;
|
||||
|
||||
{
|
||||
auto empty = std::initializer_list<int>{};
|
||||
auto a = make(Tag(), C(empty, "foo"));
|
||||
auto b = make(Tag(), C(empty, "foo"));
|
||||
a(2);
|
||||
a(1);
|
||||
b(2);
|
||||
b(3);
|
||||
b(4);
|
||||
a += b;
|
||||
BOOST_TEST_EQ(a.axis(), C({2, 1, 3, 4}, "foo"));
|
||||
BOOST_TEST_EQ(a[0], 2);
|
||||
BOOST_TEST_EQ(a[1], 1);
|
||||
BOOST_TEST_EQ(a[2], 1);
|
||||
BOOST_TEST_EQ(a[3], 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = make(Tag(), C{1, 2}, I{4, 5});
|
||||
auto b = make(Tag(), C{2, 3}, I{5, 6});
|
||||
|
||||
std::fill(a.begin(), a.end(), 1);
|
||||
std::fill(b.begin(), b.end(), 1);
|
||||
|
||||
a += b;
|
||||
|
||||
BOOST_TEST_EQ(a.axis(0), (C{1, 2, 3}));
|
||||
BOOST_TEST_EQ(a.axis(1), (I{4, 6}));
|
||||
BOOST_TEST_EQ(a.at(0, 0), 1);
|
||||
BOOST_TEST_EQ(a.at(1, 0), 1);
|
||||
BOOST_TEST_EQ(a.at(2, 0), 0); // v=(3, 4) did not exist in a or b
|
||||
BOOST_TEST_EQ(a.at(0, 1), 0); // v=(1, 5) did not exist in a or b
|
||||
BOOST_TEST_EQ(a.at(1, 1), 1);
|
||||
BOOST_TEST_EQ(a.at(2, 1), 1);
|
||||
}
|
||||
|
||||
{
|
||||
// C2 is not growing
|
||||
using C2 = axis::category<int, use_default, axis::option::none_t>;
|
||||
auto a = make(Tag(), C{1, 2}, C2{4, 5});
|
||||
auto b = make(Tag(), C{1, 2}, C2{5, 6});
|
||||
BOOST_TEST_THROWS(a += b, std::invalid_argument);
|
||||
|
||||
b = a;
|
||||
a += b; // OK
|
||||
|
||||
// incompatible labels
|
||||
b.axis(0).metadata() = "foo";
|
||||
BOOST_TEST_THROWS(a += b, std::invalid_argument);
|
||||
|
||||
// incompatible axis types
|
||||
auto c = make(Tag(), C{1, 2}, I{4, 6});
|
||||
BOOST_TEST_THROWS(a += c, std::invalid_argument);
|
||||
}
|
||||
}
|
||||
|
||||
// bad operations
|
||||
{
|
||||
auto a = make(Tag(), axis::integer<>(0, 2));
|
||||
auto b = make(Tag(), axis::integer<>(0, 3));
|
||||
auto a = make(Tag(), axis::regular<>(2, 0, 4));
|
||||
auto b = make(Tag(), axis::regular<>(2, 0, 2));
|
||||
BOOST_TEST_THROWS(a += b, std::invalid_argument);
|
||||
BOOST_TEST_THROWS(a -= b, std::invalid_argument);
|
||||
BOOST_TEST_THROWS(a *= b, std::invalid_argument);
|
||||
BOOST_TEST_THROWS(a /= b, std::invalid_argument);
|
||||
auto c = make(Tag(), axis::regular<>(2, 0, 2), axis::regular<>(2, 0, 4));
|
||||
BOOST_TEST_THROWS(a += c, std::invalid_argument);
|
||||
}
|
||||
|
||||
// scaling
|
||||
|
@ -12,3 +12,6 @@ src:../../boost/archive/basic_binary_oprimitive.hpp
|
||||
|
||||
# boost/archive/basic_binary_iprimitive.hpp:77:16: runtime error: downcast of address X which does not point to an object of type Y
|
||||
src:../../boost/archive/basic_binary_iprimitive.hpp
|
||||
|
||||
# boost/archive/iterators/escape.hpp:85:12: runtime error: applying non-zero offset 1 to null pointer
|
||||
src:../../boost/archive/iterators/escape.hpp
|
||||
|
Loading…
x
Reference in New Issue
Block a user