diff --git a/include/boost/histogram/algorithm/reduce.hpp b/include/boost/histogram/algorithm/reduce.hpp index 8340c25b..9d45128e 100644 --- a/include/boost/histogram/algorithm/reduce.hpp +++ b/include/boost/histogram/algorithm/reduce.hpp @@ -39,20 +39,31 @@ struct reduce_option_type { operator bool() const noexcept { return merge; } }; -reduce_option_type shrink(unsigned iaxis, double lower, double upper) { - return {iaxis, lower, upper, 1}; -} - reduce_option_type shrink_and_rebin(unsigned iaxis, double lower, double upper, unsigned merge) { return {iaxis, lower, upper, merge}; } +reduce_option_type shrink(unsigned iaxis, double lower, double upper) { + return {iaxis, lower, upper, 1}; +} + reduce_option_type rebin(unsigned iaxis, unsigned merge) { return {iaxis, std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), merge}; } +/// Convenience overload for when there is only one axis. +reduce_option_type shrink_and_rebin(double lower, double upper, unsigned merge) { + return shrink_and_rebin(0, lower, upper, merge); +} + +/// Convenience overload for when there is only one axis. +reduce_option_type shrink(double lower, double upper) { return shrink(0, lower, upper); } + +/// Convenience overload for when there is only one axis. +reduce_option_type rebin(unsigned merge) { return rebin(0, merge); } + template > histogram reduce(const histogram& h, const C& c) { auto options = diff --git a/include/boost/histogram/axis/variable.hpp b/include/boost/histogram/axis/variable.hpp index f6871e83..53f879ae 100644 --- a/include/boost/histogram/axis/variable.hpp +++ b/include/boost/histogram/axis/variable.hpp @@ -107,6 +107,25 @@ public: allocator_type a = allocator_type()) : variable(l.begin(), l.end(), std::move(m), o, std::move(a)) {} + /// Constructor used by algorithm::reduce to shrink and rebin (not for users). + variable(const variable& src, unsigned begin, unsigned end, unsigned merge) + : base_type((end - begin) / merge, src.metadata(), src.options()), x_(src.x_) { + BOOST_ASSERT((end - begin) % merge == 0); + using It = const detail::unqual*; + struct skip_iterator { + It it; + unsigned skip; + skip_iterator operator++(int) { + auto tmp = *this; + it += skip; + return tmp; + } + decltype(auto) operator*() { return *it; } + bool operator==(const skip_iterator& rhs) const { return it == rhs.it; } + } iter{src.x_.first() + begin, merge}; + x_.first() = detail::create_buffer_from_iter(x_.second(), nx(), iter); + } + variable() : x_(nullptr) {} variable(const variable& o) : base_type(o), x_(o.x_) { diff --git a/test/algorithm_reduce_test.cpp b/test/algorithm_reduce_test.cpp index bb75dd84..dc84cbe9 100644 --- a/test/algorithm_reduce_test.cpp +++ b/test/algorithm_reduce_test.cpp @@ -114,33 +114,49 @@ void run_tests() { BOOST_TEST_EQ(hr2, hr); } - // rebin on integer axis must fail + // reduce on integer axis, rebin must fail { auto h = make(Tag(), axis::integer<>(1, 4)); - BOOST_TEST_THROWS(reduce(h, rebin(0, 2)), std::invalid_argument); + BOOST_TEST_THROWS(reduce(h, rebin(2)), std::invalid_argument); + auto hr = reduce(h, shrink(2, 3)); + BOOST_TEST_EQ(hr.axis().size(), 1); + BOOST_TEST_EQ(hr.axis()[0].lower(), 2); + BOOST_TEST_EQ(hr.axis()[0].upper(), 3); } // reduce on circular axis, shrink must fail, also rebin with remainder { auto h = make(Tag(), axis::circular<>(4, 1, 4)); - BOOST_TEST_THROWS(reduce(h, shrink(0, 0, 2)), std::invalid_argument); - BOOST_TEST_THROWS(reduce(h, rebin(0, 3)), std::invalid_argument); - auto hr = reduce(h, rebin(0, 2)); + BOOST_TEST_THROWS(reduce(h, shrink(0, 2)), std::invalid_argument); + BOOST_TEST_THROWS(reduce(h, rebin(3)), std::invalid_argument); + auto hr = reduce(h, rebin(2)); BOOST_TEST_EQ(hr.axis().size(), 2); BOOST_TEST_EQ(hr.axis()[0].lower(), 1); BOOST_TEST_EQ(hr.axis()[1].upper(), 5); } + // reduce on variable axis + { + auto h = make(Tag(), axis::variable<>({0, 1, 2, 3, 4, 5, 6})); + auto hr = reduce(h, shrink_and_rebin(1, 5, 2)); + BOOST_TEST_EQ(hr.axis().size(), 2); + BOOST_TEST_EQ(hr.axis().value(0), 1); + BOOST_TEST_EQ(hr.axis().value(1), 3); + BOOST_TEST_EQ(hr.axis().value(2), 5); + } + // reduce on axis with inverted range { auto h = make(Tag(), regular(4, 2, -2)); - auto hr = reduce(h, shrink(0, 1, -1)); + auto hr = reduce(h, shrink(1, -1)); BOOST_TEST_EQ(hr.axis().size(), 2); BOOST_TEST_EQ(hr.axis()[0].lower(), 1); BOOST_TEST_EQ(hr.axis()[1].upper(), -1); } - // reduce does not work with arguments not convertible to double + // reduce: + // - does not work with arguments not convertible to double + // - does not work with category axis, which is not ordered } int main() { diff --git a/test/axis_variable_test.cpp b/test/axis_variable_test.cpp index 38e36634..aa693a64 100644 --- a/test/axis_variable_test.cpp +++ b/test/axis_variable_test.cpp @@ -53,5 +53,23 @@ int main() { // iterators { test_axis_iterator(axis::variable<>({1, 2, 3}, ""), 0, 2); } + // shrink and rebin + { + using A = axis::variable<>; + auto a = A({0, 1, 2, 3, 4, 5}); + auto b = A(a, 1, 4, 1); + BOOST_TEST_EQ(b.size(), 3); + BOOST_TEST_EQ(b.value(0), 1); + BOOST_TEST_EQ(b.value(3), 4); + auto c = A(a, 0, 4, 2); + BOOST_TEST_EQ(c.size(), 2); + BOOST_TEST_EQ(c.value(0), 0); + BOOST_TEST_EQ(c.value(2), 4); + auto e = A(a, 1, 5, 2); + BOOST_TEST_EQ(e.size(), 2); + BOOST_TEST_EQ(e.value(0), 1); + BOOST_TEST_EQ(e.value(2), 5); + } + return boost::report_errors(); }