CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
rootmath.py
Go to the documentation of this file.
1 """
2 rootmath description
3 """
4 from __future__ import absolute_import
5 from __future__ import print_function
6 
7 from builtins import range
8 __license__ = '''\
9 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu>
10 
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17 
18 The above copyright notice and this permission notice shall be included in
19 all copies or substantial portions of the Software.
20 
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 THE SOFTWARE.
28 '''
29 
30 
31 ##############################################################################
32 ######## Import python libraries #############################################
33 
34 import sys
35 import shutil
36 import math
37 import os
38 import re
39 import tempfile
40 import copy
41 import fnmatch
42 from . import argparse
43 from os.path import join as joined
44 from .utilities import rootglob, loadROOT
45 
46 ROOT = loadROOT()
47 
48 ##############################################################################
49 ######## Define globals ######################################################
50 
51 from .version import __version__ # version number
52 
53 
54 ##############################################################################
55 ######## Classes #############################################################
56 
57 class Target(object):
58  """Description."""
59  def __init__(self, filename, path='', scale=1., scale_error=None):
60  self.filename = filename
61  self.path = path
62  self.scale = scale
63  self.scale_error = scale_error
64  def __repr__(self):
65  return "%s:%s:%f" % (self.filename, self.path, self.scale)
66 
67 def newadd(outfile, targets, dest_path=""):
68  """Description."""
69  if allsame([x.filename for x in targets]):
70  f = ROOT.TFile(targets[0].filename, 'read')
71  paths = [x.path for x in targets]
72  scales = [x.scale for x in targets]
73  scale_errors = [x.scale_error for x in targets]
74  if f.GetDirectory(paths[0]):
75  destdir = pathdiff2(paths) # What does this do?
76  for h in [os.path.basename(x) for x in
77  rootglob(f, paths[0] + '/*')]:
78  hists = [f.GetDirectory(x).Get(h) for x in paths]
79  if not alltrue([x and x.InheritsFrom('TH1') for x in hists]):
80  continue
81  dest = joined(destdir, h)
82  add(outfile, dest, hists, scales, dest_path, scale_errors=scale_errors)
83  else:
84  hists = [f.Get(x) for x in paths]
85  if alltrue([x and x.InheritsFrom('TH1') for x in hists]):
86  dest = pathdiff2(paths)
87  add(outfile, dest, hists, scales, scale_errors=scale_errors)
88  else:
89  dict_targets = {} # Stores paths and scales, key = filename
90  dict_tfiles = {} # Stores map from filenames to Root.TFile() objects
91  for target in targets:
92  dict_targets.setdefault(target.filename, []).append((target.path, target.scale))
93  if (target.filename not in dict_tfiles):
94  # Only open root files once
95  dict_tfiles[target.filename] = ROOT.TFile(target.filename, 'read')
96  # dict_targets now a dictionary, with keys the filenames, example:
97  # {'fileA.root': [('path0',scale0), ('path1', scale1)],
98  # 'fileB.root': [('path3', scale3)]}
99  f = ROOT.TFile(targets[0].filename, 'read')
100  if f.GetDirectory(targets[0].path):
101  # Create list of histograms to get
102  destdir = '/' # should probably use pathdiff2 somehow
103  histnames = [os.path.basename(x) for x in
104  rootglob(f, targets[0].path + '/*')]
105  f.Close()
106  # For each histogram name found, grab it from
107  # every file & path
108  for histname in histnames:
109  hists = []
110  scales = []
111  for filename in dict_targets:
112  tfile_cur = dict_tfiles[filename]
113  for path, scale in dict_targets[filename]:
114  hists.append(tfile_cur.GetDirectory(path).Get(histname))
115  scales.append(scale)
116  #print "%s:%s:%s:%f" % (filename, path, histname, scale)
117  if not alltrue([x and x.InheritsFrom('TH1') for x in hists]):
118  continue
119  dest = joined(destdir, histname)
120  add(outfile, dest, hists, scales, dest_path)
121  else:
122  print("Code not written yet to add histograms from multiple files")
123  return
124  return
125 
126 
127 ##############################################################################
128 ######## Implementation ######################################################
129 
130 def walk_rootfile(rootfile, path=''):
131  #### Yield (path, folders, objects) for each directory under path.
132  keys = rootfile.GetDirectory(path).GetListOfKeys()
133  folders, objects = [], []
134  for key in keys:
135  name = key.GetName()
136  classname = key.GetClassName()
137  newpath = joined(path, name)
138  dimension = 0
139  if 'TDirectory' in classname:
140  folders.append(name)
141  else:
142  objects.append(name)
143  yield path, folders, objects
144  for folder in folders:
145  for x in walk_rootfile(rootfile, joined(path, folder)):
146  yield x
147 
148 def allsame(iterable):
149  for element in iterable:
150  if element != iterable[0]:
151  return False
152  return True
153 
154 def alltrue(iterable):
155  for element in iterable:
156  if element != True:
157  return False
158  return True
159 
160 def pathdiff(paths, joiner):
161  """
162  Return the appropriate destination for an object.
163 
164  In all cases, the result will be placed in the deepest directory shared by
165  all paths. If the histogram names are the same, the result will be named
166  based on the first directories that they do not share. Otherwise, the
167  result will be named based on the names of the other histograms.
168 
169  >>> pathdiff(['/dirA/dirB/dirX/hist', '/dirA/dirB/dirY/hist'], '_div_')
170  '/dirA/dirB/dirX_div_dirY'
171  >>> pathdiff(['/dirA/hist1', '/dirA/hist2', '/dirA/hist3'], '_plus_')
172  '/dirA/hist1_plus_hist2_plus_hist3'
173  >>> pathdiff(['/hist1', '/dirA/hist2'], '_minus_')
174  '/hist1_minus_hist2'
175  """
176  paths = [x.split('/') for x in paths]
177  dest = '/'
178  for i in range(len(paths[0])):
179  if allsame([p[i] for p in paths]):
180  dest = joined(dest, paths[0][i])
181  else:
182  break
183  name = joiner.join([p[-1] for p in paths])
184  if allsame([p[-1] for p in paths]):
185  for i in range(len(paths[0])):
186  if not allsame([p[i] for p in paths]):
187  name = joiner.join([p[i] for p in paths])
188  return joined(dest, name)
189 
190 def pathdiff2(paths, joiner='__', truncate=False):
191  """
192  Placeholder.
193  """
194  paths = [x.split('/') for x in paths]
195  commonbeginning = ''
196  for i in range(len(paths[0])):
197  if allsame([p[i] for p in paths]):
198  commonbeginning = joined(commonbeginning, paths[0][i])
199  else:
200  break
201  commonending = ''
202  for i in range(-1, -1 * len(paths[0]), -1):
203  if allsame([p[i] for p in paths]):
204  commonending = joined(paths[0][i], commonending)
205  else:
206  break
207  #return commonbeginning, commonending
208  if truncate:
209  return commonending
210  else:
211  return joined(commonbeginning, commonending)
212 
213 def pathdiff3(paths, joiner='__'):
214  """
215  Return the appropriate destination for an object.
216 
217  If the final objects in each path match, then the return value will be the
218  matching part of the paths. Otherwise, the output path will simply be those
219  names joined together with *joiner*. See the examples below.
220 
221  >>> pathdiff3(['/dirA/dirX/hist', '/dirA/dirY/hist'])
222  '/hist'
223  >>> pathdiff3(['/dirA/dirX/dirB/hist', '/dirA/dirY/dirB/hist'])
224  '/dirB/hist'
225  >>> pathdiff3(['/dirA/hist1', '/dirA/hist2', '/dirA/hist3'], '_plus_')
226  '/hist1_plus_hist2_plus_hist3'
227  >>> pathdiff3(['/hist1', '/dirA/hist2'], '_div_')
228  '/hist1_div_hist2'
229  """
230  paths = [x.split('/') for x in paths]
231  if allsame([x[-1] for x in paths]):
232  dest = paths[0][-1]
233  for i in range(-2, min([len(x) for x in paths]) * -1, -1):
234  if allsame([p[i] for p in paths]):
235  dest = joined(paths[0][i], dest)
236  else:
237  break
238  return '/' + dest
239  else:
240  return '/' + joiner.join([x[-1] for x in paths])
241 
243  def newfunc(outfile, dest, hists, scales=None, dest_path="", scale_errors=None):
244  outfile.cd()
245  for d in os.path.dirname(dest).split('/'):
246  if not ROOT.gDirectory.GetDirectory(d):
247  ROOT.gDirectory.mkdir(d)
248  ROOT.gDirectory.cd(d)
249  fn(outfile, dest, hists, scales, dest_path, scale_errors)
250  return newfunc
251 
252 def scale_with_error(hist, scale, scale_error=None):
253  '''Scale a histogram by a scale factor that has an error.
254  This takes into account the scale error to set new error bars.'''
255  hist_new = hist.Clone()
256  if scale_error:
257  for i in range(hist_new.GetNbinsX()+2):
258  hist_new.SetBinContent(i, scale)
259  hist_new.SetBinError(i, scale_error)
260  hist_new.Multiply(hist)
261  else:
262  hist_new.Scale(scale)
263  return hist_new
264 
265 @operator_func
266 def add(outfile, dest, hists, scales=None, dest_path="", scale_errors=None):
267  if not scales:
268  scales = [1. for i in range(len(hists))]
269  if not scale_errors:
270  scale_errors = [None for i in range(len(hists))]
271  sumhist = hists[0].Clone(os.path.basename(dest))
272  sumhist = scale_with_error(sumhist, scales[0], scale_errors[0])
273  #sumhist.Scale(scales[0])
274  for i in range(1,len(hists)):
275  sumhist.Add(scale_with_error(hists[i], scales[i], scale_errors[i]))
276  #sumhist.Add(hists[i], scales[i])
277  if dest_path:
278  outfile.cd()
279  if not ROOT.gDirectory.GetDirectory(dest_path):
280  ROOT.gDirectory.mkdir(dest_path)
281  ROOT.gDirectory.cd(dest_path)
282  sumhist.Write()
283  ROOT.gDirectory.cd("/")
284 
285 @operator_func
286 def subtract(outfile, dest, hists):
287  diffhist = hists[0].Clone(os.path.basename(dest))
288  for hist in hists[1:]:
289  diffhist.Add(hist, -1)
290  diffhist.Write()
291 
292 @operator_func
293 def divide(outfile, dest, numer, denom):
294  quotient = numer.Clone(os.path.basename(dest))
295  quotient.Divide(numer, denom)
296  quotient.Write()
297 
298 @operator_func
299 def bayes_divide(outfile, dest, numer, denom):
300  quotient = ROOT.TGraphAsymmErrors()
301  quotient.SetName(os.path.basename(dest))
302  quotient.BayesDivide(numer, denom)
303  quotient.Write()
304 
305 def main():
306  parser = argparse.ArgumentParser()
307  parser.add_argument('filenames', type=str, nargs='+',
308  help='root files to process')
309  parser.add_argument('--dirs', type=str, nargs='+', default=['/'],
310  help='target directories in the root files; paths to '
311  'histograms will be relative to these')
312  parser.add_argument('--add', default=[], action='append', nargs='*',
313  help='a list of directories or histograms to add')
314  parser.add_argument('--subtract', default=[], action='append', nargs='*',
315  help='a list of directories or histograms to subtract')
316  parser.add_argument('--divide', default=[], action='append', nargs='*',
317  help='2 directories or histograms to divide')
318  parser.add_argument('--bayes-divide', default=[], action='append', nargs='*',
319  help='2 directories or histograms from which to make '
320  'an efficiency plot')
321  args = parser.parse_args()
322  separators = {'add' : '_plus_',
323  'subtract' : '_minus_',
324  'divide' : '_div_',
325  'bayes_divide' : '_eff_'}
326 
327  files = [ROOT.TFile(x, 'read') for x in args.filenames]
328  outfile = ROOT.TFile('out.root', 'recreate')
329  dirs = []
330  for d in args.dirs:
331  dirs += rootglob(files[0], d)
332 
333  if len(files) == 1:
334  f = files[0]
335  for thisdir in dirs:
336  for operation_type, separator in separators.items():
337  for arg_set in getattr(args, operation_type):
338  paths = [joined(thisdir, x) for x in arg_set]
339  if f.GetDirectory(paths[0]):
340  destdir = pathdiff(paths, separator)
341  for target in [os.path.basename(x) for x in
342  rootglob(f, paths[0] + '/*')]:
343  hists = [f.GetDirectory(x).Get(target)
344  for x in paths]
345  if not alltrue([x and x.InheritsFrom('TH1')
346  for x in hists]):
347  continue
348  dest = joined(destdir, target)
349  math_func = globals()[operation_type]
350  math_func(outfile, dest, hists)
351  else:
352  hists = [f.GetDirectory(thisdir).Get(x) for x in paths]
353  if not alltrue([x and x.InheritsFrom('TH1')
354  for x in hists]):
355  continue
356  dest = pathdiff(paths, separator)
357  math_func = globals()[operation_type]
358  math_func(outfile, dest, hists)
359  else:
360  for operation_type, separator in separators.items():
361  arg_sets = getattr(args, operation_type)
362  if arg_sets and arg_sets != [[]]:
363  raise ValueError("No arguments to --%s allowed when multiple "
364  "files are specified" % operation_type)
365  elif arg_sets:
366  if 'divide' in operation_type and len(files) != 2:
367  raise ValueError("Exactly 2 files are expected with --%s; "
368  "%i given" % (operation_type, len(files)))
369  for path, folders, objects in walk_rootfile(files[0]):
370  for obj in objects:
371  hists = [x.GetDirectory(path).Get(obj) for x in files]
372  if not alltrue([x and x.InheritsFrom('TH1')
373  for x in hists]):
374  continue
375  math_func = globals()[operation_type]
376  math_func(outfile, joined(path, obj), hists)
377 
378  outfile.Close()
379 
380 if __name__ == '__main__':
381  import doctest
382  doctest.testmod()
def walk_rootfile
Implementation ######################################################.
Definition: rootmath.py:130
boost::dynamic_bitset append(const boost::dynamic_bitset<> &bs1, const boost::dynamic_bitset<> &bs2)
this method takes two bitsets bs1 and bs2 and returns result of bs2 appended to the end of bs1 ...
const uint16_t range(const Frame &aFrame)
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
T min(T a, T b)
Definition: MathUtil.h:58
def loadROOT
Define additional helping functions.
Definition: utilities.py:435
Classes #############################################################.
Definition: rootmath.py:57