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_REGULAR_HPP
8 #define BOOST_HISTOGRAM_AXIS_REGULAR_HPP
9 
10 #include <boost/core/nvp.hpp>
11 #include <boost/histogram/axis/interval_view.hpp>
12 #include <boost/histogram/axis/iterator.hpp>
13 #include <boost/histogram/axis/metadata_base.hpp>
14 #include <boost/histogram/axis/option.hpp>
15 #include <boost/histogram/detail/convert_integer.hpp>
16 #include <boost/histogram/detail/relaxed_equal.hpp>
17 #include <boost/histogram/detail/replace_type.hpp>
18 #include <boost/histogram/fwd.hpp>
19 #include <boost/mp11/utility.hpp>
20 #include <boost/throw_exception.hpp>
21 #include <cassert>
22 #include <cmath>
23 #include <limits>
24 #include <stdexcept>
25 #include <string>
26 #include <type_traits>
27 #include <utility>
28 
29 namespace boost {
30 namespace histogram {
31 namespace detail {
32 
33 template <class T>
34 using get_scale_type_helper = typename T::value_type;
35 
36 template <class T>
37 using get_scale_type = mp11::mp_eval_or<T, detail::get_scale_type_helper, T>;
38 
39 struct one_unit {};
40 
41 template <class T>
operator *(T && t,const one_unit &)42 T operator*(T&& t, const one_unit&) {
43   return std::forward<T>(t);
44 }
45 
46 template <class T>
operator /(T && t,const one_unit &)47 T operator/(T&& t, const one_unit&) {
48   return std::forward<T>(t);
49 }
50 
51 template <class T>
52 using get_unit_type_helper = typename T::unit_type;
53 
54 template <class T>
55 using get_unit_type = mp11::mp_eval_or<one_unit, detail::get_unit_type_helper, T>;
56 
57 template <class T, class R = get_scale_type<T>>
get_scale(const T & t)58 R get_scale(const T& t) {
59   return t / get_unit_type<T>();
60 }
61 
62 } // namespace detail
63 
64 namespace axis {
65 
66 namespace transform {
67 
68 /// Identity transform for equidistant bins.
69 struct id {
70   /// Pass-through.
71   template <class T>
forwardboost::histogram::axis::transform::id72   static T forward(T&& x) noexcept {
73     return std::forward<T>(x);
74   }
75 
76   /// Pass-through.
77   template <class T>
inverseboost::histogram::axis::transform::id78   static T inverse(T&& x) noexcept {
79     return std::forward<T>(x);
80   }
81 
82   template <class Archive>
serializeboost::histogram::axis::transform::id83   void serialize(Archive&, unsigned /* version */) {}
84 };
85 
86 /// Log transform for equidistant bins in log-space.
87 struct log {
88   /// Returns log(x) of external value x.
89   template <class T>
forwardboost::histogram::axis::transform::log90   static T forward(T x) {
91     return std::log(x);
92   }
93 
94   /// Returns exp(x) for internal value x.
95   template <class T>
inverseboost::histogram::axis::transform::log96   static T inverse(T x) {
97     return std::exp(x);
98   }
99 
100   template <class Archive>
serializeboost::histogram::axis::transform::log101   void serialize(Archive&, unsigned /* version */) {}
102 };
103 
104 /// Sqrt transform for equidistant bins in sqrt-space.
105 struct sqrt {
106   /// Returns sqrt(x) of external value x.
107   template <class T>
forwardboost::histogram::axis::transform::sqrt108   static T forward(T x) {
109     return std::sqrt(x);
110   }
111 
112   /// Returns x^2 of internal value x.
113   template <class T>
inverseboost::histogram::axis::transform::sqrt114   static T inverse(T x) {
115     return x * x;
116   }
117 
118   template <class Archive>
serializeboost::histogram::axis::transform::sqrt119   void serialize(Archive&, unsigned /* version */) {}
120 };
121 
122 /// Pow transform for equidistant bins in pow-space.
123 struct pow {
124   double power = 1; /**< power index */
125 
126   /// Make transform with index p.
powboost::histogram::axis::transform::pow127   explicit pow(double p) : power(p) {}
128   pow() = default;
129 
130   /// Returns pow(x, power) of external value x.
131   template <class T>
forwardboost::histogram::axis::transform::pow132   auto forward(T x) const {
133     return std::pow(x, power);
134   }
135 
136   /// Returns pow(x, 1/power) of external value x.
137   template <class T>
inverseboost::histogram::axis::transform::pow138   auto inverse(T x) const {
139     return std::pow(x, 1.0 / power);
140   }
141 
operator ==boost::histogram::axis::transform::pow142   bool operator==(const pow& o) const noexcept { return power == o.power; }
143 
144   template <class Archive>
serializeboost::histogram::axis::transform::pow145   void serialize(Archive& ar, unsigned /* version */) {
146     ar& make_nvp("power", power);
147   }
148 };
149 
150 } // namespace transform
151 
152 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
153 // Type envelope to mark value as step size
154 template <class T>
155 struct step_type {
156   T value;
157 };
158 #endif
159 
160 /**
161   Helper function to mark argument as step size.
162  */
163 template <class T>
step(T t)164 step_type<T> step(T t) {
165   return step_type<T>{t};
166 }
167 
168 /**
169   Axis for equidistant intervals on the real line.
170 
171   The most common binning strategy. Very fast. Binning is a O(1) operation.
172 
173   @tparam Value input value type, must be floating point.
174   @tparam Transform builtin or user-defined transform type.
175   @tparam MetaData type to store meta data.
176   @tparam Options see boost::histogram::axis::option (all values allowed).
177  */
178 template <class Value, class Transform, class MetaData, class Options>
179 class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
180                 protected detail::replace_default<Transform, transform::id>,
181                 public metadata_base_t<MetaData> {
182   // these must be private, so that they are not automatically inherited
183   using value_type = Value;
184   using transform_type = detail::replace_default<Transform, transform::id>;
185   using metadata_base = metadata_base_t<MetaData>;
186   using metadata_type = typename metadata_base::metadata_type;
187   using options_type =
188       detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
189 
190   static_assert(std::is_nothrow_move_constructible<transform_type>::value,
191                 "transform must be no-throw move constructible");
192   static_assert(std::is_nothrow_move_assignable<transform_type>::value,
193                 "transform must be no-throw move assignable");
194 
195   using unit_type = detail::get_unit_type<value_type>;
196   using internal_value_type = detail::get_scale_type<value_type>;
197 
198   static_assert(std::is_floating_point<internal_value_type>::value,
199                 "regular axis requires floating point type");
200 
201   static_assert(
202       (!options_type::test(option::circular) && !options_type::test(option::growth)) ||
203           (options_type::test(option::circular) ^ options_type::test(option::growth)),
204       "circular and growth options are mutually exclusive");
205 
206 public:
207   constexpr regular() = default;
208 
209   /** Construct n bins over real transformed range [start, stop).
210    *
211    * @param trans    transform instance to use.
212    * @param n        number of bins.
213    * @param start    low edge of first bin.
214    * @param stop     high edge of last bin.
215    * @param meta     description of the axis (optional).
216    */
regular(transform_type trans,unsigned n,value_type start,value_type stop,metadata_type meta={})217   regular(transform_type trans, unsigned n, value_type start, value_type stop,
218           metadata_type meta = {})
219       : transform_type(std::move(trans))
220       , metadata_base(std::move(meta))
221       , size_(static_cast<index_type>(n))
222       , min_(this->forward(detail::get_scale(start)))
223       , delta_(this->forward(detail::get_scale(stop)) - min_) {
224     if (size() == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
225     if (!std::isfinite(min_) || !std::isfinite(delta_))
226       BOOST_THROW_EXCEPTION(
227           std::invalid_argument("forward transform of start or stop invalid"));
228     if (delta_ == 0)
229       BOOST_THROW_EXCEPTION(std::invalid_argument("range of axis is zero"));
230   }
231 
232   /** Construct n bins over real range [start, stop).
233    *
234    * @param n        number of bins.
235    * @param start    low edge of first bin.
236    * @param stop     high edge of last bin.
237    * @param meta     description of the axis (optional).
238    */
regular(unsigned n,value_type start,value_type stop,metadata_type meta={})239   regular(unsigned n, value_type start, value_type stop, metadata_type meta = {})
240       : regular({}, n, start, stop, std::move(meta)) {}
241 
242   /** Construct bins with the given step size over real transformed range
243    * [start, stop).
244    *
245    * @param trans   transform instance to use.
246    * @param step    width of a single bin.
247    * @param start   low edge of first bin.
248    * @param stop    upper limit of high edge of last bin (see below).
249    * @param meta    description of the axis (optional).
250    *
251    * The axis computes the number of bins as n = abs(stop - start) / step,
252    * rounded down. This means that stop is an upper limit to the actual value
253    * (start + n * step).
254    */
255   template <class T>
regular(transform_type trans,step_type<T> step,value_type start,value_type stop,metadata_type meta={})256   regular(transform_type trans, step_type<T> step, value_type start, value_type stop,
257           metadata_type meta = {})
258       : regular(trans, static_cast<index_type>(std::abs(stop - start) / step.value),
259                 start,
260                 start + static_cast<index_type>(std::abs(stop - start) / step.value) *
261                             step.value,
262                 std::move(meta)) {}
263 
264   /** Construct bins with the given step size over real range [start, stop).
265    *
266    * @param step    width of a single bin.
267    * @param start   low edge of first bin.
268    * @param stop    upper limit of high edge of last bin (see below).
269    * @param meta    description of the axis (optional).
270    *
271    * The axis computes the number of bins as n = abs(stop - start) / step,
272    * rounded down. This means that stop is an upper limit to the actual value
273    * (start + n * step).
274    */
275   template <class T>
regular(step_type<T> step,value_type start,value_type stop,metadata_type meta={})276   regular(step_type<T> step, value_type start, value_type stop, metadata_type meta = {})
277       : regular({}, step, start, stop, std::move(meta)) {}
278 
279   /// Constructor used by algorithm::reduce to shrink and rebin (not for users).
regular(const regular & src,index_type begin,index_type end,unsigned merge)280   regular(const regular& src, index_type begin, index_type end, unsigned merge)
281       : regular(src.transform(), (end - begin) / merge, src.value(begin), src.value(end),
282                 src.metadata()) {
283     assert((end - begin) % merge == 0);
284     if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
285       BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
286   }
287 
288   /// Return instance of the transform type.
transform() const289   const transform_type& transform() const noexcept { return *this; }
290 
291   /// Return index for value argument.
index(value_type x) const292   index_type index(value_type x) const noexcept {
293     // Runs in hot loop, please measure impact of changes
294     auto z = (this->forward(x / unit_type{}) - min_) / delta_;
295     if (options_type::test(option::circular)) {
296       if (std::isfinite(z)) {
297         z -= std::floor(z);
298         return static_cast<index_type>(z * size());
299       }
300     } else {
301       if (z < 1) {
302         if (z >= 0)
303           return static_cast<index_type>(z * size());
304         else
305           return -1;
306       }
307     }
308     return size(); // also returned if x is NaN
309   }
310 
311   /// Returns index and shift (if axis has grown) for the passed argument.
update(value_type x)312   std::pair<index_type, index_type> update(value_type x) noexcept {
313     assert(options_type::test(option::growth));
314     const auto z = (this->forward(x / unit_type{}) - min_) / delta_;
315     if (z < 1) { // don't use i here!
316       if (z >= 0) {
317         const auto i = static_cast<axis::index_type>(z * size());
318         return {i, 0};
319       }
320       if (z != -std::numeric_limits<internal_value_type>::infinity()) {
321         const auto stop = min_ + delta_;
322         const auto i = static_cast<axis::index_type>(std::floor(z * size()));
323         min_ += i * (delta_ / size());
324         delta_ = stop - min_;
325         size_ -= i;
326         return {0, -i};
327       }
328       // z is -infinity
329       return {-1, 0};
330     }
331     // z either beyond range, infinite, or NaN
332     if (z < std::numeric_limits<internal_value_type>::infinity()) {
333       const auto i = static_cast<axis::index_type>(z * size());
334       const auto n = i - size() + 1;
335       delta_ /= size();
336       delta_ *= size() + n;
337       size_ += n;
338       return {i, -n};
339     }
340     // z either infinite or NaN
341     return {size(), 0};
342   }
343 
344   /// Return value for fractional index argument.
value(real_index_type i) const345   value_type value(real_index_type i) const noexcept {
346     auto z = i / size();
347     if (!options_type::test(option::circular) && z < 0.0)
348       z = -std::numeric_limits<internal_value_type>::infinity() * delta_;
349     else if (options_type::test(option::circular) || z <= 1.0)
350       z = (1.0 - z) * min_ + z * (min_ + delta_);
351     else {
352       z = std::numeric_limits<internal_value_type>::infinity() * delta_;
353     }
354     return static_cast<value_type>(this->inverse(z) * unit_type());
355   }
356 
357   /// Return bin for index argument.
bin(index_type idx) const358   decltype(auto) bin(index_type idx) const noexcept {
359     return interval_view<regular>(*this, idx);
360   }
361 
362   /// Returns the number of bins, without over- or underflow.
size() const363   index_type size() const noexcept { return size_; }
364 
365   /// Returns the options.
options()366   static constexpr unsigned options() noexcept { return options_type::value; }
367 
368   template <class V, class T, class M, class O>
operator ==(const regular<V,T,M,O> & o) const369   bool operator==(const regular<V, T, M, O>& o) const noexcept {
370     return detail::relaxed_equal{}(transform(), o.transform()) && size() == o.size() &&
371            min_ == o.min_ && delta_ == o.delta_ &&
372            detail::relaxed_equal{}(this->metadata(), o.metadata());
373   }
374   template <class V, class T, class M, class O>
operator !=(const regular<V,T,M,O> & o) const375   bool operator!=(const regular<V, T, M, O>& o) const noexcept {
376     return !operator==(o);
377   }
378 
379   template <class Archive>
serialize(Archive & ar,unsigned)380   void serialize(Archive& ar, unsigned /* version */) {
381     ar& make_nvp("transform", static_cast<transform_type&>(*this));
382     ar& make_nvp("size", size_);
383     ar& make_nvp("meta", this->metadata());
384     ar& make_nvp("min", min_);
385     ar& make_nvp("delta", delta_);
386   }
387 
388 private:
389   index_type size_{0};
390   internal_value_type min_{0}, delta_{1};
391 
392   template <class V, class T, class M, class O>
393   friend class regular;
394 };
395 
396 #if __cpp_deduction_guides >= 201606
397 
398 template <class T>
399 regular(unsigned, T, T)
400     ->regular<detail::convert_integer<T, double>, transform::id, null_type>;
401 
402 template <class T, class M>
403 regular(unsigned, T, T, M)
404     ->regular<detail::convert_integer<T, double>, transform::id,
405               detail::replace_cstring<std::decay_t<M>>>;
406 
407 template <class Tr, class T, class = detail::requires_transform<Tr, T>>
408 regular(Tr, unsigned, T, T)->regular<detail::convert_integer<T, double>, Tr, null_type>;
409 
410 template <class Tr, class T, class M>
411 regular(Tr, unsigned, T, T, M)
412     ->regular<detail::convert_integer<T, double>, Tr,
413               detail::replace_cstring<std::decay_t<M>>>;
414 
415 #endif
416 
417 /// Regular axis with circular option already set.
418 template <class Value = double, class MetaData = use_default, class Options = use_default>
419 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
420 using circular = regular<Value, transform::id, MetaData,
421                          decltype(detail::replace_default<Options, option::overflow_t>{} |
422                                   option::circular)>;
423 #else
424 class circular;
425 #endif
426 
427 } // namespace axis
428 } // namespace histogram
429 } // namespace boost
430 
431 #endif
432