• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015-2018 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_AXIS_INTEGER_HPP
8 #define BOOST_HISTOGRAM_AXIS_INTEGER_HPP
9 
10 #include <boost/core/nvp.hpp>
11 #include <boost/histogram/axis/iterator.hpp>
12 #include <boost/histogram/axis/metadata_base.hpp>
13 #include <boost/histogram/axis/option.hpp>
14 #include <boost/histogram/detail/convert_integer.hpp>
15 #include <boost/histogram/detail/limits.hpp>
16 #include <boost/histogram/detail/relaxed_equal.hpp>
17 #include <boost/histogram/detail/replace_type.hpp>
18 #include <boost/histogram/detail/static_if.hpp>
19 #include <boost/histogram/fwd.hpp>
20 #include <boost/throw_exception.hpp>
21 #include <cmath>
22 #include <limits>
23 #include <stdexcept>
24 #include <string>
25 #include <type_traits>
26 #include <utility>
27 
28 namespace boost {
29 namespace histogram {
30 namespace axis {
31 
32 /**
33   Axis for an interval of integer values with unit steps.
34 
35   Binning is a O(1) operation. This axis bins faster than a regular axis.
36 
37   @tparam Value input value type. Must be integer or floating point.
38   @tparam MetaData type to store meta data.
39   @tparam Options see boost::histogram::axis::option (all values allowed).
40  */
41 template <class Value, class MetaData, class Options>
42 class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
43                 public metadata_base_t<MetaData> {
44   // these must be private, so that they are not automatically inherited
45   using value_type = Value;
46   using metadata_base = metadata_base_t<MetaData>;
47   using metadata_type = typename metadata_base::metadata_type;
48   using options_type =
49       detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
50 
51   static_assert(std::is_integral<value_type>::value ||
52                     std::is_floating_point<value_type>::value,
53                 "integer axis requires floating point or integral type");
54 
55   static_assert(!options_type::test(option::circular | option::growth) ||
56                     (options_type::test(option::circular) ^
57                      options_type::test(option::growth)),
58                 "circular and growth options are mutually exclusive");
59 
60   static_assert(std::is_floating_point<value_type>::value ||
61                     (!options_type::test(option::circular) &&
62                      !options_type::test(option::growth)) ||
63                     (!options_type::test(option::overflow) &&
64                      !options_type::test(option::underflow)),
65                 "circular or growing integer axis with integral type "
66                 "cannot have entries in underflow or overflow bins");
67 
68   using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
69                                               index_type, real_index_type>;
70 
71 public:
72   constexpr integer() = default;
73 
74   /** Construct over semi-open integer interval [start, stop).
75    *
76    * \param start    first integer of covered range.
77    * \param stop     one past last integer of covered range.
78    * \param meta     description of the axis.
79    */
integer(value_type start,value_type stop,metadata_type meta={})80   integer(value_type start, value_type stop, metadata_type meta = {})
81       : metadata_base(std::move(meta))
82       , size_(static_cast<index_type>(stop - start))
83       , min_(start) {
84     if (!(stop >= start))
85       BOOST_THROW_EXCEPTION(std::invalid_argument("stop >= start required"));
86   }
87 
88   /// Constructor used by algorithm::reduce to shrink and rebin.
integer(const integer & src,index_type begin,index_type end,unsigned merge)89   integer(const integer& src, index_type begin, index_type end, unsigned merge)
90       : integer(src.value(begin), src.value(end), src.metadata()) {
91     if (merge > 1)
92       BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for integer axis"));
93     if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
94       BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
95   }
96 
97   /// Return index for value argument.
index(value_type x) const98   index_type index(value_type x) const noexcept {
99     return index_impl(options_type::test(axis::option::circular),
100                       std::is_floating_point<value_type>{},
101                       static_cast<double>(x - min_));
102   }
103 
104   /// Returns index and shift (if axis has grown) for the passed argument.
update(value_type x)105   auto update(value_type x) noexcept {
106     auto impl = [this](long x) -> std::pair<index_type, index_type> {
107       const auto i = x - min_;
108       if (i >= 0) {
109         const auto k = static_cast<axis::index_type>(i);
110         if (k < size()) return {k, 0};
111         const auto n = k - size() + 1;
112         size_ += n;
113         return {k, -n};
114       }
115       const auto k = static_cast<axis::index_type>(
116           detail::static_if<std::is_floating_point<value_type>>(
117               [](auto x) { return std::floor(x); }, [](auto x) { return x; }, i));
118       min_ += k;
119       size_ -= k;
120       return {0, -k};
121     };
122 
123     return detail::static_if<std::is_floating_point<value_type>>(
124         [this, impl](auto x) -> std::pair<index_type, index_type> {
125           if (std::isfinite(x)) return impl(static_cast<long>(std::floor(x)));
126           return {x < 0 ? -1 : this->size(), 0};
127         },
128         impl, x);
129   }
130 
131   /// Return value for index argument.
value(local_index_type i) const132   value_type value(local_index_type i) const noexcept {
133     if (!options_type::test(option::circular) &&
134         std::is_floating_point<value_type>::value) {
135       if (i < 0) return detail::lowest<value_type>();
136       if (i > size()) return detail::highest<value_type>();
137     }
138     return min_ + i;
139   }
140 
141   /// Return bin for index argument.
bin(index_type idx) const142   decltype(auto) bin(index_type idx) const noexcept {
143     return detail::static_if<std::is_floating_point<value_type>>(
144         [this](auto idx) { return interval_view<integer>(*this, idx); },
145         [this](auto idx) { return this->value(idx); }, idx);
146   }
147 
148   /// Returns the number of bins, without over- or underflow.
size() const149   index_type size() const noexcept { return size_; }
150 
151   /// Returns the options.
options()152   static constexpr unsigned options() noexcept { return options_type::value; }
153 
154   /// Whether the axis is inclusive (see axis::traits::is_inclusive).
inclusive()155   static constexpr bool inclusive() noexcept {
156     return (options() & option::underflow || options() & option::overflow) ||
157            (std::is_integral<value_type>::value &&
158             (options() & (option::growth | option::circular)));
159   }
160 
161   template <class V, class M, class O>
operator ==(const integer<V,M,O> & o) const162   bool operator==(const integer<V, M, O>& o) const noexcept {
163     return size() == o.size() && min_ == o.min_ &&
164            detail::relaxed_equal{}(this->metadata(), o.metadata());
165   }
166 
167   template <class V, class M, class O>
operator !=(const integer<V,M,O> & o) const168   bool operator!=(const integer<V, M, O>& o) const noexcept {
169     return !operator==(o);
170   }
171 
172   template <class Archive>
serialize(Archive & ar,unsigned)173   void serialize(Archive& ar, unsigned /* version */) {
174     ar& make_nvp("size", size_);
175     ar& make_nvp("meta", this->metadata());
176     ar& make_nvp("min", min_);
177   }
178 
179 private:
180   // axis not circular
181   template <class B>
index_impl(std::false_type,B,double z) const182   index_type index_impl(std::false_type, B, double z) const noexcept {
183     if (z < size()) return z >= 0 ? static_cast<index_type>(z) : -1;
184     return size();
185   }
186 
187   // value_type is integer, axis circular
index_impl(std::true_type,std::false_type,double z) const188   index_type index_impl(std::true_type, std::false_type, double z) const noexcept {
189     return static_cast<index_type>(z - std::floor(z / size()) * size());
190   }
191 
192   // value_type is floating point, must handle +/-infinite or nan, axis circular
index_impl(std::true_type,std::true_type,double z) const193   index_type index_impl(std::true_type, std::true_type, double z) const noexcept {
194     if (std::isfinite(z)) return index_impl(std::true_type{}, std::false_type{}, z);
195     return z < size() ? -1 : size();
196   }
197 
198   index_type size_{0};
199   value_type min_{0};
200 
201   template <class V, class M, class O>
202   friend class integer;
203 };
204 
205 #if __cpp_deduction_guides >= 201606
206 
207 template <class T>
208 integer(T, T)->integer<detail::convert_integer<T, index_type>, null_type>;
209 
210 template <class T, class M>
211 integer(T, T, M)
212     ->integer<detail::convert_integer<T, index_type>,
213               detail::replace_type<std::decay_t<M>, const char*, std::string>>;
214 
215 #endif
216 
217 } // namespace axis
218 } // namespace histogram
219 } // namespace boost
220 
221 #endif
222