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" 
    8 from TriggerMenuMT.TriggerAPI 
import SerializeAPI
 
    9 from TriggerMenuMT.TriggerAPI.TriggerAPI 
import TriggerAPI
 
   10 from TriggerMenuMT.TriggerAPI.TriggerEnums 
import TriggerPeriod,TriggerType
 
   11 from AthenaCommon.Logging 
import logging
 
   12 log = logging.getLogger(__name__)
 
   16     -------------------------------------------------------------------------------------------------------------------- 
   17     TriggerAPI helper class. Use the following import in your code: 
   19       from TriggerMenuMT.TriggerAPI import TriggerAPISession,TriggerType,TriggerPeriod 
   24     Set of triggers of a given type that are unprescaled for an entire GRL: 
   26       s = TriggerAPISession("path/to/grl.xml") # can be a PathResolver path as well 
   27       triggers = s.getLowestUnprescaled(triggerType=TriggerType.el_single) 
   29     Dictionary of sets of triggers of a given type that are unprescaled, for each run in the GRL: 
   31       s = TriggerAPISession("path/to/grl.xml") 
   32       triggersByRun = s.getLowestUnprescaledByRun(triggerType=TriggerType.el_single) 
   34     Set of triggers that are unprescaled for all runs between two run numbers (inclusive), in a GRL: 
   36       s = TriggerAPISession("path/to/grl.xml") 
   37       triggers = s.getLowestUnprescaledByRun(triggerType=TriggerType.el_single,runStart=123456,runEnd=234567) 
   39     Other helpful methods are: 
   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") 
   49     Each method accepts an "additionalTriggerType" parameter that is used for multi-leg triggers of different type 
   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. 
   58     Sessions can be saved to json file and reloaded at a later time (to save requerying the database): 
   61         s2 = TriggerAPISession(json="myDump.json") # reloads the session 
   63     -------------------------------------------------------------------------------------------------------------------- 
   67     def __init__(self, input=None, *, grl=None, flags=None, json=None, menu=None, file=None, period=None):
 
   69         Specify one and only one of the following parameters to construct your API session: 
   71         :param input: If specified, will try to auto-infer which of the things below it is: 
   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) 
   85                 if input.endswith(
".xml"):
 
   86                     log.info(
"Loading session for GRL:" + input)
 
   88                 elif input.endswith(
".json"):
 
   89                     log.info(
"Loading saved session from:" + input)
 
   91                 elif os.path.exists(input):
 
   92                     log.info(
"Loading session with menu from file:" + input)
 
   95                     log.info(
"Loading session for menu:" + input)
 
   98                 raise RuntimeError(
"Unsupported input type:" + 
type(input).__name__)
 
  110         elif grl 
is not None:
 
  111             from PathResolver 
import PathResolver
 
  114         elif flags 
is not None:
 
  116         elif menu 
is not None:
 
  117             from AthenaConfiguration.AllConfigFlags 
import initConfigFlags
 
  119             self.
flags.Trigger.triggerMenuSetup = menu
 
  121         elif file 
is not None:
 
  122             from AthenaConfiguration.AllConfigFlags 
import initConfigFlags
 
  124             self.
flags.Input.Files = [file]
 
  126         elif period 
is not None:
 
  128             TriggerAPI._loadTriggerPeriod(period,reparse=
False)
 
  129             if not TriggerAPI.dbQueries:
 
  130                 raise RuntimeError(
"Failed to load TriggerAPI information for period")
 
  132             self.
dbQueries = copy.deepcopy(TriggerAPI.dbQueries)
 
  134             raise RuntimeError(
"Must specify one of: grl, flags, json, menu, period")
 
  138             period = TriggerPeriod.future2e34 
 
  139             if self.
flags is not None:
 
  140                 TriggerAPI.setConfigFlags(self.
flags)
 
  143                 period = TriggerPeriod.customGRL
 
  144             TriggerAPI._loadTriggerPeriod(period,reparse=
False)
 
  145             if not TriggerAPI.dbQueries:
 
  146                 raise RuntimeError(
"Failed to load TriggerAPI information")
 
  148             self.
dbQueries = copy.deepcopy(TriggerAPI.dbQueries)
 
  160         :param path: Save a cache of the current session to the given json file 
  161         :return: result of json dump 
  163         return SerializeAPI.dump(self.
dbQueries,path)
 
  166     def chains(self,*,triggerType=TriggerType.ALL):
 
  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 
  172             raise RuntimeError(
"Unsupported in multi-period TriggerAPI sessions (should only happen if using a period enum or an old json cache)")
 
  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")
 
  181             if not tc.passType(triggerType[0],triggerType[1]): 
continue 
  190         :return: set of runs covered by this session 
  194             for tc 
in ti.triggerChains:
 
  195                 for run 
