variable.hpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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_VARIABLE_HPP
  7. #define BOOST_HISTOGRAM_AXIS_VARIABLE_HPP
  8. #include <algorithm>
  9. #include <boost/assert.hpp>
  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/detect.hpp>
  17. #include <boost/histogram/detail/limits.hpp>
  18. #include <boost/histogram/detail/replace_type.hpp>
  19. #include <boost/histogram/fwd.hpp>
  20. #include <boost/throw_exception.hpp>
  21. #include <cmath>
  22. #include <limits>
  23. #include <memory>
  24. #include <stdexcept>
  25. #include <string>
  26. #include <type_traits>
  27. #include <utility>
  28. #include <vector>
  29. namespace boost {
  30. namespace histogram {
  31. namespace axis {
  32. /**
  33. Axis for non-equidistant bins on the real line.
  34. Binning is a O(log(N)) operation. If speed matters and the problem domain
  35. allows it, prefer a regular axis, possibly with a transform.
  36. @tparam Value input value type, must be floating point.
  37. @tparam MetaData type to store meta data.
  38. @tparam Options see boost::histogram::axis::option (all values allowed).
  39. @tparam Allocator allocator to use for dynamic memory management.
  40. */
  41. template <class Value, class MetaData, class Options, class Allocator>
  42. class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>>,
  43. public metadata_base<MetaData> {
  44. using value_type = Value;
  45. using metadata_type = typename metadata_base<MetaData>::metadata_type;
  46. using options_type =
  47. detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
  48. using allocator_type = Allocator;
  49. using vector_type = std::vector<Value, allocator_type>;
  50. static_assert(
  51. std::is_floating_point<value_type>::value,
  52. "current version of variable axis requires floating point type; "
  53. "if you need a variable axis with an integral type, please submit an issue");
  54. static_assert(
  55. (!options_type::test(option::circular) && !options_type::test(option::growth)) ||
  56. (options_type::test(option::circular) ^ options_type::test(option::growth)),
  57. "circular and growth options are mutually exclusive");
  58. public:
  59. constexpr variable() = default;
  60. explicit variable(allocator_type alloc) : vec_(alloc) {}
  61. /** Construct from iterator range of bin edges.
  62. *
  63. * \param begin begin of edge sequence.
  64. * \param end end of edge sequence.
  65. * \param meta description of the axis.
  66. * \param alloc allocator instance to use.
  67. */
  68. template <class It, class = detail::requires_iterator<It>>
  69. variable(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
  70. : metadata_base<MetaData>(std::move(meta)), vec_(std::move(alloc)) {
  71. if (std::distance(begin, end) < 2)
  72. BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
  73. vec_.reserve(std::distance(begin, end));
  74. vec_.emplace_back(*begin++);
  75. bool strictly_ascending = true;
  76. while (begin != end) {
  77. if (*begin <= vec_.back()) strictly_ascending = false;
  78. vec_.emplace_back(*begin++);
  79. }
  80. if (!strictly_ascending)
  81. BOOST_THROW_EXCEPTION(
  82. std::invalid_argument("input sequence must be strictly ascending"));
  83. }
  84. /** Construct variable axis from iterable range of bin edges.
  85. *
  86. * \param iterable iterable range of bin edges.
  87. * \param meta description of the axis.
  88. * \param alloc allocator instance to use.
  89. */
  90. template <class U, class = detail::requires_iterable<U>>
  91. variable(const U& iterable, metadata_type meta = {}, allocator_type alloc = {})
  92. : variable(std::begin(iterable), std::end(iterable), std::move(meta),
  93. std::move(alloc)) {}
  94. /** Construct variable axis from initializer list of bin edges.
  95. *
  96. * @param list `std::initializer_list` of bin edges.
  97. * @param meta description of the axis.
  98. * @param alloc allocator instance to use.
  99. */
  100. template <class U>
  101. variable(std::initializer_list<U> list, metadata_type meta = {},
  102. allocator_type alloc = {})
  103. : variable(list.begin(), list.end(), std::move(meta), std::move(alloc)) {}
  104. /// Constructor used by algorithm::reduce to shrink and rebin (not for users).
  105. variable(const variable& src, index_type begin, index_type end, unsigned merge)
  106. : metadata_base<MetaData>(src), vec_(src.get_allocator()) {
  107. BOOST_ASSERT((end - begin) % merge == 0);
  108. if (options_type::test(option::circular) && !(begin == 0 && end == src.size()))
  109. BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
  110. vec_.reserve((end - begin) / merge);
  111. const auto beg = src.vec_.begin();
  112. for (index_type i = begin; i <= end; i += merge) vec_.emplace_back(*(beg + i));
  113. }
  114. /// Return index for value argument.
  115. index_type index(value_type x) const noexcept {
  116. if (options_type::test(option::circular)) {
  117. const auto a = vec_[0];
  118. const auto b = vec_[size()];
  119. x -= std::floor((x - a) / (b - a)) * (b - a);
  120. }
  121. return static_cast<index_type>(std::upper_bound(vec_.begin(), vec_.end(), x) -
  122. vec_.begin() - 1);
  123. }
  124. auto update(value_type x) noexcept {
  125. const auto i = index(x);
  126. if (std::isfinite(x)) {
  127. if (0 <= i) {
  128. if (i < size()) return std::make_pair(i, 0);
  129. const auto d = value(size()) - value(size() - 0.5);
  130. x = std::nextafter(x, (std::numeric_limits<value_type>::max)());
  131. x = (std::max)(x, vec_.back() + d);
  132. vec_.push_back(x);
  133. return std::make_pair(i, -1);
  134. }
  135. const auto d = value(0.5) - value(0);
  136. x = (std::min)(x, value(0) - d);
  137. vec_.insert(vec_.begin(), x);
  138. return std::make_pair(0, -i);
  139. }
  140. return std::make_pair(x < 0 ? -1 : size(), 0);
  141. }
  142. /// Return value for fractional index argument.
  143. value_type value(real_index_type i) const noexcept {
  144. if (options_type::test(option::circular)) {
  145. auto shift = std::floor(i / size());
  146. i -= shift * size();
  147. double z;
  148. const auto k = static_cast<index_type>(std::modf(i, &z));
  149. const auto a = vec_[0];
  150. const auto b = vec_[size()];
  151. return (1.0 - z) * vec_[k] + z * vec_[k + 1] + shift * (b - a);
  152. }
  153. if (i < 0) return detail::lowest<value_type>();
  154. if (i == size()) return vec_.back();
  155. if (i > size()) return detail::highest<value_type>();
  156. const auto k = static_cast<index_type>(i); // precond: i >= 0
  157. const real_index_type z = i - k;
  158. return (1.0 - z) * vec_[k] + z * vec_[k + 1];
  159. }
  160. /// Return bin for index argument.
  161. auto bin(index_type idx) const noexcept { return interval_view<variable>(*this, idx); }
  162. /// Returns the number of bins, without over- or underflow.
  163. index_type size() const noexcept { return static_cast<index_type>(vec_.size()) - 1; }
  164. /// Returns the options.
  165. static constexpr unsigned options() noexcept { return options_type::value; }
  166. template <class V, class M, class O, class A>
  167. bool operator==(const variable<V, M, O, A>& o) const noexcept {
  168. const auto& a = vec_;
  169. const auto& b = o.vec_;
  170. return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
  171. metadata_base<MetaData>::operator==(o);
  172. }
  173. template <class V, class M, class O, class A>
  174. bool operator!=(const variable<V, M, O, A>& o) const noexcept {
  175. return !operator==(o);
  176. }
  177. /// Return allocator instance.
  178. auto get_allocator() const { return vec_.get_allocator(); }
  179. template <class Archive>
  180. void serialize(Archive& ar, unsigned /* version */) {
  181. ar& make_nvp("seq", vec_);
  182. ar& make_nvp("meta", this->metadata());
  183. }
  184. private:
  185. vector_type vec_;
  186. template <class V, class M, class O, class A>
  187. friend class variable;
  188. };
  189. #if __cpp_deduction_guides >= 201606
  190. template <class T>
  191. variable(std::initializer_list<T>)
  192. ->variable<detail::convert_integer<T, double>, null_type>;
  193. template <class T, class M>
  194. variable(std::initializer_list<T>, M)
  195. ->variable<detail::convert_integer<T, double>,
  196. detail::replace_type<std::decay_t<M>, const char*, std::string>>;
  197. template <class Iterable, class = detail::requires_iterable<Iterable>>
  198. variable(Iterable)
  199. ->variable<
  200. detail::convert_integer<
  201. std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
  202. null_type>;
  203. template <class Iterable, class M>
  204. variable(Iterable, M)
  205. ->variable<
  206. detail::convert_integer<
  207. std::decay_t<decltype(*std::begin(std::declval<Iterable&>()))>, double>,
  208. detail::replace_type<std::decay_t<M>, const char*, std::string>>;
  209. #endif
  210. } // namespace axis
  211. } // namespace histogram
  212. } // namespace boost
  213. #endif