]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/histogram/test/algorithm_reduce_test.cpp
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / boost / libs / histogram / test / algorithm_reduce_test.cpp
1 // Copyright 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 #include <boost/core/lightweight_test.hpp>
8 #include <boost/histogram/algorithm/reduce.hpp>
9 #include <boost/histogram/algorithm/sum.hpp>
10 #include <boost/histogram/axis/category.hpp>
11 #include <boost/histogram/axis/integer.hpp>
12 #include <boost/histogram/axis/ostream.hpp>
13 #include <boost/histogram/axis/regular.hpp>
14 #include <boost/histogram/axis/variable.hpp>
15 #include <boost/histogram/ostream.hpp>
16 #include <boost/histogram/unsafe_access.hpp>
17 #include <vector>
18 #include "throw_exception.hpp"
19 #include "utility_histogram.hpp"
20
21 using namespace boost::histogram;
22 using namespace boost::histogram::algorithm;
23
24 struct unreducible {
25 axis::index_type index(int) const { return 0; }
26 axis::index_type size() const { return 1; }
27 friend std::ostream& operator<<(std::ostream& os, const unreducible&) {
28 os << "unreducible";
29 return os;
30 }
31 };
32
33 template <typename Tag>
34 void run_tests() {
35 // limitations: shrink does not work with arguments not convertible to double
36
37 using R = axis::regular<double>;
38 using ID = axis::integer<double, axis::empty_type>;
39 using V = axis::variable<double, axis::empty_type>;
40 using CI = axis::category<int, axis::empty_type>;
41
42 // various failures
43 {
44 auto h = make(Tag(), R(4, 1, 5), R(3, -1, 2));
45
46 // not allowed: invalid axis index
47 BOOST_TEST_THROWS((void)reduce(h, slice(10, 2, 3)), std::invalid_argument);
48 // two slice requests for same axis not allowed
49 BOOST_TEST_THROWS((void)reduce(h, slice(1, 0, 2), slice(1, 1, 3)),
50 std::invalid_argument);
51 // two rebin requests for same axis not allowed
52 BOOST_TEST_THROWS((void)reduce(h, rebin(0, 2), rebin(0, 2)), std::invalid_argument);
53 // rebin and slice_and_rebin with merge > 1 requests for same axis cannot be fused
54 BOOST_TEST_THROWS((void)reduce(h, slice_and_rebin(0, 1, 3, 2), rebin(0, 2)),
55 std::invalid_argument);
56 BOOST_TEST_THROWS((void)reduce(h, shrink(1, 0, 2), crop(1, 0, 2)),
57 std::invalid_argument);
58 // not allowed: slice with begin >= end
59 BOOST_TEST_THROWS((void)reduce(h, slice(0, 1, 1)), std::invalid_argument);
60 BOOST_TEST_THROWS((void)reduce(h, slice(0, 2, 1)), std::invalid_argument);
61 // not allowed: shrink with lower == upper
62 BOOST_TEST_THROWS((void)reduce(h, shrink(0, 0, 0)), std::invalid_argument);
63 // not allowed: crop with lower == upper
64 BOOST_TEST_THROWS((void)reduce(h, crop(0, 0, 0)), std::invalid_argument);
65 // not allowed: shrink axis to zero size
66 BOOST_TEST_THROWS((void)reduce(h, shrink(0, 10, 11)), std::invalid_argument);
67 // not allowed: rebin with zero merge
68 BOOST_TEST_THROWS((void)reduce(h, rebin(0, 0)), std::invalid_argument);
69 // not allowed: reducing unreducible axis
70 BOOST_TEST_THROWS((void)reduce(make(Tag(), unreducible{}), slice(0, 1)),
71 std::invalid_argument);
72 }
73
74 // shrink and crop behavior when value on edge and not on edge is inclusive:
75 // - lower edge of shrink: pick bin which contains edge, lower <= x < upper
76 // - upper edge of shrink: pick bin which contains edge + 1, lower < x <= upper
77 {
78 auto h = make(Tag(), ID(0, 3));
79 const auto& ax = h.axis();
80 BOOST_TEST_EQ(ax.value(0), 0);
81 BOOST_TEST_EQ(ax.value(3), 3);
82 BOOST_TEST_EQ(ax.index(-1), -1);
83 BOOST_TEST_EQ(ax.index(3), 3);
84
85 BOOST_TEST_EQ(reduce(h, shrink(-1, 5)).axis(), ID(0, 3));
86 BOOST_TEST_EQ(reduce(h, shrink(0, 3)).axis(), ID(0, 3));
87 BOOST_TEST_EQ(reduce(h, shrink(1, 3)).axis(), ID(1, 3));
88 BOOST_TEST_EQ(reduce(h, shrink(1.001, 3)).axis(), ID(1, 3));
89 BOOST_TEST_EQ(reduce(h, shrink(1.999, 3)).axis(), ID(1, 3));
90 BOOST_TEST_EQ(reduce(h, shrink(2, 3)).axis(), ID(2, 3));
91 BOOST_TEST_EQ(reduce(h, shrink(0, 2.999)).axis(), ID(0, 3));
92 BOOST_TEST_EQ(reduce(h, shrink(0, 2.001)).axis(), ID(0, 3));
93 BOOST_TEST_EQ(reduce(h, shrink(0, 2)).axis(), ID(0, 2));
94 BOOST_TEST_EQ(reduce(h, shrink(0, 1.999)).axis(), ID(0, 2));
95
96 BOOST_TEST_EQ(reduce(h, crop(-1, 5)).axis(), ID(0, 3));
97 BOOST_TEST_EQ(reduce(h, crop(0, 3)).axis(), ID(0, 3));
98 BOOST_TEST_EQ(reduce(h, crop(1, 3)).axis(), ID(1, 3));
99 BOOST_TEST_EQ(reduce(h, crop(1.001, 3)).axis(), ID(1, 3));
100 BOOST_TEST_EQ(reduce(h, crop(1.999, 3)).axis(), ID(1, 3));
101 BOOST_TEST_EQ(reduce(h, crop(2, 3)).axis(), ID(2, 3));
102 BOOST_TEST_EQ(reduce(h, crop(0, 2.999)).axis(), ID(0, 3));
103 BOOST_TEST_EQ(reduce(h, crop(0, 2.001)).axis(), ID(0, 3));
104 BOOST_TEST_EQ(reduce(h, crop(0, 2)).axis(), ID(0, 2));
105 BOOST_TEST_EQ(reduce(h, crop(0, 1.999)).axis(), ID(0, 2));
106 }
107
108 // shrink and rebin
109 {
110 auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5, "1"), R(3, -1, 2, "2"));
111
112 /*
113 matrix layout:
114 x ->
115 y 1 0 1 0
116 | 1 1 0 0
117 v 0 2 1 3
118 */
119 h.at(0, 0) = 1;
120 h.at(0, 1) = 1;
121 h.at(1, 1) = 1;
122 h.at(1, 2) = 2;
123 h.at(2, 0) = 1;
124 h.at(2, 2) = 1;
125 h.at(3, 2) = 3;
126 h.at(-1, -1) = 1; // underflow
127 h.at(4, 3) = 1; // overflow
128
129 // should do nothing, index order does not matter
130 auto hr = reduce(h, shrink(1, -1, 2), rebin(0, 1));
131 BOOST_TEST_EQ(hr.rank(), 2);
132 BOOST_TEST_EQ(sum(hr), 12);
133 BOOST_TEST_EQ(hr.axis(0), R(4, 1, 5, "1"));
134 BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
135 BOOST_TEST_EQ(hr, h);
136
137 // noop slice
138 hr = reduce(h, slice(1, 0, 4), slice(0, 0, 4));
139 BOOST_TEST_EQ(hr, h);
140
141 // shrinking along first axis
142 hr = reduce(h, shrink(0, 2, 4));
143 BOOST_TEST_EQ(hr.rank(), 2);
144 BOOST_TEST_EQ(sum(hr), 12);
145 BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4, "1"));
146 BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
147 BOOST_TEST_EQ(hr.at(-1, 0), 1); // underflow
148 BOOST_TEST_EQ(hr.at(0, 0), 0);
149 BOOST_TEST_EQ(hr.at(1, 0), 1);
150 BOOST_TEST_EQ(hr.at(2, 0), 0); // overflow
151 BOOST_TEST_EQ(hr.at(-1, 1), 1);
152 BOOST_TEST_EQ(hr.at(0, 1), 1);
153 BOOST_TEST_EQ(hr.at(1, 1), 0);
154 BOOST_TEST_EQ(hr.at(2, 1), 0);
155 BOOST_TEST_EQ(hr.at(-1, 2), 0);
156 BOOST_TEST_EQ(hr.at(0, 2), 2);
157 BOOST_TEST_EQ(hr.at(1, 2), 1);
158 BOOST_TEST_EQ(hr.at(2, 2), 3);
159
160 /*
161 matrix layout:
162 x
163 y 1 0 1 0
164 1 1 0 0
165 0 2 1 3
166 */
167
168 hr = reduce(h, shrink_and_rebin(0, 2, 5, 2), rebin(1, 3));
169 BOOST_TEST_EQ(hr.rank(), 2);
170 BOOST_TEST_EQ(sum(hr), 12);
171 BOOST_TEST_EQ(hr.axis(0), R(1, 2, 4, "1"));
172 BOOST_TEST_EQ(hr.axis(1), R(1, -1, 2, "2"));
173 BOOST_TEST_EQ(hr.at(-1, 0), 2); // underflow
174 BOOST_TEST_EQ(hr.at(0, 0), 5);
175 BOOST_TEST_EQ(hr.at(1, 0), 3); // overflow
176
177 // test overload that accepts iterable and test option fusion
178 std::vector<reduce_command> opts{{shrink(0, 2, 5), rebin(0, 2), rebin(1, 3)}};
179 auto hr2 = reduce(h, opts);
180 BOOST_TEST_EQ(hr2, hr);
181 reduce_command opts2[3] = {rebin(1, 3), rebin(0, 2), shrink(0, 2, 5)};
182 auto hr3 = reduce(h, opts2);
183 BOOST_TEST_EQ(hr3, hr);
184
185 // test positional args
186 auto hr4 = reduce(h, shrink_and_rebin(2, 5, 2), rebin(3));
187 BOOST_TEST_EQ(hr4, hr);
188 }
189
190 // crop and rebin
191 {
192 auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5), R(3, 1, 4));
193
194 /*
195 matrix layout:
196 x ->
197 y 1 0 1 0
198 | 1 1 0 0
199 v 0 2 1 3
200 */
201 h.at(0, 0) = 1;
202 h.at(0, 1) = 1;
203 h.at(1, 1) = 1;
204 h.at(1, 2) = 2;
205 h.at(2, 0) = 1;
206 h.at(2, 2) = 1;
207 h.at(3, 2) = 3;
208 h.at(-1, -1) = 1; // underflow
209 h.at(4, 3) = 1; // overflow
210
211 /*
212 crop first and last column in x and y
213 matrix layout after:
214 x
215 y 3 1
216 */
217
218 auto hr = reduce(h, crop(2, 4), crop_and_rebin(2, 4, 2));
219 BOOST_TEST_EQ(hr.rank(), 2);
220 BOOST_TEST_EQ(sum(hr), 4);
221 BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4));
222 BOOST_TEST_EQ(hr.axis(1), R(1, 2, 4));
223 BOOST_TEST_EQ(hr.at(0, 0), 3);
224 BOOST_TEST_EQ(hr.at(1, 0), 1);
225
226 // slice with crop mode
227 auto hr2 = reduce(h, slice(1, 3, slice_mode::crop),
228 slice_and_rebin(1, 3, 2, slice_mode::crop));
229 BOOST_TEST_EQ(hr, hr2);
230
231 // explicit axis indices
232 auto hr3 = reduce(h, crop_and_rebin(1, 2, 4, 2), crop(0, 2, 4));
233 BOOST_TEST_EQ(hr, hr3);
234 auto hr4 = reduce(h, slice_and_rebin(1, 1, 3, 2, slice_mode::crop),
235 slice(0, 1, 3, slice_mode::crop));
236 BOOST_TEST_EQ(hr, hr4);
237 }
238
239 // one-sided crop
240 {
241 auto h = make_s(Tag(), std::vector<int>(), ID(1, 4));
242 std::fill(h.begin(), h.end(), 1);
243 // underflow: 1
244 // index 0, x 1: 1
245 // index 1, x 2: 1
246 // index 2, x 3: 1
247 // overflow: 1
248 BOOST_TEST_EQ(sum(h), 5);
249 BOOST_TEST_EQ(h.size(), 5);
250
251 // keep underflow
252 auto hr1 = reduce(h, crop(0, 3));
253 BOOST_TEST_EQ(hr1, reduce(h, slice(-1, 2, slice_mode::crop)));
254 BOOST_TEST_EQ(sum(hr1), 3);
255 BOOST_TEST_EQ(hr1.size(), 4); // flow bins are not physically removed, only zeroed
256
257 // remove underflow
258 auto hr2 = reduce(h, crop(1, 3));
259 BOOST_TEST_EQ(hr2, reduce(h, slice(0, 2, slice_mode::crop)));
260 BOOST_TEST_EQ(sum(hr2), 2);
261 BOOST_TEST_EQ(hr2.size(), 4); // flow bins are not physically removed, only zeroed
262
263 // keep overflow
264 auto hr3 = reduce(h, crop(2, 5));
265 BOOST_TEST_EQ(hr3, reduce(h, slice(1, 4, slice_mode::crop)));
266 BOOST_TEST_EQ(sum(hr3), 3);
267 BOOST_TEST_EQ(hr3.size(), 4); // flow bins are not physically removed, only zeroed
268
269 // remove overflow
270 auto hr4 = reduce(h, crop(2, 4));
271 BOOST_TEST_EQ(hr4, reduce(h, slice(1, 3, slice_mode::crop)));
272 BOOST_TEST_EQ(sum(hr4), 2);
273 BOOST_TEST_EQ(hr4.size(), 4); // flow bins are not physically removed, only zeroed
274 }
275
276 // one-sided crop and rebin with regular axis
277 {
278 auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5));
279 std::fill(h.begin(), h.end(), 1);
280 // underflow: 1
281 // index 0, x [1, 2): 1
282 // index 1, x [2, 3): 1
283 // index 2, x [3, 4): 1
284 // index 3, x [4, 5): 1
285 // overflow: 1
286 BOOST_TEST_EQ(sum(h), 6);
287 BOOST_TEST_EQ(h.size(), 6);
288
289 // keep underflow
290 auto hr1 = reduce(h, crop_and_rebin(0, 5, 2));
291 BOOST_TEST_EQ(sum(hr1), 5);
292 BOOST_TEST_EQ(hr1.size(), 4); // flow bins are not physically removed, only zeroed
293 auto hr2 = reduce(h, crop_and_rebin(0, 3, 2));
294 BOOST_TEST_EQ(sum(hr2), 3);
295 BOOST_TEST_EQ(hr2.size(), 3); // flow bins are not physically removed, only zeroed
296
297 // remove underflow but keep overflow
298 auto hr3 = reduce(h, crop_and_rebin(1, 6, 2));
299 BOOST_TEST_EQ(sum(hr3), 5);
300 BOOST_TEST_EQ(hr3.size(), 4); // flow bins are not physically removed, only zeroed
301
302 // remove underflow and overflow
303 auto hr4 = reduce(h, crop_and_rebin(1, 3, 2));
304 BOOST_TEST_EQ(sum(hr4), 2);
305 BOOST_TEST_EQ(hr4.size(), 3); // flow bins are not physically removed, only zeroed
306 }
307
308 // mixed axis types
309 {
310 R r(5, 0.0, 5.0);
311 V v{{1., 2., 3.}};
312 CI c{{1, 2, 3}};
313 unreducible u;
314
315 auto h = make(Tag(), r, v, c, u);
316 auto hr = algorithm::reduce(h, shrink(0, 2, 4), slice(2, 1, 3));
317 BOOST_TEST_EQ(hr.axis(0), (R{2, 2, 4}));
318 BOOST_TEST_EQ(hr.axis(1), (V{{1., 2., 3.}}));
319 BOOST_TEST_EQ(hr.axis(2), (CI{{2, 3}}));
320 BOOST_TEST_EQ(hr.axis(3), u);
321 BOOST_TEST_THROWS((void)algorithm::reduce(h, rebin(2, 2)), std::invalid_argument);
322 }
323
324 // reduce on integer axis, rebin must fail
325 {
326 auto h = make(Tag(), axis::integer<>(1, 4));
327 BOOST_TEST_THROWS((void)reduce(h, rebin(2)), std::invalid_argument);
328 auto hr = reduce(h, shrink(2, 3));
329 BOOST_TEST_EQ(hr.axis().size(), 1);
330 BOOST_TEST_EQ(hr.axis().bin(0), 2);
331 BOOST_TEST_EQ(hr.axis().bin(1), 3);
332 }
333
334 // reduce on circular axis, shrink must fail, also rebin with remainder
335 {
336 auto h = make(Tag(), axis::circular<>(4, 1, 4));
337 BOOST_TEST_THROWS((void)reduce(h, shrink(0, 2)), std::invalid_argument);
338 BOOST_TEST_THROWS((void)reduce(h, rebin(3)), std::invalid_argument);
339 auto hr = reduce(h, rebin(2));
340 BOOST_TEST_EQ(hr.axis().size(), 2);
341 BOOST_TEST_EQ(hr.axis().bin(0).lower(), 1);
342 BOOST_TEST_EQ(hr.axis().bin(1).upper(), 4);
343 }
344
345 // reduce on variable axis
346 {
347 auto h = make(Tag(), V({0, 1, 2, 3, 4, 5, 6}));
348 auto hr = reduce(h, shrink_and_rebin(1, 5, 2));
349 BOOST_TEST_EQ(hr.axis().size(), 2);
350 BOOST_TEST_EQ(hr.axis().value(0), 1);
351 BOOST_TEST_EQ(hr.axis().value(1), 3);
352 BOOST_TEST_EQ(hr.axis().value(2), 5);
353 }
354
355 // reduce on axis with inverted range
356 {
357 auto h = make(Tag(), R(4, 2, -2));
358 const auto& ax = h.axis();
359 BOOST_TEST_EQ(ax.index(-0.999), 2);
360 BOOST_TEST_EQ(ax.index(-1.0), 3);
361 BOOST_TEST_EQ(ax.index(-1.5), 3);
362
363 BOOST_TEST_EQ(reduce(h, shrink(3, -3)).axis(), R(4, 2, -2));
364 BOOST_TEST_EQ(reduce(h, shrink(2, -2)).axis(), R(4, 2, -2));
365 BOOST_TEST_EQ(reduce(h, shrink(1.999, -2)).axis(), R(4, 2, -2));
366 BOOST_TEST_EQ(reduce(h, shrink(1.001, -2)).axis(), R(4, 2, -2));
367 BOOST_TEST_EQ(reduce(h, shrink(1, -2)).axis(), R(3, 1, -2));
368 BOOST_TEST_EQ(reduce(h, shrink(2, -1.999)).axis(), R(4, 2, -2));
369 BOOST_TEST_EQ(reduce(h, shrink(2, -1.001)).axis(), R(4, 2, -2));
370 BOOST_TEST_EQ(reduce(h, shrink(2, -1)).axis(), R(3, 2, -1));
371 }
372
373 // reduce on histogram with axis without flow bins, see GitHub issue #257
374 {
375 auto h = make(Tag(), axis::integer<int, use_default, axis::option::underflow_t>(0, 3),
376 axis::integer<int, use_default, axis::option::overflow_t>(0, 3));
377
378 std::fill(h.begin(), h.end(), 1);
379
380 /*
381 Original histogram:
382 x
383 -1 0 1 2
384 -------------
385 0| 1 1 1 1
386 x 1| 1 1 1 1
387 2| 1 1 1 1
388 3| 1 1 1 1
389
390 Shrunk histogram:
391 -1 0
392 -------
393 0| 2 1
394 1| 4 2
395 */
396
397 auto hr = reduce(h, slice(0, 1, 2), slice(1, 1, 2));
398 BOOST_TEST_EQ(hr.size(), 2 * 2);
399 BOOST_TEST_EQ(hr.axis(0).size(), 1);
400 BOOST_TEST_EQ(hr.axis(1).size(), 1);
401 BOOST_TEST_EQ(hr.axis(0).bin(0), 1);
402 BOOST_TEST_EQ(hr.axis(1).bin(0), 1);
403
404 BOOST_TEST_EQ(hr.at(-1, 0), 2);
405 BOOST_TEST_EQ(hr.at(0, 0), 1);
406 BOOST_TEST_EQ(hr.at(-1, 1), 4);
407 BOOST_TEST_EQ(hr.at(0, 1), 2);
408 }
409
410 // reduce on category axis: removed bins are added to overflow bin
411 {
412 auto h = make(Tag(), CI{{1, 2, 3}});
413 std::fill(h.begin(), h.end(), 1);
414 // original: [1: 1, 2: 1, 3: 1, overflow: 1]
415 auto hr = reduce(h, slice(1, 2));
416 // reduced: [2: 1, overflow: 3]
417 BOOST_TEST_EQ(hr[0], 1);
418 BOOST_TEST_EQ(hr[1], 3);
419 }
420 }
421
422 int main() {
423 run_tests<static_tag>();
424 run_tests<dynamic_tag>();
425
426 return boost::report_errors();
427 }