CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
pileupCalc.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 from __future__ import print_function
3 from builtins import range
4 VERSION='1.00'
5 import os, sys, time
6 import argparse
7 from RecoLuminosity.LumiDB import pileupParser
8 from RecoLuminosity.LumiDB import selectionParser
9 import numpy as np
10 from scipy.special import loggamma
11 
12 def parseInputFile(inputfilename):
13  '''
14  output ({run:[ls:[inlumi, meanint]]})
15  '''
16  selectf=open(inputfilename,'r')
17  inputfilecontent=selectf.read()
18  p=pileupParser.pileupParser(inputfilecontent)
19 
20 # p=inputFilesetParser.inputFilesetParser(inputfilename)
21  runlsbyfile=p.runsandls()
22  return runlsbyfile
23 
24 def MyErf(xInput):
25  # Abramowitz and Stegun approximations for Erf (equations 7.1.25-28)
26  X = np.abs(xInput)
27 
28  p = 0.47047
29  b1 = 0.3480242
30  b2 = -0.0958798
31  b3 = 0.7478556
32 
33  T = 1.0/(1.0+p*X)
34  cErf = 1.0 - (b1*T + b2*T*T + b3*T*T*T)*np.exp(-1.0*X*X)
35 
36  # Alternate Erf approximation:
37  #A1 = 0.278393
38  #A2 = 0.230389
39  #A3 = 0.000972
40  #A4 = 0.078108
41 
42  #term = 1.0+ A1*X+ A2*X*X+ A3*X*X*X+ A4*X*X*X*X
43  #denom = term*term*term*term
44 
45  #dErf = 1.0 - 1.0/denom
46 
47  return np.where(xInput < 0, -cErf, cErf)
48 
49 def poisson(x, par):
50  ## equivalent to TMath::Poisson (without x<0 and par<0 checks)
51  return np.where(x == 0., np.exp(-par),
52  np.exp(x*np.log(par)-loggamma(x+1)-par))
53 
54 class EquidistantBinning(object):
55  def __init__(self, num, xMin, xMax):
56  self.num = num
57  self.xMin = xMin
58  self.xMax = xMax
59  self.edges = np.linspace(xMin, xMax, num=num+1)
60  self.centers = .5*(self.edges[:-1] + self.edges[1:])
61  @property
62  def width(self):
63  return (self.xMax-self.xMin)/self.num
64  def find(self, x):
65  return np.floor((x-self.xMin)*self.num/(self.xMax-self.xMin)).astype(np.int)
66 
67 Sqrt2 = np.sqrt(2)
68 
69 def fillPileupHistogram(lumiInfo, calcOption, binning, hContents, minbXsec, run, ls):
70  '''
71  lumiinfo:[intlumi per LS, mean interactions ]
72 
73  intlumi is the deadtime corrected average integrated lumi per lumisection
74  '''
75 
76  LSintLumi = lumiInfo[0]
77  RMSInt = lumiInfo[1]*minbXsec
78  AveNumInt = lumiInfo[2]*minbXsec
79 
80  # First, re-constitute lumi distribution for this LS from RMS:
81  if RMSInt > 0:
82  areaAbove = MyErf((AveNumInt-binning.edges)/Sqrt2/RMSInt)
83  ## area above edge, so areaAbove[i]-areaAbove[i+1] = area in bin
84  ProbFromRMS = .5*(areaAbove[:-1]-areaAbove[1:])
85  else:
86  ProbFromRMS = np.zeros(hContents.shape)
87  obs = binning.find(AveNumInt)
88  if ( obs < binning.num ) and ( AveNumInt >= 1.0E-5 ): # just ignore zero values
89  ProbFromRMS[obs] = 1.0
90 
91  if calcOption == 'true': # Just put distribution into histogram
92  if RMSInt > 0:
93  hContents += ProbFromRMS*LSintLumi
94  totalProb = np.sum(ProbFromRMS)
95 
96  if 1.0-totalProb > 0.01:
97  print("Run %d, LS %d: Significant probability density outside of your histogram (mean %.2f," % (run, ls, AveNumInt))
98  print("rms %.2f, integrated probability %.3f). Consider using a higher value of --maxPileupBin." % (RMSInt, totalProb))
99  else:
100  hContents[obs] += LSintLumi ## obs = FindBin(AveNumInt), -1 because hContents has no overflows
101  else: # have to convolute with a poisson distribution to get observed Nint
102  if not hasattr(binning, "poissConv"): ## only depends on binning, cache
103  ## poissConv[i,j] = TMath.Poisson(e[i], c[j])
104  binning.poissConv = poisson(
105  binning.edges[:-1,np.newaxis], ## e'[i,] = e[i]
106  binning.centers[np.newaxis,:]) ## c'[,j] = c[j]
107  # prob[i] = sum_j ProbFromRMS[j]*TMath.Poisson(e[i], c[j])
108  prob = np.sum(binning.poissConv * ProbFromRMS[np.newaxis,:], axis=1)
109  hContents += prob*LSintLumi
110  #if ( not np.all(ProbFromRMS == 0) ) and 1.0-np.sum(prob) > 0.01:
111  # print("Run %d, LS %d: significant probability density outside of your histogram, %f" % (run, ls, np.sum(prob)))
112  # print("Consider using a higher value of --maxPileupBin")
113 
114 ##############################
115 ## ######################## ##
116 ## ## ################## ## ##
117 ## ## ## Main Program ## ## ##
118 ## ## ################## ## ##
119 ## ######################## ##
120 ##############################
121 
122 if __name__ == '__main__':
123 
124  parser = argparse.ArgumentParser(description = "Script to estimate pileup distribution using bunch instantaneous luminosity information and minimum bias cross section. Outputs a TH1D of the pileup distribution stored in a ROOT file.")
125 
126  # required
127  req_group = parser.add_argument_group('required arguments')
128  req_group.add_argument('outputfile', action='store', help='output ROOT file')
129  req_group.add_argument('-i', '--input', dest='inputfile', action='store', required=True,
130  help='input Run/LS file for your analysis in JSON format')
131  req_group.add_argument('-j', '--inputLumiJSON', dest='inputLumiJSON', action='store', required=True,
132  help='input pileup file in JSON format')
133  req_group.add_argument('-c', '--calcMode' ,dest='calcMode', action='store',
134  help='calculate either "true" or "observed" distributions',
135  choices=['true', 'observed'], required=True)
136 
137  # optional
138  parser.add_argument('-x', '--minBiasXsec', dest='minBiasXsec', action='store',
139  type=float, default=69200.0,
140  help='minimum bias cross section to use (in microbarn) (default: %(default).0f)')
141  parser.add_argument('-m', '--maxPileupBin', dest='maxPileupBin', action='store',
142  type=int, default=100, help='maximum value of pileup histogram (default: %(default)d)')
143  parser.add_argument('-n', '--numPileupBins', dest='numPileupBins', action='store',
144  type=int, default=1000, help='number of bins in pileup histogram (default: %(default)d)')
145  parser.add_argument('--pileupHistName', dest='pileupHistName', action='store',
146  default='pileup', help='name of pileup histogram (default: %(default)s)')
147  parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
148  help='verbose mode for printing' )
149 
150  options = parser.parse_args()
151  output = options.outputfile
152 
153  if options.verbose:
154  print('General configuration')
155  print('\toutputfile:' ,options.outputfile)
156  print('\tAction:' ,options.calcMode, 'luminosity distribution will be calculated')
157  print('\tinput selection file:', options.inputfile)
158  print('\tinput lumi JSON:', options.inputLumiJSON)
159  print('\tMinBiasXsec:', options.minBiasXsec)
160  print('\tmaxPileupBin:', options.maxPileupBin)
161  print('\tnumPileupBins:', options.numPileupBins)
162 
163  binning = EquidistantBinning(options.numPileupBins, 0., options.maxPileupBin)
164  hContents = np.zeros(binning.centers.shape)
165 
166  inpf = open(options.inputfile, 'r')
167  inputfilecontent = inpf.read()
168  inputRange = selectionParser.selectionParser (inputfilecontent).runsandls()
169 
170  #inputRange=inputFilesetParser.inputFilesetParser(options.inputfile)
171 
172  inputPileupRange=parseInputFile(options.inputLumiJSON)
173 
174  # now, we have to find the information for the input runs and lumi sections
175  # in the Lumi/Pileup list. First, loop over inputs
176 
177  for (run, lslist) in sorted (inputRange.items()):
178  # now, look for matching run, then match lumi sections
179  # print "searching for run %d" % (run)
180  if run in inputPileupRange.keys():
181  #print run
182  LSPUlist = inputPileupRange[run]
183  # print "LSPUlist", LSPUlist
184  for LSnumber in lslist:
185  if LSnumber in LSPUlist.keys():
186  #print "found LS %d" % (LSnumber)
187  lumiInfo = LSPUlist[LSnumber]
188  # print lumiInfo
189  fillPileupHistogram(lumiInfo, options.calcMode,
190  binning, hContents,
191  options.minBiasXsec, run, LSnumber)
192  else: # trouble
193  print("Run %d, LumiSection %d not found in Lumi/Pileup input file. Check your files!" \
194  % (run,LSnumber))
195 
196  else: # trouble
197  print("Run %d not found in Lumi/Pileup input file. Check your files!" % (run))
198 
199  # print run
200  # print lslist
201 
202  ## convert hContents to TH1F
203  import ROOT
204  pileupHist = ROOT.TH1D(options.pileupHistName, options.pileupHistName,
205  options.numPileupBins, 0., options.maxPileupBin)
206  for i,ct in enumerate(hContents):
207  pileupHist.SetBinContent(i+1, ct)
208 
209  histFile = ROOT.TFile.Open(output, 'recreate')
210  if not histFile:
211  raise RuntimeError("Could not open '%s' as an output root file" % output)
212  pileupHist.Write()
213  histFile.Close()
214  print("Wrote output histogram to", output)
def poisson
Definition: pileupCalc.py:49
def parseInputFile
Definition: pileupCalc.py:12
void print(TMatrixD &m, const char *label=nullptr, bool mathematicaFormat=false)
Definition: Utilities.cc:47
def fillPileupHistogram
Definition: pileupCalc.py:69