ATLAS Offline Software
Loading...
Searching...
No Matches
MenuComponents.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3from TriggerMenuMT.HLT.Config.Utility.HLTMenuConfig import HLTMenuConfig
4from TriggerMenuMT.HLT.Config.ControlFlow.MenuComponentsNaming import CFNaming
5from TriggerMenuMT.HLT.Config.ControlFlow.HLTCFTools import (NoHypoToolCreated,
6 algColor,
7 isHypoBase,
8 isInputMakerBase)
9from AthenaCommon.CFElements import parOR, seqAND, findAlgorithmByPredicate
10from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
11from AthenaConfiguration.ComponentFactory import CompFactory
12from DecisionHandling.DecisionHandlingConfig import ComboHypoCfg
13from TriggerJobOpts.TriggerConfigFlags import ROBPrefetching
14
15from collections.abc import MutableSequence
16import functools
17import re
18
19from AthenaCommon.Logging import logging
20log = logging.getLogger( __name__ )
21# Pool of mutable ComboHypo instances (FIXME: ATR-29181)
22_ComboHypoPool = dict()
23_CustomComboHypoAllowed = set()
24
25class Node(object):
26 """base class representing one Alg + inputs + outputs, to be used to connect """
27 """stores all the inputs, even if repeated (self.inputs)"""
28 def __init__(self, Alg):
29 self.name = ("%sNode")%( Alg.getName() )
30 self.Alg=Alg
31 self.inputs=[]
32 self.outputs=[]
33
34 def addOutput(self, name):
35 self.outputs.append(str(name))
36
37 def addInput(self, name):
38 self.inputs.append(str(name))
39
40 def getOutputList(self):
41 return self.outputs
42
43 def getInputList(self):
44 return self.inputs
45
46 def __repr__(self):
47 return "Node::%s [%s] -> [%s]"%(self.Alg.getName(), ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList())))
48
49
51 """Node class that represent an algorithm: sets R/W handles (as unique input/output) and properties as parameters """
52 """Automatically de-duplicates input ReadHandles upon repeated calls to addInput."""
53 def __init__(self, Alg, inputProp, outputProp):
54 Node.__init__(self, Alg)
55 self.outputProp = outputProp
56 self.inputProp = inputProp
57
58 def setPar(self, propname, value):
59 cval = getattr( self.Alg, propname)
60 if isinstance(cval, MutableSequence):
61 cval.append(value)
62 return setattr(self.Alg, propname, cval)
63 else:
64 return setattr(self.Alg, propname, value)
65
66 def addOutput(self, name):
67 outputs = self.readOutputList()
68 if name in outputs:
69 log.debug("Output DH not added in %s: %s already set!", self.Alg.getName(), name)
70 else:
71 if self.outputProp != '':
72 self.setPar(self.outputProp, name)
73 else:
74 log.debug("no outputProp set for output of %s", self.Alg.getName())
75 Node.addOutput(self, name)
76
77 def readOutputList(self):
78 cval = getattr(self.Alg, self.outputProp)
79 return (cval if isinstance(cval, MutableSequence) else
80 ([str(cval)] if cval else []))
81
82 def addInput(self, name):
83 inputs = self.readInputList()
84 if name in inputs:
85 log.debug("Input DH not added in %s: %s already set!", self.Alg.getName(), name)
86 else:
87 if self.inputProp != '':
88 self.setPar(self.inputProp, name)
89 else:
90 log.debug("no InputProp set for input of %s", self.Alg.getName())
91 Node.addInput(self, name)
92 return len(self.readInputList())
93
94 def readInputList(self):
95 cval = getattr(self.Alg, self.inputProp)
96 return (cval if isinstance(cval, MutableSequence) else
97 ([str(cval)] if cval else []))
98
99 def __repr__(self):
100 return "Alg::%s [%s] -> [%s]"%(self.Alg.getName(), ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList())))
101
102
104 """ Class to group info on hypotools for ChainDict"""
105 def __init__(self, hypoToolGen):
106 self.hypoToolGen = hypoToolGen
107 self.name=hypoToolGen.__name__
108
109 def setConf( self, chainDict):
110 if type(chainDict) is not dict:
111 raise RuntimeError("Configuring hypo with %s, not good anymore, use chainDict" % str(chainDict) )
112 self.chainDict = chainDict
113
114 def create(self, flags):
115 """creates instance of the hypo tool"""
116 return self.hypoToolGen( flags, self.chainDict )
117
118 def confAndCreate(self, flags, chainDict):
119 """sets the configuration and creates instance of the hypo tool"""
120 self.setConf(chainDict)
121 return self.create(flags)
122
123
125 """AlgNode for HypoAlgs"""
126 initialOutput= 'StoreGateSvc+UNSPECIFIED_OUTPUT'
127 def __init__(self, Alg):
128 assert isHypoBase(Alg), "Error in creating HypoAlgNode from Alg " + Alg.name
129 AlgNode.__init__(self, Alg, 'HypoInputDecisions', 'HypoOutputDecisions')
130 self.previous=[]
131
132 def addOutput(self, name):
133 outputs = self.readOutputList()
134 if name in outputs:
135 log.debug("Output DH not added in %s: %s already set!", self.name, name)
136 elif self.initialOutput in outputs:
137 AlgNode.addOutput(self, name)
138 else:
139 log.error("Hypo %s has already %s as configured output: you may want to duplicate the Hypo!",
140 self.name, outputs[0])
141
142 def addHypoTool (self, flags, hypoToolConf):
143 log.debug("Adding HypoTool %s for chain %s to %s", hypoToolConf.name, hypoToolConf.chainDict['chainName'], self.Alg.getName())
144 try:
145 result = hypoToolConf.create(flags)
146 if isinstance(result, ComponentAccumulator):
147 tool = result.popPrivateTools()
148 assert not isinstance(tool, list), "Can not handle list of tools"
149 self.Alg.HypoTools.append(tool)
150 return result
151 else:
152 self.Alg.HypoTools = self.Alg.HypoTools + [result] # see ATEAM-773
153
154 except NoHypoToolCreated as e:
155 log.debug("%s returned empty tool: %s", hypoToolConf.name, e)
156 return None
157
158 def setPreviousDecision(self,prev):
159 self.previous.append(prev)
160 return self.addInput(prev)
161
162 def __repr__(self):
163 return "HypoAlg::%s [%s] -> [%s], previous = [%s], HypoTools=[%s]" % \
164 (self.Alg.name,' '.join(map(str, self.getInputList())),
165 ' '.join(map(str, self.getOutputList())),
166 ' '.join(map(str, self.previous)),
167 ' '.join([t.getName() for t in self.Alg.HypoTools]))
168
169
171 """AlgNode for InputMaker Algs"""
172 def __init__(self, Alg):
173 assert isInputMakerBase(Alg), "Error in creating InputMakerNode from Alg " + Alg.name
174 AlgNode.__init__(self, Alg, 'InputMakerInputDecisions', 'InputMakerOutputDecisions')
175 input_maker_output = CFNaming.inputMakerOutName(self.Alg.name)
176 self.addOutput(input_maker_output)
177
178
180 """AlgNode for Combo HypoAlgs"""
181 def __init__(self, name, comboHypoCfg):
182 self.comboHypoCfg = comboHypoCfg
183 self.acc = self.create( name )
184 thealgs= self.acc.getEventAlgos()
185 if thealgs is None:
186 log.error("ComboHypoNode: Combo alg %s not found", name)
187 if len(thealgs) != 1:
188 log.error("ComboHypoNode: Combo alg %s len is %d",name, len(thealgs))
189 Alg=thealgs[0]
190
191 log.debug("ComboHypoNode init: Alg %s", name)
192 AlgNode.__init__(self, Alg, 'HypoInputDecisions', 'HypoOutputDecisions')
193
194 def __del__(self):
195 self.acc.wasMerged()
196
197 def create (self, name):
198 log.debug("ComboHypoNode.create %s",name)
199 return self.comboHypoCfg(name=name)
200
201 """
202 AlgNode automatically de-duplicates input ReadHandles upon repeated calls to addInput.
203 Node instead stores all the inputs, even if repeated (self.inputs)
204 This function maps from the raw number of times that addInput was called to the de-duplicated index of the handle.
205 E.g. a step processing chains such as HLT_e5_mu6 would return [0,1]
206 E.g. a step processing chains such as HLT_e5_e6 would return [0,0]
207 E.g. a step processing chains such as HLT_e5_mu6_mu7 would return [0,1,1]
208 These data are needed to configure the step's ComboHypo
209 """
211 mapping = []
212 theInputs = self.readInputList() #only unique inputs
213 for rawInput in self.inputs: # all inputs
214 mapping.append( theInputs.index(rawInput) )
215 return mapping
216
217
218 def addChain(self, chainDict):
219 chainName = chainDict['chainName']
220 chainMult = chainDict['chainMultiplicities']
221 legsToInputCollections = self.mapRawInputsToInputsIndex()
222 if len(chainMult) != len(legsToInputCollections):
223 log.error("ComboHypoNode for Alg:{} with addChain for:{} Chain multiplicity:{} Per leg input collection index:{}."
224 .format(self.Alg.name, chainName, tuple(chainMult), tuple(legsToInputCollections)))
225 log.error("The size of the multiplicies vector must be the same size as the per leg input collection vector.")
226 log.error("The ComboHypo needs to know which input DecisionContainers contain the DecisionObjects to be used for each leg.")
227 log.error("Check why ComboHypoNode.addInput(...) was not called exactly once per leg.")
228 raise Exception("[createDataFlow] Error in ComboHypoNode.addChain. Cannot proceed.")
229
230 if chainName in self.Alg.MultiplicitiesMap:
231 log.error("ComboAlg %s has already been configured for chain %s", self.Alg.name, chainName)
232 raise Exception("[createDataFlow] Error in ComboHypoNode.addChain. Cannot proceed.")
233 else:
234 self.Alg.MultiplicitiesMap[chainName] = chainMult
235 self.Alg.LegToInputCollectionMap[chainName] = legsToInputCollections
236
237
238 def getChains(self):
239 return self.Alg.MultiplicitiesMap.keys()
240
241
242 def createComboHypoTools(self, flags, chainDict, comboToolConfs):
243 """Create the ComboHypoTools and add them to the main alg"""
244 if not len(comboToolConfs):
245 return
246 confs = [ HypoToolConf( tool ) for tool in comboToolConfs ]
247 log.debug("ComboHypoNode.createComboHypoTools for chain %s, Alg %s with %d tools", chainDict["chainName"],self.Alg.getName(), len(comboToolConfs))
248 for conf in confs:
249 log.debug("ComboHypoNode.createComboHypoTools adding %s", conf)
250 tools = self.Alg.ComboHypoTools
251 self.Alg.ComboHypoTools = tools + [ conf.confAndCreate( flags, chainDict ) ]
252
253
254
257
259 """Class to emulate reco sequences with no Hypo"""
260 """It contains an InputMaker and and empty seqAND used for merging"""
261 """It contains empty function to follow the same MenuSequence behaviour"""
262 def __init__(self, the_name):
263 log.debug("Made EmptySequence %s", the_name)
264 self._name = the_name
265
266 # isEmptyStep causes the IM to try at runtime to merge by feature by default
267 # (i.e for empty steps appended after a leg has finised). But if this failes then it will
268 # merge by initial ROI instead (i.e. for empy steps prepended before a leg has started)
269 makerAlg = CompFactory.InputMakerForRoI(f"IM{the_name}",
270 isEmptyStep = True,
271 RoIsLink = 'initialRoI')
272
273 self._maker = InputMakerNode( Alg = makerAlg )
274 self._sequence = Node( Alg = seqAND(the_name, [makerAlg]))
275
276 self.ca = ComponentAccumulator()
277 self.ca.addSequence(seqAND(the_name))
278 self.ca.addEventAlgo(makerAlg, sequenceName=the_name)
279
280 def __del__(self):
281 self.ca.wasMerged()
282
283 @property
284 def sequence(self):
285 return self._sequence
286
287 @property
288 def maker(self):
289 # Input makers are added during DataFlow building (connectToFilter) when a chain
290 # uses this sequence in another step. So we need to make sure to update the
291 # algorithm when accessed.
292 self._maker.Alg = self.ca.getEventAlgo(self._maker.Alg.name)
293 return self._maker
294
295 @property
296 def name(self):
297 return self._name
298
299 def getOutputList(self):
300 return self.maker.readOutputList() # Only one since it's merged
301
302 def connectToFilter(self, outfilter):
303 """Connect filter to the InputMaker"""
304 self.maker.addInput(outfilter)
305
306 def getHypoToolConf(self):
307 return None
308
309 def buildDFDot(self, cfseq_algs, all_hypos, last_step_hypo_nodes, file):
310 cfseq_algs.append(self.maker)
311 cfseq_algs.append(self.sequence )
312 file.write(" %s[fillcolor=%s]\n"%(self.maker.Alg.getName(), algColor(self.maker.Alg)))
313 file.write(" %s[fillcolor=%s]\n"%(self.sequence.Alg.getName(), algColor(self.sequence.Alg)))
314 return cfseq_algs, all_hypos, last_step_hypo_nodes
315
316 def __repr__(self):
317 return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s\n"\
318 %(self.name, "Empty", self.maker.Alg.getName(), self.sequence.Alg.getName(), "None")
319
320def createEmptyMenuSequenceCfg(flags, name):
321 """ creates the generator function named as the empty sequence"""
322 def create_sequence(flags, name):
323 return EmptyMenuSequence(name)
324 # this allows to create the function with the same name as the sequence
325 create_sequence.__name__ = name
326 globals()[name] = create_sequence
327 return globals()[name]
328
329
330def isEmptySequenceCfg(o):
331 return 'Empty' in o.func.__name__
332
333class MenuSequence:
334 """Class to group reco sequences with the Hypo.
335 By construction it has one Hypo only, which gives the name to this class object"""
336
337 def __init__(self, flags, selectionCA, HypoToolGen):
338 self.ca = selectionCA
339 # separate the HypoCA to be merged later
340 self.hypoAcc = selectionCA.hypoAcc
341
342 sequence = self.ca.topSequence()
343 self._sequence = Node(Alg=sequence)
344
345 # get the InputMaker
346 inputMaker = [ a for a in self.ca.getEventAlgos() if isInputMakerBase(a)]
347 assert len(inputMaker) == 1, f"{len(inputMaker)} input makers in the ComponentAccumulator"
348 inputMaker = inputMaker[0]
349 assert inputMaker.name.startswith("IM"), f"Input maker {inputMaker.name} name needs to start with 'IM'"
350 self._maker = InputMakerNode( Alg = inputMaker )
351 input_maker_output = self.maker.readOutputList()[0] # only one since it's merged
352
353
354 # get the HypoAlg
355 hypoAlg = selectionCA.hypoAcc.getEventAlgos()
356 assert len(hypoAlg) == 1, f"{len(hypoAlg)} hypo algs in the ComponentAccumulator"
357 hypoAlg = hypoAlg[0]
358 hypoAlg.RuntimeValidation = flags.Trigger.doRuntimeNaviVal
359
360 self._name = CFNaming.menuSequenceName(hypoAlg.name)
361 self._hypo = HypoAlgNode( Alg = hypoAlg )
362 self._hypo.addOutput( CFNaming.hypoAlgOutName(hypoAlg.name) )
363 self._hypo.setPreviousDecision( input_maker_output )
364 self._hypoToolConf = HypoToolConf( HypoToolGen )
365
366 # Connect InputMaker output to ROBPrefetchingAlg(s) if there is any
367 if ROBPrefetching.StepRoI in flags.Trigger.ROBPrefetchingOptions:
368 for child in sequence.Members:
369 if ( isinstance(child, CompFactory.ROBPrefetchingAlg) and
370 input_maker_output not in child.ROBPrefetchingInputDecisions ):
371 child.ROBPrefetchingInputDecisions.append(input_maker_output)
372
373 log.debug("connecting InputMaker and HypoAlg, adding: InputMaker::%s.output=%s",
374 self.maker.Alg.name, input_maker_output)
375 log.debug("HypoAlg::%s.HypoInputDecisions=%s, HypoAlg::%s.HypoOutputDecisions=%s",
376 self.hypo.Alg.name, self.hypo.readInputList()[0],
377 self.hypo.Alg.name, self.hypo.readOutputList()[0])
378
379 def __del__(self):
380 self.ca.wasMerged()
381 self.hypoAcc.wasMerged()
382
383 @property
384 def name(self):
385 return self._name
386
387 @property
388 def sequence(self):
389 return self._sequence
390
391 @property
392 def maker(self):
393 # Input makers are added during DataFlow building (connectToFilter) when a chain
394 # uses this sequence in another step. So we need to make sure to update the
395 # algorithm when accessed.
396 self._maker.Alg = self.ca.getEventAlgo(self._maker.Alg.name)
397 return self._maker
398
399 @property
400 def hypo(self):
401 return self._hypo
402
403 def getOutputList(self):
404 return [self._hypo.readOutputList()[0]]
405
406 def connectToFilter(self, outfilter):
407 """Connect filter to the InputMaker"""
408 log.debug("connectToFilter: connecting %s to inputs of %s", outfilter, self.maker.Alg.name)
409 self.maker.addInput(outfilter)
410
411 def getHypoToolConf(self) :
412 return self._hypoToolConf
413
414
415 def buildDFDot(self, cfseq_algs, all_hypos, last_step_hypo_nodes, file):
416 cfseq_algs.append(self.maker)
417 cfseq_algs.append(self.sequence)
418 file.write(" %s[fillcolor=%s]\n"%(self.maker.Alg.getName(), algColor(self.maker.Alg)))
419 file.write(" %s[fillcolor=%s]\n"%(self.sequence.Alg.getName(), algColor(self.sequence.Alg)))
420 cfseq_algs.append(self._hypo)
421 file.write(" %s[color=%s]\n"%(self._hypo.Alg.getName(), algColor(self._hypo.Alg)))
422 all_hypos.append(self._hypo)
423 return cfseq_algs, all_hypos, last_step_hypo_nodes
424
425 def __repr__(self):
426 hyponame = self._hypo.Alg.name
427 hypotool = self._hypoToolConf.name
428 return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s\n"\
429 %(self.name, hyponame, self.maker.Alg.name, self.sequence.Alg.name, hypotool)
430
431
432class Chain(object):
433 """Basic class to define the trigger menu """
434 __slots__ ='name','steps','nSteps','alignmentGroups','L1decisions', 'topoMap'
435 def __init__(self, name, ChainSteps, L1decisions, nSteps = None, alignmentGroups = None, topoMap=None):
436
437 """
438 Construct the Chain from the steps
439 Out of all arguments the ChainSteps & L1Thresholds are most relevant, the chain name is used in debug messages
440 """
441
442 # default mutable values must be initialized to None
443 if nSteps is None: nSteps = []
444 if alignmentGroups is None: alignmentGroups = []
445
446 self.name = name
447 self.steps = ChainSteps
448 self.nSteps = nSteps
449 self.alignmentGroups = alignmentGroups
450
451
452 # The chain holds a map of topo ComboHypoTool configurators
453 # This is needed to allow placement of the ComboHypoTool in the right position
454 # for multi-leg chains (defaults to last step)
455 # Format is {"[step name]" : ([topo config function], [topo descriptor string]), ...}
456 # Here, the topo descriptor string would usually be the chain name expression that
457 # configures the topo
458 self.topoMap = {}
459 if topoMap:
460 self.topoMap.update(topoMap)
461
462 # L1decisions are used to set the seed type (EM, MU,JET), removing the actual threshold
463 # in practice it is the HLTSeeding Decision output
464 self.L1decisions = L1decisions
465 log.debug("[Chain.__init__] Made Chain %s with seeds: %s ", name, self.L1decisions)
466
467 def append_bjet_steps(self,new_steps):
468 assert len(self.nSteps) == 1, "[Chain.append_bjet_steps] appending already-merged step lists - chain object will be broken. This should only be used to append Bjets to jets!"
469 self.steps = self.steps + new_steps
470 self.nSteps = [len(self.steps)]
471
472 def append_step_to_jet(self,new_steps):
473 assert len(self.nSteps) == 1, "[Chain.append_step_to_jet] appending already-merged step lists - chain object will be broken. This is used either for appending Beamspot algorithms to jets!"
474 self.steps = self.steps + new_steps
475 self.nSteps = [len(self.steps)]
476
477
478 def numberAllSteps(self):
479 if len(self.steps)==0:
480 return
481 else:
482 for stepID,step in enumerate(self.steps):
483 step_name = step.name
484 if re.search('^Step[0-9]_',step_name):
485 step_name = step_name[6:]
486 elif re.search('^Step[0-9]{2}_', step_name):
487 step_name = step_name[7:]
488 step.name = 'Step%d_'%(stepID+1)+step_name
489 # also modify the empty sequence names to follow the step name change
490 for iseq, seq in enumerate(step.sequenceGens):
491 if isEmptySequenceCfg(seq):
492 name = seq.func.__name__
493 if re.search('Seq[0-9]_',name):
494 newname = re.sub('Seq[0-9]_', 'Seq%d_'%(stepID+1), name)
495 #replace the empty sequence
496 thisEmpty = createEmptyMenuSequenceCfg(flags=None, name=newname)
497 step.sequenceGens[iseq]=functools.partial(thisEmpty, flags=None, name=newname)
498 return
499
500
501 def insertEmptySteps(self, empty_step_name, n_new_steps, start_position):
502 #start position indexed from 0. if start position is 3 and length is 2, it works like:
503 # [old1,old2,old3,old4,old5,old6] ==> [old1,old2,old3,empty1,empty2,old4,old5,old6]
504
505 if len(self.steps) == 0 :
506 log.error("I can't insert empty steps because the chain doesn't have any steps yet!")
507
508 if len(self.steps) < start_position :
509 log.error("I can't insert empty steps at step %d because the chain doesn't have that many steps!", start_position)
510
511
512 chain_steps_pre_split = self.steps[:start_position]
513 chain_steps_post_split = self.steps[start_position:]
514
515 next_step_name = ''
516 prev_step_name = ''
517 # copy the same dictionary as the last step, which else?
518 prev_chain_dict = []
519 if start_position == 0:
520 next_step_name = chain_steps_post_split[0].name
521 if re.search('^Step[0-9]_',next_step_name):
522 next_step_name = next_step_name[6:]
523 elif re.search('^Step[0-9]{2}_', next_step_name):
524 next_step_name = next_step_name[7:]
525
526 prev_step_name = 'empty_'+str(len(self.L1decisions))+'L1in'
527 prev_chain_dict = chain_steps_post_split[0].stepDicts
528 else:
529 if len(chain_steps_post_split) == 0:
530 log.error("Adding empty steps to the end of a chain (%s)- why would you do this?",self.name)
531 else:
532 prev_step_name = chain_steps_pre_split[-1].name
533 next_step_name = chain_steps_post_split[0].name
534 prev_chain_dict = chain_steps_pre_split[-1].stepDicts
535
536
537 steps_to_add = []
538 for stepID in range(1,n_new_steps+1):
539 new_step_name = prev_step_name+'_'+empty_step_name+'%d_'%stepID+next_step_name
540
541 log.debug("Adding empty step %s", new_step_name)
542 steps_to_add += [ChainStep(new_step_name, chainDicts=prev_chain_dict, isEmpty=True)]
543
544 self.steps = chain_steps_pre_split + steps_to_add + chain_steps_post_split
545
546 return
547
548 def checkNumberOfLegs(self):
549 """ return 0 if the chain has unexpected number of step legs """
550 if len(self.steps) == 0: # skip if it's noAlg chains
551 return 1
552
553 mult=[step.nLegs for step in self.steps] # one nLegs per step
554 not_empty_mult = [m for m in mult if m!=0]
555 # cannot accept chains with all empty steps
556 if len(not_empty_mult) == 0:
557 log.error("checkNumberOfLegs: Chain %s has all steps with nLegs =0: what to do?", self.name)
558 return 0
559
560 # cannot accept chains with steps with different number of legs
561 if not_empty_mult.count(not_empty_mult[0]) != len(not_empty_mult):
562 log.error("checkNumberOfLegs: Chain %s has steps with differnt number of legs: %s", self.name, ' '.join(mult))
563 return 0
564
565 # check that the chain number of legs is the same as the number of L1 seeds
566 if not_empty_mult[0] != len(self.L1decisions):
567 log.error("checkNumberOfLegs: Chain %s has %i legs per step, and %d L1Decisions", self.name, mult, len(self.L1decisions))
568 return 0
569 return not_empty_mult[0]
570
571
572 # Receives a pair with the topo config function and an identifier string,
573 # optionally also a target step name
574 # The string is needed to rename the step after addition of the ComboHypoTool
575 def addTopo(self,topoPair,step="last"):
576 stepname = "last step" if step=="last" else step.name
577 log.debug("Adding topo configurator %s for %s to %s", topoPair[0].__qualname__, topoPair[1], "step " + stepname)
578 self.topoMap[step] = topoPair
579
580 def __str__(self):
581 return "\n-*- Chain %s -*- \n + Seeds: %s, Steps: %s, AlignmentGroups: %s "%(\
582 self.name, ' '.join(map(str, self.L1decisions)), self.nSteps, self.alignmentGroups)
583
584 def __repr__(self):
585 return "\n-*- Chain %s -*- \n + Seeds: %s, Steps: %s, AlignmentGroups: %s \n + Steps: \n %s \n"%(\
586 self.name, ' '.join(map(str, self.L1decisions)), self.nSteps, self.alignmentGroups, '\n '.join(map(str, self.steps)))
587
588
589
590class ChainStep(object):
591 """ Class to describe one step of a chain;
592 a step is described by a list of ChainDicts and a list of sequence generators;
593 there is one leg per ChainDict;
594 a step can have one leg (single) or more legs (combined);
595 not-empty steps have one sequence per leg;
596 empty steps have zero sequences, while chainDict len is not zero;
597 legID is taken from the ChainDict;
598 """
599
600 def __init__(self, name, SequenceGens = None, chainDicts = None, comboHypoCfg = None, comboToolConfs = None, isEmpty = False, createsGhostLegs = False):
601
602 # default mutable values must be initialized to None
603 if SequenceGens is None: SequenceGens = []
604 if comboHypoCfg is None: comboHypoCfg = functools.partial(ComboHypoCfg)
605 if comboToolConfs is None: comboToolConfs = []
606 assert chainDicts is not None,"Error building a ChainStep without chainDicts"
607
608 self.name = name
609 self.sequences = []
610 self.sequenceGens = SequenceGens
611 if not isinstance(comboHypoCfg, functools.partial):
612 raise RuntimeError("[ChainStep] Tried to configure a ChainStep %s with ComboHypo %s that is not a function" % (name, comboHypoCfg) )
613
614 self.comboHypoCfg = comboHypoCfg
615 self.comboToolConfs = list(comboToolConfs)
616 self.stepDicts = chainDicts # one dict per leg
617 self.nLegs = len(self.stepDicts) # cannot be zero
618 self.isEmpty = isEmpty
619
620 # sanity check on inputs, excluding empty steps
621 if not self.isEmpty:
622 log.debug("Building step %s for chain %s: n.sequences=%d, nLegs=%i", name, chainDicts[0]['chainName'], len (self.sequenceGens) , self.nLegs )
623 if len (self.sequenceGens) != self.nLegs:
624 log.error("[ChainStep] SequenceGens: %s",self.sequenceGens)
625 log.error("[ChainStep] stepDicts: %s",self.stepDicts)
626 log.error("[ChainStep] n.legs: %i",self.nLegs)
627 raise RuntimeError("[ChainStep] Tried to configure a ChainStep %s with %i legs and %i sequences. These lists must have the same size" % (name, self.nLegs, len (self.sequenceGens) ) )
628
629
630 for iseq, seq in enumerate(self.sequenceGens):
631 if not isinstance(seq, functools.partial):
632 log.error("[ChainStep] %s SequenceGens verification failed, sequence %d is not partial function, likely ChainBase.getStep function was not used", self.name, iseq)
633 log.error("[ChainStep] It rather seems to be of type %s trying to print it", type(seq))
634 raise RuntimeError("Sequence is not packaged in a tuple, see error message above" )
635
636 self.onlyJets = False
637 sig_set = None
638 if 'signature' in chainDicts[0]:
639 sig_set = set([step['signature'] for step in chainDicts])
640 if len(sig_set) == 1 and ('Jet' in sig_set or 'Bjet' in sig_set):
641 self.onlyJets = True
642 if len(sig_set) == 2 and ('Jet' in sig_set and 'Bjet' in sig_set):
643 self.onlyJets = True
644
645
646
647 if not self.isEmpty:
648 self.setChainPartIndices()
649 self.makeCombo()
650
651 def createSequences(self):
652 """ creation of this step sequences with instantiation of the CAs"""
653 log.debug("createSequences: creating %d sequences for step %s", len(self.sequenceGens), self.name)
654 for seq in self.sequenceGens:
655 log.debug("createSequences: creating sequence %s", seq.func.__name__)
656 self.sequences.append(seq()) # create the sequences
657
658
659 #Heather updated for full jet chain dicts
660 def setChainPartIndices(self):
661 leg_counter = 0
662 lists_of_chainPartNames = []
663 for step_dict in self.stepDicts:
664 if len(lists_of_chainPartNames) == 0:
665 lists_of_chainPartNames += [[cp['chainPartName'] for cp in step_dict['chainParts']]]
666 else:
667 new_list_of_chainPartNames = [cp['chainPartName'] for cp in step_dict['chainParts']]
668 if new_list_of_chainPartNames == lists_of_chainPartNames[-1]:
669 leg_counter -= len(new_list_of_chainPartNames)
670 for chainPart in step_dict['chainParts']:
671 chainPart['chainPartIndex'] = leg_counter
672 leg_counter += 1
673 return
674
675
676 def addComboHypoTools(self, tool):
677 #this function does not add tools, it just adds one tool. do not pass it a list!
678 self.comboToolConfs.append(tool)
679
680 def getComboHypoFncName(self):
681 return self.comboHypoCfg.func.__name__
682
683
684
685 def makeCombo(self):
686 """ Configure the Combo Hypo Alg and generate the corresponding function, without instantiation which is done in createSequences() """
687 self.combo = None
688 if self.isEmpty:
689 return
690 comboNameFromStep = CFNaming.comboHypoName(self.name) # name expected from the step name
691 funcName = self.getComboHypoFncName() # name of the function generator
692 key = hash((comboNameFromStep, funcName))
693 if key not in _ComboHypoPool:
694 tmpCombo = ComboHypoNode(comboNameFromStep, self.comboHypoCfg)
695 CHname = tmpCombo.name[:-4] # remove 'Node'
696 # exceptions for BLS chains that re-use the same custom CH in differnt steps
697 # this breaks the run-one-CH-per-step, but the BLS CH are able to handle decisions internally
698 if comboNameFromStep != CHname:
699 log.debug("Created ComboHypo with name %s, expected from the step is instead %s. This is accepted only for allowed custom ComboHypos", CHname, comboNameFromStep)
700 _CustomComboHypoAllowed.add(CHname)
701 key = hash((CHname, funcName))
702 _ComboHypoPool[key] = tmpCombo
703 self.combo = _ComboHypoPool[key]
704 log.debug("Created combo %s with name %s, step comboName %s, key %s", funcName, self.combo.name, comboNameFromStep,key)
705
706
707 def createComboHypoTools(self, flags, chainName):
708 chainDict = HLTMenuConfig.getChainDictFromChainName(chainName)
709 self.combo.createComboHypoTools(flags, chainDict, self.comboToolConfs)
710
711 def getChainLegs(self):
712 """ This is extrapolating the chain legs from the step dictionaries"""
713 legs = [part['chainName'] for part in self.stepDicts]
714 return legs
715
716 def getChainNames(self):
717 if self.combo is not None:
718 return list(self.combo.getChains())
719 return self.getChainLegs()
720
721 def __repr__(self):
722 if len(self.sequenceGens) == 0:
723 return "\n--- ChainStep %s ---\n is Empty, ChainDict = %s "%(self.name, ' '.join(map(str, [dic['chainName'] for dic in self.stepDicts])) )
724
725 repr_string= "\n--- ChainStep %s ---\n , nLegs = %s ChainDict = %s \n + MenuSequenceGens = %s "%\
726 (self.name, self.nLegs,
727 ' '.join(map(str, [dic['chainName'] for dic in self.stepDicts])),
728 ' '.join(map(str, [seq.func.__name__ for seq in self.sequenceGens]) ))
729
730 if self.combo is not None:
731 repr_string += "\n + ComboHypo = %s" % self.combo.Alg.name
732 if len(self.comboToolConfs)>0:
733 repr_string +=", ComboHypoTools = %s" %(' '.join(map(str, [tool.__name__ for tool in self.comboToolConfs])))
734 repr_string += "\n"
735 return repr_string
736
737
738class InEventRecoCA( ComponentAccumulator ):
739 """ Class to handle in-event reco """
740 def __init__(self, name, inputMaker=None, **inputMakerArgs):
741 super( InEventRecoCA, self ).__init__()
742 self.name = name
743 self.recoSeq = None
744
745 if inputMaker:
746 assert len(inputMakerArgs) == 0, "No support for explicitly passed input maker and and input maker arguments at the same time"
747 self.inputMakerAlg = inputMaker
748 else:
749 assert 'name' not in inputMakerArgs, "The name of input maker is predefined by the name of sequence"
750 args = {'name': "IM"+name,
751 'RoIsLink' : 'initialRoI',
752 'RoIs' : f'{name}RoIs',
753 'RoITool': CompFactory.ViewCreatorInitialROITool(),
754 'mergeUsingFeature': False}
755 args.update(**inputMakerArgs)
756 self.inputMakerAlg = CompFactory.InputMakerForRoI(**args)
757
758 def addRecoSequence(self):
759 if self.recoSeq is None:
760 self.recoSeq = parOR( self.name )
761 self.addSequence( self.recoSeq )
762
763 def mergeReco( self, ca ):
764 """ Merged CA moving reconstruction algorithms into the right sequence """
765 self.addRecoSequence()
766 return self.merge( ca, sequenceName=self.recoSeq.name )
767
768 def addRecoAlgo( self, algo ):
769 """ Place algorithm in the correct reconstruction sequence """
770 self.addRecoSequence()
771 return self.addEventAlgo( algo, sequenceName=self.recoSeq.name )
772
773 def inputMaker( self ):
774 return self.inputMakerAlg
775
776
777
778class InViewRecoCA(ComponentAccumulator):
779 """ Class to handle in-view reco, sets up the View maker if not provided and exposes InputMaker so that more inputs to it can be added in the process of assembling the menu """
780 def __init__(self, name, viewMaker=None, isProbe=False, **viewMakerArgs):
781 super( InViewRecoCA, self ).__init__()
782 self.name = name +"_probe" if isProbe else name
783 def updateHandle(baseTool, probeTool, handleName):
784 if hasattr(baseTool, handleName) and getattr(baseTool, handleName).Path!="StoreGateSvc+":
785 setattr(probeTool, handleName, getattr(probeTool, handleName).Path + "_probe")
786
787 if len(viewMakerArgs) != 0:
788 assert viewMaker is None, "No support for explicitly passed view maker and args for EventViewCreatorAlgorithm"
789
790 if viewMaker:
791 assert len(viewMakerArgs) == 0, "No support for explicitly passed view maker and args for EventViewCreatorAlgorithm"
792 if isProbe:
793 self.viewMakerAlg = viewMaker.__class__(viewMaker.getName()+'_probe', **viewMaker._properties)
794 self.viewMakerAlg.Views = viewMaker.Views+'_probe'
795 roiTool = self.viewMakerAlg.RoITool.__class.__(self.viewMakerAlg.RoITool.getName()+'_probe', **self.viewMakerAlg.RoITool._properties)
796 log.debug(f"InViewRecoCA: Setting InputCachedViews on {self.viewMaker.getName()} to read decisions from tag leg {viewMaker.getName()}: {viewMaker.InputMakerOutputDecisions}")
797 self.viewMakerAlg.InputCachedViews = viewMaker.InputMakerOutputDecisions
798 updateHandle(viewMakerArgs['RoITool'], roiTool, "RoisWriteHandleKey")
799 if hasattr(viewMakerArgs['RoITool'], "RoiCreator"):
800 updateHandle(viewMakerArgs['RoITool'], roiTool, "ExtraPrefetchRoIsKey")
801 updateHandle(viewMakerArgs['RoITool'].RoiCreator, roiTool.RoiCreator, "RoisWriteHandleKey")
802
803 self.viewMakerAlg.RoITool = roiTool
804 else:
805 self.viewMakerAlg = viewMaker
806 else:
807 assert 'name' not in viewMakerArgs, "The name of view maker is predefined by the name of sequence"
808 assert 'Views' not in viewMakerArgs, "The Views is predefined by the name of sequence"
809 assert 'ViewsNodeName' not in viewMakerArgs, "The ViewsNodeName is predefined by the name of sequence"
810 if 'RoITool' in viewMakerArgs:
811 roiTool = viewMakerArgs['RoITool']
812 else:
813 roiTool = CompFactory.ViewCreatorInitialROITool()
814
815
816 args = {'name': f'IM_{self.name}',
817 'ViewFallThrough' : True,
818 'RoIsLink' : 'initialRoI',
819 'RoITool' : roiTool,
820 'InViewRoIs' : f'{name}RoIs',
821 'Views' : f'{name}Views'+'_probe' if isProbe else f'{name}Views',
822 'ViewNodeName' : f'{name}InViews'+'_probe' if isProbe else f'{name}InViews',
823 'RequireParentView' : False,
824 'mergeUsingFeature' : False }
825 args.update(**viewMakerArgs)
826 self.viewMakerAlg = CompFactory.EventViewCreatorAlgorithm(**args)
827 if isProbe:
828 updateHandle(args['RoITool'], roiTool, "RoisWriteHandleKey")
829 if hasattr(args['RoITool'], "RoiCreator"):
830 updateHandle(args['RoITool'], roiTool, "ExtraPrefetchRoIsKey")
831 updateHandle(args['RoITool'].RoiCreator, roiTool.RoiCreator, "RoisWriteHandleKey")
832 self.viewsSeq = parOR( self.viewMakerAlg.ViewNodeName )
833 self.addSequence( self.viewsSeq )
834
835 def mergeReco( self, ca ):
836 """ Merge CA moving reconstruction algorithms into the right sequence """
837 return self.merge( ca, sequenceName=self.viewsSeq.name )
838
839
840 def addRecoAlgo( self, algo ):
841 """ Place algorithm in the correct reconstruction sequence """
842 return self.addEventAlgo( algo, sequenceName=self.viewsSeq.name )
843
844
845 def inputMaker( self ):
846 return self.viewMakerAlg
847
848
849class SelectionCA(ComponentAccumulator):
850 """ CA component for MenuSequence sequence """
851 def __init__(self, name, isProbe=False):
852 self.name = name+"_probe" if isProbe else name
853 self.isProbe=isProbe
854 super( SelectionCA, self ).__init__()
855
856 self.stepViewSequence = seqAND(self.name)
857 self.hypoAcc = ComponentAccumulator()
858
859 def wasMerged(self):
860 super( SelectionCA, self ).wasMerged()
861 self.hypoAcc.wasMerged()
862
863 def mergeReco(self, recoCA, robPrefetchCA=None, upSequenceCA=None):
864 ''' upSequenceCA is the user CA to run before the recoCA'''
865 ca=ComponentAccumulator()
866 ca.addSequence(self.stepViewSequence)
867 if upSequenceCA:
868 ca.merge(upSequenceCA, sequenceName=self.stepViewSequence.name)
869 ca.addEventAlgo(recoCA.inputMaker(), sequenceName=self.stepViewSequence.name)
870 if robPrefetchCA:
871 ca.merge(robPrefetchCA, self.stepViewSequence.name)
872 ca.merge(recoCA, sequenceName=self.stepViewSequence.name)
873 self.merge(ca)
874
875 def mergeHypo(self, other):
876 """To be used when the hypo alg configuration comes with auxiliary tools/services"""
877 self.hypoAcc.merge(other)
878
879 def addHypoAlgo(self, algo):
880 """To be used when the hypo alg configuration does not require auxiliary tools/services"""
881 if self.isProbe:
882 newname = algo.getName()+'_probe'
883 algo.name=newname
884 self.hypoAcc.addEventAlgo(algo)
885
886 def hypo(self):
887 """Access hypo algo (or throws)"""
888 h = findAlgorithmByPredicate(self.stepViewSequence, lambda alg: "HypoInputDecisions" in alg._descriptors ) # can't use isHypo
889 assert h is not None, "No hypo in SeelectionCA {}".format(self.name)
890 return h
891
892 def inputMaker(self):
893 """Access Input Maker (or throws)"""
894 im = findAlgorithmByPredicate(self.stepViewSequence, lambda alg: "InputMakerInputDecisions" in alg._descriptors )
895 assert im is not None, "No input maker in SeelectionCA {}".format(self.name)
896 return im
897
898 def topSequence(self):
899 return self.stepViewSequence
__init__(self, Alg, inputProp, outputProp)
setPar(self, propname, value)
createComboHypoTools(self, flags, chainDict, comboToolConfs)
__init__(self, name, comboHypoCfg)
addHypoTool(self, flags, hypoToolConf)
confAndCreate(self, flags, chainDict)
__init__(self, hypoToolGen)
STL class.
STL class.