1 // Copyright 2019 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 <array>
8 #include <boost/config.hpp>
9 #include <boost/core/lightweight_test.hpp>
10 #include <boost/histogram/accumulators.hpp>
11 #include <boost/histogram/accumulators/ostream.hpp>
12 #include <boost/histogram/algorithm/sum.hpp>
13 #include <boost/histogram/axis/category.hpp>
14 #include <boost/histogram/axis/integer.hpp>
15 #include <boost/histogram/axis/ostream.hpp>
16 #include <boost/histogram/histogram.hpp>
17 #include <boost/histogram/literals.hpp>
18 #include <boost/histogram/make_histogram.hpp>
19 #include <boost/histogram/ostream.hpp>
20 #include <boost/histogram/storage_adaptor.hpp>
21 #include <boost/variant2/variant.hpp>
22 #include <random>
23 #include <sstream>
24 #include <stdexcept>
25 #include <tuple>
26 #include <utility>
27 #include <vector>
28 #include "throw_exception.hpp"
29 #include "utility_histogram.hpp"
30 
31 using namespace boost::histogram;
32 using namespace boost::histogram::algorithm;
33 using namespace boost::histogram::literals; // to get _c suffix
34 using boost::variant2::variant;
35 
36 constexpr auto ndata = 1 << 16; // should be larger than index buffer in fill_n
37 
38 using in = axis::integer<int, axis::null_type>;
39 using in0 = axis::integer<int, axis::null_type, axis::option::none_t>;
40 using ing = axis::integer<double, axis::null_type,
41                           decltype(axis::option::growth | axis::option::underflow |
42                                    axis::option::overflow)>;
43 using cs = axis::category<std::string, axis::null_type>;
44 using csg = axis::category<std::string, axis::null_type, axis::option::growth_t>;
45 
46 struct axis2d {
sizeaxis2d47   auto size() const { return axis::index_type{2}; }
48 
indexaxis2d49   auto index(const std::tuple<double, double>& xy) const {
50     const auto x = std::get<0>(xy);
51     const auto y = std::get<1>(xy);
52     const auto r = std::sqrt(x * x + y * y);
53     return std::min(static_cast<axis::index_type>(r), size());
54   }
55 
operator <<(std::ostream & os,const axis2d &)56   friend std::ostream& operator<<(std::ostream& os, const axis2d&) {
57     os << "axis2d()";
58     return os;
59   }
60 };
61 
62 template <class Tag>
run_tests(const std::vector<int> & x,const std::vector<int> & y,const std::vector<double> & w)63 void run_tests(const std::vector<int>& x, const std::vector<int>& y,
64                const std::vector<double>& w) {
65 
66   // 1D simple A
67   {
68     auto h = make(Tag(), in{1, 3});
69     auto h2 = h;
70     for (auto&& xi : x) h(xi);
71     // uses 1D specialization
72     h2.fill(x);
73     BOOST_TEST_EQ(h, h2);
74   }
75 
76   // 1D simple B
77   {
78     auto h = make(Tag(), in{1, 3});
79     auto h2 = h;
80     for (auto&& xi : x) h(xi);
81     // uses generic form
82     const auto vx = {x};
83     h2.fill(vx);
84     BOOST_TEST_EQ(h, h2);
85   }
86 
87   // 1D simple C
88   {
89     auto h = make(Tag(), in{1, 3});
90     auto h2 = h;
91     h(1);
92     for (auto&& xi : x) h(xi);
93     // uses variant
94     variant<int, std::vector<int>, std::string> v[1];
95     v[0] = 1;
96     h2.fill(v);
97     v[0] = x;
98     h2.fill(v);
99     BOOST_TEST_EQ(h, h2);
100   }
101 
102   // 1D bad arguments
103   {
104     auto h = make(Tag(), in{1, 3});
105 
106     int bad1[2][4];
107     (void)bad1;
108     BOOST_TEST_THROWS(h.fill(bad1), std::invalid_argument);
109 
110     std::vector<std::array<int, 4>> bad2;
111     (void)bad2;
112     BOOST_TEST_THROWS(h.fill(bad2), std::invalid_argument);
113   }
114 
115   // 1D with category axis
116   {
117     auto h = make(Tag(), cs{"A", "B"});
118     auto h2 = h;
119 
120     const auto s = {"A", "B", "C"};
121     for (auto&& si : s) h(si);
122     h2.fill(s);
123 
124     variant<int, std::string, std::vector<std::string>> v[1];
125     h("B");
126     v[0] = "B";
127     h2.fill(v);
128 
129     v[0] = std::vector<std::string>(s.begin(), s.end());
130     for (auto&& si : s) h(si);
131     h2.fill(v);
132 
133     BOOST_TEST_EQ(h, h2);
134   }
135 
136   // 1D weight
137   {
138     auto h = make(Tag(), in{1, 3});
139     auto h2 = h;
140 
141     for (auto&& xi : x) h(weight(2), xi);
142     h2.fill(weight(2), x);
143 
144     for (unsigned i = 0; i < ndata; ++i) h(weight(w[i]), x[i]);
145     h2.fill(weight(w), x);
146 
147     BOOST_TEST_EQ(h, h2);
148 
149     auto w2 = {1};
150     (void)w2;
151     BOOST_TEST_THROWS(h2.fill(x, weight(w2)), std::invalid_argument);
152   }
153 
154   // 2D simple
155   {
156     auto h = make(Tag(), in{1, 3}, in0{1, 5});
157     auto h2 = h;
158 
159     for (int i = 0; i < ndata; ++i) h(x[i], y[i]);
160     const auto xy = {x, y};
161     h2.fill(xy);
162 
163     BOOST_TEST_EQ(h, h2);
164 
165     // wrong rank
166     BOOST_TEST_THROWS(h.fill(x), std::invalid_argument);
167 
168     // not rectangular
169     std::array<std::vector<int>, 2> bad = {{std::vector<int>(1), std::vector<int>(2)}};
170     (void)bad;
171     BOOST_TEST_THROWS(h2.fill(bad), std::invalid_argument);
172   }
173 
174   // 2D variant and weight
175   {
176     auto h = make(Tag(), in{1, 3}, in0{1, 5});
177 
178     using V = variant<int, std::vector<int>, std::string>;
179     V xy[2];
180 
181     {
182       xy[0] = 3;
183       xy[1] = y;
184       auto h1 = h;
185       auto h2 = h;
186       for (auto&& vi : y) h1(3, vi);
187       h2.fill(xy);
188       BOOST_TEST_EQ(h1, h2);
189     }
190 
191     {
192       xy[0] = x;
193       xy[1] = 3;
194       auto h1 = h;
195       auto h2 = h;
196       for (auto&& vi : x) h1(vi, 3);
197       h2.fill(xy);
198       BOOST_TEST_EQ(h1, h2);
199     }
200 
201     {
202       xy[0] = 3;
203       xy[1] = y;
204       auto h1 = h;
205       auto h2 = h;
206       for (auto&& vi : y) h1(3, vi, weight(2));
207       h2.fill(xy, weight(2));
208       BOOST_TEST_EQ(h1, h2);
209     }
210 
211     {
212       xy[0] = 3;
213       xy[1] = y;
214       auto h1 = h;
215       auto h2 = h;
216       for (unsigned i = 0; i < ndata; ++i) h1(3, y[i], weight(w[i]));
217       h2.fill(xy, weight(w));
218       BOOST_TEST_EQ(sum(h1), sum(h2));
219       BOOST_TEST_EQ(h1, h2);
220     }
221   }
222 
223   // 1D growing
224   {
225     auto h = make(Tag(), ing());
226     auto h2 = h;
227     for (const auto& xi : x) h(xi);
228     h2.fill(x);
229     BOOST_TEST_EQ(h, h2);
230   }
231 
232   // 2D growing A
233   {
234     auto h = make(Tag(), in(1, 3), ing());
235     auto h2 = h;
236     for (unsigned i = 0; i < ndata; ++i) h(x[i], y[i]);
237     const auto xy = {x, y};
238     h2.fill(xy);
239     BOOST_TEST_EQ(h, h2);
240   }
241 
242   // 2D growing B
243   {
244     auto h = make(Tag(), ing(), ing());
245     auto h2 = h;
246     for (unsigned i = 0; i < ndata; ++i) h(x[i], y[i]);
247     const auto xy = {x, y};
248     h2.fill(xy);
249     BOOST_TEST_EQ(h, h2);
250   }
251 
252   // 2D growing with weights A
253   {
254     auto h = make(Tag(), in(1, 3), ing());
255     auto h2 = h;
256     for (unsigned i = 0; i < ndata; ++i) h(x[i], y[i], weight(w[i]));
257     const auto xy = {x, y};
258     h2.fill(xy, weight(w));
259     BOOST_TEST_EQ(h, h2);
260   }
261 
262   // 2D growing with weights B
263   {
264     auto h = make(Tag(), ing(), ing());
265     auto h2 = h;
266     for (unsigned i = 0; i < ndata; ++i) h(x[i], y[i], weight(2));
267     const auto xy = {x, y};
268     h2.fill(xy, weight(2));
269     BOOST_TEST_EQ(h, h2);
270   }
271 
272   // 2D growing and variant
273   {
274     auto h = make(Tag(), csg{}, in{1, 2});
275     auto h2 = h;
276 
277     h("foo", 1);
278     h("foo", 2);
279 
280     using V = variant<std::string, std::vector<std::string>, int, std::vector<int>>;
281     const auto xy = {V("foo"), V(std::vector<int>{1, 2})};
282     h2.fill(xy);
283 
284     BOOST_TEST_EQ(h, h2);
285 
286     const auto bad = {V(std::vector<std::string>(1, "foo")), V(std::vector<int>{1, 2})};
287     (void)bad;
288     BOOST_TEST_THROWS(h.fill(bad), std::invalid_argument);
289   }
290 
291   // 1D profile with samples
292   {
293     auto h = make_s(Tag(), profile_storage(), in(1, 3));
294     auto h2 = h;
295 
296     for (unsigned i = 0; i < ndata; ++i) h(x[i], sample(w[i]));
297     for (unsigned i = 0; i < ndata; ++i) h(x[i], sample(w[i]), weight(w[i]));
298     for (unsigned i = 0; i < ndata; ++i) h(x[i], sample(2), weight(w[i]));
299     for (unsigned i = 0; i < ndata; ++i) h(x[i], sample(w[i]), weight(2));
300 
301     h2.fill(x, sample(w));
302     h2.fill(x, sample(w), weight(w));
303     h2.fill(x, sample(2), weight(w));
304     h2.fill(x, sample(w), weight(2));
305 
306     BOOST_TEST_EQ(h, h2);
307   }
308 
309   // 2D weighted profile with samples and weights
310   {
311     auto h = make_s(Tag(), weighted_profile_storage(), in(1, 3), in0(1, 3));
312     auto h2 = h;
313 
314     for (unsigned i = 0; i < ndata; ++i) h(x[i], 3, sample(w[i]), weight(w[i]));
315     for (unsigned i = 0; i < ndata; ++i) h(x[i], 3, sample(2), weight(w[i]));
316     for (unsigned i = 0; i < ndata; ++i) h(x[i], 3, sample(w[i]), weight(2));
317 
318     using V = variant<int, std::vector<int>>;
319     std::array<V, 2> xy;
320     xy[0] = x;
321     xy[1] = 3;
322     h2.fill(xy, sample(w), weight(w));
323     h2.fill(xy, sample(2), weight(w));
324     h2.fill(xy, sample(w), weight(2));
325 
326     BOOST_TEST_EQ(h, h2);
327   }
328 
329   // axis2d
330   {
331     auto h = make(Tag(), axis2d{});
332     auto h2 = h;
333 
334     std::vector<std::tuple<double, double>> xy;
335     xy.reserve(ndata);
336     for (unsigned i = 0; i < ndata; ++i) xy.emplace_back(x[i], y[i]);
337 
338     for (auto&& xyi : xy) h(xyi);
339     h2.fill(xy);
340 
341     BOOST_TEST_EQ(h, h2);
342   }
343 }
344 
main()345 int main() {
346   std::mt19937 gen(1);
347   std::normal_distribution<> id(0, 2);
348   std::vector<int> x(ndata), y(ndata);
349   auto generator = [&] { return static_cast<int>(id(gen)); };
350   std::generate(x.begin(), x.end(), generator);
351   std::generate(y.begin(), y.end(), generator);
352   std::vector<double> w(ndata);
353   // must be all positive
354   std::generate(w.begin(), w.end(), [&] { return 0.5 + std::abs(id(gen)); });
355 
356   run_tests<static_tag>(x, y, w);
357   run_tests<dynamic_tag>(x, y, w);
358 
359   return boost::report_errors();
360 }
361