Belle II Software development
test_comparison.py
1#!/usr/bin/env python3
2
3
10
11# std
12import unittest
13import random
14from typing import List, Tuple
15
16# 3rd
17import ROOT
18
19# ours
20import validationcomparison
21import metaoptions
22
23
24class TestGetComparison(unittest.TestCase):
25 """ Test get_comparison """
26
27 def setUp(self):
28 """ Set up testing of get_comparison """
29
30 self.test_options = {
31 "chi2": "",
32 "kolmogorov": "kolmogorov",
33 "andersondarling": "andersondarling",
34 }
35 gaus_th1f = ROOT.TH1F("gaus", "gaus", 5, -3, 3)
36 gaus_th1f.FillRandom("gaus", 1000)
37 exponential_th1f = ROOT.TH1F("expo", "expo", 5, -3, 3)
38 exponential_th1f.FillRandom("expo", 1000)
39
40 self.obj_pairs: List[Tuple[ROOT.TObject, ROOT.TObject, str]] = [
41 (gaus_th1f, gaus_th1f, "equal"),
42 (gaus_th1f, exponential_th1f, "error"),
43 ]
44
46 """ Use get_tester on the metaoptions to get the requested
47 comparison object and run that on two identical ROOT object.
48 Check that this indeed returns 'equal'.
49 """
50 for tester_name in self.test_options:
51 for objs in self.obj_pairs:
52 names: Tuple[str, str] = (objs[0].GetName(), objs[1].GetName())
53 with self.subTest(
54 tester=tester_name, obj1=names[0], obj2=names[1],
55 ):
56 tester = validationcomparison.get_comparison(
57 objs[0],
58 objs[1],
60 self.test_options[tester_name].split(",")
61 ),
62 )
63 print(
64 f"{names[0]}, {names[1]}: "
65 f"{tester.comparison_result_long}. "
66 f"Short result: {tester.comparison_result}. "
67 f"Expectation: {objs[2]}"
68 )
69 self.assertEqual(tester.comparison_result, objs[2])
70
71
72class TestComparison(unittest.TestCase):
73 """
74 Various test cases for the Chi2Test wrapper class
75 """
76
77 @staticmethod
79 name, entries=5000, mu=10, sigma=0.3, max_fill=50, fixed_number=None
80 ):
81 """
82 Create a TProfile object with various content
83 """
84 p = ROOT.TProfile(name, name, 50, 0, 50.0)
85 entries_per_bin = int(entries / max_fill)
86 for i in range(0, max_fill + 1):
87 for e in range(0, entries_per_bin):
88 if fixed_number is None:
89 p.Fill(i + 0.5, random.gauss(mu, sigma))
90 else:
91 p.Fill(i + 0.5, fixed_number)
92 return p
93
94 @staticmethod
95 def create_teff(name, bins=20, eff=0.9):
96 """
97 Creates and fills a root TEfficiency plot
98 """
99 p = ROOT.TEfficiency(name, name, bins, 0, 50)
100
101 for i in range(0, 5000):
102 passed = random.uniform(0, 1.0) < eff
103 bin_content = random.uniform(0.0, 50.0)
104 p.Fill(passed, bin_content)
105
106 return p
107
108 # todo: Can't I use FillRandom for this?
109 @staticmethod
110 def create_histogram(name, entries=5000, mu=10, sigma=3):
111 """
112 Create a TH1D and fill with random content
113 """
114 p = ROOT.TH1D(name, name, 50, 0, 20.0)
115 for i in range(0, entries):
116 p.Fill(random.gauss(mu, sigma))
117 return p
118
119 def root_name(self, name):
120 """
121 Generates unique names for ROOT objects
122 """
123 return f"{name}_{self.call_iteration}"
124
125 def setUp(self):
126 """
127 Setup method to generate profiles and histograms for tests
128 """
129 random.seed(23)
130
131
135
136
137 self.profileA = self.create_profile(self.root_name("profileA"))
138
139
140 self.profileB = self.create_profile(self.root_name("profileB"))
141
142
144 self.root_name("profileC"), 5000, 5, 3
145 )
146
147
149 self.root_name("profileZeroErrorBins"), max_fill=49
150 )
151 self.profileZeroErrorBins.SetBinError(35, 0.0)
152
153
155 self.root_name("profileZeroErrorBinsTwo"), max_fill=49
156 )
157 self.profileZeroErrorBinsTwo.SetBinError(35, 0.0)
158
159
161 self.root_name("histogramA"), 5000, 5, 3
162 )
163
164
166 self.root_name("histogramB"), 5000, 5, 3
167 )
168
169
171 self.root_name("profileA_almostequal"), sigma=0.4
172 )
173
174
176 self.root_name("profileB_almostequal"), sigma=0.4
177 )
178
179
180 self.profileDifferentBins = ROOT.TProfile(
181 self.root_name("profileDifferentBins"),
182 self.root_name("profileDifferentBins"),
183 40,
184 0,
185 50.0,
186 )
187
188
189 self.teffA = self.create_teff(self.root_name("teffA"))
190
191
192 self.teffB = self.create_teff(self.root_name("teffB"))
193
195 """
196 Test if comparing two similar TProfiles works
197 """
198 c = validationcomparison.Chi2Test(self.profileA, self.profileB)
199
200 self.assertTrue(c.can_compare())
201 c.ensure_compute()
202 self.assertAlmostEqual(c._pvalue, 0.22495088947037362)
203
205 """
206 Test if comparing two identical TProfiles works
207 """
208 c = validationcomparison.Chi2Test(self.profileA, self.profileA)
209
210 self.assertTrue(c.can_compare())
211 c.ensure_compute()
212 self.assertAlmostEqual(c._pvalue, 1)
213
215 """ Test if comparing to identical TProfiles with Kolmo Test works"""
216 c = validationcomparison.KolmogorovTest(self.profileA, self.profileA)
217 self.assertTrue(c.can_compare())
218 c.ensure_compute()
219 self.assertAlmostEqual(c._pvalue, 1)
220
222 """
223 Test if the comparison of two TProfiles with very similar content works
224 """
225 c = validationcomparison.Chi2Test(
227 )
228
229 self.assertTrue(c.can_compare())
230 c.ensure_compute()
231 self.assertAlmostEqual(c._pvalue, 0.43093514577898634)
232 self.assertAlmostEqual(c._ndf, 49)
233
235 """
236 Test if bins with zero error are treated properly
237 """
238
239 # add entry in the last bin, which will increase the bin weight,
240 # but leave the error at zero
241 self.profileZeroErrorBins.Fill(49.8, 1.0)
242 self.profileZeroErrorBins.Fill(49.8, 1.0)
243
244 # the comparison should set the bin content of this bin to zero
245 # to disable comparison of this bin instead of
246 # not doing the comparison at all
247 c = validationcomparison.Chi2Test(
249 )
250
251 self.assertTrue(c.can_compare())
252
253 c.ensure_compute()
254
255 self.assertAlmostEqual(c._pvalue, 0.4835651485797353)
256 # should still be only 49 ndf
257 self.assertEqual(c._ndf, 49)
258
260 """
261 Test comparison of regular histograms
262 """
263
264 c = validationcomparison.Chi2Test(self.histogramA, self.histogramB)
265
266 self.assertTrue(c.can_compare())
267 c.ensure_compute()
268 self.assertAlmostEqual(c._pvalue, 0.371600562118221)
269 self.assertAlmostEqual(c._chi2, 42.308970111484086)
270 self.assertAlmostEqual(c._chi2ndf, 1.0577242527871022)
271 self.assertEqual(c._ndf, 40)
272
274 """
275 Test if unsupported ROOT objects are treated properly
276 """
277 obj_not_supported = ROOT.TH2D("h2d", "h2d", 50, 0, 50, 50, 0, 50)
278 c = validationcomparison.Chi2Test(self.profileA, obj_not_supported)
279 self.assertFalse(c.can_compare())
280
282 """
283 Test if the comparison of differing objects is rejected
284 """
285 c = validationcomparison.Chi2Test(self.profileA, self.histogramA)
286 self.assertFalse(c.can_compare())
287
288 with self.assertRaises(validationcomparison.ObjectsNotSupported):
289 c._compute()
290
292 """
293 Test if two TEfficiency objects can be compared. Is a bit tricky
294 as TEfficiency does not support
295 """
296
297 c = validationcomparison.Chi2Test(self.teffA, self.teffB)
298
299 self.assertTrue(c.can_compare())
300 c.ensure_compute()
301 self.assertAlmostEqual(c._pvalue, 0.9760318312199932)
302 self.assertAlmostEqual(c._chi2, 8.16784873)
303 self.assertAlmostEqual(c._chi2ndf, 0.45376937)
304 self.assertEqual(c._ndf, 18)
305
307 """
308 Test if two TEfficiency objects can be compared. Is a bit tricky as
309 TEfficiency does not support Comparing the exact same TEfficiency
310 object should give back 100% agreement
311 """
312
313 c = validationcomparison.Chi2Test(self.teffA, self.teffA)
314 self.assertTrue(c.can_compare())
315 c.ensure_compute()
316 self.assertAlmostEqual(c._pvalue, 1.0)
317 self.assertAlmostEqual(c._chi2, 0.0)
318 self.assertAlmostEqual(c._chi2ndf, 0.0)
319 self.assertEqual(c._ndf, 18)
320
322 """
323 Test if the comparison attempt of profiles with differing bin count
324 fails properly
325 """
326 c = validationcomparison.Chi2Test(
328 )
329 self.assertFalse(c.can_compare())
330
331 with self.assertRaises(validationcomparison.DifferingBinCount):
332 c._compute()
333
334
335if __name__ == "__main__":
336 unittest.main(verbosity=2)
def create_histogram(name, entries=5000, mu=10, sigma=3)
def create_profile(name, entries=5000, mu=10, sigma=0.3, max_fill=50, fixed_number=None)
call_iteration
if we would at some point later want to implement several runs, we use this as a counter variable to ...
profileZeroErrorBinsTwo
Profile with bins with 0 error.
def create_teff(name, bins=20, eff=0.9)
profileZeroErrorBins
Profile with bins with 0 error.
profileAequal
Profile should be almost equal to A.
profileBequal
Profile should be almost equal to B.
histogramB
Histogram B (should be almost equal to profile A)
def test_compare_identical_profiles_kolmogorov(self)
profileDifferentBins
Profile with different bins.
test_options
Mapping test case -> Metaoptions.