Belle II Software  release-08-01-10
test_comparison.py
1 #!/usr/bin/env python3
2 
3 
10 
11 # std
12 import unittest
13 import random
14 from typing import List, Tuple
15 
16 # 3rd
17 import ROOT
18 
19 # ours
20 import validationcomparison
21 import metaoptions
22 
23 
24 class TestGetComparison(unittest.TestCase):
25  """ Test get_comparison """
26 
27  def setUp(self):
28  """ Set up testing of get_comparison """
29 
30  self.test_optionstest_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_optionstest_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_optionstest_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 
72 class 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 
134  self.call_iterationcall_iteration = 0
135 
136 
137  self.profileAprofileA = self.create_profilecreate_profile(self.root_nameroot_name("profileA"))
138 
139 
140  self.profileBprofileB = self.create_profilecreate_profile(self.root_nameroot_name("profileB"))
141 
142 
143  self.profileCprofileC = self.create_profilecreate_profile(
144  self.root_nameroot_name("profileC"), 5000, 5, 3
145  )
146 
147 
148  self.profileZeroErrorBinsprofileZeroErrorBins = self.create_profilecreate_profile(
149  self.root_nameroot_name("profileZeroErrorBins"), max_fill=49
150  )
151  self.profileZeroErrorBinsprofileZeroErrorBins.SetBinError(35, 0.0)
152 
153 
154  self.profileZeroErrorBinsTwoprofileZeroErrorBinsTwo = self.create_profilecreate_profile(
155  self.root_nameroot_name("profileZeroErrorBinsTwo"), max_fill=49
156  )
157  self.profileZeroErrorBinsTwoprofileZeroErrorBinsTwo.SetBinError(35, 0.0)
158 
159 
160  self.histogramAhistogramA = self.create_histogramcreate_histogram(
161  self.root_nameroot_name("histogramA"), 5000, 5, 3
162  )
163 
164 
165  self.histogramBhistogramB = self.create_histogramcreate_histogram(
166  self.root_nameroot_name("histogramB"), 5000, 5, 3
167  )
168 
169 
170  self.profileAequalprofileAequal = self.create_profilecreate_profile(
171  self.root_nameroot_name("profileA_almostequal"), sigma=0.4
172  )
173 
174 
175  self.profileBequalprofileBequal = self.create_profilecreate_profile(
176  self.root_nameroot_name("profileB_almostequal"), sigma=0.4
177  )
178 
179 
180  self.profileDifferentBinsprofileDifferentBins = ROOT.TProfile(
181  self.root_nameroot_name("profileDifferentBins"),
182  self.root_nameroot_name("profileDifferentBins"),
183  40,
184  0,
185  50.0,
186  )
187 
188 
189  self.teffAteffA = self.create_teffcreate_teff(self.root_nameroot_name("teffA"))
190 
191 
192  self.teffBteffB = self.create_teffcreate_teff(self.root_nameroot_name("teffB"))
193 
195  """
196  Test if comparing two similar TProfiles works
197  """
198  c = validationcomparison.Chi2Test(self.profileAprofileA, self.profileBprofileB)
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.profileAprofileA, self.profileAprofileA)
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.profileAprofileA, self.profileAprofileA)
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(
226  self.profileAequalprofileAequal, self.profileBequalprofileBequal
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.profileZeroErrorBinsprofileZeroErrorBins.Fill(49.8, 1.0)
242  self.profileZeroErrorBinsprofileZeroErrorBins.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(
248  self.profileZeroErrorBinsprofileZeroErrorBins, self.profileZeroErrorBinsTwoprofileZeroErrorBinsTwo
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.histogramAhistogramA, self.histogramBhistogramB)
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.profileAprofileA, 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.profileAprofileA, self.histogramAhistogramA)
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.teffAteffA, self.teffBteffB)
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.teffAteffA, self.teffAteffA)
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(
327  self.profileAprofileA, self.profileDifferentBinsprofileDifferentBins
328  )
329  self.assertFalse(c.can_compare())
330 
331  with self.assertRaises(validationcomparison.DifferingBinCount):
332  c._compute()
333 
334 
335 if __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.