ATLAS Offline Software
DictFromChainName.py
Go to the documentation of this file.
1 #! /bin/env python
2 # Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3 
4 """
5 Class to obtain the chain configuration dictionary from the short or long name
6 
7 Authors of original code in TriggerMenu: Joerg Stelzer, Moritz Backes, Catrin Bernius
8 
9 """
10 __author__ = 'Catrin Bernius'
11 __version__=""
12 __doc__="Decoding of chain name into a dictionary"
13 
14 from TriggerMenuMT.HLT.Menu.EventBuildingInfo import isRoIBasedPEB
15 from TrigConfHLTUtils.HLTUtils import string2hash
16 from AthenaCommon.Logging import logging
17 import re
18 from copy import deepcopy
19 
20 import collections.abc
21 
22 log = logging.getLogger( __name__ )
23 
24 def getOverallL1item(flags, chainName):
25  """
26  Extracts the L1 seed from the chain name, checks for issue in naming, if found, fails
27 
28  In simple case the L1 items is just the last part of the name, after the _L1...
29  There are though more complicated names like ...._L1...._L1..._L1... in such cases the L1 seed item is the last one
30  """
31  assert '_L1' in chainName, 'ERROR IN CHAIN {}, missing L1 seed at the end i.e. _L1...' .format(chainName)
32 
33  from TriggerMenuMT.HLT.Menu.L1Seeds import valid_multiseeds, getSpecificL1Seeds
34  from TrigConfigSvc.TriggerConfigAccess import getL1MenuAccess
35 
36  # this assumes that the last string of a chain name is the overall L1 item
37  cNameParts = chainName.rsplit("_L1",1)
38  l1seed = 'L1_' + cNameParts[-1]
39  # For reference, remapping of L1seeds lived originally in LVL1MenuConfig/LVL1Menu/L1Seeds.py
40  # L1 collections can be read out from there in case they are needed again
41  if l1seed == 'L1_All':
42  return ''
43  if l1seed == 'L1_test': #Multiseeded chains are build like this
44  return 'L1_EM24VHI,L1_MU20'
45  if l1seed in valid_multiseeds:
46  # For these item seed specifications we need to derive the precise list of item names from the L1Menu.
47  lvl1access = getL1MenuAccess(flags)
48  itemsDict = lvl1access.items(includeKeys = ['name','ctpid','triggerType'])
49  l1seedlist = getSpecificL1Seeds(l1seed, itemsDict, flags.Trigger.triggerMenuSetup)
50  return l1seedlist
51 
52  return l1seed
53 
54 def getL1item(flags, chainName):
55  mainL1 = getOverallL1item(flags, chainName)
56  return mainL1
57 
59  """
60  Breaks the item name into parts separated by the _ and from each element extract the threshold name
61 
62  Fails when used on topo items or ill formatted items
63  Examples: L1_MU4 returns [MU4]
64  L1_2MU4 returns [MU4, MU4]
65  L1_MU4_MU6 returns [MU4, MU6]
66  """
67  assert item.startswith("L1_"), "The L1 item {} name does not start with L1".format( item )
68  assert "-" not in item, "The {} is Topo item and can not be parsed".format( item )
69 
70  multThressholds = item.split("_")[1:] # skips the L1_
71  thresholds = []
72  for mt in multThressholds:
73  assert mt[0] != '1', 'Issue while parsing {}, multiplicity of 1, should not be configured, if multiplicity greater than 9 is needed then support for it needs to be added'.format( mt )
74  if mt[0] in '23456789': # first letter is a number == multiplicity, multiplicity 1 is not specified
75  thresholds.extend( int(mt[0])*[mt[1:]] )
76  else:
77  thresholds.append( mt ) # no muliplicity prefix
78  return thresholds
79 
81  """
82  As above but eliminates repeated thresholds
83  """
84  allThreshold = getAllThresholdsFromItem(item)
85  s = set()
86  def _(t):
87  s.add(t)
88  return t
89  return [ _(t) for t in allThreshold if t not in s ]
90 
91 def getEBPartFromParts( chainName, chainParts ):
92  """
93  If EB identifier is in the name, return it.
94 
95  Checks if there is only one identifier
96  """
97  # ---- event building identifier ----
98  from TriggerMenuMT.HLT.Menu.EventBuildingInfo import getAllEventBuildingIdentifiers
99  eventBuildTypes = set( getAllEventBuildingIdentifiers() ).intersection( chainParts )
100  assert len(eventBuildTypes) <= 1, 'Chain {} has more than one Event Building identifier: {}, that is not supported'.format( chainName, eventBuildTypes)
101  if eventBuildTypes:
102  return eventBuildTypes.pop()
103  return ''
104 
105 
106 def getChainThresholdFromName(chainParts, signature):
107  """
108  Decode threshold value from the chain name
109  """
110 
111  from TriggerMenuMT.HLT.Menu.SignatureDicts import getBasePattern
112  pattern = getBasePattern()
113  trigType = []
114  thresholdToPass = 0
115 
116  allThresh = []
117  for cpart in chainParts:
118  m = pattern.match( cpart )
119  if m:
120  log.debug("In getChainThresholdFromName: Pattern found in this string: %s", cpart)
121  groupdict = m.groupdict()
122  allThresh.append(groupdict['threshold'])
123  trigType.append(groupdict['trigType'])
124  if signature == groupdict['trigType']:
125  thresholdToPass = groupdict['threshold']
126  break
127  return thresholdToPass
128 
129 def getChainMultFromDict(chainDict):
130  """
131  Look for all multiplicities stored in chains
132  """
133  allMultis = []
134 
135  for cpart in chainDict['chainParts']:
136  if cpart['multiplicity'] != '':
137  allMultis.append( int(cpart['multiplicity']))
138  return allMultis
139 
140 
141 # For certain chains, we need to include the L1 seed threshold
142 # in the leg name for disambiguation.
143 # This checks that the explicit threshold matches the threshold list
144 # In this case, the seed thresholds cannot be inferred from the L1 name
145 matchL1 = re.compile('L1([a-zA-Z0-9]+)(_.*)?')
146 def verifyExplicitL1Thresholds(chainname,chainparts,L1thresholds):
147  assert len(L1thresholds)>0, f'L1 thresholds cannot be inferred from L1 name for chain {chainname} with explicit L1 seed thresholds'
148 
149  log.debug('Verifying explicit L1 thresholds for %s, received L1thresholds %s',chainname,L1thresholds)
150 
151  counter = 0
152  for part, threshold in zip(chainparts,L1thresholds):
153  if '_L1' in part:
154  matches = matchL1.findall(part)
155  assert len(matches)==1, f"Inappropriate number of L1 thresholds {matches} in chain part '{part}'"
156  thisL1, remainder = matches[0] # list of tuples
157  assert remainder == '', f"Explicit L1 threshold should be last element in chain part {part}"
158  log.verbose(' ChainPart %s has explicit L1 threshold %s',part,thisL1)
159  assert 'PROBE' not in thisL1, f"Omit 'PROBE' from explicit L1 threshold '{thisL1}'"
160  assert thisL1 == threshold.replace('PROBE',''), f"Explicit L1 threshold for chainpart '{thisL1}' does not match threshold '{threshold}' from list"
161  counter += 1
162  assert counter>0, f"Did not find explicit L1 seeds in chain parts for {chainname}!"
163 
164 def unifyJetRecoParts(chainParts):
165  from TriggerMenuMT.HLT.Menu.SignatureDicts import getSignatureInformation, JetRecoKeys
166 
167  """
168  Postprocess the jet chainParts to set all the reco config consistently
169  For formatting reasons, we extract these from the last jet chainPart
170  then propagate to all jet chainParts.
171  Any inconsistencies in non-default options are considered a malformed chain
172  Technically we can't tell if the default was explicitly set, though.
173  """
174 
175  jetDefaults, _ = getSignatureInformation('Jet')
176  # Extract the jet chainParts, and get the reco specs from the last one
177  _jetRecoKeys = JetRecoKeys
178  jetChainParts = [cPart for cPart in chainParts if cPart['signature'] in ['Jet','Bjet']]
179  jetRecoDict = {k:jetChainParts[-1][k] for k in _jetRecoKeys}
180 
181  # Check consistency of the preceding jet parts
182  for p in jetChainParts[:-1]:
183  for k in _jetRecoKeys:
184  # Assume that keys are all defined
185  if p[k] != jetDefaults[k]: # OK if the spec is default
186  log.error('Inconsistent jet reco setting for %s in chainPart %s', k, p['chainPartName'])
187  log.error('Jet chainParts: %s', jetChainParts)
188  raise RuntimeError('Jet reco should be specified only in the last jet chainPart')
189 
190  # Substitute the reco settings consistently and return
191  newChainParts = []
192  for cPart in chainParts:
193  newCPart = deepcopy(cPart)
194  if newCPart['signature'] in ['Jet','Bjet']:
195  for k in _jetRecoKeys:
196  newCPart[k] = jetRecoDict[k]
197  newChainParts.append(newCPart)
198 
199  return newChainParts
200 
201 def analyseChainName(chainName, L1thresholds, L1item):
202  """
203  Function to obtain the chain configuration dictionary from the short name by parsing its
204  components and finding the corresponding properties which are defined in SignatureDicts.
205  The naming convention is defined in https://twiki.cern.ch/twiki/bin/view/Atlas/TriggerNamingRun2
206  and https://twiki.cern.ch/twiki/bin/view/Atlas/TriggerNamingRun3
207  """
208 
209  # ---- dictionary with all chain properties ----
210  from TriggerMenuMT.HLT.Menu.SignatureDicts import ChainDictTemplate
211  from TriggerMenuMT.HLT.Menu.SignatureDicts import getSignatureInformation
212  from TriggerMenuMT.HLT.Jet.JetRecoCommon import etaRangeAbbrev
213  genchainDict = deepcopy(ChainDictTemplate)
214  genchainDict['chainName'] = chainName
215 
216  # ---- remove the L1 item from the name ----
217  hltChainName = chainName.rsplit("_L1",1)[0]
218 
219  # ---- check for HLT_HLT in name ---
220  if 'HLT_HLT' in hltChainName:
221  log.error("%s is incorrect, please fix the HLT_HLT part", hltChainName)
222  raise RuntimeError("[analyseChainName] chain name contains HLT_HLT, please fix in corresponding menu where this chain is included")
223 
224  # ---- specific chain part information ----
225  allChainProperties=[]
226  cparts = hltChainName.split("_")
227  if 'HLT' in hltChainName:
228  cparts.remove('HLT')
229 
230  # ---- handle EB directive ----
231  genchainDict['eventBuildType'] = getEBPartFromParts( chainName, cparts )
232  if genchainDict['eventBuildType']:
233  cparts.remove( genchainDict['eventBuildType'] )
234 
235  hltChainNameShort = '_'.join(cparts)
236 
237  # ---- identify the topo algorithm and add to genchainDict -----
238  from TriggerMenuMT.HLT.Menu.SignatureDicts import AllowedTopos, AllowedTopos_comb, AllowedTopos_Bphysics_topoVariant, AllowedTopos_Bphysics_topoExtra
239  topo = ''
240  topos=[]
241  extraComboHypos = []
242  bphys_topoVariant=[]
243  bphys_topoExtra = []
244  bphysTopos = False
245  toposIndexed={}
246  topoindex = -5
247  for cindex, cpart in enumerate(cparts):
248  #should make this if...elif...?
249  if cpart in AllowedTopos:
250  log.debug('" %s" is in this part of the name %s -> topo alg', AllowedTopos, cpart)
251  topo = cpart
252  topoindex = cindex
253  toposIndexed.update({topo : topoindex})
254  hltChainNameShort=hltChainNameShort.replace('_'+cpart, '')
255  topos.append(topo)
256  elif cpart in AllowedTopos_Bphysics_topoVariant:
257  log.debug('[analyseChainName] chain part %s is a BLS topo variant, adding to bphys_topoVariant', cpart)
258  bphys_topoVariant.append(cpart)
259  toposIndexed.update({cpart : cindex})
260  bphysTopos = True
261  elif cpart in AllowedTopos_Bphysics_topoExtra:
262  log.debug('[analyseChainName] chain part %s is a BLS extra topo hypo, adding to bphys_topoExtra', cpart)
263  bphys_topoExtra.append(cpart)
264  toposIndexed.update({cpart : cindex})
265  bphysTopos = True
266  else:
267  log.debug('[analyseChainName] chain part %s is not a general topo, BLS extra or variant topo hypo, checking comb topos next', cpart)
268  if cpart in AllowedTopos_comb:
269  log.debug('[analyseChainName] chain part %s is a combined topo hypo, adding to extraComboHypo', cpart)
270  toposIndexed.update({cpart : cindex})
271  extraComboHypos.append(cpart)
272 
273  genchainDict['topo'] = topos
274  genchainDict['extraComboHypos'] = extraComboHypos
275 
276  if bphysTopos is True:
277  genchainDict['topoVariant'] = bphys_topoVariant
278  genchainDict['topoExtra'] = bphys_topoExtra
279 
280  # remove the parts that have been already identified
281  for t, i in enumerate(toposIndexed):
282  if (t in cparts):
283  log.debug('topo %s with index %s is going to be deleted from chain parts', t, i)
284  del cparts[i]
285 
286 
287  # ---- Find the signature defining patterns ----
288  # ---- and write them out in dictionary ----
289  # ---- expected format: <Multiplicity(int)><TriggerType(str)>
290  # <Threshold(int)><isolation,...(str|str+int)> ----
291  # EXCEPT FOR CHAINS ...
292  from TriggerMenuMT.HLT.Menu.SignatureDicts import getBasePattern
293  pattern = getBasePattern()
294  mdicts=[]
295  multichainindex=[]
296 
297 
298  # ---- obtain dictionary parts for signature defining patterns ----
299  from TriggerMenuMT.HLT.Menu.SignatureDicts import getSignatureNameFromToken, AllowedCosmicChainIdentifiers, \
300  AllowedCalibChainIdentifiers, AllowedMonitorChainIdentifiers, AllowedBeamspotChainIdentifiers
301 
302  from TriggerMenuMT.HLT.Config.Utility.MenuAlignmentTools import get_alignment_group_from_pattern as getAlignmentGroupFromPattern
303 
304  def buildDict(signature, sigToken ):
305  groupdict = {'signature': signature, 'threshold': '', 'multiplicity': '',
306  'trigType': sigToken, 'extra': ''}
307  mdicts.append( groupdict )
308 
309  for cpart in cparts:
310 
311  log.debug("Looping over chain part: %s", cpart)
312  m = pattern.match(cpart)
313 
314  if m:
315  log.debug("Pattern found in this string: %s", cpart)
316  groupdict = m.groupdict()
317 
318  multiChainIndices = [i for i in range(len(hltChainNameShort)) if ( hltChainNameShort.startswith(cpart, i) ) ]
319  log.debug("MultiChainIndices: %s", multiChainIndices)
320  for theMultiChainIndex in multiChainIndices:
321  # this check is necessary for the bjet chains, example: j45_bloose_3j45
322  # j45 would be found in [0, 13], and 3j45 in [12]
323  # so need to make sure the multiplicities are considered here!
324  if (theMultiChainIndex != 0) & (hltChainNameShort[theMultiChainIndex-1] != '_'):
325  continue
326 
327  if theMultiChainIndex not in multichainindex:
328  multichainindex.append(theMultiChainIndex)
329 
330  log.debug("HLTChainName: %s", hltChainName)
331  log.debug("HLTChainNameShort: %s", hltChainNameShort)
332  log.debug("cpart: %s", cpart)
333  log.debug("groupdict: %s", groupdict)
334  log.debug("multichainindex: %s", multichainindex)
335 
336  sName = getSignatureNameFromToken(cpart)
337 
338  groupdict['signature'] = sName
339  log.debug("groupdict['signature']: %s",groupdict['signature'])
340 
341  mdicts.append(groupdict)
342 
343  elif cpart =='noalg':
344  multichainindex.append(hltChainNameShort.index(cpart))
345  buildDict( 'Streaming', 'streamer')
346  break # stop loop here so that further parts like noalg_idmon are discarded, this allows have copies with different output streams and prescales. Caveat: all noalg chains go into the Streaming slice
347  elif cpart =='acceptedevts':
348  multichainindex.append(hltChainNameShort.index(cpart))
349  buildDict( 'Calib', 'calib')
350  break # stop loop here so that further parts like acceptedevts_larnoiseburst are discarded. They are only used to define different prescales
351  else:
352  for chainCategory in [(['mb'], 'MinBias', 'mb'),
353  (['hi'], 'HeavyIon', 'mb'),
354  (AllowedCosmicChainIdentifiers, 'Cosmic', 'cosmic'),
355  (AllowedCalibChainIdentifiers, 'Calib', 'calib'),
356  (AllowedMonitorChainIdentifiers, 'Monitor', 'calib'),
357  (AllowedBeamspotChainIdentifiers, 'Beamspot', 'beamspot'),
358  (['eb'], 'EnhancedBias', 'eb')]:
359  if cpart in chainCategory[0]:
360  log.debug('Doing chain type %s', chainCategory[1])
361  multichainindex.append(hltChainNameShort.index(cpart))
362  buildDict(chainCategory[1], chainCategory[2])
363 
364 
365  # If multiple parts exist, split the string and analyse each
366  # part depending on the signature it belongs to
367  multichainparts=[]
368  multichainindex = sorted(multichainindex, key=int)
369  cN = deepcopy(hltChainNameShort)
370  for i in reversed(multichainindex):
371  if i!=0:
372  log.debug('Appending to multichainparts (i!=0): %s', hltChainNameShort[i:len(cN)])
373 
374  multichainparts.append(hltChainNameShort[i:len(cN)])
375  cN = cN[0:i-1]
376  else:
377  log.debug('Appending to multichainparts: %s', hltChainNameShort[i:len(cN)])
378  multichainparts.append(cN)
379  log.debug("multichainparts: %s",multichainparts)
380 
381  # build the chainProperties dictionary for each part of the chain
382  # add it to a allChainProperties
383  multichainparts.reverse()
384  log.debug('multichainparts after reverse: %s', multichainparts)
385 
386  # verify if the L1 setup is consistent with the chain
387  # sizes of seeds matter
388  if len(L1thresholds) != 0:
389  assert len(L1thresholds) == len(multichainparts), 'ERROR IN CHAIN {} definition, l1 thresholds specified {} have different length than chain parts {}'\
390  .format(chainName, str(L1thresholds), str(multichainparts) )
391 
392  # tmp removed this check since the L1seeds depend on sym or asym cases
393  # if len(L1thresholds) == 0:
394  # getAllThresholdsFromItem, getUniqueThresholdsFromItem
395  # assert len( getAllThresholdsFromItem( L1item )) == len(multichainparts), 'ERROR IN CHAIN {} definition, l1 thresholds extracted from the L1 item name {} have different length than chain parts {}, set L1 thresholds ChainProp'\
396  # .format( chainName, str( getAllThresholdsFromItem( L1item ) ), str(multichainparts))
397 
398  # check the case when _L1 appears more than once in the name
399  if chainName.count("_L1") > 1:
400  verifyExplicitL1Thresholds(chainName,multichainparts,L1thresholds)
401 
402  for chainindex, chainparts in enumerate(multichainparts):
403  chainProperties = {} #will contain properties for one part of chain if multiple parts
404  chainpartsNoL1 = chainparts.rsplit('_L1',1)[0] if 'L1' in chainparts else chainparts
405 
406  if len(L1thresholds) != 0:
407  chainProperties['L1threshold'] = L1thresholds[chainindex]
408  else:
409  __th = getAllThresholdsFromItem ( L1item )
410  assert chainindex < len(__th), "In defintion of the chain {chainName} there is not enough thresholds to be used, index: {chainindex} >= number of thresholds, thresholds are: {__th}"
411  chainProperties['L1threshold'] = __th[chainindex] #replced getUniqueThresholdsFromItem
412 
413  parts=chainpartsNoL1.split('_')
414  log.debug("[analyseChainName] chain parts w/o L1 are %s", parts)
415  if None in parts:
416  log.error("[analyseChainName] chainpartsNoL1 -> parts: %s -> %s", chainpartsNoL1, parts)
417  raise Exception("[analyseChainName] parts contains None, please identify how this is happening")
418  parts=list(filter(None,parts))
419  log.debug("[analyseChainName] chainpartsNoL1 %s, parts %s", chainpartsNoL1, parts)
420 
421  # --- check for duplicate strings in chain part name ---
422  duplicateParts = ([item for item, count in collections.Counter(parts).items() if count > 1])
423  log.debug("[analyseChainName] chainpartsNoL1 %s contains duplicate strings %s", chainpartsNoL1, duplicateParts)
424  if duplicateParts:
425  log.error("[analyseChainName] chain %s has duplicate strings %s, please fix!!", chainName, duplicateParts)
426  raise RuntimeError("[analyseChainName] Check the chain name, there are duplicate configurations: %s", duplicateParts)
427 
428  chainProperties['trigType']=mdicts[chainindex]['trigType']
429  if 'extra' in chainProperties:
430  raise RuntimeError("[analyseChainName] Overwrote 'extra' value, only one supported!")
431  else:
432  chainProperties['extra']=mdicts[chainindex]['extra']
433  multiplicity = mdicts[chainindex]['multiplicity'] if not mdicts[chainindex]['multiplicity'] == '' else '1'
434  chainProperties['multiplicity'] = multiplicity
435  chainProperties['threshold']=mdicts[chainindex]['threshold']
436  chainProperties['signature']=mdicts[chainindex]['signature']
437 
438  # if we have a L1 topo in a multi-chain then we want to remove it from the chain name
439  # but only if it's the same as the L1item_main; otherwise it belongs to chain part and we q
440  # have to keep it in the name
441  chainProperties['chainPartName'] = chainparts
442  if ('-' in L1item) and (len(multichainparts) > 1):
443  chainProperties['chainPartName'] = chainpartsNoL1
444 
445  if len(multichainparts) > 1 and L1item.count("_") > 1 :
446  chainProperties['chainPartName'] = chainpartsNoL1
447 
448  log.debug('[analyseChainname] Chainparts: %s', chainparts)
449  if (chainProperties['signature'] != 'Cosmic') \
450  & (chainProperties['signature'] != 'Calib')\
451  & (chainProperties['signature'] != 'Streaming') \
452  & (chainProperties['signature'] != 'Beamspot') \
453  & (chainProperties['signature'] != 'Monitor') :
454  parts.pop(0)
455 
456 
457  #---- Check if topo is a bphysics topo -> change signature ----
458  from TriggerMenuMT.HLT.Menu.SignatureDicts import AllAllowedTopos_Bphysics
459  for t in genchainDict['topo']:
460  if (t in AllAllowedTopos_Bphysics):
461  chainProperties['signature'] = 'Bphysics'
462  if "tnpInfo" in chainProperties.keys() and chainProperties['tnpInfo'] != "":
463  chainProperties['alignmentGroup'] = getAlignmentGroupFromPattern('Bphysics',chainProperties['tnpInfo'])
464  else:
465  chainProperties['alignmentGroup'] = getAlignmentGroupFromPattern('Bphysics',chainProperties['extra'])
466 
467  # ---- import the relevant dictionaries for each part of the chain ----
468  SignatureDefaultValues, allowedSignaturePropertiesAndValues = getSignatureInformation(chainProperties['signature'])
469  log.debug('SignatureDefaultValues: %s', SignatureDefaultValues)
470  allDefaults = list(SignatureDefaultValues.values())
471  log.debug('All default values in a list')
472 
473  # ---- update chain properties with default properties ----
474  result = deepcopy(SignatureDefaultValues)
475  result.update(chainProperties)
476  chainProperties = result
477 
478  # ---- check that all parts to be matched are not specified as defaults already ----
479  overlaps = [x for x in parts if x in allDefaults]
480  log.debug("parts that are also in defaults: %s ", overlaps)
481 
482  # Suppress the warning until we fully decomission the old DeepSet tau triggers in a couple of months
483  if overlaps == ['tracktwoMVA']: overlaps = []
484 
485  if overlaps:
486  #log.error("[analyseChainName] The following string(s) is/are already defined as defaults, please remove: %s", overlaps)
487  #raise RuntimeError("[analyseChainname] Default config appearing in chain name, please remove: %s", overlaps)
488  log.warning("[analyseChainName] The following chainpart %s contains the string(s) %s which is/are already defined as defaults, please remove!", chainparts, overlaps)
489 
490  # ---- check remaining parts for complete matches in allowedPropertiesAndValues Dict ----
491  # ---- unmatched = list of tokens that are not found in the allowed values as a whole ----
492  matchedparts = []
493  log.debug("[analyseChainname] parts to match are %s", parts)
494  for pindex, part in enumerate(parts):
495  for prop, allowedValues in allowedSignaturePropertiesAndValues.items():
496  if part in allowedValues:
497  if type(chainProperties[prop]) is list:
498  chainProperties[prop] += [part]
499  else:
500  chainProperties[prop] = part
501  matchedparts.append(part)
502 
503  # Jet-specific operations
504  if chainProperties['signature']=='Jet':
505 
506  # ----- at this point we can figure out if the chain is a bJet chain and update defaults accordingly
507  if chainProperties['bTag'] != '':
508  log.debug('Setting b-jet chain defaults')
509  # b-jet chain, so we now use the bJet defaults if they have not already been overriden
510  bJetDefaultValues, allowedbJetPropertiesAndValues = getSignatureInformation('Bjet')
511  for prop, value in bJetDefaultValues.items():
512  propSet=False
513  for value in allowedbJetPropertiesAndValues[prop]:
514  if value in matchedparts:
515  propSet=True
516  break
517 
518  # if the property was not set already, then set if according to the b-jet defaults
519  if propSet is False:
520  log.debug('Changing %s from %s to %s', prop, str(chainProperties[prop]), str(bJetDefaultValues[prop]))
521  chainProperties[prop] = bJetDefaultValues[prop]
522 
523 
524  if chainProperties['ditauTag'] != '':
525  log.debug('Setting Ditau chain defaults')
526  # boosted ditau chain, so we now use the ditau defaults if they have not already been overriden
527  ditauJetDefaultValues, allowedDitauJetPropertiesAndValues = getSignatureInformation('Ditau')
528  for prop, value in ditauJetDefaultValues.items():
529  propSet=False
530  for value in allowedDitauJetPropertiesAndValues[prop]:
531  if value in matchedparts:
532  propSet=True
533  break
534 
535  # if the property was not set already, then set if according to the ditau defaults
536  if propSet is False:
537  log.debug('Changing %s from %s to %s', prop, str(chainProperties[prop]), str(ditauJetDefaultValues[prop]))
538  chainProperties[prop] = ditauJetDefaultValues[prop]
539 
540 
541  if chainProperties['signature'] == 'Jet' and chainProperties['beamspotChain'] != '':
542  log.debug('Setting beamspot chain defaults')
543  BeamspotDefaultValues, allowedBeamspotPropertiesAndValues = getSignatureInformation('Beamspot_Jet')
544 
545  for prop, value in BeamspotDefaultValues.items():
546  propSet=False
547  for value in allowedBeamspotPropertiesAndValues[prop]:
548  if value in matchedparts:
549  propSet=True
550  break
551 
552  # if the property was not set already, then set if according to the b-jet defaults
553  if propSet is False:
554  log.debug('Changing %s from %s to %s', prop, str(chainProperties[prop]), str(BeamspotDefaultValues[prop]))
555  chainProperties[prop] = BeamspotDefaultValues[prop]
556 
557 
558  # Substitute after b-jet defaults have been set otherwise they will be overridden
559  if chainProperties['extra']:
560  # Atypical handling here: jets translate the 1 char suffix to an eta range
561  try:
562  etarange = etaRangeAbbrev[chainProperties['extra']]
563  chainProperties['etaRange'] = etarange
564  except KeyError:
565  raise RuntimeError(f"Invalid jet 'extra' value {chainProperties['extra']} in {chainProperties['chainPartName']}. Only [a,c,f] allowed")
566 
567  # ---- checking the parts that haven't been matched yet ----
568  log.debug("matched parts %s", matchedparts)
569  leftoverparts = set(parts)-set(matchedparts)
570  log.debug('leftoverparts %s', leftoverparts)
571  for pindex, part in enumerate(leftoverparts):
572  for prop, allowedValues in allowedSignaturePropertiesAndValues.items():
573  if prop in chainProperties.keys():
574  continue
575  for aV in allowedValues:
576  if (aV in part):
577  if (chainProperties['signature'] in ['Egamma', 'Muon'] )& (prop in ['trkInfo','hypoInfo']):
578  chainProperties[prop] = part
579  part = part.replace(part,'')
580  else:
581  chainProperties[prop] = aV
582  part = part.replace(aV,'')
583  break # done with allowed values for that property
584 
585  assert len(part.split()) == 0, "These parts of the chain name {} are not understood: {}".format(chainpartsNoL1,part)
586 
587 
588  # ---- remove properties that aren't allowed in the chain properties for a given siganture ----
589  forbiddenProperties = set(chainProperties.keys()) - set(allowedSignaturePropertiesAndValues.keys())
590  log.debug('chainProperties: %s', sorted(set(chainProperties.keys())))
591  log.debug('allowedSignaturePropertiesAndValues: %s', sorted(set(allowedSignaturePropertiesAndValues.keys())))
592  for fb in forbiddenProperties:
593  forbiddenValue = chainProperties.pop(fb)
594  assert forbiddenValue == '', "Property {} not allowed for signature '{}', but specified '{}'".format (fb, chainProperties['signature'], forbiddenValue)
595 
596  # ---- set the alignment group here, once we have fully worked out the properties ----
597  # ---- this is for the benefit of the probe leg in T&P chains ----
598 
599  if any(x in chainName for x in ('larnoiseburst', 'idcalib', 'metcalo', 'mettrk')):
600  chainProperties['alignmentGroup'] = 'JetMET'
601  else:
602  if 'tnpInfo' in chainProperties.keys() and chainProperties['tnpInfo'] != "":
603  chainProperties['alignmentGroup'] = getAlignmentGroupFromPattern(mdicts[chainindex]['signature'],chainProperties['tnpInfo'])
604  else:
605  chainProperties['alignmentGroup'] = getAlignmentGroupFromPattern(mdicts[chainindex]['signature'],chainProperties['extra'])
606 
607  # ---- the info of the general and the specific chain parts dict ----
608  allChainProperties.append(chainProperties)
609 
610  # ---- depending on if signatures are different in this chain, break up the chainProperties dictionary ----
611  # ---- finally also taking care of the signature key ----
612  genchainDict['chainParts'] = allChainProperties
613  for cPart in allChainProperties:
614  if cPart['signature'] == 'Jet' and cPart['bTag'] != '':
615  cPart['signature'] = 'Bjet'
616  if 'tnpInfo' in cPart.keys() and cPart['tnpInfo'] != "":
617  cPart['alignmentGroup'] = getAlignmentGroupFromPattern('Bjet', cPart['tnpInfo'])
618  else:
619  cPart['alignmentGroup'] = getAlignmentGroupFromPattern('Bjet', cPart['extra'])
620  if cPart['signature'] == 'Jet' and cPart['ditauTag'] != '':
621  cPart['signature'] = 'Tau'
622  if 'tnpInfo' in cPart.keys() and cPart['tnpInfo'] != "":
623  cPart['alignmentGroup'] = getAlignmentGroupFromPattern('Tau', cPart['tnpInfo'])
624  else:
625  cPart['alignmentGroup'] = getAlignmentGroupFromPattern('Tau', cPart['extra'])
626  genchainDict['signatures'] += [cPart['signature']]
627  genchainDict['alignmentGroups'] += [cPart['alignmentGroup']]
628 
629  # Postprocess chains with multiple jet chainParts
630  # to set them consistently from only one specification
631  if len(genchainDict['chainParts'])>1:
632  if 'Jet' in genchainDict['signatures'] or 'Bjet' in genchainDict['signatures']:
633  genchainDict['chainParts'] = unifyJetRecoParts(genchainDict['chainParts'])
634 
635  #genchainDict['signature'] = allChainProperties[0]['signature']
636  return genchainDict
637 
638 
640  rt = []
641  for i in A:
642  if isinstance(i,list): rt.extend(flattenChainGroups(i))
643  else: rt.append(i)
644  return rt
645 
646 def checkChainStream(myStream):
647  if len(myStream) == 1:
648  if myStream[0] == 'express':
649  return False
650  return True
651 
652 def dictFromChainName(flags, chainInfo):
653  """
654  Transforms ChainProp into the ChainDict
655 
656  The logic:
657  ---- Loop over all chains (keys) in dictionary ----
658  ---- Then complete the dict with other info ----
659  Default input format will be a ChainProp object:
660  ChainProp(name: str, groups: list[str], l1SeedThresholds: list[str] = [], stream: list[str] = ['Main'],
661  monGroups: list[str] = [], mergingStrategy: str = 'auto', mergingOrder: list[str] = [], mergingOffset: int = -1),
662  but for nwo plain chain name is also supported
663  """
664 
665  # these if/elif/else statements are due to temporary development
666  if type(chainInfo) is str:
667  chainName = chainInfo
668  l1Thresholds = []
669  stream = ''
670  groups = []
671  mergingStrategy = 'parallel'
672  mergingOffset = -1
673  mergingOrder = []
674  monGroups = []
675 
676  elif 'ChainProp' in str(type(chainInfo)):
677  #this is how we define chains in the menu - the normal behaviour of this function
678  chainName = chainInfo.name
679  l1Thresholds = chainInfo.l1SeedThresholds
680  stream = chainInfo.stream
681  groups = flattenChainGroups(chainInfo.groups)
682  mergingStrategy = chainInfo.mergingStrategy
683  mergingOffset = chainInfo.mergingOffset
684  mergingOrder = chainInfo.mergingOrder
685  monGroups = chainInfo.monGroups
686 
687  else:
688  raise RuntimeError("Format of chainInfo passed to genChainDict not known")
689 
690  #check here the content of the stream
691  if not checkChainStream(stream):
692  raise RuntimeError("Chain {}, format of chainInfo:stream {} is not valid".format(chainName, stream))
693 
694  L1item = getL1item(flags, chainName)
695 
696  log.debug("Analysing chain with name: %s", chainName)
697  chainDict = analyseChainName(chainName, l1Thresholds, L1item)
698  log.debug('ChainProperties: %s', chainDict)
699 
700  for chainPart in chainDict['chainParts']:
701  # fill the sigFolder and subSigs folder
702  for sf in chainPart['sigFolder']:
703  if sf in chainDict['sigDicts']:
704  chainDict['sigDicts'][sf].extend(chainPart['subSigs'])
705  else:
706  chainDict['sigDicts'].update({sf:chainPart['subSigs']})
707  if sf == 'Bjet' or 'Ditau' in chainPart['subSigs']:
708  if 'Jet' not in chainDict['sigDicts']:
709  chainDict['sigDicts'].update({'Jet':['Jet']})
710 
711 
712  thisSignature = chainPart['signature']
713  thisSubSigs = chainPart['subSigs']
714  thisAlignGroup = chainPart['alignmentGroup']
715  thisExtra = chainPart['extra']
716  thisL1 = chainPart['L1threshold']
717  thisChainPartName = chainPart['chainPartName']
718 
719  if thisSignature in ['Muon','Bphysics']:
720  if 'MuonnoL1' in thisAlignGroup or 'lateMu' in thisExtra:
721  if 'FSNOSEED' not in thisL1:
722  log.error("Muon noL1 and lateMu chain should be seeded from FSNOSEED. Check %s seeded from %s (defined L1: %s), signature %s",chainDict['chainName'],thisL1,l1Thresholds,thisSignature)
723  else:
724  if 'MU' not in thisL1:
725  log.error("Standard muon and Bphysics chain should be seeded from L1_MU. Check %s seeded from %s (defined L1: %s), signature %s",chainDict['chainName'],thisL1,l1Thresholds,thisSignature)
726  #incorrectL1=True
727 
728  if thisSignature in ['Electron','Photon'] and not ('UPC' in stream): # don't apply for HI UPC chains
729  if 'EM' not in thisL1 and 'BKee' not in thisL1 and 'All' not in thisL1:
730  log.error("Standard egamma chains should be seeded from L1_EM. Check %s seeded from %s (defined L1: %s), signature %s",chainDict['chainName'],thisL1,l1Thresholds,thisSignature)
731  #incorrectL1=True
732 
733  if thisSignature in ['Tau'] and 'Ditau' not in thisSubSigs:
734  if 'TAU' not in thisL1:
735  log.error("Standard tau chains should be seeded from L1_TAU. Check %s seeded from %s (defined L1: %s), signature %s",chainDict['chainName'],thisL1,l1Thresholds,thisSignature)
736  #incorrectL1=True
737 
738  if thisSignature in ['Jet','Bjet','MET','UnconventionalTracking'] or (thisSignature =='Tau' and 'Ditau' in thisSubSigs):
739  if 'FSNOSEED' not in thisL1:
740  log.error("Jet, b-jet, MET and Tau:Ditau chains should be seeded from FSNOSEED. Check %s seeded from %s (defined L1: %s), signature %s",chainDict['chainName'],thisL1,l1Thresholds,thisSignature)
741  #incorrectL1=True
742 
743  if thisChainPartName in ['noalg']:
744  # All streamers should be unseeded except RoI-based PEB streamers which need a real RoI for PEB
745  if 'FSNOSEED' not in thisL1 and not isRoIBasedPEB(chainDict['eventBuildType']):
746  log.error("noalg chains should be seeded from FSNOSEED. Check %s seeded from %s (defined L1: %s), signature %s",chainDict['chainName'],thisL1,l1Thresholds,thisSignature)
747  #incorrectL1=True
748 
749 # if incorrectL1 is True:
750 # raise Exception("You are using incorrect L1 seed, please check for ERROR messages...")
751 
752 
753  log.debug('Parts: signature %s, align %s, extra %s, L1 %s', thisSignature, thisAlignGroup, thisExtra, thisL1)
754 
755 
756 
757  # setting the L1 item
758  chainDict['L1item'] = L1item
759  chainDict['stream'] = stream
760  chainDict['groups'] = groups
761  chainDict['mergingStrategy'] = mergingStrategy
762  chainDict['mergingOffset'] = mergingOffset
763  chainDict['mergingOrder'] = mergingOrder
764  chainDict['monGroups'] = monGroups
765  chainDict['chainNameHash'] = string2hash(chainDict['chainName'])
766 
767 
768  allChainMultiplicities = getChainMultFromDict(chainDict)
769  chainDict['chainMultiplicities'] = allChainMultiplicities
770  log.debug('Setting chain multiplicities: %s ', allChainMultiplicities)
771 
772 
776 
777  return chainDict
778 
779 def main():
780 
781  from AthenaConfiguration.AllConfigFlags import initConfigFlags
782  flags = initConfigFlags()
783 
784  parser = flags.getArgumentParser()
785  parser.add_argument(
786  'chain',
787  type=str,
788  help='Chain name to be parsed into chain dictionary')
789  parser.add_argument(
790  '-t', '--thresholds',
791  nargs='+',
792  help='L1 thresholds, needed for multileg or fullscan chains')
793  args = flags.fillFromArgs(parser=parser)
794  flags.lock()
795 
796  if args.thresholds:
797  from TriggerMenuMT.HLT.Config.Utility.ChainDefInMenu import ChainProp
798  cd = dictFromChainName(flags, ChainProp(args.chain,l1SeedThresholds=args.thresholds,groups=[]))
799  else:
800  cd = dictFromChainName(flags, args.chain)
801 
802  from pprint import pprint
803  pprint(cd)
804 
805  import json
806  with open(f'{args.chain}.dict.json','w') as f:
807  json.dump(cd, f, indent=2)
808 
809  return 0
810 
811 if __name__=='__main__':
812  main()
813 
DictFromChainName.getAllThresholdsFromItem
def getAllThresholdsFromItem(item)
Definition: DictFromChainName.py:58
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename R::value_type > sorted(const R &r, PROJ proj={})
Helper function to create a sorted vector from an unsorted range.
DictFromChainName.getOverallL1item
def getOverallL1item(flags, chainName)
Definition: DictFromChainName.py:24
Run3DQTestingDriver._
_
Definition: Run3DQTestingDriver.py:35
vtune_athena.format
format
Definition: vtune_athena.py:14
python.HLT.Menu.SignatureDicts.getBasePattern
def getBasePattern()
Definition: SignatureDicts.py:1503
DictFromChainName.main
def main()
Definition: DictFromChainName.py:779
DictFromChainName.dictFromChainName
def dictFromChainName(flags, chainInfo)
Definition: DictFromChainName.py:652
DictFromChainName.checkChainStream
def checkChainStream(myStream)
Definition: DictFromChainName.py:646
intersection
std::vector< std::string > intersection(std::vector< std::string > &v1, std::vector< std::string > &v2)
Definition: compareFlatTrees.cxx:25
python.CaloAddPedShiftConfig.type
type
Definition: CaloAddPedShiftConfig.py:42
covarianceTool.filter
filter
Definition: covarianceTool.py:514
DictFromChainName.unifyJetRecoParts
def unifyJetRecoParts(chainParts)
Definition: DictFromChainName.py:164
python.TriggerConfigAccess.getL1MenuAccess
L1MenuAccess getL1MenuAccess(flags=None)
Definition: TriggerConfigAccess.py:129
DictFromChainName.getChainThresholdFromName
def getChainThresholdFromName(chainParts, signature)
Definition: DictFromChainName.py:106
DictFromChainName.getUniqueThresholdsFromItem
def getUniqueThresholdsFromItem(item)
Definition: DictFromChainName.py:80
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:194
DictFromChainName.getChainMultFromDict
def getChainMultFromDict(chainDict)
Definition: DictFromChainName.py:129
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
DictFromChainName.flattenChainGroups
def flattenChainGroups(A)
Definition: DictFromChainName.py:639
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:232
DictFromChainName.getL1item
def getL1item(flags, chainName)
Definition: DictFromChainName.py:54
python.HLT.Menu.SignatureDicts.getSignatureNameFromToken
def getSignatureNameFromToken(chainpart)
Definition: SignatureDicts.py:1428
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
DictFromChainName.verifyExplicitL1Thresholds
def verifyExplicitL1Thresholds(chainname, chainparts, L1thresholds)
Definition: DictFromChainName.py:146
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:71
Trk::open
@ open
Definition: BinningType.h:40
ActsTrk::detail::MakeDerivedVariant::extend
constexpr std::variant< Args..., T > extend(const std::variant< Args... > &, const T &)
Definition: MakeDerivedVariant.h:17
python.CaloAddPedShiftConfig.int
int
Definition: CaloAddPedShiftConfig.py:45
python.HLT.Menu.SignatureDicts.getSignatureInformation
def getSignatureInformation(signature)
Definition: SignatureDicts.py:1450
python.HLT.Menu.L1Seeds.getSpecificL1Seeds
def getSpecificL1Seeds(l1seedname, l1itemobject, menu_name)
Definition: L1Seeds.py:343
python.AllConfigFlags.initConfigFlags
def initConfigFlags()
Definition: AllConfigFlags.py:19
HLTUtils.string2hash
def string2hash(string)
Definition: HLTUtils.py:5
str
Definition: BTagTrackIpAccessor.cxx:11
python.HLT.Menu.EventBuildingInfo.getAllEventBuildingIdentifiers
def getAllEventBuildingIdentifiers()
Definition: EventBuildingInfo.py:46
DictFromChainName.analyseChainName
def analyseChainName(chainName, L1thresholds, L1item)
Definition: DictFromChainName.py:201
python.HLT.CommonSequences.EventBuildingSequences.isRoIBasedPEB
isRoIBasedPEB
Definition: EventBuildingSequences.py:395
DictFromChainName.getEBPartFromParts
def getEBPartFromParts(chainName, chainParts)
Definition: DictFromChainName.py:91