ATLAS Offline Software
diff_root_files.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
2 
3 # @file PyUtils.scripts.diff_root_files
4 # @purpose check that 2 ROOT files have same content (containers and sizes).
5 # @author Sebastien Binet
6 # @date February 2010
7 
8 __doc__ = "diff two ROOT files (containers and sizes)"
9 __author__ = "Sebastien Binet"
10 
11 
12 import PyUtils.acmdlib as acmdlib
13 import re
14 from functools import cache, reduce
15 from math import isnan
16 from numbers import Real
17 from os import environ
18 
19 
20 g_ALLOWED_MODES = ('summary', 'semi-detailed', 'detailed')
21 g_ALLOWED_ERROR_MODES = ('bailout', 'resilient')
22 
23 
24 
25 
26 def _is_detailed(args):
27  return args.mode == 'detailed'
28 
29 def _is_summary(args):
30  return args.mode == 'summary'
31 
32 def _is_exit_early(args):
33  return args.error_mode == 'bailout'
34 
35 # Possibly compare two vectors. If nan_equal, then consider NaNs to be equal.
36 # Returns None if we have two matching vectors.
37 # If we have two vectors that differ at some element, return that index.
38 # Otherwise return -1 (inputs not vectors, etc).
39 _vectypes = {'std::vector<float>',
40  'std::vector<double>',
41  'std::vector<int>',
42  'std::vector<unsigned int>',
43  'std::vector<long>',
44  'std::vector<unsigned long>',
45  'std::vector<short>',
46  'std::vector<unsigned short>',
47  'std::vector<char>',
48  'std::vector<unsigned char>',
49  'std::vector<long long>',
50  'std::vector<unsigned long long>'}
51 
52 def _vecdiff (v1, v2, nan_equal):
53  if getattr(type(type(v1)), '__cpp_name__', None) not in _vectypes:
54  return -1
55  if type(v1) is not type(v2): return -1
56  sz = v1.size()
57  if sz != v2.size(): return -1
58  if nan_equal:
59  isnan_ = isnan
60  for i in range (sz):
61  val1 = v1[i]
62  val2 = v2[i]
63  if val1 != val2 and not all(
64  [isinstance(_, Real) and isnan_(_) for _ in (val1, val2)]):
65  return i
66  else:
67  for i in range (sz):
68  if v1[i] != v2[i]:
69  return i
70  return None
71 
72 @acmdlib.command(name='diff-root')
73 @acmdlib.argument('old',
74  help='path to the reference ROOT file to analyze')
75 @acmdlib.argument('new',
76  help='path to the ROOT file to compare to the reference')
77 @acmdlib.argument('-t', '--tree-name',
78  default='CollectionTree',
79  help='name of the TTree to compare')
80 @acmdlib.argument('--branches-of-interest',
81  nargs='+',
82  default=set(),
83  help='set of regex matching names of branches to compare; assumes all if none specified.')
84 @acmdlib.argument('--ignore-leaves',
85  nargs='+',
86  default=('Token', 'index_ref', r'(.*)_timings\.(.*)', r'(.*)_mems\.(.*)', r'(.*)TrigCostContainer(.*)'),
87  help='set of leaves names to ignore from comparison; can be a branch name or a partial leaf name (accepts regex)')
88 @acmdlib.argument('--enforce-leaves',
89  nargs='+',
90  default=('BCID',),
91  help='set of leaves names we make sure to compare')
92 @acmdlib.argument('--leaves-prefix',
93  default='',
94  help='Remove prefix value from all leaves')
95 @acmdlib.argument('--known-hacks',
96  nargs='+',
97  default=('m_athenabarcode', 'm_token',),
98  help='set of leaves which are known to fail (but should be fixed at some point) [default: %(default)s]')
99 @acmdlib.argument('--entries',
100  default='',
101  help='a list of entries (indices, not event numbers) or an expression (like range(3) or 0,2,1 or 0:3) leading to such a list, to compare.')
102 @acmdlib.argument('-v', '--verbose',
103  action='store_true',
104  default=False,
105  help="""Enable verbose printout""")
106 @acmdlib.argument('--order-trees',
107  action='store_true',
108  default=False,
109  help="""To order trees according to event numbers""")
110 @acmdlib.argument('--exact-branches',
111  action='store_true',
112  default=False,
113  help="""Only allow exact list of branches present""")
114 @acmdlib.argument('--mode',
115  choices=g_ALLOWED_MODES,
116  default='detailed',
117  help="""\
118 Enable a particular mode.
119  'summary': only report the number of differences.
120  'semi-detailed': report the number of differences and the leaves that differ.
121  'detailed': display everything.
122 default='%(default)s'.
123 allowed: %(choices)s
124 """
125  )
126 @acmdlib.argument('--error-mode',
127  choices=g_ALLOWED_ERROR_MODES,
128  default='bailout',
129  help="""\
130 Enable a particular error mode.
131  'bailout': bail out on first error.
132  'resilient': keep running.
133 default='%(default)s'.
134 allowed: %(choices)s
135 """
136  )
137 @acmdlib.argument('--nan-equal',
138  action='store_true',
139  default=False,
140  help="""Compare nan as equal to nan""")
141 
142 def main(args):
143  """diff two ROOT files (containers and sizes)"""
144 
145  # We allocate many python objects at once.
146  # Running GC less often by jacking up the threshold speeds things up
147  # considerably.
148  import gc
149  gc.set_threshold (100000)
150 
151  import PyUtils.RootUtils as ru
152  root = ru.import_root() # noqa: F841
153 
154  # Force load some dictionaries to work around ATLASRECTS-6261/ROOT-10940/ATEAM-942
155  if 'AtlasProject' in environ and environ['AtlasProject'] == 'Athena':
156  root.xAOD.Init().ignore()
157  root.xAOD.ParticleContainer_v1
158  root.xAOD.DiTauJetContainer_v1
159 
160  import PyUtils.Logging as L
161  msg = L.logging.getLogger('diff-root')
162  if args.verbose:
163  msg.setLevel(L.logging.VERBOSE)
164  else:
165  msg.setLevel(L.logging.INFO)
166 
167  from PyUtils.Helpers import ShutUp # noqa: F401
168 
169  if args.entries == '':
170  args.entries = -1
171 
172  msg.info('comparing tree [%s] in files:', args.tree_name)
173  msg.info(' old: [%s]', args.old)
174  msg.info(' new: [%s]', args.new)
175  msg.info('branches of interest: %s', args.branches_of_interest)
176  msg.info('ignore leaves: %s', args.ignore_leaves)
177  msg.info('enforce leaves: %s', args.enforce_leaves)
178  msg.info('leaves prefix: %s', args.leaves_prefix)
179  msg.info('hacks: %s', args.known_hacks)
180  msg.info('entries: %s', args.entries)
181  msg.info('mode: %s', args.mode)
182  msg.info('error mode: %s', args.error_mode)
183  msg.info('order trees: %s', args.order_trees)
184  msg.info('exact branches: %s', args.exact_branches)
185 
186  import PyUtils.Helpers as H
187  with H.ShutUp() :
188  fold = ru.RootFileDumper(args.old, args.tree_name)
189  fnew = ru.RootFileDumper(args.new, args.tree_name)
190  pass
191 
192  def tree_infos(tree, args):
193  nentries = tree.GetEntriesFast()
194  # l.GetBranch().GetName() gives the full leaf path name
195  leaves = [l.GetBranch().GetName() for l in tree.GetListOfLeaves()
196  if l.GetBranch().GetName() not in args.ignore_leaves]
197  if args.leaves_prefix:
198  leaves = [l.replace(args.leaves_prefix, '') for l in leaves]
199  return {
200  'entries': nentries,
201  'leaves': set(leaves),
202  }
203 
204  def ordered_indices(tree, reverse_order = False):
205  from collections import OrderedDict
206  import operator
207 
208  dict_in = {}
209  nevts = tree.GetEntriesFast()
210 
211  eiDict = {'':['EventInfoAuxDyn.eventNumber'],
212  'eventNumber':['EventInfoAux.',
213  'Bkg_EventInfoAux.',
214  'xAOD::EventAuxInfo_v3_EventInfoAux.',
215  'xAOD::EventAuxInfo_v2_EventInfoAux.',
216  'xAOD::EventAuxInfo_v1_EventInfoAux.',
217  'xAOD::EventAuxInfo_v3_Bkg_EventInfoAux.',
218  'xAOD::EventAuxInfo_v2_Bkg_EventInfoAux.',
219  'xAOD::EventAuxInfo_v1_Bkg_EventInfoAux.'],
220  'm_event_ID m_event_number':['McEventInfo',
221  'ByteStreamEventInfo',
222  'EventInfo_p4_McEventInfo',
223  'EventInfo_p4_ByteStreamEventInfo']}
224 
225  def find_attrs():
226  """Find the relevant attributes for reading the event number"""
227  for ii, jj in eiDict.items():
228  for kk in jj:
229  if hasattr(tree, kk):
230  return kk, ii
231  else:
232  return None, None
233 
234  tree.GetEntry(0)
235  attr1, attr2 = find_attrs()
236  if attr1 is None or attr2 is None:
237  msg.error('Cannot read event info, will bail out.')
238  msg.error(f"Tried attributes {attr1} and {attr2}")
239  return []
240  attrs = [attr1] + attr2.split()
241 
242  tree.SetBranchStatus ('*', 0)
243  tree.SetBranchStatus (attr1, 1)
244 
245  for idx in range(0, nevts):
246  if idx % 100 == 0:
247  msg.debug('Read {} events from the input so far'.format(idx))
248  tree.GetEntry(idx)
249  event_number = reduce(getattr, attrs, tree)
250  msg.debug('Idx : EvtNum {:10d} : {}'.format(idx,event_number))
251  dict_in[idx] = event_number
252 
253  tree.SetBranchStatus ('*', 1)
254 
255  # Sort the dictionary by event numbers
256  dict_out = OrderedDict(sorted(dict_in.items(), key=operator.itemgetter(1), reverse = reverse_order))
257 
258  # Write out the ordered index and event number pairs
259  return [(idx, ival) for idx, ival in dict_out.items()]
260 
261  def diff_tree(fold, fnew, args):
262  infos = {
263  'old' : tree_infos(fold.tree, args),
264  'new' : tree_infos(fnew.tree, args),
265  }
266 
267  nentries = min(infos['old']['entries'],
268  infos['new']['entries'])
269  itr_entries = nentries
270  if args.entries in (-1,'','-1'):
271  #msg.info('comparing over [%s] entries...', nentries)
272  itr_entries = nentries
273  if infos['old']['entries'] != infos['new']['entries']:
274  msg.info('different numbers of entries:')
275  msg.info(' old: [%s]', infos['old']['entries'])
276  msg.info(' new: [%s]', infos['new']['entries'])
277  msg.info('=> comparing [%s] first entries...', nentries)
278  else:
279  itr_entries = args.entries
280  pass
281  msg.info('comparing over [%s] entries...', itr_entries)
282 
283  @cache
284  def skip_leaf(name_from_dump, skip_leaves):
285  """ Here decide if the current leaf should be skipped.
286  Previously the matching was done based on the full or partial
287  leaf name. E.g. foo.bar.zzz would be skipped if any of the
288  following were provided:
289  * foo
290  * foo.bar
291  * foo.bar.zzz
292  * Any of the foo, bar, or zzz
293  Now, we make a regex matching such that the user doesn't
294  need to provide full branch names.
295  """
296  for pattern in skip_leaves:
297  try:
298  m = re.match(pattern, name_from_dump)
299  except TypeError:
300  continue
301  if m:
302  return True
303  else:
304  return False
305 
306  @cache
307  def skip_leaf_entry(entry2, skip_leaves):
308  leafname = '.'.join([s for s in entry2 if not s.isdigit()])
309  return skip_leaf (leafname, skip_leaves)
310 
311  def filter_branches(leaves):
312  matches = set()
313  for regex in args.branches_of_interest:
314  test = re.compile(regex)
315  matches.update({l for l in leaves if test.match(l)})
316  return matches
317 
318  skipset = frozenset(args.ignore_leaves)
319  removed_leaves = infos['old']['leaves'] - infos['new']['leaves']
320  added_leaves = infos['new']['leaves'] - infos['old']['leaves']
321 
322  if args.branches_of_interest:
323  removed_leaves = filter_branches(removed_leaves)
324  added_leaves = filter_branches(added_leaves)
325  else:
326  removed_leaves = {l for l in removed_leaves if not skip_leaf(l, skipset)}
327  added_leaves = {l for l in added_leaves if not skip_leaf(l, skipset)}
328 
329  if removed_leaves:
330  removed_leaves_list = list(removed_leaves)
331  removed_leaves_list.sort()
332  if args.exact_branches:
333  msg.error('the following variables exist only in the old file !')
334  for l in removed_leaves_list:
335  msg.error(' - [%s]', l)
336  else:
337  msg.warning('the following variables exist only in the old file !')
338  for l in removed_leaves_list:
339  msg.warning(' - [%s]', l)
340  if added_leaves:
341  added_leaves_list = list(added_leaves)
342  added_leaves_list.sort()
343  if args.exact_branches:
344  msg.error('the following variables exist only in the new file !')
345  for l in added_leaves_list:
346  msg.error(' - [%s]', l)
347  else:
348  msg.warning('the following variables exist only in the new file !')
349  for l in added_leaves_list:
350  msg.warning(' - [%s]', l)
351 
352  # need to remove trailing dots as they confuse reach_next()
353  skip_leaves = [ l.rstrip('.') for l in removed_leaves | added_leaves | set(args.ignore_leaves) ]
354  for l in skip_leaves:
355  msg.debug('skipping [%s]', l)
356  skip_leaves = frozenset (skip_leaves)
357 
358  oldBranches = set(b.GetName().rstrip('\0') for b in fold.tree.GetListOfBranches())
359  newBranches = set(b.GetName().rstrip('\0') for b in fnew.tree.GetListOfBranches())
360  branches = oldBranches & newBranches
361 
362  if args.branches_of_interest:
363  branches_of_interest = args.branches_of_interest
364 
365  # check that all branches of interest exist in the new file
366  for regex in branches_of_interest:
367  test = re.compile(regex)
368  if not {l for l in infos['new']['leaves'] if test.match(l)}:
369  msg.error(f'no match in new file for branch of interest: {regex}')
370 
371  BOI_matches = set()
372  for branch_of_interest in branches_of_interest:
373  try:
374  r = re.compile(branch_of_interest)
375  BOI_matches.update(filter(r.match, branches))
376  except TypeError:
377  continue
378 
379  if len(BOI_matches)<1:
380  msg.error('No matching branches found in both files for supplied branches of interest, quitting.')
381  return 1
382  msg.info('only the following branches of interest will be compared: ')
383  for l in BOI_matches:
384  msg.info(' - [%s]', l)
385  branches = BOI_matches
386 
387  msg.info('comparing [%s] leaves over entries...', len(infos['old']['leaves'] & infos['new']['leaves']))
388  n_good = 0
389  n_bad = 0
390  if args.exact_branches:
391  n_bad += len(removed_leaves) + len(added_leaves)
392  import collections
393  summary = collections.defaultdict(int)
394 
395  def get_event_range(entry):
396  smin, smax = 0, None
397  # Parse user input
398  if isinstance(entry, str):
399  # We support three main cases in this format: 5:10 (5th to 10th),
400  # 5: (5th to the end), and :5 (from the start to 5th)
401  if ':' in entry:
402  vals = entry.split(':')
403  smin = int(vals[0]) if len(vals) > 0 and vals[0].isdigit() else 0
404  smax = int(vals[1]) if len(vals) > 1 and vals[1].isdigit() else None
405  # This is the case where the user inputs the total number of events
406  elif entry.isdigit():
407  smin = 0
408  smax = int(entry) if int(entry) > 0 else None
409  # Handle the case where the input is a number (i.e. default)
410  elif isinstance(entry, int):
411  smin = 0
412  smax = entry if entry > 0 else None
413  # If we come across an unhandled case, bail out
414  else:
415  msg.warning(f"Unknown entries argument {entry}, will compare all events...")
416  msg.debug(f"Event slice is parsed as [{smin},{smax}]")
417  return smin, smax
418 
419  if args.order_trees:
420  smin, smax = get_event_range(itr_entries)
421  idx_old = ordered_indices(fold.tree)[smin:smax]
422  idx_new = ordered_indices(fnew.tree)[smin:smax]
423  itr_entries_old, event_numbers_old = list(map(list,zip(*idx_old)))
424  itr_entries_new, event_numbers_new = list(map(list,zip(*idx_new)))
425  msg.debug(f"List of old indices {itr_entries_old}")
426  msg.debug(f"List of new indices {itr_entries_new}")
427  msg.debug(f"List of old events {event_numbers_old}")
428  msg.debug(f"List of new events {event_numbers_new}")
429  if event_numbers_old != event_numbers_new:
430  msg.error('Events differ, quitting!')
431  msg.error(f"List of old events {event_numbers_old}")
432  msg.error(f"List of new events {event_numbers_new}")
433  return 1
434  else:
435  itr_entries_old = itr_entries
436  itr_entries_new = itr_entries
437 
438  branches = sorted(branches)
439  old_dump_iter = fold.dump(args.tree_name, itr_entries_old, branches, True, False)
440  new_dump_iter = fnew.dump(args.tree_name, itr_entries_new, branches, True, False)
441  old_skip_dict = {}
442  new_skip_dict = {}
443 
444  def leafname_fromdump(entry):
445  if entry is None:
446  return None
447  else:
448  return '.'.join([s for s in entry[2] if not s.isdigit()])
449 
450  def elindices_fromdump(entry):
451  if entry is None:
452  return None
453  else:
454  return [int(s) for s in entry[2] if s.isdigit()]
455 
456  def reach_next(dump_iter, skip_leaves, skip_dict, leaves_prefix=None):
457  keep_reading = True
458  while keep_reading:
459  try:
460  entry = next(dump_iter)
461  except StopIteration:
462  return None
463 
464  entry2_orig = entry[2][0]
465  entry[2][0] = entry[2][0].rstrip('.\0') # clean branch name
466  if leaves_prefix:
467  entry[2][0] = entry[2][0].replace(leaves_prefix, '')
468 
469  # Calling leafname_fromdump is expensive. When we can,
470  # try to make the skip decision using just the first element
471  # in entry[2]. skip_dict maps from entry[2] values to either
472  # -1 if some branch with this entry prefix is being skipped
473  # or the event index at which we first saw this value.
474  # If we get to a different index and no branches with
475  # this prefix have been skipped, then we can assume that
476  # none of them are.
477  skip = skip_dict.setdefault (entry2_orig, entry[1])
478  if skip > 0 and skip != entry[1]:
479  # Old entry --- we can assume no skipping.
480  return entry
481 
482  if not skip_leaf(leafname_fromdump(entry), skip_leaves):
483  return entry
484  skip_dict[entry2_orig] = -1
485  msg.debug('SKIP: {}'.format(leafname_fromdump(entry)))
486  pass
487 
488  read_old = True
489  read_new = True
490  d_old = None
491  d_new = None
492 
493  while True:
494  if read_old:
495  d_old = reach_next(old_dump_iter, skip_leaves, old_skip_dict, args.leaves_prefix)
496  if read_new:
497  d_new = reach_next(new_dump_iter, skip_leaves, new_skip_dict, args.leaves_prefix)
498 
499  if not d_new and not d_old:
500  break
501 
502  read_old = True
503  read_new = True
504 
505  if (args.order_trees and d_old and d_new and d_old[2:] == d_new[2:]) or d_old == d_new:
506  n_good += 1
507  continue
508 
509  if d_old:
510  tree_name, ientry, iname, iold = d_old
511  if d_new:
512  tree_name, jentry, jname, inew = d_new
513 
514  idiff = _vecdiff (iold, inew, args.nan_equal)
515  if idiff is None:
516  n_good += 1
517  continue
518  elif idiff >= 0:
519  iold = iold[idiff]
520  inew = inew[idiff]
521  iname = iname[:-1] + [idiff] + iname[-1:]
522  jname = jname[:-1] + [idiff] + jname[-1:]
523 
524  # for regression testing we should have NAN == NAN
525  if args.nan_equal:
526  if all([isinstance(x,Real) and isnan(x) for x in [iold,inew]]):
527  n_good += 1
528  continue
529 
530  # FIXME: that's a plain (temporary?) hack
531  if iname[-1] in args.known_hacks or jname[-1] in args.known_hacks:
532  continue
533 
534  n_bad += 1
535 
536  # Identifiers are event numbers if we're ordering the trees, otherwise tree indices
537  if args.order_trees:
538  id_old = dict(idx_old)[ientry]
539  id_new = dict(idx_new)[jentry]
540  else:
541  id_old = ientry
542  id_new = jentry
543 
544  if not args.order_trees:
545  in_synch = d_old and d_new and d_old[:-1] == d_new[:-1]
546  else:
547  in_synch = d_old and d_new and d_old[0] == d_new[0] and d_old[2] == d_new[2] and id_old == id_new
548  if not in_synch:
549  if _is_detailed(args):
550  if d_old:
551  msg.info('::sync-old %s','.'.join(["%03i"%ientry]+list(map(str, d_old[2]))))
552  else:
553  msg.info('::sync-old ABSENT')
554  if d_new:
555  msg.info('::sync-new %s','.'.join(["%03i"%jentry]+list(map(str, d_new[2]))))
556  else:
557  msg.info('::sync-new ABSENT')
558  pass
559  # remember for later
560  if not d_old:
561  fold.allgood = False
562  summary[leafname_fromdump(d_new)] += 1
563  elif not d_new:
564  fnew.allgood = False
565  summary[leafname_fromdump(d_old)] += 1
566  else:
567  branch_old = f"{id_old}.{d_old[2][0]}"
568  branch_new = f"{id_new}.{d_new[2][0]}"
569  leaf_old = leafname_fromdump(d_old)
570  leaf_new = leafname_fromdump(d_new)
571  indices_old = elindices_fromdump(d_old)
572  indices_new = elindices_fromdump(d_new)
573  # Branches/Leaves are alphabetically ordered
574  # If we're out-of-sync, we try to figure out
575  # if we should advance the old or the new branch
576  # For same branches, we look at the full leaf name
577  # If that fails we look at the indices
578  if branch_old > branch_new:
579  read_old = False
580  elif branch_old < branch_new:
581  read_new = False
582  else:
583  if leaf_old > leaf_new:
584  read_old = False
585  elif leaf_old < leaf_new:
586  read_new = False
587  elif indices_old and indices_new and len(indices_old) == len(indices_new):
588  if indices_old > indices_new:
589  read_old = False
590  elif indices_old < indices_new:
591  read_new = False
592  # Let's see if we can reconcile
593  # If not, just bail out to avoid false positivies
594  if read_old and not read_new:
595  if _is_detailed(args):
596  msg.info('::sync-old skipping entry')
597  fold.allgood = False
598  summary[leaf_old] += 1
599  elif read_new and not read_old:
600  if _is_detailed(args):
601  msg.info('::sync-new skipping entry')
602  fnew.allgood = False
603  summary[leaf_new] += 1
604  else:
605  msg.error('::sync attempt failed, bailing out...')
606  msg.error(f"::sync-old Leaf vs Index : {leaf_old} vs {indices_old}")
607  msg.error(f"::sync-new Leaf vs Index : {leaf_new} vs {indices_new}")
608  fold.allgood = False
609  fnew.allgood = False
610  summary[leaf_old] += 1
611  summary[leaf_new] += 1
612  break
613 
614  if _is_exit_early(args):
615  msg.info('*** exit on first error ***')
616  break
617  continue
618 
619  if not args.order_trees:
620  n = '.'.join(list(map(str, ["%03i"%ientry]+iname)))
621  else:
622  n = '.'.join(list(map(str, ["%03i"%ientry]+iname+["%03i"%jentry]+jname)))
623  diff_value = 'N/A'
624  try:
625  diff_value = 50.*(iold-inew)/(iold+inew)
626  diff_value = '%.8f%%' % (diff_value,)
627  except Exception:
628  pass
629  if _is_detailed(args):
630  msg.info('%s %r -> %r => diff= [%s]', n, iold, inew, diff_value)
631  pass
632  summary[leafname_fromdump(d_old)] += 1
633 
634  if iname[0] in args.enforce_leaves or jname[0] in args.enforce_leaves:
635  msg.info("don't compare further")
636  break
637  pass # loop over events/branches
638 
639  msg.info('Found [%s] identical leaves', n_good)
640  msg.info('Found [%s] different leaves', n_bad)
641 
642  if not _is_summary(args):
643  keys = sorted(summary.keys())
644  for n in keys:
645  v = summary[n]
646  msg.info(' [%s]: %i leaves differ', n, v)
647  pass
648  pass
649 
650  if (not fold.allgood) or (not fnew.allgood):
651  msg.error('NOTE: there were errors during the dump')
652  msg.info('fold.allgood: %s' , fold.allgood)
653  msg.info('fnew.allgood: %s' , fnew.allgood)
654  n_bad += 0.5
655  return n_bad
656 
657  ndiff = diff_tree(fold, fnew, args)
658  if ndiff != 0:
659  msg.error('files differ!')
660  return 2
661  msg.info('all good.')
662  return 0
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:307
vtune_athena.format
format
Definition: vtune_athena.py:14
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.scripts.diff_root_files._is_exit_early
def _is_exit_early(args)
Definition: diff_root_files.py:32
reduce
void reduce(HepMC::GenEvent *ge, std::vector< HepMC::GenParticlePtr > toremove)
Remove unwanted particles from the event, collapsing the graph structure consistently.
Definition: FixHepMC.cxx:81
python.scripts.diff_root_files._is_summary
def _is_summary(args)
Definition: diff_root_files.py:29
covarianceTool.filter
filter
Definition: covarianceTool.py:514
DiTauMassTools::ignore
void ignore(T &&)
Definition: PhysicsAnalysis/TauID/DiTauMassTools/DiTauMassTools/HelperFunctions.h:54
python.scripts.diff_root_files.main
def main(args)
Definition: diff_root_files.py:142
python.scripts.diff_root_files._is_detailed
def _is_detailed(args)
classes ----------------------------------------------------------------—
Definition: diff_root_files.py:26
fillPileUpNoiseLumi.next
next
Definition: fillPileUpNoiseLumi.py:52
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
python.scripts.diff_root_files._vecdiff
def _vecdiff(v1, v2, nan_equal)
Definition: diff_root_files.py:52
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename T::value_type > sorted(T begin, T end)
Helper function to create a sorted vector from an unsorted one.
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:224
min
#define min(a, b)
Definition: cfImp.cxx:40
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
Cut::all
@ all
Definition: SUSYToolsAlg.cxx:64
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78