diff --git a/include/boost/histogram/algorithm/project.hpp b/include/boost/histogram/algorithm/project.hpp index 90ad8cd9..bff48f9d 100644 --- a/include/boost/histogram/algorithm/project.hpp +++ b/include/boost/histogram/algorithm/project.hpp @@ -35,7 +35,8 @@ namespace algorithm { histogram is summed over the removed axes. */ template -auto project(const histogram& h, std::integral_constant, Ns...) { +auto project(const histogram& h, coverage cov, std::integral_constant, + Ns...) { using LN = mp11::mp_list, Ns...>; static_assert(mp11::mp_is_set::value, "indices must be unique"); @@ -53,7 +54,7 @@ auto project(const histogram& h, std::integral_constant, Ns.. using A2 = decltype(axes); auto result = histogram(std::move(axes), detail::make_default(old_storage)); auto idx = detail::make_stack_buffer(unsafe_access::axes(result)); - for (auto&& x : indexed(h, coverage::all)) { + for (auto&& x : indexed(h, cov)) { auto i = idx.begin(); mp11::mp_for_each([&i, &x](auto J) { *i++ = x.index(J); }); result.at(idx) += *x; @@ -61,6 +62,16 @@ auto project(const histogram& h, std::integral_constant, Ns.. return result; } +/** + Returns a lower-dimensional histogram, summing over removed axes. + + This overload uses coverage::all by default. +*/ +template +auto project(const histogram& h, std::integral_constant n, Ns... ns) { + return project(h, coverage::all, n, ns...); +} + /** Returns a lower-dimensional histogram, summing over removed axes. @@ -68,7 +79,7 @@ auto project(const histogram& h, std::integral_constant, Ns.. indices. */ template > -auto project(const histogram& h, const Iterable& c) { +auto project(const histogram& h, coverage cov, const Iterable& c) { using namespace boost::mp11; const auto& old_axes = unsafe_access::axes(h); @@ -88,7 +99,7 @@ auto project(const histogram& h, const Iterable& c) { auto result = histogram(std::move(axes), detail::make_default(old_storage)); auto idx = detail::make_stack_buffer(unsafe_access::axes(result)); - for (auto&& x : indexed(h, coverage::all)) { + for (auto&& x : indexed(h, cov)) { auto i = idx.begin(); for (auto d : c) *i++ = x.index(d); result.at(idx) += *x; @@ -97,6 +108,16 @@ auto project(const histogram& h, const Iterable& c) { return result; } +/** + Returns a lower-dimensional histogram, summing over removed axes. + + This overload uses coverage::all by default. +*/ +template > +auto project(const histogram& h, const Iterable& c) { + return project(h, coverage::all, c); +} + } // namespace algorithm } // namespace histogram } // namespace boost diff --git a/test/algorithm_project_test.cpp b/test/algorithm_project_test.cpp index e9fd3753..88b997f7 100644 --- a/test/algorithm_project_test.cpp +++ b/test/algorithm_project_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,17 @@ void run_tests() { BOOST_TEST_EQ(hyx.at(0, 1), 1); BOOST_TEST_EQ(hyx.at(1, 1), 1); BOOST_TEST_EQ(hyx.at(2, 1), 2); + + // explicit coverage::all should match default behavior + auto hx_all = project(h, coverage::all, 0_c); + BOOST_TEST_EQ(sum(hx_all), sum(hx)); + + // coverage::inner excludes underflow/overflow; for integer axes with default + // options there are no underflow/overflow bins, so inner gives same result + auto hx_inner = project(h, coverage::inner, 0_c); + BOOST_TEST_EQ(sum(hx_inner), 6); + BOOST_TEST_EQ(hx_inner.at(0), 2); + BOOST_TEST_EQ(hx_inner.at(1), 4); } { @@ -134,6 +146,35 @@ void run_tests() { BOOST_TEST_EQ(h_210.at(2, 0, 0), 1); BOOST_TEST_EQ(h_210.at(2, 0, 1), 1); } + + { + // regular axes have underflow/overflow by default + auto h = make(Tag(), axis::regular<>(2, 0.0, 2.0), axis::regular<>(3, 0.0, 3.0)); + h(-1, -1); // underflow,underflow + h(0.5, 0.5); + h(0.5, 1.5); + h(1.5, 0.5); + h(1.5, 1.5); + h(1.5, 2.5); // overflow on second axis + h(2.5, 0.5); // overflow on first axis + + auto hx_all = project(h, coverage::all, 0_c); + BOOST_TEST_EQ(hx_all.rank(), 1); + // all bins summed: 7 entries total + BOOST_TEST_EQ(sum(hx_all), 7); + // hx has underflow + 2 bins + overflow + BOOST_TEST_EQ(hx_all.at(-1), 1); + BOOST_TEST_EQ(hx_all.at(0), 2); + BOOST_TEST_EQ(hx_all.at(1), 3); + BOOST_TEST_EQ(hx_all.at(2), 1); + + auto hx_inner = project(h, coverage::inner, 0_c); + BOOST_TEST_EQ(hx_inner.rank(), 1); + // only inner bins of original histogram are summed + BOOST_TEST_EQ(sum(hx_inner), 5); + BOOST_TEST_EQ(hx_inner.at(0), 2); + BOOST_TEST_EQ(hx_inner.at(1), 3); + } } // split out dynamic tests as workaround for compiler bug in @@ -179,6 +220,19 @@ void run_dynamic_tests() { BOOST_TEST_EQ(hyx.at(1, 1), 1); BOOST_TEST_EQ(hyx.at(2, 1), 2); + // explicit coverage::all should match default behavior + x = {0}; + auto hx_all = project(h, coverage::all, x); + BOOST_TEST_EQ(sum(hx_all), sum(hx)); + + // coverage::inner excludes underflow/overflow; for integer axes with default + // options there are no underflow/overflow bins, so inner gives same result + x = {0}; + auto hx_inner = project(h, coverage::inner, x); + BOOST_TEST_EQ(sum(hx_inner), 6); + BOOST_TEST_EQ(hx_inner.at(0), 2); + BOOST_TEST_EQ(hx_inner.at(1), 4); + // indices must be unique x = {0, 0}; BOOST_TEST_THROWS((void)project(h, x), std::invalid_argument);