indexed.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // Copyright 2015-2016 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. #ifndef BOOST_HISTOGRAM_INDEXED_HPP
  7. #define BOOST_HISTOGRAM_INDEXED_HPP
  8. #include <array>
  9. #include <boost/config.hpp> // BOOST_ATTRIBUTE_NODISCARD
  10. #include <boost/histogram/axis/traits.hpp>
  11. #include <boost/histogram/detail/axes.hpp>
  12. #include <boost/histogram/detail/detect.hpp>
  13. #include <boost/histogram/detail/iterator_adaptor.hpp>
  14. #include <boost/histogram/detail/operators.hpp>
  15. #include <boost/histogram/fwd.hpp>
  16. #include <boost/histogram/unsafe_access.hpp>
  17. #include <iterator>
  18. #include <type_traits>
  19. #include <utility> // std::get
  20. namespace boost {
  21. namespace histogram {
  22. namespace detail {
  23. using std::get;
  24. template <std::size_t I, class T>
  25. auto get(T&& t) -> decltype(t[0]) {
  26. return t[I];
  27. }
  28. } // namespace detail
  29. /** Coverage mode of the indexed range generator.
  30. Defines options for the iteration strategy.
  31. */
  32. enum class coverage {
  33. inner, /*!< iterate over inner bins, exclude underflow and overflow */
  34. all, /*!< iterate over all bins, including underflow and overflow */
  35. };
  36. /** Input iterator range over histogram bins with multi-dimensional index.
  37. The iterator returned by begin() can only be incremented. If several copies of the
  38. input iterators exist, the other copies become invalid if one of them is incremented.
  39. */
  40. template <class Histogram>
  41. class BOOST_ATTRIBUTE_NODISCARD indexed_range {
  42. private:
  43. using histogram_type = Histogram;
  44. static constexpr unsigned buffer_size =
  45. detail::buffer_size<typename std::decay_t<histogram_type>::axes_type>::value;
  46. public:
  47. /// implementation detail
  48. using value_iterator = std::conditional_t<std::is_const<histogram_type>::value,
  49. typename histogram_type::const_iterator,
  50. typename histogram_type::iterator>;
  51. /// implementation detail
  52. using value_reference = typename std::iterator_traits<value_iterator>::reference;
  53. /// implementation detail
  54. using value_type = typename std::iterator_traits<value_iterator>::value_type;
  55. class iterator;
  56. /** Lightweight view to access value and index of current cell.
  57. The methods provide access to the current cell indices and bins. It acts like a
  58. pointer to the cell value, and in a limited way also like a reference. To interoperate
  59. with the algorithms of the standard library, the accessor is implicitly convertible to
  60. a cell value. Assignments and comparisons are passed through to the cell. An accessor
  61. is coupled to its parent indexed_range::iterator. Moving the parent iterator
  62. forward also updates the linked accessor. Accessors are not copyable. They cannot be
  63. stored in containers, but indexed_range::iterator can be stored.
  64. */
  65. class BOOST_ATTRIBUTE_NODISCARD accessor : detail::mirrored<accessor, void> {
  66. public:
  67. /// Array-like view into the current multi-dimensional index.
  68. class index_view {
  69. using index_pointer = const typename iterator::index_data*;
  70. public:
  71. using const_reference = const axis::index_type&;
  72. /// implementation detail
  73. class const_iterator
  74. : public detail::iterator_adaptor<const_iterator, index_pointer,
  75. const_reference> {
  76. public:
  77. const_reference operator*() const noexcept { return const_iterator::base()->idx; }
  78. private:
  79. explicit const_iterator(index_pointer i) noexcept
  80. : const_iterator::iterator_adaptor_(i) {}
  81. friend class index_view;
  82. };
  83. const_iterator begin() const noexcept { return const_iterator{begin_}; }
  84. const_iterator end() const noexcept { return const_iterator{end_}; }
  85. std::size_t size() const noexcept {
  86. return static_cast<std::size_t>(end_ - begin_);
  87. }
  88. const_reference operator[](unsigned d) const noexcept { return begin_[d].idx; }
  89. const_reference at(unsigned d) const { return begin_[d].idx; }
  90. private:
  91. /// implementation detail
  92. index_view(index_pointer b, index_pointer e) : begin_(b), end_(e) {}
  93. index_pointer begin_, end_;
  94. friend class accessor;
  95. };
  96. // assignment is pass-through
  97. accessor& operator=(const accessor& o) {
  98. get() = o.get();
  99. return *this;
  100. }
  101. // assignment is pass-through
  102. template <class T>
  103. accessor& operator=(const T& x) {
  104. get() = x;
  105. return *this;
  106. }
  107. /// Returns the cell reference.
  108. value_reference get() const noexcept { return *(iter_.iter_); }
  109. /// @copydoc get()
  110. value_reference operator*() const noexcept { return get(); }
  111. /// Access fields and methods of the cell object.
  112. value_iterator operator->() const noexcept { return iter_.iter_; }
  113. /// Access current index.
  114. /// @param d axis dimension.
  115. axis::index_type index(unsigned d = 0) const noexcept {
  116. return iter_.indices_[d].idx;
  117. }
  118. /// Access indices as an iterable range.
  119. index_view indices() const noexcept {
  120. assert(iter_.indices_.hist_);
  121. return {iter_.indices_.begin(), iter_.indices_.end()};
  122. }
  123. /// Access current bin.
  124. /// @tparam N axis dimension.
  125. template <unsigned N = 0>
  126. decltype(auto) bin(std::integral_constant<unsigned, N> = {}) const {
  127. assert(iter_.indices_.hist_);
  128. return iter_.indices_.hist_->axis(std::integral_constant<unsigned, N>())
  129. .bin(index(N));
  130. }
  131. /// Access current bin.
  132. /// @param d axis dimension.
  133. decltype(auto) bin(unsigned d) const {
  134. assert(iter_.indices_.hist_);
  135. return iter_.indices_.hist_->axis(d).bin(index(d));
  136. }
  137. /** Computes density in current cell.
  138. The density is computed as the cell value divided by the product of bin widths. Axes
  139. without bin widths, like axis::category, are treated as having unit bin with.
  140. */
  141. double density() const {
  142. assert(iter_.indices_.hist_);
  143. double x = 1;
  144. unsigned d = 0;
  145. iter_.indices_.hist_->for_each_axis([&](const auto& a) {
  146. const auto w = axis::traits::width_as<double>(a, this->index(d++));
  147. x *= w != 0 ? w : 1;
  148. });
  149. return get() / x;
  150. }
  151. // forward all comparison operators to the value
  152. bool operator<(const accessor& o) noexcept { return get() < o.get(); }
  153. bool operator>(const accessor& o) noexcept { return get() > o.get(); }
  154. bool operator==(const accessor& o) noexcept { return get() == o.get(); }
  155. bool operator!=(const accessor& o) noexcept { return get() != o.get(); }
  156. bool operator<=(const accessor& o) noexcept { return get() <= o.get(); }
  157. bool operator>=(const accessor& o) noexcept { return get() >= o.get(); }
  158. template <class U>
  159. bool operator<(const U& o) const noexcept {
  160. return get() < o;
  161. }
  162. template <class U>
  163. bool operator>(const U& o) const noexcept {
  164. return get() > o;
  165. }
  166. template <class U>
  167. bool operator==(const U& o) const noexcept {
  168. return get() == o;
  169. }
  170. template <class U>
  171. bool operator!=(const U& o) const noexcept {
  172. return get() != o;
  173. }
  174. template <class U>
  175. bool operator<=(const U& o) const noexcept {
  176. return get() <= o;
  177. }
  178. template <class U>
  179. bool operator>=(const U& o) const noexcept {
  180. return get() >= o;
  181. }
  182. operator value_type() const noexcept { return get(); }
  183. private:
  184. accessor(iterator& i) noexcept : iter_(i) {}
  185. accessor(const accessor&) = default; // only callable by indexed_range::iterator
  186. iterator& iter_;
  187. friend class iterator;
  188. };
  189. /// implementation detail
  190. class iterator {
  191. public:
  192. using value_type = typename indexed_range::value_type;
  193. using reference = accessor;
  194. private:
  195. struct pointer_proxy {
  196. reference* operator->() noexcept { return std::addressof(ref_); }
  197. reference ref_;
  198. };
  199. public:
  200. using pointer = pointer_proxy;
  201. using difference_type = std::ptrdiff_t;
  202. using iterator_category = std::forward_iterator_tag;
  203. reference operator*() noexcept { return *this; }
  204. pointer operator->() noexcept { return pointer_proxy{operator*()}; }
  205. iterator& operator++() {
  206. assert(iter_ < indices_.hist_->end());
  207. const auto cbeg = indices_.begin();
  208. auto c = cbeg;
  209. ++iter_;
  210. ++c->idx;
  211. if (c->idx < c->end) return *this;
  212. while (c->idx == c->end) {
  213. iter_ += c->end_skip;
  214. if (++c == indices_.end()) return *this;
  215. ++c->idx;
  216. }
  217. while (c-- != cbeg) {
  218. c->idx = c->begin;
  219. iter_ += c->begin_skip;
  220. }
  221. return *this;
  222. }
  223. iterator operator++(int) {
  224. auto prev = *this;
  225. operator++();
  226. return prev;
  227. }
  228. bool operator==(const iterator& x) const noexcept { return iter_ == x.iter_; }
  229. bool operator!=(const iterator& x) const noexcept { return !operator==(x); }
  230. // make iterator ready for C++17 sentinels
  231. bool operator==(const value_iterator& x) const noexcept { return iter_ == x; }
  232. bool operator!=(const value_iterator& x) const noexcept { return !operator==(x); }
  233. // useful for iterator debugging
  234. std::size_t offset() const noexcept { return iter_ - indices_.hist_->begin(); }
  235. private:
  236. iterator(value_iterator i, histogram_type& h) : iter_(i), indices_(&h) {}
  237. value_iterator iter_; // original histogram iterator
  238. struct index_data {
  239. axis::index_type idx, begin, end;
  240. std::size_t begin_skip, end_skip;
  241. };
  242. struct indices_t : private std::array<index_data, buffer_size> {
  243. using base_type = std::array<index_data, buffer_size>;
  244. using pointer = index_data*;
  245. using const_pointer = const index_data*;
  246. indices_t(histogram_type* h) noexcept : hist_{h} {}
  247. using base_type::operator[];
  248. unsigned size() const noexcept { return hist_->rank(); }
  249. pointer begin() noexcept { return base_type::data(); }
  250. const_pointer begin() const noexcept { return base_type::data(); }
  251. pointer end() noexcept { return begin() + size(); }
  252. const_pointer end() const noexcept { return begin() + size(); }
  253. histogram_type* hist_;
  254. } indices_;
  255. friend class indexed_range;
  256. };
  257. indexed_range(histogram_type& hist, coverage cov)
  258. : indexed_range(hist, make_range(hist, cov)) {}
  259. template <class Iterable, class = detail::requires_iterable<Iterable>>
  260. indexed_range(histogram_type& hist, Iterable&& range)
  261. : begin_(hist.begin(), hist), end_(hist.end(), hist) {
  262. // if histogram is empty, incrementing begin_.iter_ may be undefined behavior
  263. if (begin_ == end_) return;
  264. auto r_begin = std::begin(range);
  265. assert(std::distance(r_begin, std::end(range)) == static_cast<int>(hist.rank()));
  266. begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), r_begin,
  267. stride = std::size_t{1},
  268. this](const auto& a) mutable {
  269. const auto size = a.size();
  270. using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
  271. constexpr axis::index_type start = opt::test(axis::option::underflow) ? -1 : 0;
  272. const auto stop = size + (opt::test(axis::option::overflow) ? 1 : 0);
  273. ca->begin = (std::max)(start, detail::get<0>(*r_begin));
  274. ca->end = (std::min)(stop, detail::get<1>(*r_begin));
  275. assert(ca->begin <= ca->end);
  276. ca->idx = ca->begin;
  277. ca->begin_skip = static_cast<std::size_t>(ca->begin - start) * stride;
  278. ca->end_skip = static_cast<std::size_t>(stop - ca->end) * stride;
  279. begin_.iter_ += ca->begin_skip;
  280. end_.iter_ -= ca->end_skip;
  281. stride *= stop - start;
  282. ++ca;
  283. ++r_begin;
  284. });
  285. // check if selected range is empty
  286. if (end_.iter_ < begin_.iter_) {
  287. begin_ = end_;
  288. } else {
  289. // reset end_ to hist.end(), since end_skips are done in operator++
  290. end_.iter_ = hist.end();
  291. }
  292. }
  293. iterator begin() noexcept { return begin_; }
  294. iterator end() noexcept { return end_; }
  295. private:
  296. auto make_range(histogram_type& hist, coverage cov) {
  297. using range_item = std::array<axis::index_type, 2>;
  298. auto b = detail::make_stack_buffer<range_item>(unsafe_access::axes(hist));
  299. hist.for_each_axis([cov, it = std::begin(b)](const auto& a) mutable {
  300. (*it)[0] = 0;
  301. (*it)[1] = a.size();
  302. if (cov == coverage::all) {
  303. (*it)[0] -= 1; // making this wider than actual range is safe
  304. (*it)[1] += 1; // making this wider than actual range is safe
  305. } else
  306. assert(cov == coverage::inner);
  307. ++it;
  308. });
  309. return b;
  310. }
  311. iterator begin_, end_;
  312. };
  313. /** Generates an indexed range of <a
  314. href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
  315. over the histogram cells.
  316. Use this in a range-based for loop:
  317. ```
  318. for (auto&& x : indexed(hist)) { ... }
  319. ```
  320. This generates an optimized loop which is nearly always faster than a hand-written loop
  321. over the histogram cells. The iterators dereference to an indexed_range::accessor, which
  322. has methods to query the current indices and bins and acts like a pointer to the cell
  323. value. The returned iterators are forward iterators. They can be stored in a container,
  324. but may not be used after the life-time of the histogram ends.
  325. @returns indexed_range
  326. @param hist Reference to the histogram.
  327. @param cov Iterate over all or only inner bins (optional, default: inner).
  328. */
  329. template <class Histogram>
  330. auto indexed(Histogram&& hist, coverage cov = coverage::inner) {
  331. return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
  332. cov};
  333. }
  334. /** Generates and indexed range <a
  335. href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
  336. over a rectangular region of histogram cells.
  337. Use this in a range-based for loop. Example:
  338. ```
  339. auto hist = make_histogram(axis::integer<>(0, 4), axis::integer<>(2, 6));
  340. axis::index_type range[2] = {{1, 3}, {0, 2}};
  341. for (auto&& x : indexed(hist, range)) { ... }
  342. ```
  343. This skips the first and last index of the first axis, and the last two indices of the
  344. second.
  345. @returns indexed_range
  346. @param hist Reference to the histogram.
  347. @param range Iterable over items with two axis::index_type values, which mark the
  348. begin and end index of each axis. The length of the iterable must be
  349. equal to the rank of the histogram. The begin index must be smaller than
  350. the end index. Index ranges wider than the actual range are reduced to
  351. the actual range including underflow and overflow indices.
  352. */
  353. template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
  354. auto indexed(Histogram&& hist, Iterable&& range) {
  355. return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
  356. std::forward<Iterable>(range)};
  357. }
  358. } // namespace histogram
  359. } // namespace boost
  360. #endif