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