integer.hpp 6.8 KB

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