Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions include/boost/histogram/algorithm/project.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ namespace algorithm {
histogram is summed over the removed axes.
*/
template <class A, class S, unsigned N, typename... Ns>
auto project(const histogram<A, S>& h, std::integral_constant<unsigned, N>, Ns...) {
auto project(const histogram<A, S>& h, coverage cov, std::integral_constant<unsigned, N>,
Ns...) {
using LN = mp11::mp_list<std::integral_constant<unsigned, N>, Ns...>;
static_assert(mp11::mp_is_set<LN>::value, "indices must be unique");

Expand All @@ -53,22 +54,32 @@ auto project(const histogram<A, S>& h, std::integral_constant<unsigned, N>, Ns..
using A2 = decltype(axes);
auto result = histogram<A2, S>(std::move(axes), detail::make_default(old_storage));
auto idx = detail::make_stack_buffer<int>(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<LN>([&i, &x](auto J) { *i++ = x.index(J); });
result.at(idx) += *x;
}
return result;
}

/**
Returns a lower-dimensional histogram, summing over removed axes.

This overload uses coverage::all by default.
*/
template <class A, class S, unsigned N, typename... Ns>
auto project(const histogram<A, S>& h, std::integral_constant<unsigned, N> n, Ns... ns) {
return project(h, coverage::all, n, ns...);
}

/**
Returns a lower-dimensional histogram, summing over removed axes.

This version accepts a source histogram and an iterable range containing the remaining
indices.
*/
template <class A, class S, class Iterable, class = detail::requires_iterable<Iterable>>
auto project(const histogram<A, S>& h, const Iterable& c) {
auto project(const histogram<A, S>& h, coverage cov, const Iterable& c) {
using namespace boost::mp11;
const auto& old_axes = unsafe_access::axes(h);

Expand All @@ -88,7 +99,7 @@ auto project(const histogram<A, S>& h, const Iterable& c) {
auto result =
histogram<decltype(axes), S>(std::move(axes), detail::make_default(old_storage));
auto idx = detail::make_stack_buffer<int>(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;
Expand All @@ -97,6 +108,16 @@ auto project(const histogram<A, S>& h, const Iterable& c) {
return result;
}

/**
Returns a lower-dimensional histogram, summing over removed axes.

This overload uses coverage::all by default.
*/
template <class A, class S, class Iterable, class = detail::requires_iterable<Iterable>>
auto project(const histogram<A, S>& h, const Iterable& c) {
return project(h, coverage::all, c);
}

} // namespace algorithm
} // namespace histogram
} // namespace boost
Expand Down
54 changes: 54 additions & 0 deletions test/algorithm_project_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <boost/histogram/algorithm/project.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/axis/ostream.hpp>
#include <boost/histogram/literals.hpp>
#include <boost/histogram/ostream.hpp>
Expand Down Expand Up @@ -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);
}

{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Loading