Skip to content

Commit 2d9623d

Browse files
authored
Generalize indexed; allow interation over user-provided rectangular range (#321)
1 parent c1b51ad commit 2d9623d

File tree

6 files changed

+152
-20
lines changed

6 files changed

+152
-20
lines changed

doc/doxygen_postprocessing.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def is_detail(x):
2525
if x.text is not None:
2626
if "detail" in x.text:
2727
return True
28-
m = re.match("(?:typename)? *([A-Za-z0-9_\:]+)", x.text)
28+
m = re.match(r"(?:typename)? *([A-Za-z0-9_\:]+)", x.text)
2929
if m is not None:
3030
s = m.group(1)
3131
if s.startswith("detail") or s.endswith("_impl"):
@@ -122,6 +122,17 @@ def item_sorter(elem):
122122
)
123123
parent.remove(item)
124124

125+
# replace any typedef with "unspecified"
126+
for item in select(is_detail, "typedef"):
127+
log("replacing typedef", item.get("name"), 'with "unspecified"')
128+
name = item.get("name")
129+
item.clear()
130+
item.tag = "typedef"
131+
item.set("name", name)
132+
type = ET.Element("type")
133+
type.append(unspecified)
134+
item.append(type)
135+
125136
# hide private member functions
126137
for item in select(
127138
lambda x: x.get("name") == "private member functions", "method-group"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2019 Hans Dembinski
2+
//
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#ifndef BOOST_HISTOGRAM_DETAIL_DEBUG_HPP
8+
#define BOOST_HISTOGRAM_DETAIL_DEBUG_HPP
9+
10+
#warning "debug.hpp included"
11+
12+
#include <boost/histogram/detail/type_name.hpp>
13+
#include <iostream>
14+
15+
#define DEBUG(x) \
16+
std::cout << __FILE__ << ":" << __LINE__ << " [" \
17+
<< boost::histogram::detail::type_name<decltype(x)>() << "] " #x "=" << x \
18+
<< std::endl;
19+
20+
#endif

include/boost/histogram/indexed.hpp

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,27 @@
1111
#include <boost/config.hpp> // BOOST_ATTRIBUTE_NODISCARD
1212
#include <boost/histogram/axis/traits.hpp>
1313
#include <boost/histogram/detail/axes.hpp>
14+
#include <boost/histogram/detail/detect.hpp>
1415
#include <boost/histogram/detail/iterator_adaptor.hpp>
1516
#include <boost/histogram/detail/operators.hpp>
1617
#include <boost/histogram/fwd.hpp>
18+
#include <boost/histogram/unsafe_access.hpp>
1719
#include <iterator>
1820
#include <type_traits>
19-
#include <utility>
21+
#include <utility> // std::get
2022

2123
namespace boost {
2224
namespace histogram {
2325

26+
namespace detail {
27+
using std::get;
28+
29+
template <std::size_t I, class T>
30+
auto get(T&& t) -> decltype(t[0]) {
31+
return t[I];
32+
}
33+
} // namespace detail
34+
2435
/** Coverage mode of the indexed range generator.
2536
2637
Defines options for the iteration strategy.
@@ -32,8 +43,7 @@ enum class coverage {
3243

3344
/** Input iterator range over histogram bins with multi-dimensional index.
3445
35-
The iterator returned by begin() can only be incremented. begin() may only be called
36-
once, calling it a second time returns the end() iterator. If several copies of the
46+
The iterator returned by begin() can only be incremented. If several copies of the
3747
input iterators exist, the other copies become invalid if one of them is incremented.
3848
*/
3949
template <class Histogram>
@@ -44,10 +54,13 @@ class BOOST_ATTRIBUTE_NODISCARD indexed_range {
4454
detail::buffer_size<typename std::decay_t<histogram_type>::axes_type>::value;
4555

4656
public:
57+
/// implementation detail
4758
using value_iterator = std::conditional_t<std::is_const<histogram_type>::value,
4859
typename histogram_type::const_iterator,
4960
typename histogram_type::iterator>;
61+
/// implementation detail
5062
using value_reference = typename std::iterator_traits<value_iterator>::reference;
63+
/// implementation detail
5164
using value_type = typename std::iterator_traits<value_iterator>::value_type;
5265

5366
class iterator;
@@ -304,38 +317,59 @@ class BOOST_ATTRIBUTE_NODISCARD indexed_range {
304317
};
305318

306319
indexed_range(histogram_type& hist, coverage cov)
320+
: indexed_range(hist, make_range(hist, cov)) {}
321+
322+
template <class Iterable, class = detail::requires_iterable<Iterable>>
323+
indexed_range(histogram_type& hist, Iterable&& range)
307324
: begin_(hist.begin(), hist), end_(hist.end(), hist) {
308-
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), cov,
325+
auto r_begin = std::begin(range);
326+
assert(std::distance(r_begin, std::end(range)) == hist.rank());
327+
328+
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), r_begin,
309329
stride = std::size_t{1},
310330
this](const auto& a) mutable {
311-
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
312-
constexpr axis::index_type under = opt::test(axis::option::underflow);
313-
constexpr axis::index_type over = opt::test(axis::option::overflow);
314331
const auto size = a.size();
315332

316-
// -1 if underflow and cover all, else 0
317-
ca->begin = cov == coverage::all ? -under : 0;
318-
// size + 1 if overflow and cover all, else size
319-
ca->end = cov == coverage::all ? size + over : size;
333+
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
334+
constexpr axis::index_type start = opt::test(axis::option::underflow) ? -1 : 0;
335+
const auto stop = size + (opt::test(axis::option::overflow) ? 1 : 0);
336+
337+
ca->begin = std::max(start, detail::get<0>(*r_begin));
338+
ca->end = std::min(stop, detail::get<1>(*r_begin));
339+
assert(ca->begin <= ca->end);
320340
ca->idx = ca->begin;
321341

322-
// if axis has *flow and coverage::all OR axis has no *flow:
323-
// begin + under == 0, size + over - end == 0
324-
// if axis has *flow and coverage::inner:
325-
// begin + under == 1, size + over - end == 1
326-
ca->begin_skip = static_cast<std::size_t>(ca->begin + under) * stride;
327-
ca->end_skip = static_cast<std::size_t>(size + over - ca->end) * stride;
342+
ca->begin_skip = static_cast<std::size_t>(ca->begin - start) * stride;
343+
ca->end_skip = static_cast<std::size_t>(stop - ca->end) * stride;
328344
begin_.iter_ += ca->begin_skip;
329345

330-
stride *= size + under + over;
346+
stride *= stop - start;
347+
331348
++ca;
349+
++r_begin;
332350
});
333351
}
334352

