ATLAS Offline Software
Loading...
Searching...
No Matches
TriggerAPISession.py
Go to the documentation of this file.
1#!/usr/bin/env python
2# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3
4__author__ = 'Will Buttinger'
5__version__="$Revision: 1.0 $"
6__doc__="Provides a helper class for managing a session of interactions with the TriggerAPI singleton"
7
8from TriggerMenuMT.TriggerAPI import SerializeAPI
9from TriggerMenuMT.TriggerAPI.TriggerAPI import TriggerAPI
10from TriggerMenuMT.TriggerAPI.TriggerEnums import TriggerPeriod,TriggerType
11from AthenaCommon.Logging import logging
12log = logging.getLogger(__name__)
13
15 """
16 --------------------------------------------------------------------------------------------------------------------
17 TriggerAPI helper class. Use the following import in your code:
18
19 from TriggerMenuMT.TriggerAPI import TriggerAPISession,TriggerType,TriggerPeriod
20
21 Examples of use:
22 ================
23
24 Set of triggers of a given type that are unprescaled for an entire GRL:
25
26 s = TriggerAPISession("path/to/grl.xml") # can be a PathResolver path as well
27 triggers = s.getLowestUnprescaled(triggerType=TriggerType.el_single)
28
29 Dictionary of sets of triggers of a given type that are unprescaled, for each run in the GRL:
30
31 s = TriggerAPISession("path/to/grl.xml")
32 triggersByRun = s.getLowestUnprescaledByRun(triggerType=TriggerType.el_single)
33
34 Set of triggers that are unprescaled for all runs between two run numbers (inclusive), in a GRL:
35
36 s = TriggerAPISession("path/to/grl.xml")
37 triggers = s.getLowestUnprescaledByRun(triggerType=TriggerType.el_single,runStart=123456,runEnd=234567)
38
39 Other helpful methods are:
40
41 - Set of runs present in the session's GRL: s.runs()
42 - List of trigger types: [x.name for x in TriggerType]
43 - Dictionary of livefractions between given runs, key = trigger chain name:
44 liveFractions = s.getLiveFractions(triggerType=TriggerType.el_single,runStart=123456,runEnd=234567)
45 - Dictionary of chains (key is chain.name): s.chains()
46 - Set of triggers that are deemed to be of same type and lower threshold than a given trigger and unprescaled:
47 triggers = s.getLowerPrescaled(chainName="myChain")
48
49 Each method accepts an "additionalTriggerType" parameter that is used for multi-leg triggers of different type
50 (e.g. e-mu triggers).
51
52 Instead of passing a GRL you can pass a menu name ("menu_name") in the constructor, and the unprescaled
53 triggers will be the Primary|TagAndProbe triggers from the menu.
54
55
56 Saving a session
57 ================
58 Sessions can be saved to json file and reloaded at a later time (to save requerying the database):
59
60 s.save("myDump.json")
61 s2 = TriggerAPISession(json="myDump.json") # reloads the session
62
63 --------------------------------------------------------------------------------------------------------------------
64 """
65
66
67 def __init__(self, input=None, *, grl=None, flags=None, json=None, menu=None, file=None, period=None):
68 """
69 Specify one and only one of the following parameters to construct your API session:
70
71 :param input: If specified, will try to auto-infer which of the things below it is:
72
73 :param grl: Path to a GRL file, locatable by PathResolver
74 :param flags: flag container, used if reading triggers from the trigger menu (in the file or the release) - EXPERT OPTION
75 :param json: Path to a JSON file, locatable by PathResolver, containing a cache of TriggerAPI session
76 :param menu: Specify a menu to use, such as "Physics_pp_run3_v1". This is otherwise taken from flags
77 :param file: Specify a root file (AOD etc) from which the menu will be taken
78 :param period: Legacy option, can specify a TriggerPeriod and will load through the hardcoded GRLs (TriggerPeriodData)
79 """
80
81 import os
82
83 if input is not None:
84 if type(input)==str:
85 if input.endswith(".xml"):
86 log.info("Loading session for GRL:" + input)
87 grl = input
88 elif input.endswith(".json"):
89 log.info("Loading saved session from:" + input)
90 json = input
91 elif os.path.exists(input):
92 log.info("Loading session with menu from file:" + input)
93 file = input
94 else:
95 log.info("Loading session for menu:" + input)
96 menu = input
97 else:
98 raise RuntimeError("Unsupported input type:" + type(input).__name__)
99
100
101 # the following represents the complete "state" of the TriggerAPI
102 self.dbQueries = {}
103 self.customGRL = None
104 self.flags = None
105 self.release = None
106 self.cacheread = True # always prevent auto-loading of cache in singleton
107
108 if json is not None:
109 self.dbQueries = SerializeAPI.load(json)
110 elif grl is not None:
111 from PathResolver import PathResolver
112 grl = PathResolver.FindCalibFile(grl) if grl[0] != "/" else grl
113 self.customGRL = grl
114 elif flags is not None:
115 self.flags = flags
116 elif menu is not None:
117 from AthenaConfiguration.AllConfigFlags import initConfigFlags
118 self.flags = initConfigFlags()
119 self.flags.Trigger.triggerMenuSetup = menu
120 self.flags.lock()
121 elif file is not None:
122 from AthenaConfiguration.AllConfigFlags import initConfigFlags
123 self.flags = initConfigFlags()
124 self.flags.Input.Files = [file]
125 self.flags.lock()
126 elif period is not None:
127 TriggerAPI.reset()
128 TriggerAPI._loadTriggerPeriod(period,reparse=False)
129 if not TriggerAPI.dbQueries:
130 raise RuntimeError("Failed to load TriggerAPI information for period")
131 import copy
132 self.dbQueries = copy.deepcopy(TriggerAPI.dbQueries)
133 else:
134 raise RuntimeError("Must specify one of: grl, flags, json, menu, period")
135
136 if self.flags is not None or self.customGRL is not None:
137 TriggerAPI.reset()
138 period = TriggerPeriod.future2e34 # used when loading with a flags container
139 if self.flags is not None:
140 TriggerAPI.setConfigFlags(self.flags)
141 else:
142 TriggerAPI.setCustomGRL(self.customGRL)
143 period = TriggerPeriod.customGRL
144 TriggerAPI._loadTriggerPeriod(period,reparse=False)
145 if not TriggerAPI.dbQueries:
146 raise RuntimeError("Failed to load TriggerAPI information")
147 import copy
148 self.dbQueries = copy.deepcopy(TriggerAPI.dbQueries)
149
150 # TODO:
151 # for any query loaded with an actual period enum (so through json or period arg)
152 # we should use TriggerPeriodData to assign per run values of activeLB and totalLB
153 # could then merge into a single ti object ... but then need to look at is2015 in isLowerThan
154 # since there is special behaviour for 2015 that will be lost
155
156 pass
157
158 def save(self, path):
159 """
160 :param path: Save a cache of the current session to the given json file
161 :return: result of json dump
162 """
163 return SerializeAPI.dump(self.dbQueries,path)
164
165
166 def chains(self,*,triggerType=TriggerType.ALL):
167 """
168 :param triggerType: you can list available types with "[x.name for x in TriggerType]"
169 :return: dictionary of triggerChain objects of given types, key = chain Name
170 """
171 if len(self.dbQueries)>1:
172 raise RuntimeError("Unsupported in multi-period TriggerAPI sessions (should only happen if using a period enum or an old json cache)")
173
174 if not isinstance(triggerType,list): triggerType = [triggerType,TriggerType.UNDEFINED]
175 if len(triggerType)==1: triggerType += [TriggerType.UNDEFINED]
176 elif len(triggerType) > 2:
177 raise RuntimeError("More than two trigger types not currently supported")
178
179 out = {}
180 for tc in self.triggerInfo().triggerChains:
181 if not tc.passType(triggerType[0],triggerType[1]): continue
182 out[tc.name] = tc
183 return out
184
185 def triggerInfo(self):
186 return self.dbQueries[list(self.dbQueries.keys())[0]]
187
188 def runs(self):
189 """
190 :return: set of runs covered by this session
191 """
192 out = set()
193 for ti in self.dbQueries.values():
194 for tc in ti.triggerChains:
195 for run in tc.activeLBByRun.keys():
196 out.add(run)
197 return out
198
199 def setRunRange(self,start=0,end=999999):
200 for ti in self.dbQueries.values():
201 ti.setRunRange(start,end)
202
203 def getLowestUnprescaled(self,*, triggerType=TriggerType.ALL,livefraction=1.0,runStart=0,runEnd=999999):
204 """
205 :param triggerType: list available types with "[x.name for x in TriggerType] .. provide a list of length 2 for multi-leg types"
206 :param livefraction: threshold to be considered unprescaled
207 :param runStart:
208 :param runEnd:
209 :return: set of lowest unprescaled (according to livefraction) triggers of given type
210 """
211
212
213 if not isinstance(triggerType,list): triggerType = [triggerType,TriggerType.UNDEFINED]
214 if len(triggerType)==1: triggerType += [TriggerType.UNDEFINED]
215 elif len(triggerType) > 2:
216 raise RuntimeError("More than two trigger types not currently supported")
217
218 self.setRunRange(runStart,runEnd)
219 out = set()
220 for ti in self.dbQueries.values():
221 out.update(ti._getLowestUnprescaled(triggerType[0], triggerType[1], "", livefraction))
222 self.setRunRange() # reset to include all ranges
223
224 if not out and livefraction==1.0 and list(self.dbQueries.keys())[0][1] and runStart!=runEnd:
225 log.warning("No triggers found that are fully unprescaled in your GRL ... checking for livefractions per run:")
226 # check result by-run to see if there are problems with individual runs (possibly lumiblocks included in each)
227 for run in sorted(list(self.runs())):
228 liveFractions = self.getLiveFractions(triggerType=triggerType,runStart=run,runEnd=run)
229 lf = max(liveFractions.values())
230 if lf < 1 and lf > 0.9:
231 log.warning(f"run {run} has maximum livefraction {lf} - prescaled LBs may have been included in your GRL accidentally. Please report this to Data Preparation")
232 elif lf==1.0:
233 log.info(f"run {run} is unprescaled")
234
235 return out
236
237 def getLowestUnprescaledByRun(self,*,triggerType=TriggerType.ALL,livefraction=1.0,runStart=0,runEnd=999999):
238 """
239
240 :param triggerType:
241 :param livefraction:
242 :param runStart:
243 :param runEnd:
244 :return: lowest unprescaled trigger by run. If this session does not have per-run info, all triggers will be listed under a dummy key of ""
245 """
246 if not self.runs(): # case where loaded from trigger menu, for example
247 return {"":self.getLowestUnprescaled(triggerType=triggerType,livefraction=livefraction,runStart=runStart,runEnd=runEnd)}
248 out = {}
249 import tqdm
250 pbar = tqdm.tqdm(self.runs(),unit=" runs",bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}')
251 for run in pbar:
252 pbar.set_description(f"Determining lowest unprescaled for run {run}")
253 if int(run)<runStart or int(run)>runEnd: continue
254 out[run] = self.getLowestUnprescaled(triggerType=triggerType,livefraction=livefraction,runStart=run,runEnd=run)
255 return out
256
257 def getLowestUnprescaledAnyRun(self,*,triggerType=TriggerType.ALL,livefraction=1.0,runStart=0,runEnd=999999):
258 out = set()
259 for tc in self.getLowestUnprescaledByRun(triggerType=triggerType,livefraction=livefraction,runStart=runStart,runEnd=runEnd).values():
260 out.update(tc)
261 return out
262 def getLiveFractions(self,*,triggerType=TriggerType.ALL,runStart=0,runEnd=999999):
263 """
264 :param triggerType: can be a single type or a list of types
265 :param runStart:
266 :param runEnd:
267 :return: a dictionary of live fractions for triggers matching given trigger types
268 """
269 out = {}
270 self.setRunRange(runStart,runEnd)
271 for x in self.chains(triggerType=triggerType).values():
272 out[x.name] = x.livefraction
273 self.setRunRange()
274 return out
275
276 def getLowerUnprescaled(self,*,chainName,triggerType=TriggerType.ALL,livefraction=1.0,runStart=0,runEnd=999999):
277 """
278 :param chainName:
279 :param triggerType:
280 :param livefraction:
281 :param runStart:
282 :param runEnd:
283 :return: set of chains of unprescaled triggers that were lower than the given chain
284 """
285
286 chains = self.chains()
287 if chainName not in chains:
288 raise RuntimeError(chainName + " not found")
289 chain = chains[chainName]
290 self.setRunRange(runStart,runEnd)
291 out = set()
292 for x in self.chains(triggerType=triggerType).values():
293 if x.name==chain.name: continue
294 if not x.isUnprescaled(livefraction): continue
295 if x.isLowerThan(chain,period=self.triggerInfo().period)==1: out.add(x)
296 self.setRunRange()
297 return out
298
299
300if __name__ == "__main__":
301 import argparse
302
303 class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass
304
305 parser = argparse.ArgumentParser(
306 prog='tapis',
307 description=""" Example: tapis path/to/grl.xml getLowestUnprescaledByRun
308
309 See below for available commands. For help on a command, do: tapis dummy [command] --help""",
310 epilog='General command structure is: tapis [grl/menu/file/json] [command] [--commandOpt1] [--commandOpt2] ...',
311 formatter_class=Formatter)
312
313 parser.add_argument("--save",default=None,help="If specified, the path to save the session to as a json file")
314
315 parser.add_argument("input",metavar="grl/menu/file/json",help="Either a GRL, a menu name, a pool file (with menu metadata), or a json session cache file. PathResolver paths supported")
316 subparsers = parser.add_subparsers(help="Available commands",dest="command",required=True)
317
318
319 parser_getLowestUnprescaled = subparsers.add_parser('getLowestUnprescaled',help='Get lowest unprescaled chain names',
320 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
321 parser_getLowestUnprescaled.add_argument("--livefraction",type=float,default=1.0,help="EXPERT OPTION: lower the livefraction threshold for trigger to be considered unprescaled")
322
323 parser_chains = subparsers.add_parser('chains',help='Show info about a chain or selection of chains',
324 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
325 parser_chains.add_argument('chainName',type=str,help="name of chain or wildcarded string",default="*",nargs='?')
326 parser_chains.add_argument('--debug',action='store_true',help="Show additional information about each chain")
327
328 parser_runs = subparsers.add_parser('runs',help='List runs available in the session')
329
330 parser_getLowerUnprescaled = subparsers.add_parser('getLowerUnprescaled',help='Get chains that are deemed to be of same type but lower and also unprescaled compared to a given chain',
331 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
332 parser_getLowerUnprescaled.add_argument('chainName',type=str,help="name of chain")
333 parser_getLowerUnprescaled.add_argument("--livefraction",type=float,default=1.0,help="EXPERT OPTION: lower the livefraction threshold for trigger to be considered unprescaled")
334
335 parser_getLowerUnprescaledByRun = subparsers.add_parser('getLowestUnprescaledByRun',
336 help='Get lowest unprescaled chain names by run, results presented in terms of run ranges',
337 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
338 parser_getLowerUnprescaledByRun.add_argument("--livefraction",type=float,default=1.0,help="EXPERT OPTION: lower the livefraction threshold for trigger to be considered unprescaled")
339
340
341 for p in [parser_getLowestUnprescaled,parser_chains,parser_runs,parser_getLowerUnprescaled,parser_getLowerUnprescaledByRun]:
342 p.add_argument("--triggerType",choices=[x.name for x in TriggerType],nargs='+',default=["ALL"],help="can specify up to two trigger types")
343 p.add_argument("--runStart",type=int,default=0,help="First runNumber to consider")
344 p.add_argument("--runEnd",type=int,default=999999,help="Last runNumber to consider")
345
346
347
348 args = parser.parse_args()
349
350 if args.command is None: args.command = "getLowestUnprescaled"
351
352 s = TriggerAPISession(args.input)
353 if args.save: s.save(args.save)
354
355 # convert triggerTypes into required enums
356 if "triggerType" in args:
357 args.triggerType = [TriggerType[t] for t in args.triggerType]
358
359 pandasPrint=False
360 extraWarning = None
361
362 if args.command == "getLowestUnprescaled":
363 result = s.getLowestUnprescaled(triggerType=args.triggerType,
364 livefraction=args.livefraction,
365 runStart=args.runStart,runEnd=args.runEnd)
366 s.setRunRange(args.runStart,args.runEnd) # do so that livefractions are correctly updated
367 chains = s.chains(triggerType=args.triggerType)
368 result = [{"name":chains[c].name,"triggerType":TriggerType.toStr(chains[c].triggerType).replace("|"," "),"livefraction":chains[c].livefraction} for c in result]
369 pandasPrint=True
370 elif args.command == "getLowestUnprescaledByRun":
371 result = s.getLowestUnprescaledByRun(triggerType=args.triggerType,
372 livefraction=args.livefraction,
373 runStart=args.runStart,runEnd=args.runEnd)
374 # result is sets of lowest unprescaled triggers in a dict indexed by runNumber
375 # instead need each trigger and a list of run ranges
376 # will start a new range if the livefraction changes as wel
377 runRanges = {}
378 chains = s.chains(triggerType=args.triggerType) # used to get livefractions for each trigger chain
379 badRuns = []
380 prevRun = 0
381 import tqdm
382 pbar = tqdm.tqdm(sorted(result.keys()),unit=" runs",bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # go through runs in order
383 for run in pbar:
384 pbar.set_description(f"Collating result for run {run}")
385 s.setRunRange(run,run) # do so that livefractions are calculated for the chains
386 if not result[run]: # no chain met livefraction and triggerType requirement for this run, so declare a dummy trigger
387 result[run].update(["---"])
388 badRuns += [str(run)]
389 for trig in result[run]:
390 lf = chains[trig].livefraction if trig != "---" else -1
391 if trig not in runRanges: # new trigger
392 runRanges[trig] = [[run,run,lf]] # values are start and end run numbers of the range, and livefraction
393 elif runRanges[trig][-1][1] == prevRun and runRanges[trig][-1][2]==lf: # can just extend the run range
394 runRanges[trig][-1][1] = run
395 else: # must start a new run range because gap in range or livefraction changed
396 runRanges[trig] += [[run,run,lf]]
397 prevRun = run
398 # now loop over each run range of each trigger, and add to the final result
399 result = []
400 for c,ranges in runRanges.items():
401 for start,end,livefraction in ranges:
402 #s.setRunRange(start,end) # do so that livefractions are correctly updated
403 result += [{"runStart":start,"runEnd":end,"name":c,"triggerType":TriggerType.toStr(chains[c].triggerType).replace("|"," ") if c != "---" else "---",
404 "livefraction":livefraction
405 }]
406
407 pandasPrint=True
408 if badRuns:
409 extraWarning = "The following runs did not have a trigger of the requested type with livefraction >= " + str(args.livefraction) + ": "
410 extraWarning += ",".join(badRuns)
411 if args.livefraction==1.0: extraWarning += ". If this is unexpected please report the issue to Data Preparation"
412
413 elif args.command == "chains":
414 s.setRunRange(args.runStart,args.runEnd)
415 import fnmatch
416 result = {k: v for k,v in s.chains(triggerType=args.triggerType).items() if fnmatch.fnmatch(k,args.chainName)}
417 if args.debug:
418 result = [{"name":c.name,"legs":str({l.legname:TriggerType.toStr(l.legtype) for l in c.legs}),"triggerType":TriggerType.toStr(c.triggerType).replace("|"," "),"livefraction":c.livefraction} for c in result.values()]
419 else:
420 result = [{"name":c.name,"triggerType":TriggerType.toStr(c.triggerType).replace("|"," "),"livefraction":c.livefraction} for c in result.values()]
421 pandasPrint = True
422 elif args.command == "runs":
423 s.setRunRange(args.runStart,args.runEnd)
424 result = sorted(list(s.runs()))
425 elif args.command == "getLowerUnprescaled":
426 result = s.getLowerUnprescaled(chainName=args.chainName,triggerType=args.triggerType,livefraction=args.livefraction,runStart=args.runStart,runEnd=args.runEnd)
427 result = [{"name":c.name,"triggerType":TriggerType.toStr(c.triggerType).replace("|"," "),"livefraction":c.livefraction} for c in result]
428 pandasPrint=True
429 if pandasPrint:
430 import pandas as pd
431 #pd.options.display.max_colwidth = None
432 df = pd.DataFrame(result) if len(result) else pd.DataFrame(columns=['name','triggerType','livefraction'])
433 if 'runStart' in df.columns:
434 # order by type and name and then runStart
435 dfStr = df.sort_values(by=['triggerType','name','runStart'],ascending=[True,True,True]).to_string(index=False)
436 else:
437 # order by type and livefraction, then name
438 dfStr = df.sort_values(by=['triggerType','livefraction','name'],ascending=[True,False,True]).to_string(index=False)
439 print(dfStr) # noqa ATL901
440 result = None # so we don't print again below
441
442 if result is not None:
443 import pprint
444 pprint.pp(result)
445 if extraWarning: log.warning(extraWarning)
446
static std::string to_string(const std::vector< T > &v)
void print(char *figname, TCanvas *c1)
#define max(a, b)
Definition cfImp.cxx:41
static std::string FindCalibFile(const std::string &logical_file_name)
getLiveFractions(self, *, triggerType=TriggerType.ALL, runStart=0, runEnd=999999)
getLowestUnprescaled(self, *, triggerType=TriggerType.ALL, livefraction=1.0, runStart=0, runEnd=999999)
__init__(self, input=None, *, grl=None, flags=None, json=None, menu=None, file=None, period=None)
getLowestUnprescaledByRun(self, *, triggerType=TriggerType.ALL, livefraction=1.0, runStart=0, runEnd=999999)
getLowerUnprescaled(self, *, chainName, triggerType=TriggerType.ALL, livefraction=1.0, runStart=0, runEnd=999999)
getLowestUnprescaledAnyRun(self, *, triggerType=TriggerType.ALL, livefraction=1.0, runStart=0, runEnd=999999)
STL class.
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition hcg.cxx:310