]>
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_REGULAR_HPP | |
8 | #define BOOST_HISTOGRAM_AXIS_REGULAR_HPP | |
9 | ||
10 | #include <boost/assert.hpp> | |
11 | #include <boost/core/nvp.hpp> | |
12 | #include <boost/histogram/axis/interval_view.hpp> | |
13 | #include <boost/histogram/axis/iterator.hpp> | |
14 | #include <boost/histogram/axis/metadata_base.hpp> | |
15 | #include <boost/histogram/axis/option.hpp> | |
16 | #include <boost/histogram/detail/convert_integer.hpp> | |
17 | #include <boost/histogram/detail/relaxed_equal.hpp> | |
18 | #include <boost/histogram/detail/replace_type.hpp> | |
19 | #include <boost/histogram/fwd.hpp> | |
20 | #include <boost/mp11/utility.hpp> | |
21 | #include <boost/throw_exception.hpp> | |
22 | #include <cmath> | |
23 | #include <limits> | |
24 | #include <stdexcept> | |
25 | #include <string> | |
26 | #include <type_traits> | |
27 | #include <utility> | |
28 | ||
29 | namespace boost { | |
30 | namespace histogram { | |
31 | namespace detail { | |
32 | ||
33 | template <class T> | |
34 | using get_scale_type_helper = typename T::value_type; | |
35 | ||
36 | template <class T> | |
37 | using get_scale_type = mp11::mp_eval_or<T, detail::get_scale_type_helper, T>; | |
38 | ||
39 | struct one_unit {}; | |
40 | ||
41 | template <class T> | |
42 | T operator*(T&& t, const one_unit&) { | |
43 | return std::forward<T>(t); | |
44 | } | |
45 | ||
46 | template <class T> | |
47 | T operator/(T&& t, const one_unit&) { | |
48 | return std::forward<T>(t); | |
49 | } | |
50 | ||
51 | template <class T> | |
52 | using get_unit_type_helper = typename T::unit_type; | |
53 | ||
54 | template <class T> | |
55 | using get_unit_type = mp11::mp_eval_or<one_unit, detail::get_unit_type_helper, T>; | |
56 | ||
57 | template <class T, class R = get_scale_type<T>> | |
58 | R get_scale(const T& t) { | |
59 | return t / get_unit_type<T>(); | |
60 | } | |
61 | ||
62 | } // namespace detail | |
63 | ||
64 | namespace axis { | |
65 | ||
66 | namespace transform { | |
67 | ||
68 | /// Identity transform for equidistant bins. | |
69 | struct id { | |
70 | /// Pass-through. | |
71 | template <class T> | |
72 | static T forward(T&& x) noexcept { | |
73 | return std::forward<T>(x); | |
74 | } | |
75 | ||
76 | /// Pass-through. | |
77 | template <class T> | |
78 | static T inverse(T&& x) noexcept { | |
79 | return std::forward<T>(x); | |
80 | } | |
81 | ||
82 | template <class Archive> | |
83 | void serialize(Archive&, unsigned /* version */) {} | |
84 | }; | |
85 | ||
86 | /// Log transform for equidistant bins in log-space. | |
87 | struct log { | |
88 | /// Returns log(x) of external value x. | |
89 | template <class T> | |
90 | static T forward(T x) { | |
91 | return std::log(x); | |
92 | } | |
93 | ||
94 | /// Returns exp(x) for internal value x. | |
95 | template <class T> | |
96 | static T inverse(T x) { | |
97 | return std::exp(x); | |
98 | } | |
99 | ||
100 | template <class Archive> | |
101 | void serialize(Archive&, unsigned /* version */) {} | |
102 | }; | |
103 | ||
104 | /// Sqrt transform for equidistant bins in sqrt-space. | |
105 | struct sqrt { | |
106 | /// Returns sqrt(x) of external value x. | |
107 | template <class T> | |
108 | static T forward(T x) { | |
109 | return std::sqrt(x); | |
110 | } | |
111 | ||
112 | /// Returns x^2 of internal value x. | |
113 | template <class T> | |
114 | static T inverse(T x) { | |
115 | return x * x; | |
116 | } | |
117 | ||
118 | template <class Archive> | |
119 | void serialize(Archive&, unsigned /* version */) {} | |
120 | }; | |
121 | ||
122 | /// Pow transform for equidistant bins in pow-space. | |
123 | struct pow { | |
124 | double power = 1; /**< power index */ | |
125 | ||
126 | /// Make transform with index p. | |
127 | explicit pow(double p) : power(p) {} | |
128 | pow() = default; | |
129 | ||
130 | /// Returns pow(x, power) of external value x. | |
131 | template <class T> | |
132 | auto forward(T x) const { | |
133 | return std::pow(x, power); | |
134 | } | |
135 | ||
136 | /// Returns pow(x, 1/power) of external value x. | |
137 | template <class T> | |
138 | auto inverse(T x) const { | |
139 | return std::pow(x, 1.0 / power); | |
140 | } | |
141 | ||
142 | bool operator==(const pow& o) const noexcept { return power == o.power; } | |
143 | ||
144 | template <class Archive> | |
145 | void serialize(Archive& ar, unsigned /* version */) { | |
146 | ar& make_nvp("power", power); | |
147 | } | |
148 | }; | |
149 | ||
150 | } // namespace transform | |
151 | ||
152 | #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED | |
153 | // Type envelope to mark value as step size | |
154 | template <class T> | |
155 | struct step_type { | |
156 | T value; | |
157 | }; | |
158 | #endif | |
159 | ||
160 | /** | |
161 | Helper function to mark argument as step size. | |
162 | */ | |
163 | template <class T> | |
164 | step_type<T> step(T t) { | |
165 | return step_type<T>{t}; | |
166 | } | |
167 | ||
168 | /** | |
169 | Axis for equidistant intervals on the real line. | |
170 | ||
171 | The most common binning strategy. Very fast. Binning is a O(1) operation. | |
172 | ||
173 | @tparam Value input value type, must be floating point. | |
174 | @tparam Transform builtin or user-defined transform type. | |
175 | @tparam MetaData type to store meta data. | |
176 | @tparam Options see boost::histogram::axis::option (all values allowed). | |
177 | */ | |
178 | template <class Value, class Transform, class MetaData, class Options> | |
179 | class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>, | |
180 | protected detail::replace_default<Transform, transform::id>, | |
181 | public metadata_base<MetaData> { | |
f67539c2 | 182 | // these must be private, so that they are not automatically inherited |
92f5a8d4 TL |
183 | using value_type = Value; |
184 | using transform_type = detail::replace_default<Transform, transform::id>; | |
185 | using metadata_type = typename metadata_base<MetaData>::metadata_type; | |
186 | using options_type = | |
187 | detail::replace_default<Options, decltype(option::underflow | option::overflow)>; | |
188 | ||
189 | static_assert(std::is_nothrow_move_constructible<transform_type>::value, | |
190 | "transform must be no-throw move constructible"); | |
191 | static_assert(std::is_nothrow_move_assignable<transform_type>::value, | |
192 | "transform must be no-throw move assignable"); | |
193 | ||
194 | using unit_type = detail::get_unit_type<value_type>; | |
195 | using internal_value_type = detail::get_scale_type<value_type>; | |
196 | ||
197 | static_assert(std::is_floating_point<internal_value_type>::value, | |
198 | "regular axis requires floating point type"); | |
199 | ||
200 | static_assert( | |
201 | (!options_type::test(option::circular) && !options_type::test(option::growth)) || | |
202 | (options_type::test(option::circular) ^ options_type::test(option::growth)), | |
203 | "circular and growth options are mutually exclusive"); | |
204 | ||
205 | public: | |
206 | constexpr regular() = default; | |
207 | ||
208 | /** Construct n bins over real transformed range [start, stop). | |
209 | * | |
210 | * @param trans transform instance to use. | |
211 | * @param n number of bins. | |
212 | * @param start low edge of first bin. | |
213 | * @param stop high edge of last bin. | |
214 | * @param meta description of the axis (optional). | |
215 | */ | |
216 | regular(transform_type trans, unsigned n, value_type start, value_type stop, | |
217 | metadata_type meta = {}) | |
218 | : transform_type(std::move(trans)) | |
219 | , metadata_base<MetaData>(std::move(meta)) | |
220 | , size_(static_cast<index_type>(n)) | |
221 | , min_(this->forward(detail::get_scale(start))) | |
222 | , delta_(this->forward(detail::get_scale(stop)) - min_) { | |
223 | if (size() == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required")); | |
224 | if (!std::isfinite(min_) || !std::isfinite(delta_)) | |
225 | BOOST_THROW_EXCEPTION( | |
226 | std::invalid_argument("forward transform of start or stop invalid")); | |
227 | if (delta_ == 0) | |
228 | BOOST_THROW_EXCEPTION(std::invalid_argument("range of axis is zero")); | |
229 | } | |
230 | ||
231 | /** Construct n bins over real range [start, stop). | |
232 | * | |
233 | * @param n number of bins. | |
234 | * @param start low edge of first bin. | |
235 | * @param stop high edge of last bin. | |
236 | * @param meta description of the axis (optional). | |
237 | */ | |
238 | regular(unsigned n, value_type start, value_type stop, metadata_type meta = {}) | |
239 | : regular({}, n, start, stop, std::move(meta)) {} | |
240 | ||
241 | /** Construct bins with the given step size over real transformed range | |
242 | * [start, stop). | |
243 | * | |
244 | * @param trans transform instance to use. | |
245 | * @param step width of a single bin. | |
246 | * @param start low edge of first bin. | |
247 | * @param stop upper limit of high edge of last bin (see below). | |
248 | * @param meta description of the axis (optional). | |
249 | * | |
250 | * The axis computes the number of bins as n = abs(stop - start) / step, | |
251 | * rounded down. This means that stop is an upper limit to the actual value | |
252 | * (start + n * step). | |
253 | */ | |
254 | template <class T> | |
255 | regular(transform_type trans, step_type<T> step, value_type start, value_type stop, | |
256 | metadata_type meta = {}) | |
257 | : regular(trans, static_cast<index_type>(std::abs(stop - start) / step.value), | |
258 | start, | |
259 | start + static_cast<index_type>(std::abs(stop - start) / step.value) * | |
260 | step.value, | |
261 | std::move(meta)) {} | |
262 | ||
263 | /** Construct bins with the given step size over real range [start, stop). | |
264 | * | |
265 | * @param step width of a single bin. | |
266 | * @param start low edge of first bin. | |
267 | * @param stop upper limit of high edge of last bin (see below). | |
268 | * @param meta description of the axis (optional). | |
269 | * | |
270 | * The axis computes the number of bins as n = abs(stop - start) / step, | |
271 | * rounded down. This means that stop is an upper limit to the actual value | |
272 | * (start + n * step). | |
273 | */ | |
274 | template <class T> | |
275 | regular(step_type<T> step, value_type start, value_type stop, metadata_type meta = {}) | |
276 | : regular({}, step, start, stop, std::move(meta)) {} | |
277 | ||
278 | /// Constructor used by algorithm::reduce to shrink and rebin (not for users). | |
279 | regular(const regular& src, index_type begin, index_type end, unsigned merge) | |
280 | : regular(src.transform(), (end - begin) / merge, src.value(begin), src.value(end), | |
281 | src.metadata()) { | |
282 | BOOST_ASSERT((end - begin) % merge == 0); | |
283 | if (options_type::test(option::circular) && !(begin == 0 && end == src.size())) | |
284 | BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis")); | |
285 | } | |
286 | ||
287 | /// Return instance of the transform type. | |
288 | const transform_type& transform() const noexcept { return *this; } | |
289 | ||
290 | /// Return index for value argument. | |
291 | index_type index(value_type x) const noexcept { | |
292 | // Runs in hot loop, please measure impact of changes | |
293 | auto z = (this->forward(x / unit_type{}) - min_) / delta_; | |
294 | if (options_type::test(option::circular)) { | |
295 | if (std::isfinite(z)) { | |
296 | z -= std::floor(z); | |
297 | return static_cast<index_type>(z * size()); | |
298 | } | |
299 | } else { | |
300 | if (z < 1) { | |
301 | if (z >= 0) | |
302 | return static_cast<index_type>(z * size()); | |
303 | else | |
304 | return -1; | |
305 | } | |
306 | } | |
307 | return size(); // also returned if x is NaN | |
308 | } | |
309 | ||
310 | /// Returns index and shift (if axis has grown) for the passed argument. | |
f67539c2 | 311 | std::pair<index_type, index_type> update(value_type x) noexcept { |
92f5a8d4 TL |
312 | BOOST_ASSERT(options_type::test(option::growth)); |
313 | const auto z = (this->forward(x / unit_type{}) - min_) / delta_; | |
314 | if (z < 1) { // don't use i here! | |
315 | if (z >= 0) { | |
316 | const auto i = static_cast<axis::index_type>(z * size()); | |
f67539c2 | 317 | return {i, 0}; |
92f5a8d4 TL |
318 | } |
319 | if (z != -std::numeric_limits<internal_value_type>::infinity()) { | |
320 | const auto stop = min_ + delta_; | |
321 | const auto i = static_cast<axis::index_type>(std::floor(z * size())); | |
322 | min_ += i * (delta_ / size()); | |
323 | delta_ = stop - min_; | |
324 | size_ -= i; | |
f67539c2 | 325 | return {0, -i}; |
92f5a8d4 TL |
326 | } |
327 | // z is -infinity | |
f67539c2 | 328 | return {-1, 0}; |
92f5a8d4 TL |
329 | } |
330 | // z either beyond range, infinite, or NaN | |
331 | if (z < std::numeric_limits<internal_value_type>::infinity()) { | |
332 | const auto i = static_cast<axis::index_type>(z * size()); | |
333 | const auto n = i - size() + 1; | |
334 | delta_ /= size(); | |
335 | delta_ *= size() + n; | |
336 | size_ += n; | |
f67539c2 | 337 | return {i, -n}; |
92f5a8d4 TL |
338 | } |
339 | // z either infinite or NaN | |
f67539c2 | 340 | return {size(), 0}; |
92f5a8d4 TL |
341 | } |
342 | ||
343 | /// Return value for fractional index argument. | |
344 | value_type value(real_index_type i) const noexcept { | |
345 | auto z = i / size(); | |
346 | if (!options_type::test(option::circular) && z < 0.0) | |
347 | z = -std::numeric_limits<internal_value_type>::infinity() * delta_; | |
348 | else if (options_type::test(option::circular) || z <= 1.0) | |
349 | z = (1.0 - z) * min_ + z * (min_ + delta_); | |
350 | else { | |
351 | z = std::numeric_limits<internal_value_type>::infinity() * delta_; | |
352 | } | |
353 | return static_cast<value_type>(this->inverse(z) * unit_type()); | |
354 | } | |
355 | ||
356 | /// Return bin for index argument. | |
357 | decltype(auto) bin(index_type idx) const noexcept { | |
358 | return interval_view<regular>(*this, idx); | |
359 | } | |
360 | ||
361 | /// Returns the number of bins, without over- or underflow. | |
362 | index_type size() const noexcept { return size_; } | |
363 | ||
364 | /// Returns the options. | |
365 | static constexpr unsigned options() noexcept { return options_type::value; } | |
366 | ||
367 | template <class V, class T, class M, class O> | |
368 | bool operator==(const regular<V, T, M, O>& o) const noexcept { | |
369 | return detail::relaxed_equal(transform(), o.transform()) && size() == o.size() && | |
370 | min_ == o.min_ && delta_ == o.delta_ && metadata_base<MetaData>::operator==(o); | |
371 | } | |
372 | template <class V, class T, class M, class O> | |
373 | bool operator!=(const regular<V, T, M, O>& o) const noexcept { | |
374 | return !operator==(o); | |
375 | } | |
376 | ||
377 | template <class Archive> | |
378 | void serialize(Archive& ar, unsigned /* version */) { | |
379 | ar& make_nvp("transform", static_cast<transform_type&>(*this)); | |
380 | ar& make_nvp("size", size_); | |
381 | ar& make_nvp("meta", this->metadata()); | |
382 | ar& make_nvp("min", min_); | |
383 | ar& make_nvp("delta", delta_); | |
384 | } | |
385 | ||
386 | private: | |
387 | index_type size_{0}; | |
388 | internal_value_type min_{0}, delta_{1}; | |
389 | ||
390 | template <class V, class T, class M, class O> | |
391 | friend class regular; | |
392 | }; | |
393 | ||
394 | #if __cpp_deduction_guides >= 201606 | |
395 | ||
396 | template <class T> | |
397 | regular(unsigned, T, T) | |
398 | ->regular<detail::convert_integer<T, double>, transform::id, null_type>; | |
399 | ||
400 | template <class T, class M> | |
401 | regular(unsigned, T, T, M) | |
402 | ->regular<detail::convert_integer<T, double>, transform::id, | |
403 | detail::replace_cstring<std::decay_t<M>>>; | |
404 | ||
405 | template <class Tr, class T, class = detail::requires_transform<Tr, T>> | |
406 | regular(Tr, unsigned, T, T)->regular<detail::convert_integer<T, double>, Tr, null_type>; | |
407 | ||
408 | template <class Tr, class T, class M> | |
409 | regular(Tr, unsigned, T, T, M) | |
410 | ->regular<detail::convert_integer<T, double>, Tr, | |
411 | detail::replace_cstring<std::decay_t<M>>>; | |
412 | ||
413 | #endif | |
414 | ||
415 | /// Regular axis with circular option already set. | |
416 | template <class Value = double, class MetaData = use_default, class Options = use_default> | |
417 | #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED | |
418 | using circular = regular<Value, transform::id, MetaData, | |
419 | decltype(detail::replace_default<Options, option::overflow_t>{} | | |
420 | option::circular)>; | |
421 | #else | |
422 | class circular; | |
423 | #endif | |
424 | ||
425 | } // namespace axis | |
426 | } // namespace histogram | |
427 | } // namespace boost | |
428 | ||
429 | #endif |