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