Generalize indexed; allow interation over user-provided rectangular range (#321)

This commit is contained in:
Hans Dembinski
2021-04-24 22:37:42 +02:00
committed by GitHub
parent c1b51ad640
commit 2d9623d7ff
6 changed files with 152 additions and 20 deletions

View File

@@ -25,7 +25,7 @@ def is_detail(x):
if x.text is not None:
if "detail" in x.text:
return True
m = re.match("(?:typename)? *([A-Za-z0-9_\:]+)", x.text)
m = re.match(r"(?:typename)? *([A-Za-z0-9_\:]+)", x.text)
if m is not None:
s = m.group(1)
if s.startswith("detail") or s.endswith("_impl"):
@@ -122,6 +122,17 @@ for item in select(is_deprecated, "typedef"):
)
parent.remove(item)
# replace any typedef with "unspecified"
for item in select(is_detail, "typedef"):
log("replacing typedef", item.get("name"), 'with "unspecified"')
name = item.get("name")
item.clear()
item.tag = "typedef"
item.set("name", name)
type = ET.Element("type")
type.append(unspecified)
item.append(type)
# hide private member functions
for item in select(
lambda x: x.get("name") == "private member functions", "method-group"

View File

@@ -0,0 +1,20 @@
// 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_DEBUG_HPP
#define BOOST_HISTOGRAM_DETAIL_DEBUG_HPP
#warning "debug.hpp included"
#include <boost/histogram/detail/type_name.hpp>
#include <iostream>
#define DEBUG(x) \
std::cout << __FILE__ << ":" << __LINE__ << " [" \
<< boost::histogram::detail::type_name<decltype(x)>() << "] " #x "=" << x \
<< std::endl;
#endif

View File

@@ -11,16 +11,27 @@
#include <boost/config.hpp> // BOOST_ATTRIBUTE_NODISCARD
#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>
#include <boost/histogram/detail/operators.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <iterator>
#include <type_traits>
#include <utility>
#include <utility> // std::get
namespace boost {
namespace histogram {
namespace detail {
using std::get;
template <std::size_t I, class T>
auto get(T&& t) -> decltype(t[0]) {
return t[I];
}
} // namespace detail
/** Coverage mode of the indexed range generator.
Defines options for the iteration strategy.
@@ -32,8 +43,7 @@ enum class coverage {
/** Input iterator range over histogram bins with multi-dimensional index.
The iterator returned by begin() can only be incremented. begin() may only be called
once, calling it a second time returns the end() iterator. If several copies of the
The iterator returned by begin() can only be incremented. If several copies of the
input iterators exist, the other copies become invalid if one of them is incremented.
*/
template <class Histogram>
@@ -44,10 +54,13 @@ private:
detail::buffer_size<typename std::decay_t<histogram_type>::axes_type>::value;
public:
/// implementation detail
using value_iterator = std::conditional_t<std::is_const<histogram_type>::value,
typename histogram_type::const_iterator,
typename histogram_type::iterator>;
/// implementation detail
using value_reference = typename std::iterator_traits<value_iterator>::reference;
/// implementation detail
using value_type = typename std::iterator_traits<value_iterator>::value_type;
class iterator;
@@ -304,31 +317,36 @@ public:
};
indexed_range(histogram_type& hist, coverage cov)
: indexed_range(hist, make_range(hist, cov)) {}
template <class Iterable, class = detail::requires_iterable<Iterable>>
indexed_range(histogram_type& hist, Iterable&& range)
: begin_(hist.begin(), hist), end_(hist.end(), hist) {
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), cov,
auto r_begin = std::begin(range);
assert(std::distance(r_begin, std::end(range)) == hist.rank());
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), r_begin,
stride = std::size_t{1},
this](const auto& a) mutable {
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
constexpr axis::index_type under = opt::test(axis::option::underflow);
constexpr axis::index_type over = opt::test(axis::option::overflow);
const auto size = a.size();
// -1 if underflow and cover all, else 0
ca->begin = cov == coverage::all ? -under : 0;
// size + 1 if overflow and cover all, else size
ca->end = cov == coverage::all ? size + over : size;
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
constexpr axis::index_type start = opt::test(axis::option::underflow) ? -1 : 0;
const auto stop = size + (opt::test(axis::option::overflow) ? 1 : 0);
ca->begin = std::max(start, detail::get<0>(*r_begin));
ca->end = std::min(stop, detail::get<1>(*r_begin));
assert(ca->begin <= ca->end);
ca->idx = ca->begin;
// if axis has *flow and coverage::all OR axis has no *flow:
// begin + under == 0, size + over - end == 0
// if axis has *flow and coverage::inner:
// begin + under == 1, size + over - end == 1
ca->begin_skip = static_cast<std::size_t>(ca->begin + under) * stride;
ca->end_skip = static_cast<std::size_t>(size + over - ca->end) * stride;
ca->begin_skip = static_cast<std::size_t>(ca->begin - start) * stride;
ca->end_skip = static_cast<std::size_t>(stop - ca->end) * stride;
begin_.iter_ += ca->begin_skip;
stride *= size + under + over;
stride *= stop - start;
++ca;
++r_begin;
});
}
@@ -336,6 +354,22 @@ public:
iterator end() noexcept { return end_; }
private:
auto make_range(histogram_type& hist, coverage cov) {
using range_item = std::array<axis::index_type, 2>;
auto b = detail::make_stack_buffer<range_item>(unsafe_access::axes(hist));
hist.for_each_axis([cov, it = std::begin(b)](const auto& a) mutable {
(*it)[0] = 0;
(*it)[1] = a.size();
if (cov == coverage::all) {
(*it)[0] -= 1;
(*it)[1] += 1;
} else
assert(cov == coverage::inner);
++it;
});
return b;
}
iterator begin_, end_;
};
@@ -366,6 +400,34 @@ auto indexed(Histogram&& hist, coverage cov = coverage::inner) {
cov};
}
/** Generates and indexed range <a
href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
over a rectangular region of histogram cells.
Use this in a range-based for loop. Example:
```
auto hist = make_histogram(axis::integer<>(0, 4), axis::integer<>(2, 6));
axis::index_type range[2] = {{1, 3}, {0, 2}};
for (auto&& x : indexed(hist, range)) { ... }
```
This skips the first and last index of the first axis, and the last two indices of the
second.
@returns indexed_range
@param hist Reference to the histogram.
@param range Iterable over items with two axis::index_type values, which mark the
begin and end index of each axis. The length of the iterable must be
equal to the rank of the histogram. The begin index must be smaller than
the end index. Index ranges wider than the actual range are reduced to
the actual range including underflow and overflow indices.
*/
template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
auto indexed(Histogram&& hist, Iterable&& range) {
return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
std::forward<Iterable>(range)};
}
} // namespace histogram
} // namespace boost

