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