• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
indexunreducible25   axis::index_type index(int) const { return 0; }
sizeunreducible26   axis::index_type size() const { return 1; }
operator <<(std::ostream & os,const unreducible &)27   friend std::ostream& operator<<(std::ostream& os, const unreducible&) {
28     os << "unreducible";
29     return os;
30   }
31 };
32 
33 template <typename Tag>
run_tests()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   {
109     auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5, "1"), R(3, -1, 2, "2"));
110 
111     /*
112       matrix layout:
113       x
114     y 1 0 1 0
115       1 1 0 0
116       0 2 1 3
117     */
118     h.at(0, 0) = 1;
119     h.at(0, 1) = 1;
120     h.at(1, 1) = 1;
121     h.at(1, 2) = 2;
122     h.at(2, 0) = 1;
123     h.at(2, 2) = 1;
124     h.at(3, 2) = 3;
125 
126     // should do nothing, index order does not matter
127     auto hr = reduce(h, shrink(1, -1, 2), rebin(0, 1));
128     BOOST_TEST_EQ(hr.rank(), 2);
129     BOOST_TEST_EQ(sum(hr), 10);
130     BOOST_TEST_EQ(hr.axis(0), R(4, 1, 5, "1"));
131     BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
132     BOOST_TEST_EQ(hr, h);
133 
134     // noop slice
135     hr = reduce(h, slice(1, 0, 4), slice(0, 0, 4));
136     BOOST_TEST_EQ(hr, h);
137 
138     // shrinking along first axis
139     hr = reduce(h, shrink(0, 2, 4));
140     BOOST_TEST_EQ(hr.rank(), 2);
141     BOOST_TEST_EQ(sum(hr), 10);
142     BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4, "1"));
143     BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2, "2"));
144     BOOST_TEST_EQ(hr.at(-1, 0), 1); // underflow
145     BOOST_TEST_EQ(hr.at(0, 0), 0);
146     BOOST_TEST_EQ(hr.at(1, 0), 1);
147     BOOST_TEST_EQ(hr.at(2, 0), 0); // overflow
148     BOOST_TEST_EQ(hr.at(-1, 1), 1);
149     BOOST_TEST_EQ(hr.at(0, 1), 1);
150     BOOST_TEST_EQ(hr.at(1, 1), 0);
151     BOOST_TEST_EQ(hr.at(2, 1), 0);
152     BOOST_TEST_EQ(hr.at(-1, 2), 0);
153     BOOST_TEST_EQ(hr.at(0, 2), 2);
154     BOOST_TEST_EQ(hr.at(1, 2), 1);
155     BOOST_TEST_EQ(hr.at(2, 2), 3);
156 
157     /*
158       matrix layout:
159       x
160     y 1 0 1 0
161       1 1 0 0
162       0 2 1 3
163     */
164 
165     hr = reduce(h, shrink_and_rebin(0, 2, 5, 2), rebin(1, 3));
166     BOOST_TEST_EQ(hr.rank(), 2);
167     BOOST_TEST_EQ(sum(hr), 10);
168     BOOST_TEST_EQ(hr.axis(0), R(1, 2, 4, "1"));
169     BOOST_TEST_EQ(hr.axis(1), R(1, -1, 2, "2"));
170     BOOST_TEST_EQ(hr.at(-1, 0), 2); // underflow
171     BOOST_TEST_EQ(hr.at(0, 0), 5);
172     BOOST_TEST_EQ(hr.at(1, 0), 3); // overflow
173 
174     // test overload that accepts iterable and test option fusion
175     std::vector<reduce_command> opts{{shrink(0, 2, 5), rebin(0, 2), rebin(1, 3)}};
176     auto hr2 = reduce(h, opts);
177     BOOST_TEST_EQ(hr2, hr);
178     reduce_command opts2[3] = {rebin(1, 3), rebin(0, 2), shrink(0, 2, 5)};
179     auto hr3 = reduce(h, opts2);
180     BOOST_TEST_EQ(hr3, hr);
181 
182     // test positional args
183     auto hr4 = reduce(h, shrink_and_rebin(2, 5, 2), rebin(3));
184     BOOST_TEST_EQ(hr4, hr);
185   }
186 
187   // crop
188   {
189     auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5), R(3, 1, 4));
190 
191     /*
192       matrix layout:
193       x
194     y 1 0 1 0
195       1 1 0 0
196       0 2 1 3
197     */
198     h.at(0, 0) = 1;
199     h.at(0, 1) = 1;
200     h.at(1, 1) = 1;
201     h.at(1, 2) = 2;
202     h.at(2, 0) = 1;
203     h.at(2, 2) = 1;
204     h.at(3, 2) = 3;
205 
206     /*
207       crop first and last column in x and y
208       matrix layout after:
209       x
210     y 3 1
211     */
212 
213     auto hr = reduce(h, crop(2, 4), crop_and_rebin(2, 4, 2));
214     BOOST_TEST_EQ(hr.rank(), 2);
215     BOOST_TEST_EQ(sum(hr), 4);
216     BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4));
217     BOOST_TEST_EQ(hr.axis(1), R(1, 2, 4));
218     BOOST_TEST_EQ(hr.at(0, 0), 3);
219     BOOST_TEST_EQ(hr.at(1, 0), 1);
220 
221     // slice with crop mode
222     auto hr2 = reduce(h, slice(1, 3, slice_mode::crop),
223                       slice_and_rebin(1, 3, 2, slice_mode::crop));
224     BOOST_TEST_EQ(hr, hr2);
225 
226     // explicit axis indices
227     auto hr3 = reduce(h, crop_and_rebin(1, 2, 4, 2), crop(0, 2, 4));
228     BOOST_TEST_EQ(hr, hr3);
229     auto hr4 = reduce(h, slice_and_rebin(1, 1, 3, 2, slice_mode::crop),
230                       slice(0, 1, 3, slice_mode::crop));
231     BOOST_TEST_EQ(hr, hr4);
232   }
233 
234   // mixed axis types
235   {
236     R r(5, 0.0, 5.0);
237     V v{{1., 2., 3.}};
238     CI c{{1, 2, 3}};
239     unreducible u;
240 
241     auto h = make(Tag(), r, v, c, u);
242     auto hr = algorithm::reduce(h, shrink(0, 2, 4), slice(2, 1, 3));
243     BOOST_TEST_EQ(hr.axis(0), (R{2, 2, 4}));
244     BOOST_TEST_EQ(hr.axis(1), (V{{1., 2., 3.}}));
245     BOOST_TEST_EQ(hr.axis(2), (CI{{2, 3}}));
246     BOOST_TEST_EQ(hr.axis(3), u);
247     BOOST_TEST_THROWS((void)algorithm::reduce(h, rebin(2, 2)), std::invalid_argument);
248   }
249 
250   // reduce on integer axis, rebin must fail
251   {
252     auto h = make(Tag(), axis::integer<>(1, 4));
253     BOOST_TEST_THROWS((void)reduce(h, rebin(2)), std::invalid_argument);
254     auto hr = reduce(h, shrink(2, 3));
255     BOOST_TEST_EQ(hr.axis().size(), 1);
256     BOOST_TEST_EQ(hr.axis().bin(0), 2);
257     BOOST_TEST_EQ(hr.axis().bin(1), 3);
258   }
259 
260   // reduce on circular axis, shrink must fail, also rebin with remainder
261   {
262     auto h = make(Tag(), axis::circular<>(4, 1, 4));
263     BOOST_TEST_THROWS((void)reduce(h, shrink(0, 2)), std::invalid_argument);
264     BOOST_TEST_THROWS((void)reduce(h, rebin(3)), std::invalid_argument);
265     auto hr = reduce(h, rebin(2));
266     BOOST_TEST_EQ(hr.axis().size(), 2);
267     BOOST_TEST_EQ(hr.axis().bin(0).lower(), 1);
268     BOOST_TEST_EQ(hr.axis().bin(1).upper(), 4);
269   }
270 
271   // reduce on variable axis
272   {
273     auto h = make(Tag(), V({0, 1, 2, 3, 4, 5, 6}));
274     auto hr = reduce(h, shrink_and_rebin(1, 5, 2));
275     BOOST_TEST_EQ(hr.axis().size(), 2);
276     BOOST_TEST_EQ(hr.axis().value(0), 1);
277     BOOST_TEST_EQ(hr.axis().value(1), 3);
278     BOOST_TEST_EQ(hr.axis().value(2), 5);
279   }
280 
281   // reduce on axis with inverted range
282   {
283     auto h = make(Tag(), R(4, 2, -2));
284     const auto& ax = h.axis();
285     BOOST_TEST_EQ(ax.index(-0.999), 2);
286     BOOST_TEST_EQ(ax.index(-1.0), 3);
287     BOOST_TEST_EQ(ax.index(-1.5), 3);
288 
289     BOOST_TEST_EQ(reduce(h, shrink(3, -3)).axis(), R(4, 2, -2));
290     BOOST_TEST_EQ(reduce(h, shrink(2, -2)).axis(), R(4, 2, -2));
291     BOOST_TEST_EQ(reduce(h, shrink(1.999, -2)).axis(), R(4, 2, -2));
292     BOOST_TEST_EQ(reduce(h, shrink(1.001, -2)).axis(), R(4, 2, -2));
293     BOOST_TEST_EQ(reduce(h, shrink(1, -2)).axis(), R(3, 1, -2));
294     BOOST_TEST_EQ(reduce(h, shrink(2, -1.999)).axis(), R(4, 2, -2));
295     BOOST_TEST_EQ(reduce(h, shrink(2, -1.001)).axis(), R(4, 2, -2));
296     BOOST_TEST_EQ(reduce(h, shrink(2, -1)).axis(), R(3, 2, -1));
297   }
298 
299   // reduce on histogram with axis without flow bins, see GitHub issue #257
300   {
301     auto h = make(Tag(), axis::integer<int, use_default, axis::option::underflow_t>(0, 3),
302                   axis::integer<int, use_default, axis::option::overflow_t>(0, 3));
303 
304     std::fill(h.begin(), h.end(), 1);
305 
306     /*
307     Original histogram:
308                 x
309          -1  0  1  2
310        -------------
311       0|  1  1  1  1
312     x 1|  1  1  1  1
313       2|  1  1  1  1
314       3|  1  1  1  1
315 
316     Shrunk histogram:
317          -1  0
318        -------
319       0|  2  1
320       1|  4  2
321     */
322 
323     auto hr = reduce(h, slice(0, 1, 2), slice(1, 1, 2));
324     BOOST_TEST_EQ(hr.size(), 2 * 2);
325     BOOST_TEST_EQ(hr.axis(0).size(), 1);
326     BOOST_TEST_EQ(hr.axis(1).size(), 1);
327     BOOST_TEST_EQ(hr.axis(0).bin(0), 1);
328     BOOST_TEST_EQ(hr.axis(1).bin(0), 1);
329 
330     BOOST_TEST_EQ(hr.at(-1, 0), 2);
331     BOOST_TEST_EQ(hr.at(0, 0), 1);
332     BOOST_TEST_EQ(hr.at(-1, 1), 4);
333     BOOST_TEST_EQ(hr.at(0, 1), 2);
334   }
335 
336   // reduce on category axis: removed bins are added to overflow bin
337   {
338     auto h = make(Tag(), CI{{1, 2, 3}});
339     std::fill(h.begin(), h.end(), 1);
340     // original: [1: 1, 2: 1, 3: 1, overflow: 1]
341     auto hr = reduce(h, slice(1, 2));
342     // reduced: [2: 1, overflow: 3]
343     BOOST_TEST_EQ(hr[0], 1);
344     BOOST_TEST_EQ(hr[1], 3);
345   }
346 }
347 
main()348 int main() {
349   run_tests<static_tag>();
350   run_tests<dynamic_tag>();
351 
352   return boost::report_errors();
353 }
354