• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
7 #ifndef BOOST_HISTOGRAM_INDEXED_HPP
8 #define BOOST_HISTOGRAM_INDEXED_HPP
9 
10 #include <array>
11 #include <boost/config.hpp> // BOOST_ATTRIBUTE_NODISCARD
12 #include <boost/histogram/axis/traits.hpp>
13 #include <boost/histogram/detail/axes.hpp>
14 #include <boost/histogram/detail/iterator_adaptor.hpp>
15 #include <boost/histogram/detail/operators.hpp>
16 #include <boost/histogram/fwd.hpp>
17 #include <iterator>
18 #include <type_traits>
19 #include <utility>
20 
21 namespace boost {
22 namespace histogram {
23 
24 /** Coverage mode of the indexed range generator.
25 
26   Defines options for the iteration strategy.
27 */
28 enum class coverage {
29   inner, /*!< iterate over inner bins, exclude underflow and overflow */
30   all,   /*!< iterate over all bins, including underflow and overflow */
31 };
32 
33 /** Input iterator range over histogram bins with multi-dimensional index.
34 
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
37   input iterators exist, the other copies become invalid if one of them is incremented.
38 */
39 template <class Histogram>
40 class BOOST_ATTRIBUTE_NODISCARD indexed_range {
41 private:
42   using histogram_type = Histogram;
43   static constexpr unsigned buffer_size =
44       detail::buffer_size<typename std::decay_t<histogram_type>::axes_type>::value;
45 
46 public:
47   using value_iterator = std::conditional_t<std::is_const<histogram_type>::value,
48                                             typename histogram_type::const_iterator,
49                                             typename histogram_type::iterator>;
50   using value_reference = typename std::iterator_traits<value_iterator>::reference;
51   using value_type = typename std::iterator_traits<value_iterator>::value_type;
52 
53   class iterator;
54   using range_iterator [[deprecated("use iterator instead")]] = iterator; ///< deprecated
55 
56   /** Lightweight view to access value and index of current cell.
57 
58     The methods provide access to the current cell indices and bins. It acts like a
59     pointer to the cell value, and in a limited way also like a reference. To interoperate
60     with the algorithms of the standard library, the accessor is implicitly convertible to
61     a cell value. Assignments and comparisons are passed through to the cell. An accessor
62     is coupled to its parent indexed_range::iterator. Moving the parent iterator
63     forward also updates the linked accessor. Accessors are not copyable. They cannot be
64     stored in containers, but indexed_range::iterator can be stored.
65   */
66   class BOOST_ATTRIBUTE_NODISCARD accessor : detail::mirrored<accessor, void> {
67   public:
68     /// Array-like view into the current multi-dimensional index.
69     class index_view {
70       using index_pointer = const typename iterator::index_data*;
71 
72     public:
73       using const_reference = const axis::index_type&;
74       using reference [[deprecated("use const_reference instead")]] =
75           const_reference; ///< deprecated
76 
77       /// implementation detail
78       class const_iterator
79           : public detail::iterator_adaptor<const_iterator, index_pointer,
80                                             const_reference> {
81       public:
operator *() const82         const_reference operator*() const noexcept { return const_iterator::base()->idx; }
83 
84       private:
const_iterator(index_pointer i)85         explicit const_iterator(index_pointer i) noexcept
86             : const_iterator::iterator_adaptor_(i) {}
87 
88         friend class index_view;
89       };
90 
begin() const91       const_iterator begin() const noexcept { return const_iterator{begin_}; }
end() const92       const_iterator end() const noexcept { return const_iterator{end_}; }
size() const93       std::size_t size() const noexcept {
94         return static_cast<std::size_t>(end_ - begin_);
95       }
operator [](unsigned d) const96       const_reference operator[](unsigned d) const noexcept { return begin_[d].idx; }
at(unsigned d) const97       const_reference at(unsigned d) const { return begin_[d].idx; }
98 
99     private:
100       /// implementation detail
index_view(index_pointer b,index_pointer e)101       index_view(index_pointer b, index_pointer e) : begin_(b), end_(e) {}
102 
103       index_pointer begin_, end_;
104       friend class accessor;
105     };
106 
107     // assignment is pass-through
operator =(const accessor & o)108     accessor& operator=(const accessor& o) {
109       get() = o.get();
110       return *this;
111     }
112 
113     // assignment is pass-through
114     template <class T>
operator =(const T & x)115     accessor& operator=(const T& x) {
116       get() = x;
117       return *this;
118     }
119 
120     /// Returns the cell reference.
get() const121     value_reference get() const noexcept { return *(iter_.iter_); }
122     /// @copydoc get()
operator *() const123     value_reference operator*() const noexcept { return get(); }
124     /// Access fields and methods of the cell object.
operator ->() const125     value_iterator operator->() const noexcept { return iter_.iter_; }
126 
127     /// Access current index.
128     /// @param d axis dimension.
index(unsigned d=0) const129     axis::index_type index(unsigned d = 0) const noexcept {
130       return iter_.indices_[d].idx;
131     }
132 
133     /// Access indices as an iterable range.
indices() const134     index_view indices() const noexcept {
135       assert(iter_.indices_.hist_);
136       return {iter_.indices_.begin(), iter_.indices_.end()};
137     }
138 
139     /// Access current bin.
140     /// @tparam N axis dimension.
141     template <unsigned N = 0>
bin(std::integral_constant<unsigned,N>={}) const142     decltype(auto) bin(std::integral_constant<unsigned, N> = {}) const {
143       assert(iter_.indices_.hist_);
144       return iter_.indices_.hist_->axis(std::integral_constant<unsigned, N>())
145           .bin(index(N));
146     }
147 
148     /// Access current bin.
149     /// @param d axis dimension.
bin(unsigned d) const150     decltype(auto) bin(unsigned d) const {
151       assert(iter_.indices_.hist_);
152       return iter_.indices_.hist_->axis(d).bin(index(d));
153     }
154 
155     /** Computes density in current cell.
156 
157       The density is computed as the cell value divided by the product of bin widths. Axes
158       without bin widths, like axis::category, are treated as having unit bin with.
159     */
density() const160     double density() const {
161       assert(iter_.indices_.hist_);
162       double x = 1;
163       unsigned d = 0;
164       iter_.indices_.hist_->for_each_axis([&](const auto& a) {
165         const auto w = axis::traits::width_as<double>(a, this->index(d++));
166         x *= w != 0 ? w : 1;
167       });
168       return get() / x;
169     }
170 
171     // forward all comparison operators to the value
operator <(const accessor & o)172     bool operator<(const accessor& o) noexcept { return get() < o.get(); }
operator >(const accessor & o)173     bool operator>(const accessor& o) noexcept { return get() > o.get(); }
operator ==(const accessor & o)174     bool operator==(const accessor& o) noexcept { return get() == o.get(); }
operator !=(const accessor & o)175     bool operator!=(const accessor& o) noexcept { return get() != o.get(); }
operator <=(const accessor & o)176     bool operator<=(const accessor& o) noexcept { return get() <= o.get(); }
operator >=(const accessor & o)177     bool operator>=(const accessor& o) noexcept { return get() >= o.get(); }
178 
179     template <class U>
operator <(const U & o) const180     bool operator<(const U& o) const noexcept {
181       return get() < o;
182     }
183 
184     template <class U>
operator >(const U & o) const185     bool operator>(const U& o) const noexcept {
186       return get() > o;
187     }
188 
189     template <class U>
operator ==(const U & o) const190     bool operator==(const U& o) const noexcept {
191       return get() == o;
192     }
193 
194     template <class U>
operator !=(const U & o) const195     bool operator!=(const U& o) const noexcept {
196       return get() != o;
197     }
198 
199     template <class U>
operator <=(const U & o) const200     bool operator<=(const U& o) const noexcept {
201       return get() <= o;
202     }
203 
204     template <class U>
operator >=(const U & o) const205     bool operator>=(const U& o) const noexcept {
206       return get() >= o;
207     }
208 
operator value_type() const209     operator value_type() const noexcept { return get(); }
210 
211   private:
accessor(iterator & i)212     accessor(iterator& i) noexcept : iter_(i) {}
213 
214     accessor(const accessor&) = default; // only callable by indexed_range::iterator
215 
216     iterator& iter_;
217 
218     friend class iterator;
219   };
220 
221   /// implementation detail
222   class iterator {
223   public:
224     using value_type = typename indexed_range::value_type;
225     using reference = accessor;
226 
227   private:
228     struct pointer_proxy {
operator ->boost::histogram::indexed_range::iterator::pointer_proxy229       reference* operator->() noexcept { return std::addressof(ref_); }
230       reference ref_;
231     };
232 
233   public:
234     using pointer = pointer_proxy;
235     using difference_type = std::ptrdiff_t;
236     using iterator_category = std::forward_iterator_tag;
237 
operator *()238     reference operator*() noexcept { return *this; }
operator ->()239     pointer operator->() noexcept { return pointer_proxy{operator*()}; }
240 
operator ++()241     iterator& operator++() {
242       assert(iter_ < indices_.hist_->end());
243       const auto cbeg = indices_.begin();
244       auto c = cbeg;
245       ++iter_;
246       ++c->idx;
247       if (c->idx < c->end) return *this;
248       while (c->idx == c->end) {
249         iter_ += c->end_skip;
250         if (++c == indices_.end()) return *this;
251         ++c->idx;
252       }
253       while (c-- != cbeg) {
254         c->idx = c->begin;
255         iter_ += c->begin_skip;
256       }
257       return *this;
258     }
259 
operator ++(int)260     iterator operator++(int) {
261       auto prev = *this;
262       operator++();
263       return prev;
264     }
265 
operator ==(const iterator & x) const266     bool operator==(const iterator& x) const noexcept { return iter_ == x.iter_; }
operator !=(const iterator & x) const267     bool operator!=(const iterator& x) const noexcept { return !operator==(x); }
268 
269     // make iterator ready for C++17 sentinels
operator ==(const value_iterator & x) const270     bool operator==(const value_iterator& x) const noexcept { return iter_ == x; }
operator !=(const value_iterator & x) const271     bool operator!=(const value_iterator& x) const noexcept { return !operator==(x); }
272 
273     // useful for iterator debugging
offset() const274     std::size_t offset() const noexcept { return iter_ - indices_.hist_->begin(); }
275 
276   private:
iterator(value_iterator i,histogram_type & h)277     iterator(value_iterator i, histogram_type& h) : iter_(i), indices_(&h) {}
278 
279     value_iterator iter_;
280 
281     struct index_data {
282       axis::index_type idx, begin, end;
283       std::size_t begin_skip, end_skip;
284     };
285 
286     struct indices_t : private std::array<index_data, buffer_size> {
287       using base_type = std::array<index_data, buffer_size>;
288       using pointer = index_data*;
289       using const_pointer = const index_data*;
290 
indices_tboost::histogram::indexed_range::iterator::indices_t291       indices_t(histogram_type* h) noexcept : hist_{h} {}
292 
293       using base_type::operator[];
sizeboost::histogram::indexed_range::iterator::indices_t294       unsigned size() const noexcept { return hist_->rank(); }
beginboost::histogram::indexed_range::iterator::indices_t295       pointer begin() noexcept { return base_type::data(); }
beginboost::histogram::indexed_range::iterator::indices_t296       const_pointer begin() const noexcept { return base_type::data(); }
endboost::histogram::indexed_range::iterator::indices_t297       pointer end() noexcept { return begin() + size(); }
endboost::histogram::indexed_range::iterator::indices_t298       const_pointer end() const noexcept { return begin() + size(); }
299 
300       histogram_type* hist_;
301     } indices_;
302 
303     friend class indexed_range;
304   };
305 
indexed_range(histogram_type & hist,coverage cov)306   indexed_range(histogram_type& hist, coverage cov)
307       : begin_(hist.begin(), hist), end_(hist.end(), hist) {
308     begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), cov,
309                                           stride = std::size_t{1},
310                                           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);
314       const auto size = a.size();
315 
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;
320       ca->idx = ca->begin;
321 
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;
328       begin_.iter_ += ca->begin_skip;
329 
330       stride *= size + under + over;
331       ++ca;
332     });
333   }
334 
begin()335   iterator begin() noexcept { return begin_; }
end()336   iterator end() noexcept { return end_; }
337 
338 private:
339   iterator begin_, end_;
340 };
341 
342 /** Generates an indexed range of <a
343   href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a>
344   over the histogram cells.
345 
346   Use this in a range-based for loop:
347 
348   ```
349   for (auto&& x : indexed(hist)) { ... }
350   ```
351 
352   This generates an optimized loop which is nearly always faster than a hand-written loop
353   over the histogram cells. The iterators dereference to an indexed_range::accessor, which
354   has methods to query the current indices and bins and acts like a pointer to the cell
355   value. The returned iterators are forward iterators. They can be stored in a container,
356   but may not be used after the life-time of the histogram ends.
357 
358   @returns indexed_range
359 
360   @param hist Reference to the histogram.
361   @param cov  Iterate over all or only inner bins (optional, default: inner).
362  */
363 template <class Histogram>
indexed(Histogram && hist,coverage cov=coverage::inner)364 auto indexed(Histogram&& hist, coverage cov = coverage::inner) {
365   return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
366                                                            cov};
367 }
368 
369 } // namespace histogram
370 } // namespace boost
371 
372 #endif
373