335353
iterator begin() noexcept { return begin_; }
336354
iterator end() noexcept { return end_; }
337355

338356
private:
357+
auto make_range(histogram_type& hist, coverage cov) {
358+
using range_item = std::array<axis::index_type, 2>;
359+
auto b = detail::make_stack_buffer<range_item>(unsafe_access::axes(hist));
360+
hist.for_each_axis([cov, it = std::begin(b)](const auto& a) mutable {
361+
(*it)[0] = 0;
362+
(*it)[1] = a.size();
363+
if (cov == coverage::all) {
364+
(*it)[0] -= 1;
365+
(*it)[1] += 1;
366+
} else
367+
assert(cov == coverage::inner);
368+
++it;
369+
});
370+
return b;
371+
}
372+
339373
iterator begin_, end_;
340374
};
341375

@@ -366,6 +400,34 @@ auto indexed(Histogram&& hist, coverage cov = coverage::inner) {
366400
cov};
367401
}
368402

403+
/** Generates and indexed range <a
404+
href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
405+
over a rectangular region of histogram cells.
406+
407+
Use this in a range-based for loop. Example:
408+
```
409+
auto hist = make_histogram(axis::integer<>(0, 4), axis::integer<>(2, 6));
410+
axis::index_type range[2] = {{1, 3}, {0, 2}};
411+
for (auto&& x : indexed(hist, range)) { ... }
412+
```
413+
This skips the first and last index of the first axis, and the last two indices of the
414+
second.
415+
416+
@returns indexed_range
417+
418+
@param hist Reference to the histogram.
419+
@param range Iterable over items with two axis::index_type values, which mark the
420+
begin and end index of each axis. The length of the iterable must be
421+
equal to the rank of the histogram. The begin index must be smaller than
422+
the end index. Index ranges wider than the actual range are reduced to
423+
the actual range including underflow and overflow indices.
424+
*/
425+
template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
426+
auto indexed(Histogram&& hist, Iterable&& range) {
427+
return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
428+
std::forward<Iterable>(range)};
429+
}
430+
369431
} // namespace histogram
370432
} // namespace boost
371433

