ATLAS Offline Software
Loading...
Searching...
No Matches
GenerateMenuMT.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3from typing import Optional
4import importlib, re, string
5
6from TriggerMenuMT.HLT.Config.Utility.HLTMenuConfig import HLTMenuConfig
7
8from AthenaCommon.Logging import logging
9log = logging.getLogger(__name__)
10
11# _maxAllowedCustomCH: this variable keeps track of the number of custom ComboHypo that are named differently as expected from the ControlFlow rule (which sets one ComboHypo per step, both named the same). These violations are already investigated and allowed because the ComboHypos are able to handle the decisions internally. Any change of this parameter needs to be discussed with experts
12_maxAllowedCustomCH = 10
13
15 return ['Streaming','Monitor','Beamspot','Cosmic', 'Calib', 'EnhancedBias']
16
18 return ['MinBias','Electron','Photon','Muon','Tau','Jet', 'Bjet','MET','UnconventionalTracking','HeavyIon']
19
21 return ['Bjet', 'Egamma', 'Combined']
22
24 return ['Streaming']
25
27 return ['Test']
28
30 return ['Bphysics']
31
37 """Standard chain filter"""
38 def __init__(self, flags):
39 for f in ("enabledSignatures", "disabledSignatures", "selectChains", "disableChains"):
40 # Ensure flag values have correct type
41 value = getattr(flags.Trigger, f)
42 assert isinstance(value, list), f"Flag Trigger.{f}={value!r} is not of type list"
43 # Store flag
44 setattr(self, f, value)
45
46 def __call__(self, signame, chain):
47 return ((signame in self.enabledSignatures and signame not in self.disabledSignatures) and \
48 (not self.selectChains or chain in self.selectChains) and chain not in self.disableChains)
49
50 def __str__(self) -> str:
51 return f'FilterChainsToGenerate(enabledSignatures={self.enabledSignatures!r}, disabledSignatures={self.disabledSignatures!r}, selectChains={self.selectChains!r}, disableChains={self.disableChains!r})'
52
53
54class Singleton(type):
55 _instances = {}
56 def __call__(cls, *args, **kwargs):
57 if cls not in cls._instances:
58 cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
59 return cls._instances[cls]
60
61 def clear(cls):
62 cls._instances.clear()
63
64
65class GenerateMenuMT(metaclass=Singleton):
66 """Singleton class for the Trigger Menu"""
67 def __init__(self):
68 self.base_menu_name: str = ''
69
70 self.chainsInMenu = {} # signature : [chains]
71
72 self.allChainsForAlignment = []
73 self.chainDicts = []
74 self.combinationsInMenu = []
75 self.alignmentGroupsToAlign = set()
76 self.configLengthDict = {}
77
78 self.signaturesOverwritten = False
79 self.L1Prescales = None
80 self.HLTPrescales = None
81
82 self.chainFilter = None
83 self.availableSignatures = []
84
85 self.sigDicts = {}
86
87 self.chainDefModule = {} # Generate[SIG]ChainDefs module for each SIGnature
88 self.defaultFlagsForSignature = {}
89
90
91 # Define which signatures (folders) are required for each slice
92 def getRequiredSignatures(theslice):
93 allSigs = allSignatures()
94 signatureDeps = {sig:[sig] for sig in allSigs}
95 # Special cases
96 signatureDeps.update({
97 # Bjet always requires jet
98 'Bjet': ['Bjet','Jet'],
99 # Egamma contains two signatures
100 'Egamma': ['Electron','Photon'],
101 'Combined': combinedSignatures(),
102 })
103 return set(signatureDeps[theslice]+defaultSignatures()) # always allow streamers
104
105
106 def setChainFilter(self, f):
107 """Set chain filter for menu generation.
108
109 This can be any callable object taking two
110 arguments for signature and chain name and returning a boolean.
111 E.g. to only generate Egamma chains:
112 menu.setChainFilter(lambda slice,chain : slice=='Egamma').
113
114 In the special case that f is a functor with the list attributes
115 selectChains and/or disableChains, the contents will be explicitly
116 checked to be in the menu.
117 """
118 fname = f.__class__.__name__ if isinstance(f,object) else f.__name__
119 import inspect
120 if len(inspect.signature(f).parameters)!=2:
121 log.error('%s is not a valid chain filter. Function/callable needs take two arguments '
122 'for signature and chain name and return a boolean', fname)
123 else:
124 log.info('Setting chain filter to: %s', f)
125 self.chainFilter = f
126
127
128 def getChainDicts(self, flags):
129
130 def validSignature(currentSig, chainSig):
131 """Check if chain is assigned to the correct signature"""
132 reqd = GenerateMenuMT.getRequiredSignatures(currentSig)
133 isValid = chainSig.issubset( reqd )
134 log.debug("Chain signatures: %s, required signatures: %s",chainSig,reqd)
135 if not isValid:
136 log.error("Chain signatures %s not a subset of required signatures %s",set(chainSig),reqd)
137 return isValid
138
139 from TriggerMenuMT.HLT.Config.Utility.DictFromChainName import dictFromChainName
140
141 chainCounter = 0
142 invalid = False
143 for sig, chains in self.chainsInMenu.items():
144 for chain in chains:
145 log.debug("Now processing chain: %s from signature %s", chain, sig)
146 chainCounter += 1
147 chainDict = dictFromChainName(flags, chain)
148 chainDict['chainCounter'] = chainCounter
149 chainDict['prescale'] = 1 # set default chain prescale
150
151 # Pick out the folder and subsignature directories to import
152 for sigfo, subsig in chainDict['sigDicts'].items():
153 if sigfo not in self.sigDicts:
154 self.sigDicts[sigfo] = subsig
155 else:
156 for ss in subsig:
157 if ss not in self.sigDicts[sigfo]:
158 self.sigDicts[sigfo].append(ss)
159
160 self.chainDicts.append(chainDict)
161
162 if not validSignature(sig, set(chainDict['signatures'])):
163 invalid=True
164 log.error('Chain %s assigned to signature %s but creates %s',
165 chainDict['chainName'], sig, set(chainDict['signatures']))
166 if invalid:
167 raise RuntimeError('Incorrect assignment of chains to slices -- see preceding messages.')
168
169 def importSignaturesToGenerate(self):
170 """check if all the signature files can be imported and then import them"""
171
172 for sig, subSigs in self.sigDicts.items():
173 try:
174 for ss in subSigs:
175 import_module = 'TriggerMenuMT.HLT.' + sig +'.Generate' + ss + 'ChainDefs'
176 self.chainDefModule[ss] = importlib.import_module(import_module)
177
178 if ss not in self.availableSignatures:
179 self.availableSignatures.append(ss)
180
181 except ImportError:
182 log.exception('Problems when importing ChainDef generating code for %s', sig)
183 import traceback
184 traceback.print_exc()
185
186 log.info('Available signature(s) for chain generation: %s', self.availableSignatures)
187
188 return
189
190 def generateChains(self, flags):
191 all_chains = []
192 combinations_in_menu = []
193 alignmentGroups_to_align = set()
194 length_of_configs = {}
195
196 nchainDicts = len(self.chainDicts)
197 notify_increment = max(int(nchainDicts / 10),1)
198 for ichainDict, chainDict in enumerate(self.chainDicts):
199 log.debug("Next: getting chain configuration for chain %s ", chainDict['chainName'])
200 if ichainDict % notify_increment==0:
201 log.info("Generating HLT chain %d / %d", ichainDict+1, nchainDicts)
202 chainConfig,lengthOfChainConfigs = self.__generateChainConfig(flags, chainDict)
203 all_chains += [(chainDict,chainConfig,lengthOfChainConfigs)]
204
205 #update the alignment group length dictionary if we have a longer number of steps
206 #or the signature isn't registered in the dictionary yet
207 for config_length, config_grp in lengthOfChainConfigs:
208 if config_grp in length_of_configs:
209 if config_length > length_of_configs[config_grp]:
210 length_of_configs[config_grp] = config_length
211 else:
212 length_of_configs[config_grp] = config_length
213
214 # find the chains that contain more than one alignment group, to keep track
215 # of what combinations do we need to deal with.
216 # we're using sets here so we don't end up with duplicates
217 if len(set(chainDict['alignmentGroups'])) > 1:
218 combinations_in_menu += [list(set(chainDict['alignmentGroups']))]
219 for align_group in list(set(chainDict['alignmentGroups'])):
220 alignmentGroups_to_align.update([align_group])
221
222 self.allChainsForAlignment = all_chains
223 self.combinationsInMenu = combinations_in_menu
224 self.alignmentGroupsToAlign = alignmentGroups_to_align
225 self.configLengthDict = length_of_configs
226
227 return
228
229
230 def generateAllChainConfigs(self, flags):
231 """
232 == Obtains chain configs for all chains in menu
233 """
234
235 from TriggerMenuMT.HLT.Config.Utility.MenuAlignmentTools import MenuAlignment
236 from TriggerMenuMT.HLT.CommonSequences import EventBuildingSequences, TLABuildingSequences
237
238 # get all chain names from menu
239 log.info("Will now get chains from the menu")
240 self.getChainsFromMenu(flags)
241
242 # decoding of the chain name
243 log.info("Will now get chain dictionaries for each chain")
244 self.getChainDicts(flags)
245
246 if flags.Trigger.disableCPS:
247 log.warning('Removing all CPS group because the flag Trigger.disableCPS is set')
248 for chainDict in self.chainDicts:
249 chainDict['groups'] = [g for g in chainDict['groups'] if not g.startswith('RATE:CPS_')]
250
251 #import the necessary signatures
252 log.debug("Importing the necessary signatures")
253 self.importSignaturesToGenerate()
254
255 log.info("Will now generate the chain configuration for each chain")
256 self.generateChains(flags)
257
258 log.info("Will now calculate the alignment parameters")
259 #dict of signature: set it belongs to
260 #e.g. {'Electron': ['Electron','Muon','Photon']}
261 menuAlignment = MenuAlignment(self.combinationsInMenu,
262 self.alignmentGroupsToAlign,
263 self.configLengthDict)
264 menuAlignment.analyse_combinations()
265
266 # alignmentGroups_to_align = menuAlignment.groupsToAlign
267 # lengthOfChainConfigs = self.configLengthDict
268 # combinationsInMenu = menuAlignment.combinationsInMenu
269 # alignmentGroup_sets_to_align = menuAlignment.setsToAlign
270
271 log.info('Aligning the following signatures: %s',sorted(menuAlignment.sets_to_align))
272 log.debug('Length of each of the alignment groups: %s',self.configLengthDict)
273
274 chainConfigs = []
275
276 for chainDict,chainConfig,lengthOfChainConfigs in self.allChainsForAlignment:
277
278 # start by ordering electron, photon, muon by having e+mu, g+mu, e+g chains
279 # desired ordering: electron, photon, muon, tau, jet, met, b-jet
280
281 # lengthOfChainConfigs is something like this: [(4, 'Photon'), (5, 'Muon')]
282 # needs to match up with the maximum number of steps in a signature in the menu (length_of_configs)
283 # start with electron! Only need to add post-steps for combined electron chains if the max length in a combined chain
284 # is greater than the number of electron steps combined chain. Assume that the max length of an electron chain occurs
285 # in a combined chain.
286
287 log.debug("[generateAllChainConfigs] chain %s has config lengths %s and alignment groups %s", chainDict['chainName'], lengthOfChainConfigs, chainDict['alignmentGroups'])
288
289 alignmentGroups = chainDict['alignmentGroups']
290
291 #parallel-merged single-signature chains or single signature chains. Anything that needs no splitting!
292 if len(set(alignmentGroups)) == 1:
293 alignedChainConfig = menuAlignment.single_align(chainDict, chainConfig)
294 HLTMenuConfig.registerChain( chainDict )
295 chainConfigs.append( alignedChainConfig )
296
297 elif len(alignmentGroups) >= 2:
298 alignedChainConfig = menuAlignment.multi_align(chainDict, chainConfig, lengthOfChainConfigs)
299 HLTMenuConfig.registerChain( chainDict )
300 chainConfigs.append( alignedChainConfig )
301
302 else:
303 log.error("Menu can't deal with combined chains with more than two alignmentGroups at the moment. oops...")
304 raise NotImplementedError("more than three alignment groups still needs implementing in ChainMerging.py, ATR-22206")
305
306 if not HLTMenuConfig.isChainRegistered(chainDict['chainName']):
307 log.error("Chain %s has not been registered in the menu!", chainDict['chainName'])
308 import pprint
309 pp = pprint.PrettyPrinter(indent=4, depth=8)
310 log.error('The chain dictionary is: %s', pp.pformat(chainDict))
311 raise Exception("Please fix the menu or the chain.")
312
313 # align event building sequences
314 log.info("[generateAllChainConfigs] general alignment complete, will now align TLA chains")
315 TLABuildingSequences.alignTLASteps(chainConfigs, HLTMenuConfig.dicts())
316 log.info("[generateAllChainConfigs] general and TLA alignment complete, will now align PEB chains")
317 EventBuildingSequences.alignEventBuildingSteps(chainConfigs, HLTMenuConfig.dicts())
318
319 log.info("[generateAllChainConfigs] all chain configurations have been generated.")
320 return chainConfigs
321
322
323 def getChainsFromMenu(self, flags):
324 """
325 == Returns the list of chain names that are in the menu
326 """
327
328 self.base_menu_name = re.match(r'\w*_v\d*', flags.Trigger.triggerMenuSetup).group(0)
329 log.info(f'Menu name: {flags.Trigger.triggerMenuSetup}')
330 log.debug('Base menu name: %s', self.base_menu_name)
331
332 # Generate the list of chains from the basic menu (terminated in a version number)
333 try:
334 menu_module = importlib.import_module(f'TriggerMenuMT.HLT.Menu.{self.base_menu_name}')
335 except Exception as e:
336 log.fatal(f'Failed to import menu module "{self.base_menu_name}" inferred from menu "{flags.Trigger.triggerMenuSetup}"')
337 raise e
338
339 # Load Menu
340 self.chainsInMenu = menu_module.setupMenu()
341
342 # Filter chains if requested
343 if self.chainFilter is not None:
344 self.signaturesOverwritten = True
345
346 # Verify that if the chain filter has lists of chains
347 # they are all in the menu
348 chainsToCheck = []
349 if hasattr(self.chainFilter,'selectChains'):
350 chainsToCheck += self.chainFilter.selectChains
351 if hasattr(self.chainFilter,'disableChains'):
352 chainsToCheck += self.chainFilter.disableChains
353 for chain in chainsToCheck:
354 inMenu = False
355 for signame in self.chainsInMenu:
356 if chain in [c.name for c in self.chainsInMenu[signame]]:
357 inMenu = True
358 break
359 if not inMenu:
360 raise RuntimeError(f'Request to enable/disable chain {chain} that is not in menu')
361
362 for signame in self.chainsInMenu:
363 self.chainsInMenu[signame] = [c for c in self.chainsInMenu[signame]
364 if self.chainFilter(signame, c.name)]
365
366 if not self.chainsInMenu:
367 log.warning("There seem to be no chains in the menu - please check")
368 elif log.isEnabledFor(logging.DEBUG):
369 import pprint
370 log.debug("The following chains were found in the menu:")
371 pprint.pprint(self.chainsInMenu)
372
373
374 def __generateChainConfig(self, flags, mainChainDict):
375 """
376 # Assembles the chain configuration and returns a chain object with (name, L1see and list of ChainSteps)
377 """
378
379 from TriggerMenuMT.HLT.Config.Utility.ChainDictTools import splitInterSignatureChainDict
380 from TriggerMenuMT.HLT.Config.Utility.ComboHypoHandling import addTopoInfo, comboConfigurator, topoLegIndices, anomdetWPIndices, topo3VarLegIndices
381 from TriggerMenuMT.HLT.Config.Utility.ChainMerging import mergeChainDefs
382 from TriggerMenuMT.HLT.CommonSequences import EventBuildingSequences, TLABuildingSequences
383
384 # split the the chainDictionaries for each chain and print them in a pretty way
385 chainDicts = splitInterSignatureChainDict(mainChainDict)
386
387 if log.isEnabledFor(logging.DEBUG):
388 import pprint
389 pp = pprint.PrettyPrinter(indent=4, depth=8)
390 log.debug('dictionary is: %s', pp.pformat(chainDicts))
391
392 # Loop over all chainDicts and send them off to their respective assembly code
393 listOfChainConfigs = []
394 perSig_lengthOfChainConfigs = []
395
396 for chainPartDict in chainDicts:
397 chainPartConfig = None
398 currentSig = chainPartDict['signature']
399 currentAlignGroup = None
400 if len(chainPartDict['chainParts']) == 1:
401 currentAlignGroup = chainPartDict['chainParts'][0]['alignmentGroup']
402
403 chainName = chainPartDict['chainName']
404 log.debug('Checking chainDict for chain %s in signature %s, alignment group %s' , chainName, currentSig, currentAlignGroup)
405
406 if currentSig in self.availableSignatures:
407 try:
408 log.debug("[__generateChainConfigs] Trying to get chain config for %s", currentSig)
409 if currentSig in self.defaultFlagsForSignature:
410 sigFlags = self.defaultFlagsForSignature[currentSig]
411 else:
412 try:
413 sigFlags = self.chainDefModule[currentSig].prepareDefaultSignatureFlags(flags)
414 except AttributeError:
415 log.debug("prepareDefaultSignatureFlags not present")
416 sigFlags = flags
417 except Exception as e:
418 log.error(f"Unexpected error invoking prepareDefaultSignatureFlags {e}")
419 sigFlags = flags
420
421 self.defaultFlagsForSignature[currentSig] = sigFlags
422
423 if currentSig in ['Electron', 'Photon', 'Muon', 'Tau', 'Bphysics'] :
424 chainPartConfig, perSig_lengthOfChainConfigs = self.chainDefModule[currentSig].generateChainConfigs(sigFlags, chainPartDict, perSig_lengthOfChainConfigs)
425 else:
426 chainPartConfig = self.chainDefModule[currentSig].generateChainConfigs(sigFlags, chainPartDict)
427 if currentSig == 'Test' and isinstance(chainPartConfig, tuple):
428 chainPartConfig = chainPartConfig[0]
429 except Exception:
430 log.error('[__generateChainConfigs] Problems creating ChainDef for chain %s ', chainName)
431 log.error('[__generateChainConfigs] I am in chain part\n %s ', chainPartDict)
432 log.exception('[__generateChainConfigs] Full chain dictionary is\n %s ', mainChainDict)
433 raise Exception('[__generateChainConfigs] Stopping menu generation. Please investigate the exception shown above.')
434 else:
435 log.error('Chain %s cannot be generated - Signature "%s" not available', chainPartDict['chainName'], currentSig)
436 log.error('Available signature(s): %s', self.availableSignatures)
437 raise Exception('Stopping the execution. Please correct the configuration.')
438
439 log.debug("Chain %s \n chain config: %s",chainPartDict['chainName'],chainPartConfig)
440
441 listOfChainConfigs.append(chainPartConfig)
442 log.debug("[__generateChainConfigs] adding to the perSig_lengthOfChainConfigs list (%s, %s)",chainPartConfig.nSteps,chainPartConfig.alignmentGroups)
443 perSig_lengthOfChainConfigs.append((chainPartConfig.nSteps,chainPartConfig.alignmentGroups))
444
445 # this will be a list of lists for inter-sig combined chains and a list with one
446 # multi-element list for intra-sig combined chains
447 # here, we flatten it accordingly (works for both cases!)
448 lengthOfChainConfigs = []
449 for nSteps, aGrps in perSig_lengthOfChainConfigs:
450 if len(nSteps) != len(aGrps):
451 log.error("Chain part has %s steps and %s alignment groups - these don't match!",nSteps,aGrps)
452 else:
453 for a,b in zip(nSteps,aGrps):
454 lengthOfChainConfigs.append((a,b))
455
456
457 # This part is to deal with combined chains between different signatures
458 try:
459 if len(listOfChainConfigs) == 0:
460 raise Exception('[__generateChainConfigs] No Chain Configuration found for {0}'.format(mainChainDict['chainName']))
461 else:
462 if len(listOfChainConfigs)>1:
463 log.debug("Merging strategy from dictionary: %s", mainChainDict["mergingStrategy"])
464 theChainConfig, perSig_lengthOfChainConfigs = mergeChainDefs(listOfChainConfigs, mainChainDict, perSig_lengthOfChainConfigs)
465 lengthOfChainConfigs = []
466 for nSteps, aGrps in perSig_lengthOfChainConfigs:
467 if len(nSteps) != len(aGrps):
468 log.error("Post-merged chain part has %s steps and %s alignment groups - these don't match!",nSteps,aGrps)
469 else:
470 for a,b in zip(nSteps,aGrps):
471 lengthOfChainConfigs.append((a,b))
472 else:
473 theChainConfig = listOfChainConfigs[0]
474
475 for topoID in range(len(mainChainDict['extraComboHypos'])):
476 thetopo = mainChainDict['extraComboHypos'][topoID].strip(string.digits).rstrip(topoLegIndices)
477
478
479 if "anomdet" in thetopo:
480 thetopo = thetopo.rstrip(anomdetWPIndices)
481
482 if "masswiso" in thetopo:
483 thetopo = thetopo.rstrip(topo3VarLegIndices)
484
485 theChainConfig.addTopo((comboConfigurator[thetopo],thetopo))
486
487 # Now we know where the topos should go, we can insert them in the right steps
488 if len(theChainConfig.topoMap) > 0:
489 log.debug("Trying to add extra ComboHypoTool for %s",mainChainDict['extraComboHypos'])
490 addTopoInfo(theChainConfig,mainChainDict,listOfChainConfigs,lengthOfChainConfigs)
491 except RuntimeError:
492 log.error('[__generateChainConfigs] Problems creating ChainDef for chain %s ', chainName)
493 log.error('[__generateChainConfigs] I am in the extraComboHypos section, for %s ', mainChainDict['extraComboHypos'])
494 log.exception('[__generateChainConfigs] Full chain dictionary is\n %s ', mainChainDict)
495 raise Exception('[__generateChainConfigs] Stopping menu generation. Please investigate the exception shown above.')
496 except AttributeError:
497 raise Exception('[__generateChainConfigs] Stopping menu generation. Please investigate the exception shown above.')
498
499 # Configure event building strategy
500 eventBuildType = mainChainDict['eventBuildType']
501 TLAEventBuildTypes = ('PhysicsTLA', 'FTagPEBTLA', 'EgammaPEBTLA', 'DarkJetPEBTLA')
502 if eventBuildType:
503 try:
504 if any(ebtype in eventBuildType for ebtype in TLAEventBuildTypes):
505 log.debug("Adding TLA Step for chain %s", mainChainDict['chainName'])
506 TLABuildingSequences.addTLAStep(flags, theChainConfig, mainChainDict)
507 log.debug('Configuring event building sequence %s for chain %s', eventBuildType, mainChainDict['chainName'])
508 EventBuildingSequences.addEventBuildingSequence(flags, theChainConfig, eventBuildType, mainChainDict)
509 except TypeError as ex:
510 log.error(ex)
511 raise Exception('[__generateChainConfigs] Stopping menu generation for EventBuilding/TLA sequences. Please investigate the exception shown above.')
512
513 log.debug('[__generateChainConfigs] lengthOfChainConfigs %s, ChainConfigs %s ', lengthOfChainConfigs, theChainConfig)
514 return theChainConfig,lengthOfChainConfigs
515
516
517 def resolveEmptySteps(self,chainConfigs):
518 max_steps = max([len(cc.steps) for cc in chainConfigs], default=0)
519 steps_are_empty = [True for i in range(0,max_steps)]
520 emptySteps = []
521 for cc in chainConfigs:
522 for istep, the_step in enumerate(cc.steps):
523 if not the_step.isEmpty:
524 steps_are_empty[istep] = False
525 else:
526 emptySteps.append(the_step)
527
528 log.debug("Are there any fully empty steps? %s", steps_are_empty)
529 log.debug("The empty step(s) and associated chain(s) are: %s", emptySteps)
530 empty_step_indices = [i for i,is_empty in enumerate(steps_are_empty) if is_empty]
531
532 if len(empty_step_indices) == 0:
533 return chainConfigs
534
535 special_test_menu = self.chainFilter and ( getattr(self.chainFilter, "selectChains", False) or \
536 getattr(self.chainFilter, "disableChains", False) or \
537 getattr(self.chainFilter, "disabledSignatures", False) or \
538 getattr(self.chainFilter, "enabledSignatures", False) )
539
540
541 if len(self.availableSignatures) != 1 and not special_test_menu:
542 raise Exception("[resolveEmptySteps] Please find the reason for this empty step and resolve it / remove it from the menu: %s", emptySteps)
543
544 log.info("Will now delete steps %s (indexed from zero)",empty_step_indices)
545
546 for cc in chainConfigs:
547 new_steps = []
548 #only add non-empty steps to the new steps list!
549 for istep,step in enumerate(cc.steps):
550 if istep not in empty_step_indices:
551 new_steps += [step]
552 cc.steps = new_steps
553
554 return chainConfigs
555
556 def generatePrescales(self, flags, prescale_set: Optional[str] = '__auto__'):
557 '''Add prescales for disabling items (e.g. MC production)'''
558
559 menu_name = flags.Trigger.triggerMenuSetup
560 if prescale_set == '__auto__':
561 if menu_name.endswith('_prescale'):
562 # Get the prescale set name from the Menu
563 prescale_set = menu_name.removeprefix(f'{self.base_menu_name}_').removesuffix('_prescale')
564 else:
565 prescale_set = None
566
567 if prescale_set:
568 from TriggerMenuMT.HLT.Menu.MenuPrescaleConfig import menu_prescale_set_gens
569 if prescale_set not in menu_prescale_set_gens:
570 raise RuntimeError(f'Unknown menu prescale set for menu {flags.Trigger.triggerMenuSetup}')
571
572 gen = menu_prescale_set_gens[prescale_set]
573 else:
574 from TriggerMenuMT.HLT.Config.Utility.MenuPrescaleSet import AutoPrescaleSetGen
575 gen = AutoPrescaleSetGen()
576
577 # Generate prescale set
578 log.info(f'Generating automatic prescale set: {prescale_set}')
579 ps_set = gen.generate(flags, store=True)
580 self.L1Prescales = ps_set.l1_prescales
581 self.HLTPrescales = ps_set.hlt_prescales
582
583
584
585def generateMenuMT(flags):
586 """
587 == Main function to generate the L1, L1Topo and HLT menu configs and CA, using the GenerateMenuMT class
588 """
589 # Generate L1 menu
590 # The L1Menu json file is produced here
591 from TrigConfigSvc.TrigConfigSvcCfg import generateL1Menu
592 generateL1Menu(flags)
593
594
595 menu = GenerateMenuMT()
596
597 # Apply generation filter (enable/disable chains and signatures)
598 chains_gen_filter = FilterChainsToGenerate(flags)
599 menu.setChainFilter(chains_gen_filter)
600 log.debug('Filtering chains: %d', menu.chainFilter is not None)
601
602 # Generate all chains configuration
603 finalListOfChainConfigs = menu.generateAllChainConfigs(flags)
604
605 checkNumberOfLegs = [chain.checkNumberOfLegs() for chain in finalListOfChainConfigs]
606 if 0 in checkNumberOfLegs:
607 log.error('There is a chain with unexpected number of legs. Revisit your configuration')
608
609 log.info('Number of configured chains: %d', len(finalListOfChainConfigs))
610
611 from TriggerMenuMT.HLT.Config import MenuComponents
612 if len(MenuComponents._CustomComboHypoAllowed)> _maxAllowedCustomCH:
613 log.error(f'Found {len(MenuComponents._CustomComboHypoAllowed)} ComboHypo algorithms violating the one-CH-per-step rule, only {_maxAllowedCustomCH} are allowed (which are BLS ComboHypos). This is the list of current violations: {MenuComponents._CustomComboHypoAllowed}. Please consolidate your choice of ComboHypo, by checking that it is able to handle decisions internally; if yes eventually increase the limit set by _maxAllowedCustomCH, after discussing with experts')
614 # Generate and apply the automatic prescale sets (e.g. for disabling items in an MC production)
615 menu.generatePrescales(flags)
616
617
618 # Remove any remaining steps that are fully empty in all chains
619 finalListOfChainConfigs = menu.resolveEmptySteps(finalListOfChainConfigs)
620 log.debug("finalListOfChainConfig: %s", finalListOfChainConfigs)
621
622 # Make the HLT configuration tree
623 # The HLTMenu json file is produced here.
624 log.info("Making the HLT configuration tree")
625 menuAcc, CFseq_list = makeHLTTree(flags, finalListOfChainConfigs)
626
627 # Configure ChainFilters for ROBPrefetching
628 from TriggerJobOpts.TriggerConfigFlags import ROBPrefetching
629 if ROBPrefetching.InitialRoI in flags.Trigger.ROBPrefetchingOptions:
630 from TrigGenericAlgs.TrigGenericAlgsConfig import prefetchingInitialRoIConfig
631 menuAcc.merge(prefetchingInitialRoIConfig(flags, CFseq_list), 'HLTBeginSeq')
632
633
634 # Post-generation checks:
635 log.info("Checking the L1HLTConsistency...")
636 from TriggerMenuMT.HLT.Config.Validation.CheckL1HLTConsistency import checkL1HLTConsistency
637 checkL1HLTConsistency(flags)
638
639 log.info("Checking the Coherent Prescale assignments...")
640 from TriggerMenuMT.HLT.Config.Validation.CheckCPSGroups import checkCPSGroups
641 checkCPSGroups(HLTMenuConfig.dictsList())
642
643 log.info("Checking that all chains streamed in express have a signature or detctor monGroup")
644 from TriggerMenuMT.HLT.Config.Validation.CheckMonGroups import checkMonGroups
645 checkMonGroups(HLTMenuConfig.dictsList())
646
647 # Cleanup menu singletons to allow garbage collection (ATR-28855)
648 GenerateMenuMT.clear()
649 from TriggerMenuMT.HLT.Config import MenuComponents
650 MenuComponents._ComboHypoPool.clear()
651 MenuComponents._CustomComboHypoAllowed.clear()
652
653 return menuAcc
654
655
656def makeHLTTree(flags, chainConfigs):
657 """
658 Generate appropriate Control Flow Graph wiht all HLT algorithms
659 """
660 from TriggerMenuMT.HLT.Config.ControlFlow.HLTCFConfig import decisionTreeFromChains, sequenceScanner
661 from TriggerJobOpts.TriggerConfig import collectViewMakers
662 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
663 from AthenaCommon.CFElements import seqAND
664
665 acc = ComponentAccumulator()
666 steps = seqAND('HLTAllSteps')
667 finalDecisions, CFseq_list, menuAcc = decisionTreeFromChains(flags, steps, chainConfigs, HLTMenuConfig.dictsList())
668 if log.getEffectiveLevel() <= logging.DEBUG:
669 menuAcc.printConfig()
670
671 acc.merge(menuAcc)
672 successful_scan = sequenceScanner( steps )
673 if not successful_scan:
674 raise Exception("[makeHLTTree] At least one sequence is expected in more than one step. Check error messages and fix!")
675
676 flatDecisions=[]
677 for step in finalDecisions:
678 flatDecisions.extend (step)
679
680 viewMakers = collectViewMakers(steps)
681 viewMakerMap = {vm.name:vm for vm in viewMakers}
682 for vmname, vm in viewMakerMap.items():
683 log.debug(f"[makeHLTTree] {vmname} InputMakerOutputDecisions: {vm.InputMakerOutputDecisions}")
684 if vmname.endswith("_probe"):
685 try:
686 log.debug(f"Setting InputCachedViews on {vmname} to read decisions from tag leg {vmname[:-6]}: {vm.InputMakerOutputDecisions}")
687 vm.InputCachedViews = viewMakerMap[vmname[:-6]].InputMakerOutputDecisions
688 except KeyError: # We may be using a probe leg that has different reco from the tag
689 log.debug(f"Tag leg does not match probe: '{vmname[:-6]}', will not use cached views")
690
691
692 # Generate JSON representation of the config
693 from TriggerMenuMT.HLT.Config.JSON.HLTMenuJSON import generateJSON
694 generateJSON(flags, HLTMenuConfig.dictsList(), menuAcc.getSequence("HLTAllSteps"))
695
696 # Store the HLTMonitoring json file
697 from TriggerMenuMT.HLT.Config.JSON.HLTMonitoringJSON import generateDefaultMonitoringJSON
698 generateDefaultMonitoringJSON(flags, HLTMenuConfig.dictsList())
699
700 return acc, CFseq_list
STL class.