in tc.activeLBByRun.keys():
 
  201             ti.setRunRange(start,end)
 
  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 
  209         :return: set of lowest unprescaled (according to livefraction) triggers of given type 
  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")
 
  221             out.update(ti._getLowestUnprescaled(triggerType[0], triggerType[1], 
"", livefraction))
 
  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:")
 
  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")
 
  233                     log.info(f
"run {run} is unprescaled")
 
  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 "" 
  247             return {
"":self.
getLowestUnprescaled(triggerType=triggerType,livefraction=livefraction,runStart=runStart,runEnd=runEnd)}
 
  250         pbar = tqdm.tqdm(self.
runs(),unit=
" runs",bar_format=
'{l_bar}{bar:10}{r_bar}{bar:-10b}')
 
  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)
 
  264         :param triggerType: can be a single type or a list of types 
  267         :return: a dictionary of live fractions for triggers matching given trigger types 
  272             out[x.name] = x.livefraction
 
  276     def getLowerUnprescaled(self,*,chainName,triggerType=TriggerType.ALL,livefraction=1.0,runStart=0,runEnd=999999):
 
  283         :return: set of chains of unprescaled triggers that were lower than the given chain 
  287         if chainName 
not in chains:
 
  288             raise RuntimeError(chainName + 
" not found")
 
  289         chain = chains[chainName]
 
  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)
 
  300 if __name__ == 
"__main__":
 
  303     class Formatter(     argparse.ArgumentDefaultsHelpFormatter,     argparse.RawDescriptionHelpFormatter): 
pass 
  305     parser = argparse.ArgumentParser(
 
  307         description=
"""    Example: tapis path/to/grl.xml getLowestUnprescaledByRun 
  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)
 
  313     parser.add_argument(
"--save",default=
None,help=
"If specified, the path to save the session to as a json file")
 
  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)
 
  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")
 
  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")
 
  328     parser_runs = subparsers.add_parser(
'runs',help=
'List runs available in the session')
 
  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")
 
  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")
 
  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")
 
  348     args = parser.parse_args()
 
  350     if args.command 
is None: args.command = 
"getLowestUnprescaled" 
  353     if args.save: s.save(args.save)
 
  356     if "triggerType" in args:
 
  357         args.triggerType = [TriggerType[t] 
for t 
in args.triggerType]
 
  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) 
 
  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]
 
  370     elif args.command == 
"getLowestUnprescaledByRun":
 
  371         result = s.getLowestUnprescaledByRun(triggerType=args.triggerType,
 
  372                                         livefraction=args.livefraction,
 
  373                                         runStart=args.runStart,runEnd=args.runEnd)
 
  378         chains = s.chains(triggerType=args.triggerType) 
 
  382         pbar = tqdm.tqdm(
sorted(result.keys()),unit=
" runs",bar_format=
'{l_bar}{bar:10}{r_bar}{bar:-10b}') 
 
  384             pbar.set_description(f
"Collating result for run {run}")
 
  385             s.setRunRange(run,run) 
 
  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: 
 
  392                     runRanges[trig] = [[run,run,lf]] 
 
  393                 elif runRanges[trig][-1][1] == prevRun 
and runRanges[trig][-1][2]==lf: 
 
  394                     runRanges[trig][-1][1] = run
 
  396                     runRanges[trig] += [[run,run,lf]]
 
  400         for c,ranges 
in runRanges.items():
 
  401             for start,end,livefraction 
in ranges:
 
  403                 result += [{
"runStart":start,
"runEnd":end,
"name":c,
"triggerType":TriggerType.toStr(chains[c].triggerType).
replace(
"|",
" ") 
if c != 
"---" else "---",
 
  404                             "livefraction":livefraction
 
  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" 
  413     elif args.command == 
"chains":
 
  414         s.setRunRange(args.runStart,args.runEnd)
 
  416         result = {k: v 
for k,v 
in s.chains(triggerType=args.triggerType).
items() 
if fnmatch.fnmatch(k,args.chainName)}
 
  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()]
 
  420             result = [{
"name":c.name,
"triggerType":TriggerType.toStr(c.triggerType).
replace(
"|",
" "),
"livefraction":c.livefraction} 
for c 
in result.values()]
 
  422     elif args.command == 
"runs":
 
  423         s.setRunRange(args.runStart,args.runEnd)
 
  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]
 
  432         df = pd.DataFrame(result) 
if len(result) 
else pd.DataFrame(columns=[
'name',
'triggerType',
'livefraction'])
 
  433         if 'runStart' in df.columns:
 
  435             dfStr = df.sort_values(by=[
'triggerType',
'name',
'runStart'],ascending=[
True,
True,
True]).
to_string(index=
False)
 
  438             dfStr = df.sort_values(by=[
'triggerType',
'livefraction',
'name'],ascending=[
True,
False,
True]).
to_string(index=
False)
 
  442     if result 
is not None:
 
  445     if extraWarning: log.warning(extraWarning)