# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration from AthenaCommon.Logging import logging log = logging.getLogger( __name__ ) from collections import MutableSequence from TriggerMenuMT.HLTMenuConfig.Menu.MenuComponentsNaming import CFNaming from AthenaCommon.CFElements import parOR, seqAND, compName, getProp from DecisionHandling.DecisionHandlingConfig import ComboHypoCfg from AthenaConfiguration.ComponentFactory import CompFactory RoRSeqFilter=CompFactory.RoRSeqFilter class Node(object): """base class representing one Alg + inputs + outputs, to be used to Draw dot diagrams and connect objects""" def __init__(self, Alg): self.name = ("%sNode")%( Alg.name ) self.Alg=Alg self.inputs=[] self.outputs=[] def addOutput(self, name): self.outputs.append(name) def addInput(self, name): self.inputs.append(name) def getOutputList(self): return self.outputs def getInputList(self): return self.inputs def __repr__(self): return "Node::%s [%s] -> [%s]"%(self.Alg.name, ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList()))) class AlgNode(Node): """Node class that connects inputs and outputs to basic alg. properties """ def __init__(self, Alg, inputProp, outputProp): Node.__init__(self, Alg) self.outputProp=outputProp self.inputProp=inputProp def addDefaultOutput(self): if self.outputProp != '': self.addOutput(("%s_%s"%(self.Alg.getName(),self.outputProp))) def setPar(self, propname, value): cval = getProp( self.Alg, propname) if isinstance(cval, MutableSequence): cval.append(value) return setattr(self.Alg, propname, cval) else: return setattr(self.Alg, propname, value) def resetPar(self, prop): cval = getProp(self.Alg, prop) if isinstance(cval, MutableSequence): return setattr(self.Alg, prop, []) else: return setattr(self.Alg, prop, "") def getPar(self, prop): return getProp(self.Alg, prop) def resetOutput(self): self.resetPar(self.outputProp) def resetInput(self): self.resetPar(self.inputProp) def addOutput(self, name): outputs = self.readOutputList() if name in outputs: log.debug("Output DH not added in %s: %s already set!", self.Alg.getName(), name) else: if self.outputProp != '': self.setPar(self.outputProp,name) else: log.error("no OutputProp set") Node.addOutput(self, name) def readOutputList(self): outputs = [] cval = self.getPar(self.outputProp) if cval == '': return outputs if isinstance(cval, MutableSequence): outputs.extend(cval) else: outputs.append(cval) return outputs def addInput(self, name): inputs = self.readInputList() if name in inputs: log.debug("Input DH not added in %s: %s already set!", self.Alg.getName(), name) else: if self.inputProp != '': self.setPar(self.inputProp,name) else: log.error("no InputProp set") Node.addInput(self, name) def readInputList(self): inputs = [] cval = self.getPar(self.inputProp) if cval =='': return inputs if isinstance(cval, MutableSequence): inputs.extend(cval) else: inputs.append(cval) return inputs def __repr__(self): return "Alg::%s [%s] -> [%s]"%(self.Alg.getName(), ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList()))) def algColor(alg): """ Set given color to Alg type""" if isComboHypoAlg(alg): return "darkorange" if isHypoBase(alg): return "darkorchid1" if isInputMakerBase(alg): return "cyan3" if isFilterAlg(alg): return "chartreuse3" return "cadetblue1" class HypoToolConf(object): """ Class to group info on hypotools for ChainDict""" def __init__(self, hypoToolGen): self.hypoToolGen = hypoToolGen self.name=hypoToolGen.__name__ def setConf( self, chainDict): if type(chainDict) is not dict: raise RuntimeError("Configuring hypo with %s, not good anymore, use chainDict" % str(chainDict) ) self.chainDict = chainDict def create(self): """creates instance of the hypo tool""" return self.hypoToolGen( self.chainDict ) def confAndCreate(self, chainDict): """sets the configuration and creates instance of the hypo tool""" self.setConf(chainDict) return self.create() class HypoAlgNode(AlgNode): """Node for HypoAlgs""" initialOutput= 'StoreGateSvc+UNSPECIFIED_OUTPUT' def __init__(self, Alg): assert isHypoBase(Alg), "Error in creating HypoAlgNode from Alg " + compName(Alg) AlgNode.__init__(self, Alg, 'HypoInputDecisions', 'HypoOutputDecisions') self.tools = [] self.previous=[] def addOutput(self, name): outputs = self.readOutputList() if name in outputs: log.debug("Output DH not added in %s: %s already set!", self.name, name) elif self.initialOutput in outputs: AlgNode.addOutput(self, name) else: log.error("Hypo " + self.name +" has already %s as configured output: you may want to duplicate the Hypo!" + outputs[0]) def addHypoTool (self, hypoToolConf): log.debug(" Adding HypoTool %s to %s", hypoToolConf.chainDict['chainName'], compName(self.Alg)) if hypoToolConf.chainDict['chainName'] not in self.tools: ## HypoTools are private, so need to be created when added to the Alg ## this incantation may seem strange, however it is the only one which works ## trying tool = hypoToolConf.create() and then assignement does not work! will be no problem in run3 config tools = self.Alg.HypoTools self.Alg.HypoTools = tools+[hypoToolConf.create()] self.tools.append( self.Alg.HypoTools[-1].getName() ) # should not be needed anymore else: raise RuntimeError("The hypo tool of name "+ hypoToolConf.chainDict['chainName'] +" already present") def setPreviousDecision(self,prev): self.previous.append(prev) return self.addInput(prev) def resetDecisions(self): self.previous = [] self.resetOutput() self.resetInput() def __repr__(self): return "HypoAlg::%s [%s] -> [%s], previous = [%s], HypoTools=[%s]"%(compName(self.Alg),' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList())), ' '.join(map(str, self.previous)), ' '.join(map(str, self.tools))) class SequenceFilterNode(AlgNode): """Node for any kind of sequence filter""" def __init__(self, Alg, inputProp, outputProp): AlgNode.__init__(self, Alg, inputProp, outputProp) def addChain(self, name): return self.setPar("Chains", name) def getChains(self): return self.getPar("Chains") def __repr__(self): return "SequenceFilter::%s [%s] -> [%s], chains=%s"%(compName(self.Alg),' '.join(map(str, self.getInputList())),' '.join(map(str, self.getOutputList())), self.getChains()) class RoRSequenceFilterNode(SequenceFilterNode): def __init__(self, name): Alg= RoRSeqFilter(name) SequenceFilterNode.__init__(self, Alg, 'Input', 'Output') class InputMakerNode(AlgNode): def __init__(self, Alg): assert isInputMakerBase(Alg), "Error in creating InputMakerNode from Alg " + compName(Alg) AlgNode.__init__(self, Alg, 'InputMakerInputDecisions', 'InputMakerOutputDecisions') input_maker_output = CFNaming.inputMakerOutName(compName(self.Alg)) self.addOutput(input_maker_output) class ComboMaker(AlgNode): def __init__(self, name, multiplicity, comboHypoCfg): self.prop="MultiplicitiesMap" self.mult=list(multiplicity) self.comboHypoCfg = comboHypoCfg Alg = RecoFragmentsPool.retrieve( self.create, name ) log.debug("ComboMaker init: Alg %s", name) AlgNode.__init__(self, Alg, 'HypoInputDecisions', 'HypoOutputDecisions') def create (self, name): log.debug("ComboMaker.create %s",name) return self.comboHypoCfg(name) def addChain(self, chainDict): chainName = chainDict['chainName'] log.debug("ComboMaker %s adding chain %s", compName(self.Alg), chainName) allMultis = self.mult newdict = {chainName : allMultis} cval = self.Alg.getProperties()[self.prop] # check necessary to see if chain was added already? if type(cval) is dict: if chainName in cval.keys(): log.error("ERROR in cofiguration: ComboAlg %s has already been configured for chain %s", compName(self.Alg), chainName) else: cval[chainName]=allMultis else: cval=newdict setattr(self.Alg, self.prop, cval) def getChains(self): cval = self.Alg.getProperties()[self.prop] return cval.keys() def createComboHypoTools(self, chainDict, comboToolConfs): """Created the ComboHypoTools""" if not len(comboToolConfs): return confs = [ HypoToolConf( tool ) for tool in comboToolConfs ] log.debug("ComboMaker.createComboHypoTools for chain %s, Alg %s with %d tools", chainDict["chainName"],self.Alg.getName(), len(comboToolConfs)) for conf in confs: tools = self.Alg.ComboHypoTools self.Alg.ComboHypoTools = tools + [ conf.confAndCreate( chainDict ) ] ######################################################### # USEFULL TOOLS ######################################################### def isHypoBase(alg): if 'HypoInputDecisions' in alg.__class__.__dict__: return True prop = alg.__class__.__dict__.get('_properties') if type(prop) is dict: return ('HypoInputDecisions' in prop) else: return False def isInputMakerBase(alg): return ('InputMakerInputDecisions' in alg.__class__.__dict__) def isFilterAlg(alg): return isinstance(alg, RoRSeqFilter) def isComboHypoAlg(alg): return ('MultiplicitiesMap' in alg.__class__.__dict__) ########################################################## # Now sequences and chains ########################################################## class EmptyMenuSequence(object): """ Class to emulate reco sequences with no Hypo""" """ By construction it has no Hypo;""" def __init__(self, the_name): self._name = the_name Maker = CompFactory.InputMakerForRoI("IM"+the_name) Maker.RoITool = CompFactory.ViewCreatorInitialROITool() self._maker = InputMakerNode( Alg = Maker ) self._seed='' self._sequence = Node( Alg = seqAND(the_name, [Maker])) log.debug("Made EmptySequence %s",the_name) @property def sequence(self): return self._sequence @property def seed(self): return self._seed @property def name(self): return self._name def getOutputList(self): return self._maker.readOutputList() # Only one since it's merged def connectToFilter(self, outfilter): """ Connect filter to the InputMaker""" self._maker.addInput(outfilter) def createHypoTools(self, chainDict): log.debug("This sequence is empty. No Hypo to conficure") def addToSequencer(self, recoSeq_list, hypo_list): recoSeq_list.add(self.sequence.Alg) def buildDFDot(self, cfseq_algs, all_hypos, isCombo, last_step_hypo_nodes, file): cfseq_algs.append(self._maker) cfseq_algs.append(self.sequence ) file.write(" %s[fillcolor=%s]\n"%(self._maker.Alg.getName(), algColor(self._maker.Alg))) file.write(" %s[fillcolor=%s]\n"%(self.sequence.Alg.getName(), algColor(self.sequence.Alg))) return cfseq_algs, all_hypos, last_step_hypo_nodes def getTools(self): log.debug("No tools for empty menu sequences") def setSeed( self, seed ): self._seed = seed def __repr__(self): return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s\n"\ %(self.name, "Empty", self._maker.Alg.getName(), self.sequence.Alg.getName(), "None") class MenuSequence(object): """ Class to group reco sequences with the Hypo""" """ By construction it has one Hypo Only; behaviour changed to support muFastOvlpRmSequence() which has two, but this will change""" def __init__(self, Sequence, Maker, Hypo, HypoToolGen): assert compName(Maker).startswith("IM"), "The input maker {} name needs to start with letter: IM".format(compName(Maker)) self._sequence = Node( Alg=Sequence) self._maker = InputMakerNode( Alg = Maker ) self._seed='' input_maker_output= self.maker.readOutputList()[0] # only one since it's merged self._name = CFNaming.menuSequenceName(compName(Hypo)) self._hypoToolConf = HypoToolConf( HypoToolGen ) self._hypo = HypoAlgNode( Alg = Hypo ) hypo_output = CFNaming.hypoAlgOutName(compName(Hypo)) self._hypo.addOutput(hypo_output) self._hypo.setPreviousDecision( input_maker_output) log.debug("MenuSequence.connect: connecting InputMaker and HypoAlg, adding: \n\ InputMaker::%s.output=%s",\ compName(self.maker.Alg), input_maker_output) log.debug("HypoAlg::%s.HypoInputDecisions=%s, \n \ HypoAlg::%s.HypoOutputDecisions=%s",\ compName(self.hypo.Alg), self.hypo.readInputList()[0], compName(self.hypo.Alg), self.hypo.readOutputList()[0]) @property def seed(self): return self._seed @property def name(self): return self._name @property def sequence(self): return self._sequence @property def maker(self): return self._maker @property def hypo(self): return self._hypo def getOutputList(self): outputlist = [] outputlist.append(self._hypo.readOutputList()[0]) return outputlist def connectToFilter(self, outfilter): """ Connect filter to the InputMaker""" self._maker.addInput(outfilter) def connect(self, Hypo, HypoToolGen): """ Sets the input and output of the hypo, and links to the input maker """ input_maker_output= self._maker.readOutputList()[0] # only one since it's merged #### Add input/output Decision to Hypo self.name = CFNaming.menuSequenceName(compName(Hypo)) self.hypoToolConf = HypoToolConf( HypoToolGen ) self._hypo = HypoAlgNode( Alg = Hypo ) hypo_output = CFNaming.hypoAlgOutName(compName(Hypo)) self._hypo.addOutput(hypo_output) self._hypo.setPreviousDecision( input_maker_output) log.debug("MenuSequence.connect: connecting InputMaker and HypoAlg and OverlapRemoverAlg, adding: \n\ InputMaker::%s.output=%s",\ compName(self._maker.Alg), input_maker_output) log.debug("HypoAlg::%s.previousDecision=%s, \n HypoAlg::%s.output=%s",\ compName(self._hypo.Alg), input_maker_output, compName(self._hypo.Alg), self._hypo.readOutputList()[0]) def createHypoTools(self, chainDict): if type(self._hypoToolConf) is list: log.warning ("This sequence %s has %d multiple HypoTools ",self.sequence.name, len(self.hypoToolConf)) for hypo, hypoToolConf in zip(self._hypo, self._hypoToolConf): hypoToolConf.setConf( chainDict ) hypo.addHypoTool(self._hypoToolConf) else: self._hypoToolConf.setConf( chainDict ) self._hypo.addHypoTool(self._hypoToolConf) #this creates the HypoTools def addToSequencer(self, recoSeq_list, hypo_list): recoSeq_list.add(self.sequence.Alg) hypo_list.add(self._hypo.Alg) def buildDFDot(self, cfseq_algs, all_hypos, isCombo, last_step_hypo_nodes, file): cfseq_algs.append(self._maker) cfseq_algs.append(self.sequence ) file.write(" %s[fillcolor=%s]\n"%(self._maker.Alg.getName(), algColor(self._maker.Alg))) file.write(" %s[fillcolor=%s]\n"%(self.sequence.Alg.getName(), algColor(self.sequence.Alg))) cfseq_algs.append(self._hypo) file.write(" %s[color=%s]\n"%(self._hypo.Alg.getName(), algColor(self._hypo.Alg))) all_hypos.append(self._hypo) if not isCombo: last_step_hypo_nodes.append(self._hypo) return cfseq_algs, all_hypos, last_step_hypo_nodes def getTools(self): return self._hypo.tools def setSeed( self, seed ): self._seed = seed def __repr__(self): hyponame = self._hypo.Alg.getName() hypotool = self._hypoToolConf.name return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s\n"\ %(self.name, hyponame, self._maker.Alg.getName(), self.sequence.Alg.getName(), hypotool) class CAMenuSequence(MenuSequence): ''' MenuSequence with Compoment Accumulator ''' def __init__(self, Sequence, Maker, Hypo, HypoToolGen, CA): self.ca = CA MenuSequence.__init__(self, Sequence, Maker, Hypo, HypoToolGen) @property def sequence(self): makerAlg = self.ca.getEventAlgo(self._maker.Alg.name) self._maker.Alg = makerAlg return self._sequence @property def maker(self): makerAlg = self.ca.getEventAlgo(self._maker.Alg.name) self._maker.Alg = makerAlg return self._maker @property def hypo(self): hypoAlg = self.ca.getEventAlgo(self._hypo.Alg.name) self._hypo.Alg = hypoAlg return self._hypo class Chain(object): """Basic class to define the trigger menu """ __slots__ ='name','steps','nSteps','alignmentGroups','vseeds','L1decisions' def __init__(self, name, ChainSteps, L1Thresholds, nSteps = [], alignmentGroups = []): """ Construct the Chain from the steps Out of all arguments the ChainSteps & L1Thresholds are most relevant, the chain name is used in debug messages """ self.name = name self.steps=ChainSteps self.nSteps = nSteps self.alignmentGroups = alignmentGroups self.vseeds=L1Thresholds from L1Decoder.L1DecoderConfig import mapThresholdToL1DecisionCollection # L1decisions are used to set the seed type (EM, MU,JET), removing the actual threshold # in practice it is the L1Decoder Decision output self.L1decisions = [ mapThresholdToL1DecisionCollection(stri) for stri in L1Thresholds] log.debug("L1Decisions: %s", ' '.join(self.L1decisions)) self.setSeedsToSequences() isCombo=False #TO DO: check that all the steps are combo for step in self.steps: if step.isCombo: isCombo=True log.debug("Made %s Chain %s with seeds: %s ", "combo" if isCombo else "", name, self.L1decisions) def numberAllSteps(self): if len(self.steps)==0: return else: import re for stepID,step in enumerate(self.steps): step_name = step.name if re.search('^Step[0-9]_',step_name): step_name = step_name[6:] step.name = 'Step%d_'%(stepID+1)+step_name return def insertEmptySteps(self, chainDict, empty_step_name, n_new_steps, start_position): #start position indexed from 0. if start position is 3 and length is 2, it works like: # [old1,old2,old3,old4,old5,old6] ==> [old1,old2,old3,empty1,empty2,old4,old5,old6] import re if len(self.steps) == 0 : log.error("I can't insert empty steps because the chain doesn't have any steps yet!") if len(self.steps) < start_position : log.error("I can't insert empty steps at step %d because the chain doesn't have that many steps!", start_position) chain_steps_pre_split = self.steps[:start_position] chain_steps_post_split = self.steps[start_position:] next_step_name = '' prev_step_name = '' if start_position == 0: next_step_name = chain_steps_post_split[0].name if re.search('^Step[0-9]_',next_step_name): next_step_name = next_step_name[6:] prev_step_name = 'empty_'+str(len(self.L1decisions))+'L1in' else: if len(chain_steps_post_split) == 0: log.error("Adding empty steps to the end of a chain - why would you do this?") else: prev_step_name = chain_steps_pre_split[-1].name next_step_name = chain_steps_post_split[0].name steps_to_add = [] for stepID in range(1,n_new_steps+1): new_step_name = prev_step_name+'_'+empty_step_name+'%d_'%stepID+next_step_name log.debug("Configuring empty step " + new_step_name) steps_to_add += [ChainStep(new_step_name, [], [], [chainDict], comboHypoCfg=ComboHypoCfg)] self.steps = chain_steps_pre_split + steps_to_add + chain_steps_post_split return def checkMultiplicity(self): if len(self.steps) == 0: return 0 mult=[sum(step.multiplicity) for step in self.steps] # on mult per step not_empty_mult = [m for m in mult if m!=0] if len(not_empty_mult) == 0: #empty chain? log.error("checkMultiplicity: Chain %s has all steps with multiplicity =0: what to do?", self.name) return 0 if not_empty_mult.count(not_empty_mult[0]) != len(not_empty_mult): log.error("checkMultiplicity: Chain %s has steps with differnt multiplicities: %s", self.name, ' '.join(mult)) return 0 if not_empty_mult[0] != len(self.vseeds): log.error("checkMultiplicity: Chain %s has %d multiplicity per step, and %d L1Decisions", self.name, mult, len(self.vseeds)) return 0 return not_empty_mult[0] def setSeedsToSequences(self): """ Set the L1 seeds (L1Decisions) to the menu sequences """ if len(self.steps) == 0: return # TODO: check if the number of seeds is sufficient for all the seuqences, no action of no steps are configured for step in self.steps: for seed, seq in zip(self.L1decisions, step.sequences): seq.setSeed( seed ) log.debug( "setSeedsToSequences: Chain %s adding seed %s to sequence in step %s", self.name, seed, step.name ) def getChainLegs(self): """ This is extrapolating the chain legs from the chain dictionary""" from TriggerMenuMT.HLTMenuConfig.Menu.ChainDictTools import splitChainInDict listOfChainDictsLegs = splitChainInDict(self.name) legs = [part['chainName'] for part in listOfChainDictsLegs] return legs def createHypoTools(self): """ This is extrapolating the hypotool configuration from the chain name""" log.debug("createHypoTools for chain %s", self.name) from TriggerMenuMT.HLTMenuConfig.Menu.ChainDictTools import splitChainInDict # this spliting is only needed for chains which don't yet attach # the dictionaries to the chain steps. It should be removed # later once that migration is done. listOfChainDictsLegs = splitChainInDict(self.name) for step in self.steps: log.debug("createHypoTools for Step %s", step.name) if len(step.sequences) == 0: continue if sum(step.multiplicity) >1 and not step.isCombo: log.error("This should be an error, because step mult > 1 (%d), but step is not combo", sum(step.multiplicity)) if len(step.chainDicts) > 0: # new way to configure hypo tools, works if the chain dictionaries have been attached to the steps log.debug('%s in new hypo tool creation method, step mult= %d, isCombo=%d', self.name, sum(step.multiplicity), step.isCombo) log.debug("N(seq)=%d, N(chainDicts)=%d", len(step.sequences), len(step.chainDicts)) assert len(step.sequences)==len(step.chainDicts), "createHypoTools only makes sense if number of sequences == number of chain dicts" for seq, onePartChainDict in zip(step.sequences, step.chainDicts): log.debug(' seq: %s, onePartChainDict:', seq.name) log.debug(' ' + str(onePartChainDict)) seq.createHypoTools( onePartChainDict ) else: # legacy way, to be removed once all signatures pass the chainDicts to the steps step_mult = [str(m) for m in step.multiplicity] log.debug('%s in old hypo tool creation method', self.name) menu_mult = [ part['chainParts'][0]['multiplicity'] for part in listOfChainDictsLegs ] if step_mult != menu_mult: # Probably this shouldn't happen, but it currently does log.warning("Got multiplicty %s from chain parts, but have %s multiplicity in this step. This is expected only for jet chains, but it has happened for %s, using the first chain dict", menu_mult, step.multiplicity, self.name) firstChainDict = listOfChainDictsLegs[0] firstChainDict['chainName']= self.name # rename the chaindict to remove the leg name for seq in step.sequences: seq.createHypoTools( firstChainDict ) else: # add one hypotool per sequence and chain part for seq, onePartChainDict in zip(step.sequences, listOfChainDictsLegs): seq.createHypoTools( onePartChainDict ) step.createComboHypoTools(self.name) def __repr__(self): return "-*- Chain %s -*- \n + Seeds: %s \n + Steps: \n %s \n"%(\ self.name, ' '.join(map(str, self.L1decisions)), '\n '.join(map(str, self.steps))) class CFSequence(object): """Class to describe the flow of decisions through ChainStep + filter with their connections (input, output) A Filter can have more than one input/output if used in different chains, so this class stores and manages all of them (when doing the connect) """ def __init__(self, ChainStep, FilterAlg): self.filter = FilterAlg self.step = ChainStep if self.step.isCombo: self.connectCombo() self.setDecisions() log.debug("CFSequence.__init: created %s ",self) def setDecisions(self): """ Set the output decision of this CFSequence as the hypo outputdecision; In case of combo, takes the Combo outputs""" self.decisions=[] # empty steps: if not len(self.step.sequences): self.decisions.extend(self.filter.getOutputList()) else: if self.step.isCombo: self.decisions.extend(self.step.combo.getOutputList()) else: for sequence in self.step.sequences: sequence_outputs=sequence.getOutputList() for output in sequence_outputs: self.decisions.append(output) log.debug("CFSequence: set out decisions: %s", self.decisions) def connect(self, connections): """Connect filter to ChainStep (and all its sequences) through these connections (which are sets of filter outputs) if a ChainStep contains the same sequence multiple times (for multi-object chains), the filter is connected only once (to avoid multiple DH links) """ log.debug("CFSequence: connect Filter %s with %d menuSequences of step %s, using %d connections", compName(self.filter.Alg), len(self.step.sequences), self.step.name, len(connections)) if len(connections) == 0: log.error("ERROR, no filter outputs are set!") if len(self.step.sequences): # check whether the number of filter outputs are the same as the number of sequences in the step if len(connections) != len(self.step.sequences): log.error("Found %d connections and %d MenuSequences in Step %s", len(connections), len(self.step.sequences), self.step.name) nseq=0 for seq in self.step.sequences: filter_out = connections[nseq] log.debug("CFSequence: Found input %s to sequence::%s from Filter::%s (from seed %s)", filter_out, seq.name, compName(self.filter.Alg), seq.seed) seq.connectToFilter( filter_out ) nseq+=1 else: log.debug("This CFSequence has no sequences: outputs are the Filter outputs, which are %d", len(self.decisions)) def connectCombo(self): """ connect Combo to Hypos""" for seq in self.step.sequences: if type(seq.getOutputList()) is list: combo_input=seq.getOutputList()[-1] # last one? else: combo_input=seq.getOutputList()[0] self.step.combo.addInput(combo_input) log.debug("CFSequence.connectCombo: adding input to %s: %s", self.step.combo.Alg.getName(), combo_input) # inputs are the output decisions of the hypos of the sequences combo_output=CFNaming.comboHypoOutputName (self.step.combo.Alg.getName(), combo_input) self.step.combo.addOutput(combo_output) log.debug("CFSequence.connectCombo: adding output to %s: %s", self.step.combo.Alg.getName(), combo_output) def __repr__(self): return "--- CFSequence ---\n + Filter: %s \n + decisions: %s\n + %s \n"%(\ self.filter.Alg.getName(), self.decisions, self.step) class StepComponent(object): """ Class to build hte ChainStep, for including empty sequences""" def __init__(self, sequence, multiplicity,empty): self.sequence=sequence self.multiplicity=multiplicity self.empty=empty class ChainStep(object): """Class to describe one step of a chain; if multiplicity is greater than 1, the step is combo/combined. Set one multiplicity value per sequence""" def __init__(self, name, Sequences=[], multiplicity=[1], chainDicts=[], comboHypoCfg=ComboHypoCfg, comboToolConfs=[]): # include cases of emtpy steps with multiplicity = [] or multiplicity=[0,0,0///] if sum(multiplicity)==0: multiplicity=[] # sanity check on inputs if len(Sequences) != len(multiplicity): raise RuntimeError("Tried to configure a ChainStep %s with %i Sequences and %i multiplicities. These lists must have the same size" % (name, len(Sequences), len(multiplicity)) ) self.name = name self.sequences=Sequences self.multiplicity = multiplicity self.comboHypoCfg=comboHypoCfg self.comboToolConfs=comboToolConfs self.isCombo=sum(multiplicity)>1 self.combo=None self.chainDicts = chainDicts if self.isCombo: self.makeCombo() def addComboHypoTools(self, tools): self.comboToolConfs.append(tools) def makeCombo(self): if len(self.sequences)==0: return hashableMult = tuple(self.multiplicity) self.combo = RecoFragmentsPool.retrieve(createComboAlg, None, name=CFNaming.comboHypoName(self.name), multiplicity=hashableMult, comboHypoCfg=self.comboHypoCfg) def createComboHypoTools(self, chainName): if self.isCombo: from TriggerMenuMT.HLTMenuConfig.Menu.TriggerConfigHLT import TriggerConfigHLT chainDict = TriggerConfigHLT.getChainDictFromChainName(chainName) self.combo.createComboHypoTools(chainDict, self.comboToolConfs) def getChainNames(self): if not self.isCombo: return [seq.getTools() for seq in self.sequences] else: return list(self.combo.getChains()) def __repr__(self): if len(self.sequences) == 0: return "--- ChainStep %s ---\n is Empty, ChainDict = %s "%(self.name, ' '.join(map(str, [dic['chainName'] for dic in self.chainDicts])) ) if not self.isCombo: return "--- ChainStep %s ---\n , multiplicity = %d ChainDict = %s \n + MenuSequences = %s "%(self.name, sum(self.multiplicity), ' '.join(map(str, [dic['chainName'] for dic in self.chainDicts])), ' '.join(map(str, [seq.name for seq in self.sequences]) )) else: if self.combo: calg = self.combo.Alg.name() else: calg = 'NONE' return "--- ChainStep %s ---\n + isCombo, multiplicity = %d ChainDict = %s \n + MenuSequences = %s \n + ComboHypo = %s, ComboHypoTools = %s" %\ (self.name, sum(self.multiplicity), ' '.join(map(str, [dic['chainName'] for dic in self.chainDicts])), ' '.join(map(str, [seq.name for seq in self.sequences]) ), calg, ' '.join(map(str, [tool.__name__ for tool in self.comboToolConfs]))) def createComboAlg(dummyFlags, name, multiplicity, comboHypoCfg): return ComboMaker(name, multiplicity, comboHypoCfg) # this is fragment for New JO from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator class InEventReco( ComponentAccumulator ): """ Class to handle in-event reco """ def __init__(self, name, inputMaker=None): super( InEventReco, self ).__init__() self.name = name self.mainSeq = seqAND( name ) self.addSequence( self.mainSeq ) # Details below to be checked self.inputMakerAlg = inputMaker # Avoid registering a duplicate self.addEventAlgo( self.inputMakerAlg, self.mainSeq.name ) self.recoSeq = parOR( "InputSeq_"+self.inputMakerAlg.name ) self.addSequence( self.recoSeq, self.mainSeq.name ) pass def mergeReco( self, ca ): """ Merged CA movnig reconstruction algorithms into the right sequence """ return self.merge( ca, sequenceName=self.recoSeq.getName() ) def addHypoAlg(self, alg): self.addEventAlgo( alg, self.mainSeq.name ) def sequence( self ): return self.mainSeq def inputMaker( self ): return self.inputMakerAlg class InViewReco( ComponentAccumulator ): """ 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 """ def __init__(self, name, viewMaker=None, roisKey=None): super( InViewReco, self ).__init__() self.name = name self.mainSeq = seqAND( name ) self.addSequence( self.mainSeq ) ViewCreatorInitialROITool=CompFactory.ViewCreatorInitialROITool if viewMaker: self.viewMakerAlg = viewMaker else: self.viewMakerAlg = CompFactory.EventViewCreatorAlgorithm("IM"+name, ViewFallThrough = True, RoIsLink = 'initialRoI', RoITool = ViewCreatorInitialROITool(), InViewRoIs = roisKey if roisKey else name+'RoIs', Views = name+'Views', ViewNodeName = name+"InView") self.addEventAlgo( self.viewMakerAlg, self.mainSeq.name ) self.viewsSeq = parOR( self.viewMakerAlg.ViewNodeName ) self.addSequence( self.viewsSeq, self.mainSeq.name ) def addInputFromFilter(self, filterAlg ): assert len(filterAlg.Output) == 1, "Can only oprate on filter algs with one configured output, use addInput to setup specific inputs" self.addInput( filterAlg.Output[0], "Reco_"+( filterAlg.Output[0].replace("Filtered_", "") ) ) def addInput(self, inKey, outKey ): """Adds input (DecisionsContainer) from which the views should be created """ self.viewMakerAlg.InputMakerInputDecisions += [ inKey ] self.viewMakerAlg.InputMakerOutputDecisions = outKey def mergeReco( self, ca ): """ Merged CA movnig reconstruction algorithms into the right sequence """ return self.merge( ca, sequenceName=self.viewsSeq.getName() ) def addHypoAlg(self, alg): self.addEventAlgo( alg, self.mainSeq.name ) def sequence( self ): return self.mainSeq def inputMaker( self ): return self.viewMakerAlg class RecoFragmentsPool(object): """ Class to host all the reco fragments that need to be reused """ fragments = {} @classmethod def retrieve( cls, creator, flags, **kwargs ): """ create, or return created earlier reco fragment Reco fragment is uniquelly identified by the function and set og **kwargs. The flags are not part of unique identifier as creation of new reco fragments should not be caused by difference in the unrelated flags. TODO, if that code survives migration to New JO we need to handle the case when the creator is an inner function """ requestHash = hash( ( creator, tuple(kwargs.keys()), tuple(kwargs.values()) ) ) if requestHash not in cls.fragments: recoFragment = creator( flags, **kwargs ) cls.fragments[requestHash] = recoFragment return recoFragment else: return cls.fragments[requestHash] def getChainStepName(chainName, stepNumber): return '{}_step{}'.format(chainName, stepNumber) def createStepView(stepName): stepReco = parOR(CFNaming.stepRecoName(stepName)) stepView = seqAND(CFNaming.stepViewName(stepName), [stepReco]) return stepReco, stepView