]>
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_DETAIL_FILL_HPP | |
8 | #define BOOST_HISTOGRAM_DETAIL_FILL_HPP | |
9 | ||
10 | #include <algorithm> | |
11 | #include <boost/assert.hpp> | |
12 | #include <boost/config/workaround.hpp> | |
13 | #include <boost/histogram/axis/traits.hpp> | |
14 | #include <boost/histogram/axis/variant.hpp> | |
92f5a8d4 TL |
15 | #include <boost/histogram/detail/argument_traits.hpp> |
16 | #include <boost/histogram/detail/axes.hpp> | |
17 | #include <boost/histogram/detail/linearize.hpp> | |
18 | #include <boost/histogram/detail/make_default.hpp> | |
19 | #include <boost/histogram/detail/optional_index.hpp> | |
f67539c2 | 20 | #include <boost/histogram/detail/priority.hpp> |
92f5a8d4 TL |
21 | #include <boost/histogram/detail/tuple_slice.hpp> |
22 | #include <boost/histogram/fwd.hpp> | |
23 | #include <boost/mp11/algorithm.hpp> | |
24 | #include <boost/mp11/integral.hpp> | |
25 | #include <boost/mp11/tuple.hpp> | |
26 | #include <boost/mp11/utility.hpp> | |
27 | #include <mutex> | |
28 | #include <tuple> | |
29 | #include <type_traits> | |
30 | ||
31 | namespace boost { | |
32 | namespace histogram { | |
33 | namespace detail { | |
34 | ||
35 | template <class T, class U> | |
36 | struct sample_args_passed_vs_expected; | |
37 | ||
38 | template <class... Passed, class... Expected> | |
39 | struct sample_args_passed_vs_expected<std::tuple<Passed...>, std::tuple<Expected...>> { | |
40 | static_assert(!(sizeof...(Expected) > 0 && sizeof...(Passed) == 0), | |
41 | "error: accumulator requires samples, but sample argument is missing"); | |
42 | static_assert( | |
43 | !(sizeof...(Passed) > 0 && sizeof...(Expected) == 0), | |
44 | "error: accumulator does not accept samples, but sample argument is passed"); | |
45 | static_assert(sizeof...(Passed) == sizeof...(Expected), | |
46 | "error: numbers of passed and expected sample arguments differ"); | |
47 | static_assert( | |
48 | std::is_convertible<std::tuple<Passed...>, std::tuple<Expected...>>::value, | |
49 | "error: sample argument(s) not convertible to accumulator argument(s)"); | |
50 | }; | |
51 | ||
52 | template <class A> | |
53 | struct storage_grower { | |
54 | const A& axes_; | |
55 | struct { | |
56 | axis::index_type idx, old_extent; | |
57 | std::size_t new_stride; | |
58 | } data_[buffer_size<A>::value]; | |
59 | std::size_t new_size_; | |
60 | ||
61 | storage_grower(const A& axes) noexcept : axes_(axes) {} | |
62 | ||
63 | void from_shifts(const axis::index_type* shifts) noexcept { | |
64 | auto dit = data_; | |
65 | std::size_t s = 1; | |
66 | for_each_axis(axes_, [&](const auto& a) { | |
67 | const auto n = axis::traits::extent(a); | |
68 | *dit++ = {0, n - std::abs(*shifts++), s}; | |
69 | s *= n; | |
70 | }); | |
71 | new_size_ = s; | |
72 | } | |
73 | ||
74 | // must be extents before any shifts were applied | |
75 | void from_extents(const axis::index_type* old_extents) noexcept { | |
76 | auto dit = data_; | |
77 | std::size_t s = 1; | |
78 | for_each_axis(axes_, [&](const auto& a) { | |
79 | const auto n = axis::traits::extent(a); | |
80 | *dit++ = {0, *old_extents++, s}; | |
81 | s *= n; | |
82 | }); | |
83 | new_size_ = s; | |
84 | } | |
85 | ||
86 | template <class S> | |
87 | void apply(S& storage, const axis::index_type* shifts) { | |
88 | auto new_storage = make_default(storage); | |
89 | new_storage.reset(new_size_); | |
90 | const auto dlast = data_ + axes_rank(axes_) - 1; | |
91 | for (const auto& x : storage) { | |
92 | auto ns = new_storage.begin(); | |
93 | auto sit = shifts; | |
94 | auto dit = data_; | |
95 | for_each_axis(axes_, [&](const auto& a) { | |
f67539c2 | 96 | using opt = axis::traits::get_options<std::decay_t<decltype(a)>>; |
92f5a8d4 TL |
97 | if (opt::test(axis::option::underflow)) { |
98 | if (dit->idx == 0) { | |
99 | // axis has underflow and we are in the underflow bin: | |
100 | // keep storage pointer unchanged | |
101 | ++dit; | |
102 | ++sit; | |
103 | return; | |
104 | } | |
105 | } | |
106 | if (opt::test(axis::option::overflow)) { | |
107 | if (dit->idx == dit->old_extent - 1) { | |
108 | // axis has overflow and we are in the overflow bin: | |
109 | // move storage pointer to corresponding overflow bin position | |
110 | ns += (axis::traits::extent(a) - 1) * dit->new_stride; | |
111 | ++dit; | |
112 | ++sit; | |
113 | return; | |
114 | } | |
115 | } | |
116 | // we are in a normal bin: | |
f67539c2 TL |
117 | // move storage pointer to index position; apply positive shifts if any |
118 | ns += (dit->idx + (*sit >= 0 ? *sit : 0)) * dit->new_stride; | |
92f5a8d4 TL |
119 | ++dit; |
120 | ++sit; | |
121 | }); | |
122 | // assign old value to new location | |
123 | *ns = x; | |
124 | // advance multi-dimensional index | |
125 | dit = data_; | |
126 | ++dit->idx; | |
127 | while (dit != dlast && dit->idx == dit->old_extent) { | |
128 | dit->idx = 0; | |
129 | ++(++dit)->idx; | |
130 | } | |
131 | } | |
132 | storage = std::move(new_storage); | |
133 | } | |
134 | }; | |
135 | ||
136 | template <class T, class... Us> | |
f67539c2 TL |
137 | auto fill_storage_element_impl(priority<2>, T&& t, const Us&... args) noexcept |
138 | -> decltype(t(args...), void()) { | |
139 | t(args...); | |
92f5a8d4 TL |
140 | } |
141 | ||
f67539c2 TL |
142 | template <class T, class U> |
143 | auto fill_storage_element_impl(priority<1>, T&& t, const weight_type<U>& w) noexcept | |
144 | -> decltype(t += w, void()) { | |
145 | t += w; | |
92f5a8d4 TL |
146 | } |
147 | ||
f67539c2 | 148 | // fallback for arithmetic types and accumulators that do not handle the weight |
92f5a8d4 | 149 | template <class T, class U> |
f67539c2 TL |
150 | auto fill_storage_element_impl(priority<0>, T&& t, const weight_type<U>& w) noexcept |
151 | -> decltype(t += w.value, void()) { | |
92f5a8d4 TL |
152 | t += w.value; |
153 | } | |
154 | ||
f67539c2 TL |
155 | template <class T> |
156 | auto fill_storage_element_impl(priority<1>, T&& t) noexcept -> decltype(++t, void()) { | |
157 | ++t; | |
158 | } | |
159 | ||
92f5a8d4 | 160 | template <class T, class... Us> |
f67539c2 TL |
161 | void fill_storage_element(T&& t, const Us&... args) noexcept { |
162 | fill_storage_element_impl(priority<2>{}, std::forward<T>(t), args...); | |
92f5a8d4 TL |
163 | } |
164 | ||
f67539c2 | 165 | // t may be a proxy and then it is an rvalue reference, not an lvalue reference |
92f5a8d4 TL |
166 | template <class IW, class IS, class T, class U> |
167 | void fill_storage_2(IW, IS, T&& t, U&& u) noexcept { | |
168 | mp11::tuple_apply( | |
f67539c2 TL |
169 | [&](const auto&... args) { |
170 | fill_storage_element(std::forward<T>(t), std::get<IW::value>(u), args...); | |
92f5a8d4 TL |
171 | }, |
172 | std::get<IS::value>(u).value); | |
173 | } | |
174 | ||
f67539c2 | 175 | // t may be a proxy and then it is an rvalue reference, not an lvalue reference |
92f5a8d4 TL |
176 | template <class IS, class T, class U> |
177 | void fill_storage_2(mp11::mp_int<-1>, IS, T&& t, const U& u) noexcept { | |
178 | mp11::tuple_apply( | |
f67539c2 | 179 | [&](const auto&... args) { fill_storage_element(std::forward<T>(t), args...); }, |
92f5a8d4 TL |
180 | std::get<IS::value>(u).value); |
181 | } | |
182 | ||
f67539c2 | 183 | // t may be a proxy and then it is an rvalue reference, not an lvalue reference |
92f5a8d4 TL |
184 | template <class IW, class T, class U> |
185 | void fill_storage_2(IW, mp11::mp_int<-1>, T&& t, const U& u) noexcept { | |
f67539c2 | 186 | fill_storage_element(std::forward<T>(t), std::get<IW::value>(u)); |
92f5a8d4 TL |
187 | } |
188 | ||
f67539c2 | 189 | // t may be a proxy and then it is an rvalue reference, not an lvalue reference |
92f5a8d4 TL |
190 | template <class T, class U> |
191 | void fill_storage_2(mp11::mp_int<-1>, mp11::mp_int<-1>, T&& t, const U&) noexcept { | |
f67539c2 | 192 | fill_storage_element(std::forward<T>(t)); |
92f5a8d4 TL |
193 | } |
194 | ||
195 | template <class IW, class IS, class Storage, class Index, class Args> | |
196 | auto fill_storage(IW, IS, Storage& s, const Index idx, const Args& a) noexcept { | |
197 | if (is_valid(idx)) { | |
198 | BOOST_ASSERT(idx < s.size()); | |
199 | fill_storage_2(IW{}, IS{}, s[idx], a); | |
200 | return s.begin() + idx; | |
201 | } | |
202 | return s.end(); | |
203 | } | |
204 | ||
205 | template <int S, int N> | |
206 | struct linearize_args { | |
207 | template <class Index, class A, class Args> | |
208 | static void impl(mp11::mp_int<N>, Index&, const std::size_t, A&, const Args&) {} | |
209 | ||
210 | template <int I, class Index, class A, class Args> | |
211 | static void impl(mp11::mp_int<I>, Index& o, const std::size_t s, A& ax, | |
212 | const Args& args) { | |
213 | const auto e = linearize(o, s, axis_get<I>(ax), std::get<(S + I)>(args)); | |
214 | impl(mp11::mp_int<(I + 1)>{}, o, s * e, ax, args); | |
215 | } | |
216 | ||
217 | template <class Index, class A, class Args> | |
218 | static void apply(Index& o, A& ax, const Args& args) { | |
219 | impl(mp11::mp_int<0>{}, o, 1, ax, args); | |
220 | } | |
221 | }; | |
222 | ||
223 | template <int S> | |
224 | struct linearize_args<S, 1> { | |
225 | template <class Index, class A, class Args> | |
226 | static void apply(Index& o, A& ax, const Args& args) { | |
227 | linearize(o, 1, axis_get<0>(ax), std::get<S>(args)); | |
228 | } | |
229 | }; | |
230 | ||
231 | template <class A> | |
232 | constexpr unsigned min(const unsigned n) noexcept { | |
233 | constexpr unsigned a = static_cast<unsigned>(buffer_size<A>::value); | |
234 | return a < n ? a : n; | |
235 | } | |
236 | ||
237 | // not growing | |
238 | template <class ArgTraits, class Storage, class Axes, class Args> | |
239 | auto fill_2(ArgTraits, mp11::mp_false, const std::size_t offset, Storage& st, | |
240 | const Axes& axes, const Args& args) { | |
241 | mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{offset}; | |
242 | linearize_args<ArgTraits::start::value, min<Axes>(ArgTraits::nargs::value)>::apply( | |
243 | idx, axes, args); | |
244 | return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx, | |
245 | args); | |
246 | } | |
247 | ||
248 | // at least one axis is growing | |
249 | template <class ArgTraits, class Storage, class Axes, class Args> | |
250 | auto fill_2(ArgTraits, mp11::mp_true, const std::size_t, Storage& st, Axes& axes, | |
251 | const Args& args) { | |
252 | std::array<axis::index_type, ArgTraits::nargs::value> shifts; | |
253 | // offset must be zero for linearize_growth | |
254 | mp11::mp_if<has_non_inclusive_axis<Axes>, optional_index, std::size_t> idx{0}; | |
255 | std::size_t stride = 1; | |
256 | bool update_needed = false; | |
257 | mp11::mp_for_each<mp11::mp_iota_c<min<Axes>(ArgTraits::nargs::value)>>([&](auto i) { | |
258 | auto& ax = axis_get<i>(axes); | |
259 | const auto extent = linearize_growth(idx, shifts[i], stride, ax, | |
260 | std::get<(ArgTraits::start::value + i)>(args)); | |
261 | update_needed |= shifts[i] != 0; | |
262 | stride *= extent; | |
263 | }); | |
264 | if (update_needed) { | |
265 | storage_grower<Axes> g(axes); | |
266 | g.from_shifts(shifts.data()); | |
267 | g.apply(st, shifts.data()); | |
268 | } | |
269 | return fill_storage(typename ArgTraits::wpos{}, typename ArgTraits::spos{}, st, idx, | |
270 | args); | |
271 | } | |
272 | ||
273 | // pack original args tuple into another tuple (which is unpacked later) | |
274 | template <int Start, int Size, class IW, class IS, class Args> | |
275 | decltype(auto) pack_args(IW, IS, const Args& args) noexcept { | |
276 | return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args), | |
277 | std::get<IS::value>(args)); | |
278 | } | |
279 | ||
280 | template <int Start, int Size, class IW, class Args> | |
281 | decltype(auto) pack_args(IW, mp11::mp_int<-1>, const Args& args) noexcept { | |
282 | return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IW::value>(args)); | |
283 | } | |
284 | ||
285 | template <int Start, int Size, class IS, class Args> | |
286 | decltype(auto) pack_args(mp11::mp_int<-1>, IS, const Args& args) noexcept { | |
287 | return std::make_tuple(tuple_slice<Start, Size>(args), std::get<IS::value>(args)); | |
288 | } | |
289 | ||
290 | template <int Start, int Size, class Args> | |
291 | decltype(auto) pack_args(mp11::mp_int<-1>, mp11::mp_int<-1>, const Args& args) noexcept { | |
292 | return std::make_tuple(args); | |
293 | } | |
294 | ||
295 | #if BOOST_WORKAROUND(BOOST_MSVC, >= 0) | |
296 | #pragma warning(disable : 4702) // fixing warning would reduce code readability a lot | |
297 | #endif | |
298 | ||
299 | template <class ArgTraits, class S, class A, class Args> | |
300 | auto fill(std::true_type, ArgTraits, const std::size_t offset, S& storage, A& axes, | |
f67539c2 | 301 | const Args& args) -> typename S::iterator { |
92f5a8d4 TL |
302 | using growing = has_growing_axis<A>; |
303 | ||
304 | // Sometimes we need to pack the tuple into another tuple: | |
305 | // - histogram contains one axis which accepts tuple | |
306 | // - user passes tuple to fill(...) | |
307 | // Tuple is normally unpacked and arguments are processed, this causes pos::nargs > 1. | |
308 | // Now we pack tuple into another tuple so that original tuple is send to axis. | |
309 | // Notes: | |
310 | // - has nice side-effect of making histogram::operator(1, 2) work as well | |
311 | // - cannot detect call signature of axis at compile-time in all configurations | |
312 | // (axis::variant provides generic call interface and hides concrete | |
313 | // interface), so we throw at runtime if incompatible argument is passed (e.g. | |
314 | // 3d tuple) | |
315 | ||
316 | if (axes_rank(axes) == ArgTraits::nargs::value) | |
317 | return fill_2(ArgTraits{}, growing{}, offset, storage, axes, args); | |
318 | else if (axes_rank(axes) == 1 && | |
319 | axis::traits::rank(axis_get<0>(axes)) == ArgTraits::nargs::value) | |
320 | return fill_2( | |
321 | argument_traits_holder< | |
322 | 1, 0, (ArgTraits::wpos::value >= 0 ? 1 : -1), | |
323 | (ArgTraits::spos::value >= 0 ? (ArgTraits::wpos::value >= 0 ? 2 : 1) : -1), | |
324 | typename ArgTraits::sargs>{}, | |
325 | growing{}, offset, storage, axes, | |
326 | pack_args<ArgTraits::start::value, ArgTraits::nargs::value>( | |
327 | typename ArgTraits::wpos{}, typename ArgTraits::spos{}, args)); | |
328 | return (BOOST_THROW_EXCEPTION( | |
329 | std::invalid_argument("number of arguments != histogram rank")), | |
330 | storage.end()); | |
331 | } | |
332 | ||
333 | #if BOOST_WORKAROUND(BOOST_MSVC, >= 0) | |
334 | #pragma warning(default : 4702) | |
335 | #endif | |
336 | ||
337 | // empty implementation for bad arguments to stop compiler from showing internals | |
338 | template <class ArgTraits, class S, class A, class Args> | |
f67539c2 TL |
339 | auto fill(std::false_type, ArgTraits, const std::size_t, S& storage, A&, const Args&) -> |
340 | typename S::iterator { | |
92f5a8d4 TL |
341 | return storage.end(); |
342 | } | |
343 | ||
344 | } // namespace detail | |
345 | } // namespace histogram | |
346 | } // namespace boost | |
347 | ||
348 | #endif |