View File

@@ -25,7 +25,7 @@ for dir in (pj(project_path, "test"), pj(project_path, "examples")):
filename = os.path.join(dir, build_file)
if not os.path.exists(filename):
continue
run = set(re.findall("([a-zA-Z0-9_]+\.cpp)", open(filename).read()))
run = set(re.findall(r"([a-zA-Z0-9_]+\.cpp)", open(filename).read()))
diff = cpp - run
diff.discard("check_cmake_version.cpp") # ignore

View File

@@ -31,6 +31,8 @@ for root, dirs, files in os.walk(include_path):
for fn in files:
fn = os.path.join(root, fn)
fn = fn[len(root_include_path) :]
if fn.endswith("debug.hpp"): # is never included
continue
all_headers.add(fn)

View File

@@ -156,6 +156,40 @@ void run_stdlib_tests(mp_list<Tag, Coverage>) {
}
}
template <class Tag>
void run_indexed_with_range_tests(Tag) {
{
auto ax = axis::integer<>(0, 4);
auto ay = axis::integer<>(0, 3);
auto h = make(Tag(), ax, ay);
for (auto&& x : indexed(h, coverage::all)) x = x.index(0) + x.index(1);
BOOST_TEST_EQ(h.at(0, 0), 0);
BOOST_TEST_EQ(h.at(0, 1), 1);
BOOST_TEST_EQ(h.at(0, 2), 2);
BOOST_TEST_EQ(h.at(1, 0), 1);
BOOST_TEST_EQ(h.at(1, 1), 2);
BOOST_TEST_EQ(h.at(1, 2), 3);
BOOST_TEST_EQ(h.at(2, 0), 2);
BOOST_TEST_EQ(h.at(2, 1), 3);
BOOST_TEST_EQ(h.at(2, 2), 4);
BOOST_TEST_EQ(h.at(3, 0), 3);
BOOST_TEST_EQ(h.at(3, 1), 4);
BOOST_TEST_EQ(h.at(3, 2), 5);
/* x->
y 0 1 2 3
| 1 2 3 4
v 2 3 4 5
*/
int range[2][2] = {{1, 3}, {0, 2}};
auto ir = indexed(h, range);
auto sum = std::accumulate(ir.begin(), ir.end(), 0.0);
BOOST_TEST_EQ(sum, 1 + 2 + 2 + 3);
}
}
int main() {
mp_for_each<mp_product<mp_list, mp_list<static_tag, dynamic_tag>,
mp_list<std::integral_constant<coverage, coverage::inner>,
@@ -166,5 +200,8 @@ int main() {
run_density_tests(x);
run_stdlib_tests(x);
});
run_indexed_with_range_tests(static_tag{});
run_indexed_with_range_tests(dynamic_tag{});
return boost::report_errors();
}