ATLAS Offline Software
Loading...
Searching...
No Matches
test-histogram.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7#include "H5Cpp.h"
8#include <deque>
9#include <iostream>
10#include <stdexcept>
11#include <unordered_map>
12
13void run_tests() {
14
15 namespace bh = boost::histogram;
16 namespace h5h = H5Utils::hist;
17 using def = bh::use_default;
18
19 using dax_t = bh::axis::regular<double>;
20 using daxn_t = bh::axis::regular<double, def, def, bh::axis::option::none_t>;
21 using iax_t = bh::axis::integer<int>;
22 using cax_t = bh::axis::category<short, def, bh::axis::option::overflow_t>;
23
24 H5::H5File out_file("hists.h5", H5F_ACC_TRUNC);
25
26 // build weighted hist
27 {
28 auto h3dw = bh::make_weighted_histogram(
29 daxn_t(1, -0.5, 0.5, "ax0"),
30 dax_t(1, -0.5, 0.5, "ax1"),
31 iax_t(-1, 1, "ax2"));
32 // this should put a 0.5 at [0, 1, 2] when all the overflows are
33 // accounted for
34 h3dw(0, 0, 0, bh::weight(0.5));
35 // this is to test the overflow bin
36 h3dw(0, 0, 20, bh::weight(10));
37 h5h::write_hist_to_group(out_file, h3dw, "h3dw");
38 }
39
40 // build unweighted hist
41 {
42 auto h3d = bh::make_histogram(
43 daxn_t(1, -0.5, 0.5, "ax0"),
44 dax_t(1, -0.5, 0.5, "ax1"),
45 iax_t(-1, 1, "ax2"));
46 h3d(0, 0, 0);
47 h5h::write_hist_to_group(out_file, h3d, "h3d");
48 }
49
50 // build weighted profile hist
51 {
52 auto h3dp = bh::make_weighted_profile(
53 daxn_t(1, -0.5, 0.5, "ax0"),
54 dax_t(1, -0.5, 0.5, "ax1"),
55 iax_t(-1, 1, "ax2"));
56 // we need two calls here to check the variance too
57 h3dp(0, 0, 0, bh::weight(0.5), bh::sample(1.0));
58 h3dp(0, 0, 0, bh::weight(0.5), bh::sample(1.0));
59 h5h::write_hist_to_group(out_file, h3dp, "h3dp");
60 }
61
62 // build a dyanmic hist
63 {
64 using variant = bh::axis::variant<dax_t, daxn_t, iax_t>;
65 std::vector<variant> axes {
66 daxn_t(1, -0.5, 0.5, "ax0"),
67 dax_t(1, -0.5, 0.5, "ax1"),
68 iax_t(-1, 1, "ax2")
69 };
70 auto hdyn = bh::make_weighted_histogram(axes);
71 // seems we need to do some funny organization to make weighting
72 // work with dynamic axes.
73 std::vector<std::vector<double>> vals { {0}, {0}, {0} };
74 hdyn.fill(vals, bh::weight(0.5));
75 h5h::write_hist_to_group(out_file, hdyn, "hdyn");
76 }
77
78 // integer storage
79 {
80 using int_storage = bh::dense_storage<int64_t>;
81 auto h1i = bh::make_histogram_with(
82 int_storage{},
83 dax_t(3, -1.5, 1.5, "x"));
84 h1i(-1.0); // bin 0
85 h1i(0.0); // bin 1
86 h1i(0.0); // bin 1 again
87 h5h::write_hist_to_group(out_file, h1i, "h1i");
88 }
89
90 // thread-safe (atomic) integer storage
91 {
92 using atomic_int_storage =
93 bh::dense_storage<bh::accumulators::count<int64_t, true>>;
94 auto h1a = bh::make_histogram_with(
95 atomic_int_storage{},
96 dax_t(3, -1.5, 1.5, "x"));
97 h1a(0.0); // bin 1
98 h1a(1.0); // bin 2
99 h5h::write_hist_to_group(out_file, h1a, "h1a");
100 }
101
102 // thread-safe double storage
103 {
104 using atomic_double_storage =
105 bh::dense_storage<bh::accumulators::count<double, true>>;
106 auto h1td = bh::make_histogram_with(
107 atomic_double_storage{},
108 dax_t(3, -1.5, 1.5, "x"));
109 h1td(-1.0); // bin 0
110 h1td(0.0); // bin 1
111 h1td(0.0); // bin 1 again
112 h5h::write_hist_to_group(out_file, h1td, "h1td");
113 }
114
115 // thread-safe float storage
116 {
117 using atomic_float_storage =
118 bh::dense_storage<bh::accumulators::count<float, true>>;
119 auto h1tf = bh::make_histogram_with(
120 atomic_float_storage{},
121 dax_t(3, -1.5, 1.5, "x"));
122 h1tf(0.0); // bin 1
123 h1tf(1.0); // bin 2
124 h1tf(1.0); // bin 2 again
125 h5h::write_hist_to_group(out_file, h1tf, "h1tf");
126 }
127
128 // categorical axes test
129 {
130 // flavor labels histogram
131 auto h1c = bh::make_histogram(
132 cax_t({0, 4, 5, 15}, "flavorTruthLabel") );
133 h1c(5); // add a b-hadron
134 h1c(20); // no idea what, test overflow
135 h5h::write_hist_to_group(out_file, h1c, "h1c");
136 }
137
138 // unsigned char (uint8) storage
139 {
140 using uint8_storage = bh::dense_storage<uint8_t>;
141 auto h1u8 = bh::make_histogram_with(
142 uint8_storage{}, dax_t(3, -1.5, 1.5, "x"));
143 h1u8(0.0); // bin 1, storage index 2
144 h1u8(1.0); // bin 2, storage index 3
145 h5h::write_hist_to_group(out_file, h1u8, "h1u8");
146 }
147
148 // unsigned short (uint16) storage
149 {
150 using uint16_storage = bh::dense_storage<uint16_t>;
151 auto h1u16 = bh::make_histogram_with(
152 uint16_storage{}, dax_t(3, -1.5, 1.5, "x"));
153 h1u16(0.0); // bin 1, storage index 2
154 h1u16(1.0); // bin 2, storage index 3
155 h5h::write_hist_to_group(out_file, h1u16, "h1u16");
156 }
157
158 // Labeled categorical axis: plain enum class (no explicit underlying type)
159 {
160 enum class Flavor { light, c, b, tau };
161 using meta_t = std::pair<std::string, std::vector<std::string>>;
162 using flavor_ax_t = bh::axis::category<Flavor, meta_t,
163 bh::axis::option::overflow_t>;
164 auto h_flavor = bh::make_histogram(
165 flavor_ax_t(
166 {Flavor::light, Flavor::c, Flavor::b, Flavor::tau},
167 meta_t{"flavorLabel", {"light", "c", "b", "tau"}}
168 )
169 );
170 h_flavor(Flavor::b);
171 h_flavor(Flavor::c);
172 h5h::write_hist_to_group(out_file, h_flavor, "h_flavor_labeled");
173 }
174
175 // Labeled 2-D histogram: one regular axis, one labeled categorical
176 {
177 using meta_t = std::pair<std::string, std::vector<std::string>>;
178 using labeled_cax = bh::axis::category<int, meta_t>;
179 auto h2d_cat = bh::make_weighted_histogram(
180 dax_t(4, 0.0, 4.0, "pt_bin"),
181 labeled_cax(
182 {0, 1, 2},
183 meta_t{"nTracks", {"zero", "one", "two"}}
184 )
185 );
186 h2d_cat(1.5, 1, bh::weight(0.5));
187 h5h::write_hist_to_group(out_file, h2d_cat, "h2d_cat");
188 }
189
190 // variable-width axis - edges stored as float64 (matching Python)
191 {
192 using vax_t = bh::axis::variable<double>;
193 auto h1v = bh::make_histogram(
194 vax_t({0.0, 1.0, 3.0, 10.0}, "pt"));
195 h1v(0.5); // bin 0 [0, 1)
196 h1v(2.0); // bin 1 [1, 3)
197 h5h::write_hist_to_group(out_file, h1v, "h1v");
198 }
199
200 // Map-based labeled categorical: map order != axis order
201 // (verifies reordering).
202 {
203 enum class Flav { light, c, b, tau };
204 using map_meta_t = std::pair<std::string, std::map<Flav, std::string>>;
205 using flav_ax_t = bh::axis::category<Flav, map_meta_t,
206 bh::axis::option::overflow_t>;
207 auto h_fmap = bh::make_histogram(
208 flav_ax_t(
209 {Flav::b, Flav::light, Flav::c, Flav::tau}, // axis bin order
210 map_meta_t{"flavMap", { // alphabetical in map
211 {Flav::b, "b"}, {Flav::c, "c"},
212 {Flav::light, "light"}, {Flav::tau, "tau"}
213 }}
214 )
215 );
216 h_fmap(Flav::b); // -> bin 0
217 h_fmap(Flav::light); // -> bin 1
218 h5h::write_hist_to_group(out_file, h_fmap, "h_fmap");
219 }
220
221 // External list override: std::deque, one element per axis.
222 // The regular "pt" axis gets an empty vector (ignored by the
223 // ContinuousAxis + LabelList fallback overload).
224 {
225 using cat_ax = bh::axis::category<int>;
226 using dax_t = bh::axis::regular<double>;
227 auto h_olist = bh::make_histogram(
228 cat_ax({0, 1, 2}, "letters"),
229 cat_ax({10, 20}, "digits"),
230 dax_t(2, 0.0, 2.0, "pt"));
231 h_olist(0, 10, 0.5);
232 std::deque<std::vector<std::string>> lbl_overrides{
233 {"a", "b", "c"},
234 {"ten", "twenty"},
235 {}
236 };
237 h5h::write_hist_to_group(
238 out_file, h_olist, "h_olist", lbl_overrides);
239 }
240
241 // External map override: unordered_map<string,...>.
242 // "pt" is absent from the map; it uses its own metadata.
243 {
244 using cat_ax = bh::axis::category<int>;
245 using dax_t = bh::axis::regular<double>;
246 auto h_omap = bh::make_histogram(
247 cat_ax({0, 1, 2}, "letters"),
248 cat_ax({10, 20}, "digits"),
249 dax_t(2, 0.0, 2.0, "pt"));
250 h_omap(1, 20, 1.5);
251 std::unordered_map<std::string,
252 std::vector<std::string>> lbl_map{
253 {"letters", {"a", "b", "c"}},
254 {"digits", {"ten", "twenty"}}
255 };
256 h5h::write_hist_to_group(
257 out_file, h_omap, "h_omap", lbl_map);
258 }
259
260 // Error-path validation tests
261 auto expect_throw = [](auto fn, const char* label) {
262 bool threw = false;
263 try { fn(); }
264 catch (const std::invalid_argument&) { threw = true; }
265 if (!threw)
266 throw std::logic_error(
267 std::string("expected std::invalid_argument from: ") + label);
268 };
269
270 // Test 1 — case 3: AxisOverrideSeq length != hist.rank()
271 expect_throw([&]{
272 using cat_ax = bh::axis::category<int>;
273 auto h = bh::make_histogram(
274 cat_ax({0,1}, "a"), cat_ax({0,1}, "b"));
275 // 1 entry for a 2-axis histogram
276 std::vector<std::vector<std::string>> short_lbl{{"x","y"}};
277 h5h::write_hist_to_group(out_file, h, "err1", short_lbl);
278 }, "AxisOverrideSeq size mismatch");
279
280 // Test 2 — case 4: continuous axis key present in AxisOverrideMap
281 expect_throw([&]{
282 auto h = bh::make_histogram(
283 bh::axis::regular<double>(2, 0.0, 2.0, "pt"),
284 bh::axis::category<int>({0,1}, "flavor"));
285 std::unordered_map<std::string,
286 std::vector<std::string>> m{
287 {"pt", {"low","high"}}};
288 h5h::write_hist_to_group(out_file, h, "err2", m);
289 }, "AxisOverrideMap key for continuous axis");
290
291 // Test 3 — case 5: non-empty labels for continuous axis in seq list
292 expect_throw([&]{
293 using cat_ax = bh::axis::category<int>;
294 auto h = bh::make_histogram(
295 cat_ax({0,1}, "flavor"),
296 bh::axis::regular<double>(2, 0.0, 2.0, "pt"));
297 std::vector<std::vector<std::string>> lbl{
298 {"a","b"},
299 {"low","high"}};
300 h5h::write_hist_to_group(out_file, h, "err3", lbl);
301 }, "non-empty labels for continuous axis in list");
302}
303
304int main(int, char*[]) {
305 try {
306 run_tests();
307 } catch (const std::exception& e) {
308 std::cerr << e.what() << "\n";
309 return 1;
310 }
311 return 0;
312}
Header file for AthHistogramAlgorithm.
int main()
Definition hello.cxx:18
std::string label(const std::string &format, int i)
Definition label.h:19
void run_tests()