test/check_build_system.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
filename = os.path.join(dir, build_file)
2626
if not os.path.exists(filename):
2727
continue
28-
run = set(re.findall("([a-zA-Z0-9_]+\.cpp)", open(filename).read()))
28+
run = set(re.findall(r"([a-zA-Z0-9_]+\.cpp)", open(filename).read()))
2929

3030
diff = cpp - run
3131
diff.discard("check_cmake_version.cpp") # ignore

test/check_odr_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
for fn in files:
3232
fn = os.path.join(root, fn)
3333
fn = fn[len(root_include_path) :]
34+
if fn.endswith("debug.hpp"): # is never included
35+
continue
3436
all_headers.add(fn)
3537

3638

test/indexed_test.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,40 @@ void run_stdlib_tests(mp_list<Tag, Coverage>) {
156156
}
157157
}
158158

159+
template <class Tag>
160+
void run_indexed_with_range_tests(Tag) {
161+
{
162+
auto ax = axis::integer<>(0, 4);
163+
auto ay = axis::integer<>(0, 3);
164+
165+
auto h = make(Tag(), ax, ay);
166+
for (auto&& x : indexed(h, coverage::all)) x = x.index(0) + x.index(1);
167+
BOOST_TEST_EQ(h.at(0, 0), 0);
168+
BOOST_TEST_EQ(h.at(0, 1), 1);
169+
BOOST_TEST_EQ(h.at(0, 2), 2);
170+
BOOST_TEST_EQ(h.at(1, 0), 1);
171+
BOOST_TEST_EQ(h.at(1, 1), 2);
172+
BOOST_TEST_EQ(h.at(1, 2), 3);
173+
BOOST_TEST_EQ(h.at(2, 0), 2);
174+
BOOST_TEST_EQ(h.at(2, 1), 3);
175+
BOOST_TEST_EQ(h.at(2, 2), 4);
176+
BOOST_TEST_EQ(h.at(3, 0), 3);
177+
BOOST_TEST_EQ(h.at(3, 1), 4);
178+
BOOST_TEST_EQ(h.at(3, 2), 5);
179+
180+
/* x->
181+
y 0 1 2 3
182+
| 1 2 3 4
183+
v 2 3 4 5
184+
*/
185+
186+
int range[2][2] = {{1, 3}, {0, 2}};
187+
auto ir = indexed(h, range);
188+
auto sum = std::accumulate(ir.begin(), ir.end(), 0.0);
189+
BOOST_TEST_EQ(sum, 1 + 2 + 2 + 3);
190+
}
191+
}
192+
159193
int main() {
160194
mp_for_each<mp_product<mp_list, mp_list<static_tag, dynamic_tag>,
161195
mp_list<std::integral_constant<coverage, coverage::inner>,
@@ -166,5 +200,8 @@ int main() {
166200
run_density_tests(x);
167201
run_stdlib_tests(x);
168202
});
203+
204+
run_indexed_with_range_tests(static_tag{});
205+
run_indexed_with_range_tests(dynamic_tag{});
169206
return boost::report_errors();
170207
}

0 commit comments

Comments
 (0)