integer.hpp 7.9 KB

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