Imported Upstream version 1.72.0
[platform/upstream/boost.git] / 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 template <typename Tag>
25 void run_tests() {
26   // reduce:
27   // - does not work with arguments not convertible to double
28   // - does not work with category axis, which is not ordered
29
30   using R = axis::regular<double, axis::transform::id, axis::null_type>;
31   using ID = axis::integer<double, axis::null_type>;
32   using V = axis::variable<double, axis::empty_type>;
33   using CI = axis::category<int, axis::empty_type>;
34
35   // various failures
36   {
37     auto h = make(Tag(), R(4, 1, 5), R(3, -1, 2));
38
39     // not allowed: invalid axis index
40     BOOST_TEST_THROWS((void)reduce(h, slice(10, 2, 3)), std::invalid_argument);
41     // not allowed: repeated indices
42     BOOST_TEST_THROWS((void)reduce(h, slice(1, 0, 2), slice(1, 1, 3)),
43                       std::invalid_argument);
44     BOOST_TEST_THROWS((void)reduce(h, rebin(0, 2), rebin(0, 2)), std::invalid_argument);
45     BOOST_TEST_THROWS((void)reduce(h, shrink(1, 0, 2), shrink(1, 0, 2)),
46                       std::invalid_argument);
47     // not allowed: slice with begin >= end
48     BOOST_TEST_THROWS((void)reduce(h, slice(0, 1, 1)), std::invalid_argument);
49     BOOST_TEST_THROWS((void)reduce(h, slice(0, 2, 1)), std::invalid_argument);
50     // not allowed: shrink with lower == upper
51     BOOST_TEST_THROWS((void)reduce(h, shrink(0, 0, 0)), std::invalid_argument);
52     // not allowed: shrink axis to zero size
53     BOOST_TEST_THROWS((void)reduce(h, shrink(0, 10, 11)), std::invalid_argument);
54     // not allowed: rebin with zero merge
55     BOOST_TEST_THROWS((void)reduce(h, rebin(0, 0)), std::invalid_argument);
56   }
57
58   // shrink behavior when value on edge and not on edge is inclusive:
59   // - lower edge of shrink: pick bin which contains edge, lower <= x < upper
60   // - upper edge of shrink: pick bin which contains edge + 1, lower < x <= upper
61   {
62     auto h = make(Tag(), ID(0, 3));
63     const auto& ax = h.axis();
64     BOOST_TEST_EQ(ax.value(0), 0);
65     BOOST_TEST_EQ(ax.value(1), 1);
66     BOOST_TEST_EQ(ax.value(2), 2);
67     BOOST_TEST_EQ(ax.value(3), 3);
68     BOOST_TEST_EQ(ax.index(-1), -1);
69     BOOST_TEST_EQ(ax.index(0), 0);
70     BOOST_TEST_EQ(ax.index(1), 1);
71     BOOST_TEST_EQ(ax.index(2), 2);
72     BOOST_TEST_EQ(ax.index(3), 3);
73
74     BOOST_TEST_EQ(reduce(h, shrink(-1, 5)).axis(), ID(0, 3));
75     BOOST_TEST_EQ(reduce(h, shrink(0, 3)).axis(), ID(0, 3));
76     BOOST_TEST_EQ(reduce(h, shrink(1, 3)).axis(), ID(1, 3));
77     BOOST_TEST_EQ(reduce(h, shrink(1.001, 3)).axis(), ID(1, 3));
78     BOOST_TEST_EQ(reduce(h, shrink(1.999, 3)).axis(), ID(1, 3));
79     BOOST_TEST_EQ(reduce(h, shrink(2, 3)).axis(), ID(2, 3));
80     BOOST_TEST_EQ(reduce(h, shrink(0, 2.999)).axis(), ID(0, 3));
81     BOOST_TEST_EQ(reduce(h, shrink(0, 2.001)).axis(), ID(0, 3));
82     BOOST_TEST_EQ(reduce(h, shrink(0, 2)).axis(), ID(0, 2));
83     BOOST_TEST_EQ(reduce(h, shrink(0, 1.999)).axis(), ID(0, 2));
84   }
85
86   {
87     auto h = make_s(Tag(), std::vector<int>(), R(4, 1, 5), R(3, -1, 2));
88
89     /*
90       matrix layout:
91       x ->
92     y 1 0 1 0
93     | 1 1 0 0
94     v 0 2 1 3
95     */
96     h.at(0, 0) = 1;
97     h.at(0, 1) = 1;
98     h.at(1, 1) = 1;
99     h.at(1, 2) = 2;
100     h.at(2, 0) = 1;
101     h.at(2, 2) = 1;
102     h.at(3, 2) = 3;
103
104     // should do nothing, index order does not matter
105     auto hr = reduce(h, shrink(1, -1, 2), rebin(0, 1));
106     BOOST_TEST_EQ(hr.rank(), 2);
107     BOOST_TEST_EQ(sum(hr), 10);
108     BOOST_TEST_EQ(hr.axis(0), R(4, 1, 5));
109     BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2));
110     BOOST_TEST_EQ(hr, h);
111
112     hr = reduce(h, slice(1, 0, 4), slice(0, 0, 4));
113     BOOST_TEST_EQ(hr, h);
114
115     hr = reduce(h, shrink(0, 2, 4));
116     BOOST_TEST_EQ(hr.rank(), 2);
117     BOOST_TEST_EQ(sum(hr), 10);
118     BOOST_TEST_EQ(hr.axis(0), R(2, 2, 4));
119     BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2));
120     BOOST_TEST_EQ(hr.at(-1, 0), 1); // underflow
121     BOOST_TEST_EQ(hr.at(0, 0), 0);
122     BOOST_TEST_EQ(hr.at(1, 0), 1);
123     BOOST_TEST_EQ(hr.at(2, 0), 0); // overflow
124     BOOST_TEST_EQ(hr.at(-1, 1), 1);
125     BOOST_TEST_EQ(hr.at(0, 1), 1);
126     BOOST_TEST_EQ(hr.at(1, 1), 0);
127     BOOST_TEST_EQ(hr.at(2, 1), 0);
128     BOOST_TEST_EQ(hr.at(-1, 2), 0);
129     BOOST_TEST_EQ(hr.at(0, 2), 2);
130     BOOST_TEST_EQ(hr.at(1, 2), 1);
131     BOOST_TEST_EQ(hr.at(2, 2), 3);
132
133     /*
134       matrix layout:
135       x ->
136     y 1 0 1 0
137     | 1 1 0 0
138     v 0 2 1 3
139     */
140
141     hr = reduce(h, shrink_and_rebin(0, 2, 5, 2), rebin(1, 3));
142     BOOST_TEST_EQ(hr.rank(), 2);
143     BOOST_TEST_EQ(sum(hr), 10);
144     BOOST_TEST_EQ(hr.axis(0).size(), 1);
145     BOOST_TEST_EQ(hr.axis(1).size(), 1);
146     BOOST_TEST_EQ(hr.axis(0).bin(0).lower(), 2);
147     BOOST_TEST_EQ(hr.axis(0).bin(0).upper(), 4);
148     BOOST_TEST_EQ(hr.axis(1).bin(0).lower(), -1);
149     BOOST_TEST_EQ(hr.axis(1).bin(0).upper(), 2);
150     BOOST_TEST_EQ(hr.at(-1, 0), 2); // underflow
151     BOOST_TEST_EQ(hr.at(0, 0), 5);
152     BOOST_TEST_EQ(hr.at(1, 0), 3); // overflow
153
154     // test overload that accepts iterable and test option fusion
155     std::vector<reduce_option> opts{{shrink(0, 2, 5), rebin(0, 2), rebin(1, 3)}};
156     auto hr2 = reduce(h, opts);
157     BOOST_TEST_EQ(hr2, hr);
158     opts = {rebin(0, 2), slice(0, 1, 4), rebin(1, 3)};
159     auto hr3 = reduce(h, opts);
160     BOOST_TEST_EQ(hr3, hr);
161   }
162
163   // mixed axis types
164   {
165     R r(5, 0.0, 1.0);
166     V v{{1., 2., 3.}};
167     CI c{{1, 2, 3}};
168     auto h = make(Tag(), r, v, c);
169     auto hr = algorithm::reduce(h, shrink(0, 0.2, 0.7));
170     BOOST_TEST_EQ(hr.axis(0).size(), 3);
171     BOOST_TEST_EQ(hr.axis(0).bin(0).lower(), 0.2);
172     BOOST_TEST_EQ(hr.axis(0).bin(2).upper(), 0.8);
173     BOOST_TEST_EQ(hr.axis(1).size(), 2);
174     BOOST_TEST_EQ(hr.axis(1).bin(0).lower(), 1);
175     BOOST_TEST_EQ(hr.axis(1).bin(1).upper(), 3);
176     BOOST_TEST_THROWS((void)algorithm::reduce(h, rebin(2, 2)), std::invalid_argument);
177   }
178
179   // reduce on integer axis, rebin must fail
180   {
181     auto h = make(Tag(), axis::integer<>(1, 4));
182     BOOST_TEST_THROWS((void)reduce(h, rebin(2)), std::invalid_argument);
183     auto hr = reduce(h, shrink(2, 3));
184     BOOST_TEST_EQ(hr.axis().size(), 1);
185     BOOST_TEST_EQ(hr.axis().bin(0), 2);
186     BOOST_TEST_EQ(hr.axis().bin(1), 3);
187   }
188
189   // reduce on circular axis, shrink must fail, also rebin with remainder
190   {
191     auto h = make(Tag(), axis::circular<>(4, 1, 4));
192     BOOST_TEST_THROWS((void)reduce(h, shrink(0, 2)), std::invalid_argument);
193     BOOST_TEST_THROWS((void)reduce(h, rebin(3)), std::invalid_argument);
194     auto hr = reduce(h, rebin(2));
195     BOOST_TEST_EQ(hr.axis().size(), 2);
196     BOOST_TEST_EQ(hr.axis().bin(0).lower(), 1);
197     BOOST_TEST_EQ(hr.axis().bin(1).upper(), 4);
198   }
199
200   // reduce on variable axis
201   {
202     auto h = make(Tag(), V({0, 1, 2, 3, 4, 5, 6}));
203     auto hr = reduce(h, shrink_and_rebin(1, 5, 2));
204     BOOST_TEST_EQ(hr.axis().size(), 2);
205     BOOST_TEST_EQ(hr.axis().value(0), 1);
206     BOOST_TEST_EQ(hr.axis().value(1), 3);
207     BOOST_TEST_EQ(hr.axis().value(2), 5);
208   }
209
210   // reduce on axis with inverted range
211   {
212     auto h = make(Tag(), R(4, 2, -2));
213     const auto& ax = h.axis();
214     BOOST_TEST_EQ(ax.index(-0.999), 2);
215     BOOST_TEST_EQ(ax.index(-1.0), 3);
216     BOOST_TEST_EQ(ax.index(-1.5), 3);
217
218     BOOST_TEST_EQ(reduce(h, shrink(3, -3)).axis(), R(4, 2, -2));
219     BOOST_TEST_EQ(reduce(h, shrink(2, -2)).axis(), R(4, 2, -2));
220     BOOST_TEST_EQ(reduce(h, shrink(1.999, -2)).axis(), R(4, 2, -2));
221     BOOST_TEST_EQ(reduce(h, shrink(1.001, -2)).axis(), R(4, 2, -2));
222     BOOST_TEST_EQ(reduce(h, shrink(1, -2)).axis(), R(3, 1, -2));
223     BOOST_TEST_EQ(reduce(h, shrink(2, -1.999)).axis(), R(4, 2, -2));
224     BOOST_TEST_EQ(reduce(h, shrink(2, -1.001)).axis(), R(4, 2, -2));
225     BOOST_TEST_EQ(reduce(h, shrink(2, -1)).axis(), R(3, 2, -1));
226   }
227 }
228
229 int main() {
230   run_tests<static_tag>();
231   run_tests<dynamic_tag>();
232
233   return boost::report_errors();
234 }