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:
Hans Dembinski 2020-05-20 02:42:53 +02:00 committed by GitHub
parent 455c43ca97
commit e8b61bf483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1199 additions and 487 deletions

View File

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

View File

@ -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"));

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_DETAIL_RELAXED_TUPLE_SIZE_HPP
#define BOOST_HISTOGRAM_DETAIL_RELAXED_TUPLE_SIZE_HPP
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
using dynamic_size = std::integral_constant<std::size_t, static_cast<std::size_t>(-1)>;
// Returns static size of tuple or dynamic_size
template <class T>
constexpr dynamic_size relaxed_tuple_size(const T&) noexcept {
return {};
}
template <class... Ts>
constexpr std::integral_constant<std::size_t, sizeof...(Ts)> relaxed_tuple_size(
const std::tuple<Ts...>&) noexcept {
return {};
}
template <class T>
using relaxed_tuple_size_t = decltype(relaxed_tuple_size(std::declval<T>()));
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

View File

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

View 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

View File

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

View File

@ -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-=.

View File

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

View File

@ -0,0 +1,123 @@
// Copyright 2020 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_MULTI_INDEX_HPP
#define BOOST_HISTOGRAM_MULTI_INDEX_HPP
#include <algorithm>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/nonmember_container_access.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/mp11/integer_sequence.hpp>
#include <boost/throw_exception.hpp>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <tuple>
namespace boost {
namespace histogram {
/** Holder for multiple axis indices.
Adapts external iterable, tuple, or explicit list of indices to the same representation.
*/
template <std::size_t Size>
struct multi_index {
using value_type = axis::index_type;
using iterator = value_type*;
using const_iterator = const value_type*;
static multi_index create(std::size_t s) {
if (s != size())
BOOST_THROW_EXCEPTION(std::invalid_argument("size does not match static size"));
return multi_index(priv_tag{});
}
template <class... Is>
multi_index(axis::index_type i, Is... is)
: multi_index(std::initializer_list<axis::index_type>{
i, static_cast<axis::index_type>(is)...}) {}
template <class... Is>
multi_index(const std::tuple<axis::index_type, Is...>& is)
: multi_index(is, mp11::make_index_sequence<(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

View File

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

View File

@ -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);
}

View File

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

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

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

View File

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