ATLAS Offline Software
LumiCalculator.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 
3 # lumiCalculator.py
4 # utilities for calculation of integrated luminosity from information in COOL
5 # main class coolLumiCalc
6 # Richard Hawkings, started May 2007
7 
8 import sys
9 from PyCool import cool
10 
11 from CoolConvUtilities.AtlCoolLib import indirectOpen,RangeList
12 from DetectorStatus.DetStatusCoolLib import statusCutsToRange
13 
14 import LumiBlockComps.LumiQuery as LumiQuery
15 
16 class RLBRange:
17  """This class represents a range of lumiblocks within a single run"""
18  def __init__(self,run,lb1,lb2):
19  "Initialise to inclusive range lb1-lb2 in run run"
20  self.run=run
21  self.lb1=lb1
22  self.lb2=lb2
23 
24  def __repr__(self):
25  return 'Run %i LB [%i-%i]' % (self.run,self.lb1,self.lb2)
26 
27  def IOVRange(self):
28  "Return IOV since,until corresponding to this lumiblock range"
29  since=(self.run << 32)+self.lb1
30  until=(self.run << 32)+self.lb2+1
31  return (since,until)
32 
33 
34 class IOVCache:
35  """An in-memory cache of items of data, each associated with an IOV
36  Methods provided to add more data (specified with IOV) and to lookup the
37  data for a particular time. Not optimised, suitable only for small amounts
38  of data!"""
39  def __init__(self):
40  self.sinces=[]
41  self.untils=[]
42  self.data=[]
43 
44  def add(self,since,until,data):
45  self.sinces+=[since]
46  self.untils+=[until]
47  self.data+=[data]
48 
49  def find(self,point):
50  for i in range(0,len(self.sinces)):
51  if (self.sinces[i]<=point and self.untils[i]>point):
52  return self.data[i]
53  return None
54 
55 class lumiResult:
56  """Class to hold luminosity calculation result"""
57  def __init__(self,intL,l1acc,l2acc,l3acc,livetime,ngood,nbadstat):
58  self.intL=intL
59  self.l1acc=l1acc
60  self.l2acc=l2acc
61  self.l3acc=l3acc
62  self.livetime=livetime
63  self.ngood=ngood
64  self.nbadstat=nbadstat
65 
66  def __add__(self,other):
67  "addition of two lumiResults - accumulate totals"
68  return lumiResult(self.intL+other.intL,self.l1acc+other.l1acc,self.l2acc+other.l2acc,self.l3acc+other.l3acc,self.livetime+other.livetime,self.ngood+other.ngood,self.nbadstat+other.nbadstat)
69 
71  """Luminosity calculator from COOL - main utility class"""
72  def __init__(self,cooldbconn,statusdbconn="",readoracle=False,loglevel=1,detStatus="",detStatusTag=""):
73  """Initialisation of luminosity calculator
74  Specify the COOL database string (e.g. COOLONL_TRIGGER/COMP200),
75  COOL detector status connection string (if needed),
76  whether to force Oracle (otherwise SQLite replicas might be used)
77  and the detector status requirements and tag (if any).
78  Status requirements given in the form 'SCTB G EMEC Y' etc
79  """
80  # open the COOL database instance (readonly)
81  try:
82  self.cooldb=indirectOpen(cooldbconn,True,loglevel>1)
83  except Exception as e:
84  print(e)
85  sys.exit(-1)
86  # store other parameters
87  self.loglevel=loglevel
88  # setup detector status access if needed
89  self.detstatus=detStatus
90  self.detstatustag=detStatusTag
91  if (self.detstatus!=""):
92  # connect to detector status DB
93  try:
94  self.detstatusdb=indirectOpen(statusdbconn,True,loglevel>1)
95  except Exception as e:
96  print(e)
97  sys.exit(-1)
98 
99  def getLumiCache(self,since,until):
100  "Return a lumicache (pairs of inst lumi, deltaT) for given range"
101  lumicache=IOVCache()
102  folderLUMI=self.cooldb.getFolder('/TRIGGER/LUMI/LBLEST')
103  itr=folderLUMI.browseObjects(since,until-1,cool.ChannelSelection(0))
104  while (itr.goToNext()):
105  obj=itr.currentRef()
106  # OnlineLumiDel is the integrated luminosity in the block
107  # in units of nb^-1
108  payload=obj.payload()
109  instlumi=payload['OnlineLumiInst']
110  if (instlumi>0):
111  deltat=payload['OnlineLumiDel']/instlumi
112  else:
113  deltat=0
114  lumicache.add(obj.since(),obj.until(),(instlumi,deltat))
115  itr.close()
116  return lumicache
117 
118  def calcFromList(self,triggername,lblist):
119  """ Calculate the integrated luminosity for a list of LBs, returning
120  a lumiResult object"""
121  # setup the triggerlevel
122  triggerlevel=self.triggerLevel(triggername)
123  if triggerlevel is None: return None
124  totalL=0
125  totaltime=0.
126  totalacc=3*[0]
127  totalgoodblock=0
128  totalbadblock=0
129  # get counters folders
130  folderLBCOUNTL1=self.cooldb.getFolder('/TRIGGER/LUMI/LVL1COUNTERS')
131  folderLBCOUNTHLT=self.cooldb.getFolder('/TRIGGER/LUMI/HLTCOUNTERS')
132  folderL1PRESCALE=self.cooldb.getFolder('/TRIGGER/LVL1/Prescales')
133 
134  for lbinfo in lblist:
135  if (self.loglevel>0): print("Beginning calculation for",lbinfo)
136  # get the trigger configuration for this run
137  runstat,chainnums,hltprescale=self._getChains(lbinfo.run,triggername,triggerlevel)
138  if (self.loglevel>1): print("L1/2/3 chain numbers",chainnums[0],chainnums[1],chainnums[2])
139  if (runstat):
140  since,until=lbinfo.IOVRange()
141  # check for detector status requirements
142  if (self.detstatus!=""):
143  if (self.loglevel>0):
144  print("Applying detector status cuts: %s" % self.detstatus)
145  gooddetstatus=statusCutsToRange(self.detstatusdb,'/GLOBAL/DETSTATUS/LBSUMM',since,until,self.detstatustag,self.detstatus)
146  else:
147  gooddetstatus=RangeList(since,until)
148 
149  if (self.loglevel>0): print("LumiB L1-Acc L2-Acc L3-Acc L1-pre L2-pre L3-pre LiveTime IntL/nb-1")
150  # get and cache the LVL1 prescales for this run
151  l1precache=IOVCache()
152  itr=folderL1PRESCALE.browseObjects(since,until-1,cool.ChannelSelection(chainnums[0]))
153  while (itr.goToNext()):
154  obj=itr.currentRef()
155  l1precache.add(obj.since(),obj.until(),obj.payload()['Lvl1Prescale'])
156  itr.close()
157  # get and cache the luminosity estimates for this run
158  # note these can have >1 LB intervals
159  lumicache=self.getLumiCache(since,until)
160  # loop through the LBs for this range
161  # looping is driven by the LVL1COUNTERS folder which has
162  # one entry for EACH lumiblock
163  # assume that HLTCOUNTERS also have one entry for EACH block
164  l1countitr=folderLBCOUNTL1.browseObjects(since,until-1,cool.ChannelSelection(chainnums[0]))
165  if (triggerlevel>1):
166  l2countitr=folderLBCOUNTHLT.browseObjects(since,until-1,cool.ChannelSelection(chainnums[1]))
167  if (triggerlevel>2):
168  l3countitr=folderLBCOUNTHLT.browseObjects(since,until-1,cool.ChannelSelection(chainnums[2]))
169  while l1countitr.goToNext():
170  # access LVL1 information
171  l1countobj=l1countitr.currentRef()
172  lb=l1countobj.since() & 0xFFFFFFFF
173  l1payload=l1countobj.payload()
174  l1acc=l1payload['L1Accept']
175  # calculate livefraction from LVL1 ratios
176  # this needs to be improved to avoid rounding errors
177  if (l1payload['AfterPrescale']>0):
178  livefrac=float(l1payload['L1Accept'])/float(l1payload['AfterPrescale'])
179  else:
180  livefrac=1.
181  # access LVL2 information if needed
182  if (triggerlevel>1):
183  l2countitr.goToNext()
184  l2countobj=l2countitr.currentRef()
185  if (l2countobj.since()!=l1countobj.since()):
186  raise RuntimeError("L2/L1 counter synchronisation error")
187  l2payload=l2countobj.payload()
188  l2acc=l2payload['HLTAccept']
189  else:
190  l2acc=0
191  # access LVL3 information if needed
192  if (triggerlevel>2):
193  l3countitr.goToNext()
194  l3countobj=l3countitr.currentRef()
195  if (l3countobj.since()!=l1countobj.since()):
196  raise RuntimeError("L3/L1 counter synchronisation error")
197  l3payload=l3countobj.payload()
198  l3acc=l3payload['HLTAccept']
199  else:
200  l3acc=0
201  if (len(gooddetstatus.getAllowedRanges(l1countobj.since(),l1countobj.until()))>0):
202  # calculate intL for block
203  # lumi is being given in units of 10^33 cm^-2s^-1
204  # equivalent to 1 nb^-1s^-1
205  # instantaneous and time increment lumi
206  (lumi,deltat)=lumicache.find(l1countobj.since())
207  l1prescale=l1precache.find(l1countobj.since())
208  if (lumi is not None and l1prescale is not None):
209  # multiply by livetime in seconds to get
210  # intL in nb^-1
211  livetime=livefrac*deltat
212  intlumi=(lumi*livetime)/float(l1prescale*hltprescale[0]*hltprescale[1])
213  if (self.loglevel>1): print("%5i %7i %7i %7i %8i %8i %8i %8.2f %10.1f" % (lb,l1acc,l2acc,l3acc,l1prescale,hltprescale[0],hltprescale[1],livetime,intlumi))
214  else:
215  intlumi=0
216  print("%5i %7i %7i %7i <missing prescale or lumi>" %(lb,l1acc,l2acc,l3acc))
217  # accumulate statistics
218  totalacc[0]+=l1acc
219  totalacc[1]+=l2acc
220  totalacc[2]+=l3acc
221  totaltime+=livetime
222  totalL+=intlumi
223  totalgoodblock+=1
224  else:
225  totalbadblock+=1
226  l1countitr.close()
227  else:
228  print("Trigger not defined for run",lbinfo.run)
229  if (self.loglevel>0): print("Rng-T %7i %7i %7i %8.2f %10.1f" % (totalacc[0],totalacc[1],totalacc[2],totaltime,totalL))
230  return lumiResult(totalL,totalacc[0],totalacc[1],totalacc[2],totaltime,totalgoodblock,totalbadblock)
231 
232  def triggerLevel(self,trigname,quiet=False):
233  """Extract the trigger level from the name (L1_x, L2_x or EF_x)
234  Return 1 for unknown levels (after printing warning)"""
235  if (trigname[:2]=='L1'): return 1
236  if (trigname[:2]=='L2'): return 2
237  if (trigname[:2]=='EF'): return 3
238  if (not quiet): print('WARNING: Trigger name',trigname,'does not define trigger level, assume L1')
239  return 1
240 
241  def _getChains(self,run,triggername,triggerlevel):
242  """Returns the trigger chain numbers for the specified trigger in this
243  run, which are used as the channel indexes for the LVL1/Prescale
244  and LUMI/LVL1COUNTERS and HLTCOUNTERS folders"""
245  if (self.loglevel>1): print("Get chain numbers for run",run,triggername,"level",triggerlevel)
246  l1chain=-1
247  l2chain=-1
248  l3chain=-1
249  l2prescale=1
250  l3prescale=1
251  badrun=False
252  if (triggerlevel==3):
253  lvl3name=triggername
254  l3chain,l3prescale,lvl2name=self._getHLTChain(run,lvl3name,3)
255  if (l3chain==-1):
256  badrun=True
257  else:
258  l2chain,l2prescale,lvl1name=self._getHLTChain(run,lvl2name,2)
259  if (l2chain==-1): badrun=True
260  elif (triggerlevel==2):
261  lvl2name=triggername
262  l2chain,l2prescale,lvl1name=self._getHLTChain(run,lvl2name,2)
263  if (l2chain==-1): badrun=True
264  else:
265  lvl1name=triggername
266  # find LVL1 chain index
267  if (not badrun):
268  l1menu=self.cooldb.getFolder("/TRIGGER/LVL1/Menu")
269  since,until=self.IOVFromRun(run)
270  itr=l1menu.browseObjects(since,until-1,cool.ChannelSelection.all())
271  while itr.goToNext():
272  obj=itr.currentRef()
273  if (obj.payload()['ItemName']==lvl1name):
274  l1chain=obj.channelId()
275  break
276  if (l1chain==-1):
277  print("LVL1 trigger %s not defined for run %i" % (lvl1name,run))
278  badrun=True
279  itr.close()
280  return (not badrun,(l1chain,l2chain,l3chain),(l2prescale,l3prescale))
281 
282  def _getHLTChain(self,run,name,level):
283  "access /TRIGGER/HLT/Menu folder to get chain counter for trigger name"
284  hltmenu=self.cooldb.getFolder("/TRIGGER/HLT/Menu")
285  since,until=self.IOVFromRun(run)
286  itr=hltmenu.browseObjects(since,until-1,cool.ChannelSelection.all())
287  chain=-1
288  prescale=0
289  lowername=""
290  levelid=['L2','EF'][level-2]
291  while itr.goToNext():
292  obj=itr.currentRef()
293  payload=obj.payload()
294  if (payload['ChainName']==name and payload['TriggerLevel']==levelid):
295  chain=payload['ChainCounter']
296  if (level==3): chain+=10000
297  prescale=payload['Prescale']
298  lowername=payload['LowerChainName']
299  break
300  itr.close()
301  if (self.loglevel>1): print("Trigger",name,"identifier chain",chain,"prescale",prescale,"lowername",lowername)
302  return (chain,prescale,lowername)
303 
304  def listTriggers(self,run,level=0):
305  """Return a list of the available triggers for a particular run
306  Restrict to particular level level argument!=0"""
307  tlist=[]
308  since,until=self.IOVFromRun(run)
309  if (level<2):
310  # read the LVL1 Menu folder
311  l1menu=self.cooldb.getFolder("/TRIGGER/LVL1/Menu")
312  itr=l1menu.browseObjects(since,until-1,cool.ChannelSelection.all())
313  while itr.goToNext():
314  obj=itr.currentRef()
315  payload=obj.payload()
316  item=payload['ItemName']
317  if (item not in ['pass all','','-']):
318  tlist+=[payload['ItemName']]
319  itr.close()
320  if (level!=1):
321  # read the HLTMenu folder
322  hltmenu=self.cooldb.getFolder("/TRIGGER/HLT/Menu")
323  itr=hltmenu.browseObjects(since,until-1,cool.ChannelSelection.all())
324  while itr.goToNext():
325  obj=itr.currentRef()
326  payload=obj.payload()
327  triglevel={'L2':2, 'EF':3}[payload['TriggerLevel']]
328  if (triglevel==level or level==0):
329  tlist+=[payload['ChainName']]
330  itr.close()
331  return tlist
332 
333  def IOVFromRun(self,run):
334  # form 1-run length interval of validity
335  since=(run << 32)
336  until=((run+1) << 32)
337  return (since,until)
338 
339  def rlbFromFile(self,file,xml=False):
340  if xml: return self.rlbFromXmlFile(file)
341  else: return self.rlbFromRootFile(file)
342 
343  def rlbFromXmlFile(self,file):
344  import os
345  from ROOT import gSystem
346  CWD = os.getcwd()
347  os.chdir(CWD)
348  gSystem.Load('libGoodRunsListsLib')
349  from ROOT import Root
350 
351 
352  reader = Root.TGoodRunsListReader(file)
353  reader.Interpret()
354  goodrunslist = reader.GetMergedGoodRunsList()
355  goodrunslist.Summary(True)
356  runs=goodrunslist.GetGoodRuns()
357 
358  rangelist=[]
359  for i in range(len(runs)):
360  run=runs[i]
361  for j in range(len(run)):
362  lumi_range=run[j]
363  rangelist+=[RLBRange(run.GetRunNumber(),lumi_range.Begin(),lumi_range.End())]
364  return rangelist
365 
366  def rlbFromRootFile(self,file):
367  """Extract list of LBs from a ROOT file using LumiQuery and return
368  as RLBRange objects"""
369  lblist=LumiQuery.ListFromFile(file)
370  rangelist=[]
371  for lb in lblist:
372  start=lb[0]
373  stop=lb[1]
374  if (start.run()==stop.run()):
375  rangelist+=[RLBRange(start.run(),start.block(),stop.block())]
376  else:
377  print("ERROR: Multirun RLBRange not supported:",start,stop)
378  return rangelist
Root::TGoodRunsListReader
Definition: TGoodRunsListReader.h:34
python.LumiCalculator.lumiResult.l3acc
l3acc
Definition: LumiCalculator.py:61
python.LumiCalculator.lumiResult
Definition: LumiCalculator.py:55
python.LumiCalculator.coolLumiCalc.__init__
def __init__(self, cooldbconn, statusdbconn="", readoracle=False, loglevel=1, detStatus="", detStatusTag="")
Definition: LumiCalculator.py:72
python.LumiCalculator.coolLumiCalc.loglevel
loglevel
Definition: LumiCalculator.py:87
python.LumiCalculator.IOVCache.__init__
def __init__(self)
Definition: LumiCalculator.py:39
python.LumiCalculator.lumiResult.l1acc
l1acc
Definition: LumiCalculator.py:59
python.LumiCalculator.IOVCache.sinces
sinces
Definition: LumiCalculator.py:40
python.LumiCalculator.coolLumiCalc.rlbFromFile
def rlbFromFile(self, file, xml=False)
Definition: LumiCalculator.py:339
python.LumiCalculator.coolLumiCalc.triggerLevel
def triggerLevel(self, trigname, quiet=False)
Definition: LumiCalculator.py:232
python.LumiCalculator.coolLumiCalc.rlbFromXmlFile
def rlbFromXmlFile(self, file)
Definition: LumiCalculator.py:343
python.LumiCalculator.RLBRange
Definition: LumiCalculator.py:16
python.LumiCalculator.coolLumiCalc.detstatusdb
detstatusdb
Definition: LumiCalculator.py:94
python.LumiCalculator.IOVCache
Definition: LumiCalculator.py:34
python.LumiCalculator.coolLumiCalc.IOVFromRun
def IOVFromRun(self, run)
Definition: LumiCalculator.py:333
python.LumiCalculator.RLBRange.run
run
Definition: LumiCalculator.py:20
python.LumiCalculator.RLBRange.lb1
lb1
Definition: LumiCalculator.py:21
python.LumiCalculator.coolLumiCalc.listTriggers
def listTriggers(self, run, level=0)
Definition: LumiCalculator.py:304
python.LumiCalculator.coolLumiCalc._getHLTChain
def _getHLTChain(self, run, name, level)
Definition: LumiCalculator.py:282
python.LumiCalculator.RLBRange.IOVRange
def IOVRange(self)
Definition: LumiCalculator.py:27
python.LumiCalculator.IOVCache.add
def add(self, since, until, data)
Definition: LumiCalculator.py:44
python.LumiCalculator.coolLumiCalc.detstatustag
detstatustag
Definition: LumiCalculator.py:90
python.LumiCalculator.coolLumiCalc.cooldb
cooldb
Definition: LumiCalculator.py:82
python.LumiCalculator.lumiResult.intL
intL
Definition: LumiCalculator.py:58
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
python.LumiCalculator.RLBRange.lb2
lb2
Definition: LumiCalculator.py:22
python.LumiCalculator.lumiResult.ngood
ngood
Definition: LumiCalculator.py:63
python.LumiCalculator.RLBRange.__repr__
def __repr__(self)
Definition: LumiCalculator.py:24
python.LumiCalculator.IOVCache.data
data
Definition: LumiCalculator.py:42
python.LumiCalculator.RLBRange.__init__
def __init__(self, run, lb1, lb2)
Definition: LumiCalculator.py:18
python.LumiCalculator.coolLumiCalc.calcFromList
def calcFromList(self, triggername, lblist)
Definition: LumiCalculator.py:118
python.LumiCalculator.IOVCache.untils
untils
Definition: LumiCalculator.py:41
python.LumiCalculator.coolLumiCalc.getLumiCache
def getLumiCache(self, since, until)
Definition: LumiCalculator.py:99
python.LumiCalculator.lumiResult.l2acc
l2acc
Definition: LumiCalculator.py:60
python.DetStatusCoolLib.statusCutsToRange
def statusCutsToRange(dbconn, foldername, since, until, tag, statusreq)
Definition: DetStatusCoolLib.py:11
python.LumiCalculator.lumiResult.livetime
livetime
Definition: LumiCalculator.py:62
python.LumiCalculator.lumiResult.nbadstat
nbadstat
Definition: LumiCalculator.py:64
python.LumiCalculator.IOVCache.find
def find(self, point)
Definition: LumiCalculator.py:49
Muon::print
std::string print(const MuPatSegment &)
Definition: MuonTrackSteering.cxx:28
python.LumiCalculator.coolLumiCalc._getChains
def _getChains(self, run, triggername, triggerlevel)
Definition: LumiCalculator.py:241
python.LumiCalculator.coolLumiCalc.rlbFromRootFile
def rlbFromRootFile(self, file)
Definition: LumiCalculator.py:366
python.LumiCalculator.coolLumiCalc
Definition: LumiCalculator.py:70
python.AtlCoolLib.indirectOpen
def indirectOpen(coolstr, readOnly=True, debug=False)
Definition: AtlCoolLib.py:130
readCCLHist.float
float
Definition: readCCLHist.py:83
python.LumiCalculator.coolLumiCalc.detstatus
detstatus
Definition: LumiCalculator.py:89
python.LumiCalculator.lumiResult.__init__
def __init__(self, intL, l1acc, l2acc, l3acc, livetime, ngood, nbadstat)
Definition: LumiCalculator.py:57
python.LumiCalculator.lumiResult.__add__
def __add__(self, other)
Definition: LumiCalculator.py:66