ATLAS Offline Software
Loading...
Searching...
No Matches
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
8import sys
9from PyCool import cool
10
11from CoolConvUtilities.AtlCoolLib import indirectOpen,RangeList
12from DetectorStatus.DetStatusCoolLib import statusCutsToRange
13
14import LumiBlockComps.LumiQuery as LumiQuery
15
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
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
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
void print(char *figname, TCanvas *c1)
add(self, since, until, data)
__init__(self, run, lb1, lb2)
rlbFromFile(self, file, xml=False)
_getHLTChain(self, run, name, level)
_getChains(self, run, triggername, triggerlevel)
triggerLevel(self, trigname, quiet=False)
__init__(self, cooldbconn, statusdbconn="", readoracle=False, loglevel=1, detStatus="", detStatusTag="")
calcFromList(self, triggername, lblist)
__init__(self, intL, l1acc, l2acc, l3acc, livetime, ngood, nbadstat)