1 // Copyright 2015-2018 Hans Dembinski
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)
7 #include <boost/core/lightweight_test.hpp>
8 #include <boost/core/lightweight_test_trait.hpp>
9 #include <boost/histogram/axis/category.hpp>
10 #include <boost/histogram/axis/integer.hpp>
11 #include <boost/histogram/axis/ostream.hpp>
12 #include <boost/histogram/axis/regular.hpp>
13 #include <boost/histogram/axis/variant.hpp>
14 #include <boost/histogram/detail/type_name.hpp>
16 #include <type_traits>
18 #include "throw_exception.hpp"
19 #include "utility_allocator.hpp"
20 #include "utility_axis.hpp"
21 #include "utility_str.hpp"
24 using namespace boost::histogram;
25 namespace tr = axis::transform;
28 (void)axis::variant<>{};
32 using meta_type = std::vector<int>;
34 axis::variant<axis::integer<double>, axis::category<std::string, meta_type>>;
35 auto a = variant_type{axis::integer<double>(0, 2, "foo")};
36 BOOST_TEST_EQ(a.index(-10), -1);
37 BOOST_TEST_EQ(a.index(-1), -1);
38 BOOST_TEST_EQ(a.index(0), 0);
39 BOOST_TEST_EQ(a.index(0.5), 0);
40 BOOST_TEST_EQ(a.index(1), 1);
41 BOOST_TEST_EQ(a.index(2), 2);
42 BOOST_TEST_EQ(a.index(10), 2);
43 BOOST_TEST_EQ(a.bin(-1).lower(), -std::numeric_limits<double>::infinity());
44 BOOST_TEST_EQ(a.bin(a.size()).upper(), std::numeric_limits<double>::infinity());
45 BOOST_TEST_EQ(a.bin(-10).lower(), -std::numeric_limits<double>::infinity());
46 BOOST_TEST_EQ(a.bin(a.size() + 10).upper(), std::numeric_limits<double>::infinity());
47 BOOST_TEST_EQ(a.metadata(), "foo");
49 BOOST_TEST_EQ(static_cast<const variant_type&>(a).metadata(), "bar");
50 BOOST_TEST_EQ(a.options(), axis::option::underflow | axis::option::overflow);
52 a = axis::category<std::string, meta_type>({"A", "B"}, {1, 2, 3});
53 BOOST_TEST_EQ(a.index("A"), 0);
54 BOOST_TEST_EQ(a.index("B"), 1);
55 BOOST_TEST_THROWS(a.metadata(), std::runtime_error);
56 BOOST_TEST_THROWS(static_cast<const variant_type&>(a).metadata(), std::runtime_error);
57 BOOST_TEST_EQ(a.options(), axis::option::overflow_t::value);
60 // axis::variant with pointers
62 using A = axis::integer<>;
63 using B = axis::regular<>;
64 auto a = A(1, 5, "foo");
65 auto b = B(3, 1, 5, "bar");
66 axis::variant<A*, B*> r1(&a);
68 BOOST_TEST_NE(r1, A(2, 4));
70 BOOST_TEST_EQ(r1.size(), 4);
71 BOOST_TEST_EQ(r1.value(0), 1);
72 BOOST_TEST_EQ(r1.metadata(), a.metadata());
73 BOOST_TEST_EQ(r1.options(), a.options());
74 // change original through r1
75 axis::get<A>(r1).metadata() = "bar";
76 BOOST_TEST_EQ(a.metadata(), "bar");
80 axis::variant<const A*, const B*> r2(static_cast<const B*>(&b));
82 BOOST_TEST_NE(r2, B(4, 1, 5));
84 BOOST_TEST_EQ(r2.size(), 3);
85 BOOST_TEST_EQ(r2.value(0), 1);
86 BOOST_TEST_EQ(r2.metadata(), "bar");
88 BOOST_TEST_EQ(r2.metadata(), "baz");
91 // axis::variant copyable
93 axis::variant<axis::regular<>> a1(axis::regular<>(2, -1, 1));
94 axis::variant<axis::regular<>> a2(a1);
95 BOOST_TEST_EQ(a1, a2);
96 axis::variant<axis::regular<>> a3;
97 BOOST_TEST_NE(a3, a1);
99 BOOST_TEST_EQ(a3, a1);
100 axis::variant<axis::regular<>> a4(axis::regular<>(3, -2, 2));
101 axis::variant<axis::regular<>, axis::integer<>> a5(a4);
102 BOOST_TEST_EQ(a4, a5);
103 axis::variant<axis::regular<>> a6;
105 BOOST_TEST_EQ(a6, a1);
106 axis::variant<axis::regular<>, axis::integer<>> a7(axis::integer<>(0, 2));
107 BOOST_TEST_THROWS(axis::variant<axis::regular<>> a8(a7), std::runtime_error);
108 BOOST_TEST_THROWS(a4 = a7, std::runtime_error);
111 // axis::variant movable
113 axis::variant<axis::regular<>> a(axis::regular<>(2, -1, 1));
114 axis::variant<axis::regular<>> r(a);
115 axis::variant<axis::regular<>> b(std::move(a));
117 axis::variant<axis::regular<>> c;
123 // axis::variant streamable
125 auto test = [](auto&& a, const char* ref) {
126 using T = std::decay_t<decltype(a)>;
127 axis::variant<T> axis(std::move(a));
128 BOOST_TEST_CSTR_EQ(str(axis).c_str(), ref);
131 test(axis::regular<>(2, -1, 1, "regular1"),
132 "regular(2, -1, 1, metadata=\"regular1\", options=underflow | overflow)");
134 struct user_defined {};
135 const auto ref = "integer(-1, 1, metadata=" + detail::type_name<user_defined>() +
137 test(axis::integer<int, user_defined, axis::option::none_t>(-1, 1), ref.c_str());
140 // bin_type operator<<
142 auto test = [](auto&& a, const char* ref) {
143 using T = std::decay_t<decltype(a)>;
144 axis::variant<T> axis(std::move(a));
145 BOOST_TEST_CSTR_EQ(str(axis.bin(0)).c_str(), ref);
148 test(axis::regular<>(2, 1, 2), "[1, 1.5)");
149 test(axis::category<>({1, 2}), "1");
152 // axis::variant operator==
156 axis::variant<axis::regular<>, axis::regular<double, axis::transform::pow>,
157 axis::category<>, axis::integer<>>;
158 std::vector<variant> axes;
159 axes.push_back(axis::regular<>{2, -1, 1});
160 axes.push_back(axis::regular<double, tr::pow>(tr::pow(0.5), 2, 1, 4));
161 axes.push_back(axis::category<>({A, B, C}));
162 axes.push_back(axis::integer<>{-1, 1});
163 for (const auto& a : axes) {
164 BOOST_TEST(!(a == variant()));
165 BOOST_TEST_EQ(a, variant(a));
167 BOOST_TEST_NOT(axes == std::vector<variant>());
168 BOOST_TEST(axes == std::vector<variant>(axes));
171 // axis::variant with axis that has incompatible bin type
173 auto a = axis::variant<axis::category<std::string>>(
174 axis::category<std::string>({"A", "B", "C"}));
175 BOOST_TEST_THROWS(a.bin(0), std::runtime_error);
176 auto b = axis::variant<axis::category<int>>(axis::category<int>({2, 1, 3}));
177 BOOST_TEST_EQ(b.bin(0), 2);
178 BOOST_TEST_EQ(b.bin(0).lower(),
179 b.bin(0).upper()); // lower == upper for bin without interval
182 // axis::variant support for user-defined axis types
184 struct minimal_axis {
185 int index(int x) const { return x % 2; }
186 int size() const { return 2; }
189 axis::variant<minimal_axis, axis::category<std::string>> axis;
190 BOOST_TEST_EQ(axis.index(0), 0);
191 BOOST_TEST_EQ(axis.index(9), 1);
192 BOOST_TEST_EQ(axis.size(), 2);
193 BOOST_TEST_EQ(axis.metadata(), axis::null_type{});
194 BOOST_TEST_CSTR_EQ(str(axis).c_str(), "<unstreamable>");
195 BOOST_TEST_THROWS(axis.value(0), std::runtime_error);
197 axis = axis::category<std::string>({"A", "B"}, "category");
198 BOOST_TEST_EQ(axis.index("B"), 1);
199 BOOST_TEST_THROWS(axis.value(0), std::runtime_error);
202 // vector of axes with custom allocators
204 using M = std::vector<char, tracing_allocator<char>>;
205 using T1 = axis::regular<double, tr::id, M>;
206 using T2 = axis::integer<int, axis::null_type>;
207 using T3 = axis::category<long, axis::null_type, axis::option::overflow_t,
208 tracing_allocator<long>>;
209 using axis_type = axis::variant<T1, T2, T3>; // no heap allocation
210 using axes_type = std::vector<axis_type, tracing_allocator<axis_type>>;
212 tracing_allocator_db db;
214 auto a = tracing_allocator<char>(db);
217 axes.emplace_back(T1(1, 0, 1, M(3, 'c', a)));
218 axes.emplace_back(T2(0, 4));
219 axes.emplace_back(T3({1, 2, 3, 4, 5}, {}, a));
221 // 3 axis::variant objects
222 BOOST_TEST_EQ(db.at<axis_type>().first, 0);
223 BOOST_TEST_EQ(db.at<axis_type>().second, 3);
226 BOOST_TEST_EQ(db.at<char>().first, 0);
227 BOOST_TEST_EQ(db.at<char>().second, 3);
229 // T3 allocates storage for long array
230 BOOST_TEST_EQ(db.at<long>().first, 0);
231 BOOST_TEST_EQ(db.at<long>().second, 5);
234 // testing pass-through versions of get
236 axis::regular<> a(10, 0, 1);
237 axis::integer<> b(0, 3);
238 const auto& ta = axis::get<axis::regular<>>(a);
239 BOOST_TEST_EQ(ta, a);
240 const auto* tb = axis::get_if<axis::integer<>>(&b);
241 BOOST_TEST_EQ(tb, &b);
242 const auto* tc = axis::get_if<axis::regular<>>(&b);
243 BOOST_TEST_EQ(tc, nullptr);
247 test_axis_iterator(axis::variant<axis::regular<>>(axis::regular<>(5, 0, 1)), 0, 5);
249 return boost::report_errors();