The Gaudi Framework  master (bdbe7744)
Loading...
Searching...
No Matches
compareRootHistos.py
Go to the documentation of this file.
1#! /usr/bin/env python3
2
12
13import re
14import sys
15from optparse import OptionParser
16
17# Well known trick
18backupArgv = sys.argv[:]
19sys.argv = []
20from ROOT import TFile
21
22sys.argv = backupArgv
23
24# The list of histograms not to check. Expressed as regexps
25gRegexBlackList = []
26histos = ["TH1D", "TH1F", "TH2D", "TH2F", "TProfile"]
27ref = "REFERENCE"
28test = "TEST"
29
30# =============================================================================
31# Method : rec( o, path=None, lst=None )
32#
33# @param o : a ROOT object
34# @param path : a string like a transient store path; ie '/stat/CaloPIDs/ECALPIDE'
35# @param lst : a list to hold (path, object) tuples
36#
37# function : recursively pull apart a ROOT file, making a list of (path, TObject) tuples
38# This is done by GetListOfKeys method, which lets one work down through directories
39# until you hit the Histo at the end of the path. The list of tuples is returned
40#
41
42
43def rec(o, path=None, lst=None):
44 if not path:
45 path = "/stat"
46 lst = []
47 else:
48 path = path + "/" + o.GetName()
49 lst.append((path, o))
50 if "GetListOfKeys" in dir(o):
51 keys = o.GetListOfKeys()
52 for k in keys:
53 name = k.GetName()
54 rec(o.Get(name), path, lst)
55 else:
56 pass
57 return lst
58
59
60# =============================================================================
61
62# =============================================================================
63# Method : composition( t )
64#
65# @param t : a tuple of ( type, d ) where type is either 'REFERENCE' or 'TEST'
66# and d is a dictionary of ROOT objects, with each key = ROOT path
67#
68# function : deduce the composition, (objects/histos) counts
69#
70
71
73 typ, d = t
74 hists = 0
75 objs = 0
76 for k in d.keys():
77 if d[k].__class__.__name__ in histos:
78 hists += 1
79 else:
80 objs += 1
81 return objs, hists
82
83
84# =============================================================================
85
86# =============================================================================
87# Method : comparePaths( t1, t2 )
88#
89# @param t1, t2 : a tuple of ( type, d ) where type is either 'REFERENCE' or 'TEST'
90# and d is a dictionary of ROOT objects, with each key = ROOT path
91#
92# function : compare the paths between the two histo files. If the files are identical, they
93# should have the same set of paths. The Test file should definitely have the
94# same paths as the Reference. Perhaps the Reference file will have some more paths due
95# to extra histos added as part of Application Sequencer finalisation
96# Arguments t1 and t2 are checked and the test/reference auto-detected
97#
98
99
100def comparePaths(t1, t2):
101 if t1[0] == ref:
102 ds = t1[1]
103 dp = t2[1]
104 elif t2[0] == ref:
105 ds = t2[1]
106 dp = t1[1]
107 else:
108 print("Neither tuple is Reference Root file reference?")
109 return
110
111 dsks = list(ds.keys())
112 dpks = list(dp.keys())
113 dsks.sort()
114 dpks.sort()
115
116 sset = set(dsks)
117 pset = set(dpks)
118 os, hs = composition((ref, ds))
119 op, hp = composition((test, dp))
120 print("\n" + "=" * 80)
121 print("Comparison of Paths : Reference vs Test ROOT files")
122 print("-" * 80)
123 print(
124 f"Number of paths in Reference file : {len(dsks)} (objects, histos) = ( {os}, {hs} )"
125 )
126 print(
127 f"Number of paths in Test file : {len(dpks)} (objects, histos) = ( {op}, {hp} )"
128 )
129 matching = sset.intersection(pset)
130 matchingHistos = 0
131 for n in matching:
132 if ds[n].__class__.__name__ in histos:
133 matchingHistos += 1
134 print(f"\nMatching paths : {len(matching)}")
135 uSer = sset - pset
136 # work out histos unique to test file
137 uniqueReferenceHistos = 0
138 for n in uSer:
139 if ds[n].__class__.__name__ in histos:
140 uniqueReferenceHistos += 1
141 print(
142 f"Paths unique to Reference file : {len(uSer)} ( {uniqueReferenceHistos} Histos )"
143 )
144 if uSer:
145 for n in uSer:
146 print(f"\t{ds[n]} : \t{n}")
147 uPar = pset - sset
148 uniqueTestHistos = 0
149 for n in uPar:
150 if dp[n].__class__.__name__ in histos:
151 uniqueTestHistos += 1
152 print(f"Paths unique to Test file : {len(uPar)} ( {uniqueTestHistos} Histos )")
153 if uPar:
154 for n in uPar:
155 print(f"\t{dp[n]} : \t{n}")
156 print(f"Matching Histos to test : {matchingHistos}")
157 print("=" * 80 + "\n")
158 return (
159 ((os, hs), (op, hp)),
160 (uSer, uniqueReferenceHistos),
161 (uPar, uniqueTestHistos),
162 matchingHistos,
163 )
164
165
166# =============================================================================
167
168# =============================================================================
169# Method : bin2binIdentity(h1,h2)
170#
171# @param h1, h2 : The two histogtams to compare
172# function : Return the number of different bins
173
174
175def bin2binIdentity(h1, h2):
176 def getNbins(h):
177 biny = h.GetNbinsY()
178 if biny > 1:
179 biny += 1
180 binz = h.GetNbinsZ()
181 if binz > 1:
182 binz += 1
183 return (h.GetNbinsX() + 1) * (biny) * (binz)
184
185 nbins = getNbins(h1)
186 diffbins = 0
187 for ibin in range(0, nbins):
188 h1bin = h1.GetBinContent(ibin)
189 h2bin = h2.GetBinContent(ibin)
190 diffbins += h1bin != h2bin
191 return diffbins
192
193
194# =============================================================================
195# Method : compareHistos( t1, t2 )
196#
197# @param t1, t2 : a tuple of ( type, d ) where type is either 'REFERENCE' or 'TEST'
198# and d is a dictionary of ROOT objects, with each key = ROOT path
199#
200# function : compare the histograms in Reference/Test ROOT files. First, go through each
201# dict to collect the histos (ignore TDirectory objects, etc). Then the histos
202# in the test file (experimental) are compared to their equivalents in the
203# reference file (definitely correct) using 3 methods.
204# 1) The entries are checked, they should be equal
205# 2) If entries are equal, check the Integral(); should be equal
206# 3) If integrals are equal, check the KolmogorovTest() ; should be 1
207# 4) If identity flag is there and KS test is performed, perform bin2bin identity test
208# Arguments t1 and t2 are checked and the test/reference auto-detected
209#
210def compareHistos(t1, t2, state, checkBin2BinIdentity):
211 (
212 ((referenceObjects, referenceHistos), (parallObjects, parallHistos)),
213 (uniqueSerPaths, uniqueSerHistos),
214 (uniqueParPaths, uniqueParHistos),
215 mh,
216 ) = state
217
218 # deduce which one is test, which reference
219 if t1[0] == ref:
220 ds = t1[1]
221 dp = t2[1]
222 elif t2[0] == ref:
223 ds = t2[1]
224 dp = t1[1]
225 else:
226 print("Neither tuple is Reference Root file reference?")
227 return
228
229 # histocount, objectcount for test/reference
230 hcp = 0
231 pHistos = []
232 hcs = 0
233 sHistos = []
234
235 omit = [re.compile(regex) for regex in gRegexBlackList]
236
237 # find the histos in the reference file
238 for k in ds.keys():
239 if not any(regex.search(k) is not None for regex in omit):
240 if ds[k].__class__.__name__ in histos:
241 hcs += 1
242 sHistos.append(k)
243 # same for test
244 for k in dp.keys():
245 if not any(regex.search(k) is not None for regex in omit):
246 if dp[k].__class__.__name__ in histos:
247 hcp += 1
248 pHistos.append(k)
249
250 cEntries = 0
251 xEntries = 0
252 diffEntries = []
253 xIntegrals = 0
254 diffIntegrals = []
255 passedKol = 0
256 failedKol = 0
257 diffKols = []
258 passedIdentity = 0
259 failedIdentity = 0
260 diffIdentity = []
261 identityDiffBins = {}
262 kTested = 0
263 kTestResults = {}
264 notfound = 0
265 integralMatch = 0
266 otherTest = 0
267 zeroIntegralMatch = 0
268 for h in sHistos:
269 if h in pHistos:
270 # matching histos to check
271 cEntries += 1
272 sh = ds[h]
273 ph = dp[h]
274 # first check entries
275 if sh.GetEntries() != ph.GetEntries():
276 diffEntries.append(h)
277 xEntries += 1
278 continue
279 # check for (non-zero sum of bin error) && (non-zero integrals) for K-Test
280 sBinError = 0.0
281 pBinError = 0.0
282 for i in range(sh.GetNbinsX()):
283 sBinError += sh.GetBinError(i)
284 for i in range(ph.GetNbinsX()):
285 pBinError += ph.GetBinError(i)
286 sint = sh.Integral()
287 pint = ph.Integral()
288 doKS = (bool(sint) and bool(pint)) and (sBinError > 0 and pBinError > 0)
289 if checkBin2BinIdentity and doKS:
290 diffBins = bin2binIdentity(sh, ph)
291 if diffBins == 0:
292 passedIdentity += 1
293 else:
294 failedIdentity += 1
295 diffIdentity.append(h)
296 identityDiffBins[h] = diffBins
297 if (bool(sint) and bool(pint)) and (sBinError > 0 and pBinError > 0):
298 kTested += 1
299 kTest = sh.KolmogorovTest(ph)
300 kTestResults[h] = kTest
301 if int(kTest):
302 passedKol += 1
303 else:
304 # ; print 'KTest result : ', kTest
305 failedKol += 1
306 diffKols.append(h)
307 else:
308 # try the integral test?
309 otherTest += 1
310 if all((sint, pint)) and (sint == pint):
311 integralMatch += 1
312 elif sint == pint:
313 zeroIntegralMatch += 1
314 else:
315 diffIntegrals.append(h)
316 xIntegrals += 1
317 else:
318 notfound += 1
319 print("not found? ", h)
320
321 # report on Failed Entry-Checks
322 print("\n\n" + "-" * 80)
323 print("Summary of histos with different Entries")
324 print("-" * 80)
325 if diffEntries:
326 diffEntries.sort()
327 for e in diffEntries:
328 print(f"\t\t\t{e}:\t{int(ds[e].GetEntries())} != {int(dp[e].GetEntries())}")
329 print("-" * 80)
330
331 # report on Failed Kolmogorov Tests
332 print("\n\n" + "-" * 60)
333 print("Summary of histos which failed Kolmogorov Test")
334 print("-" * 60)
335 if diffKols:
336 diffKols.sort()
337 for e in diffKols:
338 result = kTestResults[e] # DP Calculated twice ARGH!!
339 print(f"{ds[e].ClassName()}\t\t{e} :\tK-Test Result :\t {result:5.16f}")
340 print("-" * 60)
341
342 # report on Failed Integral Checks
343 print("\n\n" + "-" * 60)
344 print("Summary of histos which failed Integral Check")
345 print("-" * 60)
346 if diffIntegrals:
347 diffIntegrals.sort()
348 for e in diffIntegrals:
349 diff = dp[e].Integral() - ds[e].Integral()
350 pc = (diff * 100) / ds[e].Integral()
351 print(
352 f"{ds[e].ClassName()}\t\t{e}:\t Diff = {diff:5.6f}\tPercent Diff to Reference : {pc:5.6f} "
353 )
354 print("-" * 60 + "\n")
355 print("=" * 80 + "\n")
356
357 # Report on failed bin2bin identity
358 if checkBin2BinIdentity:
359 # report on b2b checks
360 print("\n\n" + "-" * 80)
361 print("Summary of histos with at least one bin with different Entries")
362 print("-" * 80)
363 if diffIdentity:
364 diffIdentity.sort()
365 for e in diffIdentity:
366 print(
367 f"{ds[e].ClassName()}\t\t{e}: {identityDiffBins[e]} different bins"
368 )
369 print("-" * 80)
370
371 print("\n" + "=" * 80)
372 print("Comparison : Reference/Test ROOT Histo files")
373 print("\n\t\tReference\tTest")
374 print(
375 f"\tObjects : {referenceObjects}\t{parallObjects}\t\t( p-s = {parallObjects - referenceObjects} )"
376 )
377 print(
378 f"\tHistos : {referenceHistos}\t{parallHistos}\t\t( p-s = {parallHistos - referenceHistos} )"
379 )
380 print("\t __________")
381 print(
382 f"\tTotal : {referenceHistos + referenceObjects}\t{parallHistos + parallObjects}"
383 )
384 print(
385 f"Objects/Histos unique to Reference File : {len(uniqueSerPaths) - uniqueSerHistos} / {uniqueSerHistos}"
386 )
387 print(
388 f"Objects/Histos unique to Test File : {len(uniqueParPaths) - uniqueParHistos} / {uniqueParHistos}"
389 )
390 print(f"\nMatching Histograms valid for Comparison : {mh}")
391 print("\nOmissions' patterns : ")
392 for entry in gRegexBlackList:
393 print(f"\t{entry}")
394 print(
395 f"\nHistograms for Comparison (after Omissions) : {mh - len(gRegexBlackList)}"
396 )
397 print("\n\tHISTOGRAM TESTS : ")
398 print(f"\t\tKOLMOGOROV TEST : {kTested}")
399 print(f"\t\tINTEGRAL TEST : {otherTest}")
400 print(f"\t\tENTRIES TEST : {xEntries}")
401 if checkBin2BinIdentity:
402 print(f"\t\tBIN2BIN TEST : {passedIdentity}")
403 print("\t\t ____")
404 print(f"\t\tTested : {cEntries}")
405
406 print("\n\tDISCREPANCIES : ")
407 print(f"\t\tK-Test : {failedKol}")
408 print(f"\t\tIntegrals : {xIntegrals}")
409 print(f"\t\tEntries : {xEntries}")
410 retval = failedKol + xIntegrals + xEntries + failedIdentity
411 if retval != 0:
412 print("\nThe two sets of histograms were not identical")
413 print("\n" + "=" * 80)
414 return retval
415
416
417# =============================================================================
418
419
420def extractBlacklist(listString):
421 global gRegexBlackList
422 if listString:
423 for blackRegexp in listString.split(","):
424 gRegexBlackList.append(blackRegexp)
425 else:
426 gRegexBlackList = []
427
428
429# =============================================================================
430
431if __name__ == "__main__":
432 usage = "usage: %prog testFile.root referenceFile.root [options]"
433 parser = OptionParser()
434 parser.add_option(
435 "-b",
436 dest="blacklist",
437 help='Comma separated list of regexps matching histograms to skip (for example -b"MemoryTool,ProcTime")',
438 )
439
440 parser.add_option(
441 "-i",
442 action="store_true",
443 dest="bin2bin",
444 default=False,
445 help="Check for bin to bin identity",
446 )
447 (options, args) = parser.parse_args()
448
449 if len(args) != 2:
450 print("Wrong number of rootfiles. Usage:")
451 print(usage)
452 sys.exit(1)
453
454 extractBlacklist(options.blacklist)
455
456 testFile, referenceFile = args
457
458 tfs = TFile(testFile, "REC")
459 print(f"opening Test File : {testFile}")
460 tfp = TFile(referenceFile, "REC")
461 print(f"opening Reference File : {referenceFile}")
462
463 # get structure of TFiles in a list of (path, object) tuples
464 lref = rec(tfs)
465 ltest = rec(tfp)
466 # make a dictionary of lser and lpar. keys=paths
467 dref = dict([(n, o) for n, o in lref])
468 dtest = dict([(n, o) for n, o in ltest])
469 # make a tuple of (type, dict) where type is either 'reference' or 'test'
470 ts = (ref, dref)
471 tp = (test, dtest)
472
473 # check objs/histos in each file
474 composition(ts)
475 composition(tp)
476
477 # compare paths from each file
478 state = comparePaths(ts, tp)
479
480 # compare histos from each file
481 retval = compareHistos(ts, tp, state, checkBin2BinIdentity=options.bin2bin)
482
483 # finished with TFiles
484 tfs.Close()
485 tfp.Close()
486
487 sys.exit(retval)
rec(o, path=None, lst=None)
compareHistos(t1, t2, state, checkBin2BinIdentity)
extractBlacklist(listString)