Skip to content
Snippets Groups Projects
Forked from atlas / athena
102901 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
MenuComponents.py 29.75 KiB

# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration

import copy
from AthenaCommon.Logging import logging
log = logging.getLogger('MenuComponents')

from DecisionHandling.DecisionHandlingConf import RoRSeqFilter
from TriggerMenuMT.HLTMenuConfig.Menu.MenuComponentsNaming import CFNaming
from AthenaCommon.CFElements import parOR, seqAND


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.name(),self.outputProp)))

    def setPar(self, prop, name):
        cval = self.Alg.getProperties()[prop]
        if type(cval) is list:
            cval.append(name)
            return setattr(self.Alg, prop, cval)
        else:
            return setattr(self.Alg, prop, name)

    def resetPar(self, prop):
        cval = self.Alg.getProperties()[prop]
        if type(cval) is list:
            return setattr(self.Alg, prop, [])
        else:
            return setattr(self.Alg, prop, "")

    def getPar(self, prop):
        if hasattr(self.Alg, prop):
            return getattr(self.Alg, prop)
        else:
            return self.Alg.getDefaultProperty(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("Warning, output %s already set in %s, DH not added",name, self.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 type(cval) is list:
            outputs.extend(cval)
        else:
            outputs.append(cval)
        return outputs

    def addInput(self, name):
        inputs = self.readInputList()
        if name in inputs:
            log.debug("Warning, input %s already set in %s, DH not added",name, self.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 type(cval) is list:
            inputs.extend(cval)
        else:
            inputs.append(cval)
        return inputs

    def __repr__(self):
        return "Alg::%s  [%s] -> [%s]"%(self.Alg.name(), ' '.join(map(str, self.getInputList())), ' '.join(map(str, self.getOutputList())))


class HypoToolConf(object):
    """ Class to group info on hypotools"""
    def __init__(self, hypoToolGen):
        self.hypoToolGen = hypoToolGen
        self.name=hypoToolGen.__name__ if hypoToolGen else "None"
        self.conf=''

    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 setName(self, name):
    #     self.name=name

    # def setConf(self, conf):
    #     self.conf=conf

    def create(self):
        """creates instance of the hypo tool"""
        return self.hypoToolGen( self.chainDict )


class HypoAlgNode(AlgNode):
    """Node for HypoAlgs"""
    def __init__(self, Alg):
        assert isHypoBase(Alg), "Error in creating HypoAlgNode from Alg "  + Alg.name()
        AlgNode.__init__(self, Alg, 'HypoInputDecisions', 'HypoOutputDecisions')
        self.tools = []
        self.previous=[]

    def addHypoTool (self, hypoToolConf):
        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]"%(self.Alg.name(),' '.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 setChains(self, name):
        return self.setPar("Chains", name)

    def getChains(self):
        return self.getPar("Chains")

    def __repr__(self):
        return "SequenceFilter::%s  [%s] -> [%s], chains=%s"%(self.Alg.name(),' '.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 "  + Alg.name()
        AlgNode.__init__(self,  Alg, 'InputMakerInputDecisions', 'InputMakerOutputDecisions')


from DecisionHandling.DecisionHandlingConf import ComboHypo
class ComboMaker(AlgNode):
    def __init__(self, name):
        Alg = ComboHypo(name)
        log.debug("Making combo Alg %s", name)
        AlgNode.__init__(self,  Alg, 'HypoInputDecisions', 'HypoOutputDecisions')
        self.prop="MultiplicitiesMap"


    def addChain(self, chainDict):
        chainName = chainDict['chainName']
        log.debug("ComboMaker %s adding chain %s", self.Alg.name(),chainName)
        from TriggerMenuMT.HLTMenuConfig.Menu.DictFromChainName import getChainMultFromDict
        allMultis = [int(x) for x in getChainMultFromDict( chainDict )]

        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", self.Alg.name(), chainName)
            else:
                cval[chainName]=allMultis
        else:
            cval=newdict

        setattr(self.Alg, self.prop, cval)
        log.debug("ComboAlg %s has now these chains chain %s", self.Alg.name(), self.getPar(self.prop))


    def getChains(self):
        cval = self.Alg.getProperties()[self.prop]
        return cval



#########################################################
# USEFULL TOOLS
#########################################################

def isHypoBase(alg):
    if  'HypoInputDecisions'  in alg.__class__.__dict__:
        return True
    prop = alg.__class__.__dict__.get('_properties')
    return  ('HypoInputDecisions'  in prop)

def isInputMakerBase(alg):
    return  ('InputMakerInputDecisions'  in alg.__class__.__dict__)

def isFilterAlg(alg):
    return isinstance(alg, RoRSeqFilter)



##########################################################
# NOW sequences and chains
##########################################################

class MenuSequence(object):
    """ Class to group reco sequences with the Hypo"""
    def __init__(self, Sequence, Maker,  Hypo, HypoToolGen, CA=None ):
        assert Maker.name().startswith("IM"), "The input maker {} name needs to start with letter: IM".format(Maker.name())
        self.sequence     = Node( Alg=Sequence)
        self._maker       = InputMakerNode( Alg = Maker )
        self.inputs=[]
        self.outputs=[]
        self.seed=''
        self.reuse = False # flag to draw dot diagrmas
        self.ca = CA

        if type(Hypo) is list:
           self.name=[]
           self.hypoToolConf=[]
           self._hypo=[]
           for hp, hptool in zip(Hypo, HypoToolGen):
              self.name.append( CFNaming.menuSequenceName(hp.name()) )
              self.hypoToolConf.append( HypoToolConf( hptool ) )
              self._hypo.append( HypoAlgNode( Alg = hp ) )
        else:
           self.name = CFNaming.menuSequenceName(Hypo.name())
           self.hypoToolConf = HypoToolConf( HypoToolGen )
           self._hypo = HypoAlgNode( Alg = Hypo )



    def replaceHypoForCombo(self, HypoAlg):
        if type(HypoAlg) is list and type(self.name) is list:
           self._hypo=[]
           for hp, Name in zip(HypoAlg, self.name):
              log.debug("set new Hypo %s for combo sequence %s ", hp.name(), Name)
              self._hypo.append( HypoAlgNode( Alg=hp ) )
        else:
           log.debug("set new Hypo %s for combo sequence %s ", HypoAlg.name(), self.name)
           self._hypo = HypoAlgNode( Alg=HypoAlg )

    def replaceHypoForDuplication(self, HypoAlg):
        if type(HypoAlg) is list:
           self._hypo = []
           for hp, Name in zip(HypoAlg, self.name):
              log.debug("set new Hypo %s for duplicated sequence %s, resetting decisions ", hp.name(), Name)
              self._hypo.append( HypoAlgNode( Alg=hp ) )
        else:
           log.debug("set new Hypo %s for duplicated sequence %s, resetting decisions ", HypoAlg.name(), self.name)
           self._hypo = HypoAlgNode( Alg=HypoAlg )
        # do we need this?
        #       self._hypo.resetInput()
        #       self._hypo.resetOutput()

    def resetConnections(self):
        self.inputs=[]
        self.outputs=[]

    @property
    def maker(self):
        if self.ca is not None:
            makerAlg = self.ca.getEventAlgo(self._maker.Alg.name())
            self._maker.Alg = makerAlg
            # return InputMakerNode(Alg=makerAlg)
        return self._maker

    @property
    def hypo(self):
        if self.ca is not None:
            hypoAlg = self.ca.getEventAlgo(self._hypo.Alg.name())
            self._hypo.Alg = hypoAlg
            # return HypoAlgNode(Alg=hypoAlg)
        return self._hypo

    def connectToFilter(self, outfilter):
        """ Sets the input and output of the hypo, and links to the input maker """

        #### Connect filter to the InputMaker
        self.maker.addInput(outfilter)
        input_maker_output = CFNaming.inputMakerOutName(self.maker.Alg.name(),outfilter)
        self.maker.addOutput(input_maker_output)

        #### Add input/output Decision to Hypo
        if type(self._hypo) is list:
           hypo_input_total=[]
           hypo_output_total=[]
           hypo_input = input_maker_output
           for hp in self._hypo:
              hypo_input_total.append(hypo_input)
              hp.setPreviousDecision(hypo_input)
              hypo_output = CFNaming.hypoAlgOutName(hp.Alg.name(), hypo_input)
              if len(hp.getOutputList()):
                  log.error("Hypo " + hp.name() +" has already an output configured: you may want to duplicate the Hypo!")
              hp.addOutput(hypo_output)
              hypo_output_total.append(hypo_output)
              hypo_input = hypo_output
        else:
           self.hypo.setPreviousDecision( input_maker_output)
           hypo_output = CFNaming.hypoAlgOutName(self.hypo.Alg.name(), input_maker_output)
           if len(self.hypo.getOutputList()):
               log.error("Hypo " + self.hypo.name() +" has already an output configured: you may want to duplicate the Hypo!")
           self.hypo.addOutput(hypo_output)

        # needed for drawing
        self.inputs.append(outfilter)
        self.outputs.append(hypo_output)

        log.debug("MenuSequence.connectToFilter: connecting InputMaker and HypoAlg and OverlapRemoverAlg, adding: \n\
        InputMaker::%s.output=%s",\
                        self.maker.Alg.name(), input_maker_output)
        if type(self._hypo) is list:
           for hp, hp_in, hp_out in zip( self._hypo, hypo_input_total, hypo_output_total):
              log.debug("HypoAlg::%s.previousDecision=%s, \n\
                         HypoAlg::%s.output=%s",\
                              hp.Alg.name(), hp_in, hp.Alg.name(), hp_out)
        else:
           log.debug("HypoAlg::%s.previousDecision=%s, \n\
                      HypoAlg::%s.output=%s",\
                           self.hypo.Alg.name(), input_maker_output, self.hypo.Alg.name(), hypo_output)


    def __repr__(self):
        if type(self._hypo) is list:
           hyponame=[]
           hypotool=[]
           for hp, hptool in zip(self._hypo, self.hypoToolConf):
              hyponame.append( hp.Alg.name() )
              hypotool.append( hptool.name )
           return "MenuSequence::%s \n Hypo::%s \n Maker::%s \n Sequence::%s \n HypoTool::%s"\
           %(self.name, hyponame, self.maker.Alg.name(), self.sequence.Alg.name(), hypotool)
        else:
           hyponame = self._hypo.Alg.name()
           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.name(), self.sequence.Alg.name(), hypotool)




#################################################

#from TriggerMenuMT.HLTMenuConfig.Menu.DictFromChainName import getAllThresholdsFromItem, getUniqueThresholdsFromItem

class Chain(object):
    """Basic class to define the trigger menu """
    __slots__='name','steps','vseeds','group_seed'
    def __init__(self, name, ChainSteps, L1Thresholds):
        """
        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.vseeds=L1Thresholds


        from L1Decoder.L1DecoderConfig import mapThresholdToL1DecisionCollection
        # group_seed is used to set the seed type (EM, MU,JET), removing the actual threshold
        # in practice it is the L1Decoder Decision output
        self.group_seed = [ mapThresholdToL1DecisionCollection(stri) for stri in self.vseeds]
        self.setSeedsToSequences() # save seed of each menuseq

        isCombo=False
        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.vseeds)

    def setSeedsToSequences(self):
        """ Set the L1 seeds to the menu sequences """
        
        # check if the number of seeds is enought for all the seuqences:
        max_seq= max(len(step.sequences) for step in self.steps)
        tot_seed=len(self.vseeds)
        if max_seq==tot_seed:
            for step in self.steps:
                if len(step.sequences) == 0:
                    continue
                if len(step.sequences) != tot_seed:
                    log.warning ("setSeedsToSequences: Chain %s, step %s has %d sequences but %d seeds ", self.name, step.name, len(step.sequences), tot_seed)
                    continue
                nseq=0
                for seed in self.vseeds:
                    seq=step.sequences[nseq]
                    seq.seed ="L1"+filter(lambda x: x.isalpha(), seed)
                    log.debug( "setSeedsToSequences: Chain %s adding seed %s to sequence %d in step %s", self.name, seq.seed, nseq, step.name )
                    nseq+=1
        else:
            log.error("setSeedsToSequences: found %d sequences in the chain %s and %d seeds %s. What to do??", max_seq, self.name, tot_seed, str(self.vseeds))



    def decodeHypoToolConfs(self, allChainDicts):
        """ This is extrapolating the hypotool configuration from the (combined) chain name"""
        import copy
        from TriggerMenuMT.HLTMenuConfig.Menu.TriggerConfigHLT import TriggerConfigHLT
        chainDict = TriggerConfigHLT.getChainDictFromChainName(self.name)

        for step in self.steps:
            if len(step.sequences) == 0:
                continue

            if len(chainDict['chainParts']) != len(step.sequences):
                log.error("Error in step %s for chain %s: found %d chain parts and %d sequences", step.name, self.name, len(chainDict['chainParts']), len(step.sequences))
                log.error(chainDict['chainParts'])

            for seq, chainDictPart in zip(step.sequences, chainDict['chainParts']):
                if seq.ca is not None: # The CA merging took care of everything
                    continue

                onePartChainDict = copy.deepcopy( chainDict )
                onePartChainDict['chainParts'] = [ chainDictPart ]

                if type(seq.hypoToolConf) is list:
                   for hp, hptoolConf in zip( seq.hypo, seq.hypoToolConf ):
                      hptoolConf.setConf( onePartChainDict )
                      hp.addHypoTool(hptoolConf) #this creates tge HypoTools
                else:
                   seq.hypoToolConf.setConf( onePartChainDict )
                   seq.hypo.addHypoTool(seq.hypoToolConf) #this creates the HypoTools


    def __repr__(self):
        return "--- Chain %s --- \n + Seeds: %s \n + Steps: \n %s \n"%(\
                    self.name, ' '.join(map(str, self.vseeds)), '\n '.join(map(str, self.steps)))



class CFSequence(object):
    """Class to describe the ChainStep + filter
    One Filter can be included in many CF sequences, so the CF sequence is identified by the step name and the connections
    """
    def __init__(self, ChainStep, FilterAlg, connections):

        self.filter = FilterAlg
        self.step = ChainStep
        self.connections=connections
        log.debug("CFSequence: creating new one with Filter %s, step %s and %d connections: %s",self.filter.Alg.name(), self.step.name, len(self.connections), self.connections)
        self.connect()
        self.setDecisions()

    def setDecisions(self):
        self.decisions=[]
        for sequence in self.step.sequences:
            self.decisions.extend(sequence.outputs)
        if not len(self.step.sequences):
            self.decisions.extend(self.filter.getOutputList())


    def connect(self):
        """Connect filter to ChainStep (all its sequences)
        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", self.filter.Alg.name(), len(self.step.sequences), self.step.name)
        if len(self.connections) == 0:
            log.error("ERROR, no filter outputs are set!")
            #raise("CFSequence: Invalid Filter Configuration")

        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(self.connections) != len(self.step.sequences):
                log.error("Found %d filter outputs and %d MenuSequences in Step %s", len(self.filter.getOutputList()), len(self.step.sequences), self.step.name)
                #raise("CFSequence: Invalid Filter Configuration")
            nseq=0
            for seq in self.step.sequences:
                filter_out = self.connections[nseq]
                log.debug("CFSequence: Found input %s to sequence::%s from Filter::%s (from seed %s)", filter_out, seq.name, self.filter.Alg.name(), seq.seed)
                seq.connectToFilter( filter_out )
                nseq+=1

            if self.step.isCombo:
                self.connectCombo()

        else:
          log.debug("This CFSequence has no sequences: outputs are the Filter outputs")




    def connectCombo(self):
        """ reset sequence outputs, they will be replaced by new combo outputs"""

        for seq in self.step.sequences:
            seq.outputs=[]

        for seq in self.step.sequences:
            if type(seq.hypo) is list:
               combo_input=seq.hypo[1].getOutputList()[0]
            else:
               combo_input=seq.hypo.getOutputList()[0]
            self.step.combo.addInput(combo_input)
            log.debug("Adding inputs %s to combo %s", combo_input, self.step.combo.Alg.name())
            # inputs are the output decisions of the hypos of the sequences
            # outputs are the modified name of input deciions that need to be connected to the next filter
            combo_output=CFNaming.comboHypoOutputName (combo_input)
            self.step.combo.addOutput(combo_output)
            seq.outputs.append(combo_output)
            log.debug("CFSequence: connectCombo: Adding outputs %s to combo %s", combo_output, self.step.combo.Alg.name())


    def __repr__(self):
        return "--- CFSequence ---\n + Filter: %s \n +  %s \n + decisions: %s\n"%(\
                    self.filter.Alg.name(), self.step, self.decisions)



class ChainStep(object):
    """Class to describe one step of a chain; if multiplicity is greater than 1, the step is combo/combined"""
    def __init__(self, name,  Sequences=[], multiplicity=1):
        self.name = name
        self.sequences=[]
        self.multiplicity = multiplicity
        self.isCombo=multiplicity>1
        self.combo=None
        if self.isCombo:
            self.makeCombo(Sequences)
        else:
            self.sequences = Sequences


    def makeCombo(self, Sequences):
        if len(Sequences)==0:
            return

        # For combo sequences, duplicate the sequence, the Hypo with differnt names and create the ComboHypoAlg
        self.combo = ComboMaker(CFNaming.comboHypoName(self.name))
        duplicatedHypos = []
        for sequence in Sequences:
            if type(sequence.hypo) is list:
               new_hypoAlg = []
               NewHypoAlgName = []
               for hp in sequence.hypo:
                  oldhypo=hp.Alg
                  duplicatedHypos.append(oldhypo.name())
                  ncopy=duplicatedHypos.count(oldhypo.name())

                  new_sequence=copy.deepcopy(sequence)
                  new_sequence.name = CFNaming.comboSequenceCopyName(sequence.name,ncopy, self.name)

                  newHypoAlgName = CFNaming.comboHypoCopyName(oldhypo.name(),ncopy, self.name)
                  new_hypoAlg.append( oldhypo.clone(newHypoAlgName) ) # need to reset decisions?
                  NewHypoAlgName.append( newHypoAlgName )
               new_sequence.replaceHypoForCombo(new_hypoAlg)
               self.sequences.append(new_sequence)

            else:
               oldhypo=sequence.hypo.Alg
               duplicatedHypos.append(oldhypo.name())
               ncopy=duplicatedHypos.count(oldhypo.name())

               new_sequence=copy.deepcopy(sequence)
               new_sequence.name = CFNaming.comboSequenceCopyName(sequence.name,ncopy, self.name)

               newHypoAlgName = CFNaming.comboHypoCopyName(oldhypo.name(),ncopy, self.name)
               new_hypoAlg=oldhypo.clone(newHypoAlgName) # need to reset decisions?
               new_sequence.replaceHypoForCombo(new_hypoAlg)
               self.sequences.append(new_sequence)

    def __repr__(self):
        return "--- ChainStep %s ---\n + isCombo: %d, multiplicity= %d \n +  %s \n "%(self.name, self.isCombo,self.multiplicity, ' '.join(map(str, self.sequences) ))




# 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
        from AthenaCommon.CFElements import parOR, seqAND
        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 addRecoAlg( self, alg ):
        """Reconstruction alg to be run per event"""
        log.warning( "InViewReco.addRecoAlgo: consider using mergeReco that takes care of the CA accumulation and moving algorithms" )
        self.addEventAlgo( alg, self.recoSeq.name() )

    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):
        super( InViewReco, self ).__init__()
        self.name = name
        from AthenaCommon.CFElements import parOR, seqAND
        self.mainSeq = seqAND( name )
        self.addSequence( self.mainSeq )

        from ViewAlgs.ViewAlgsConf import EventViewCreatorAlgorithm
        if viewMaker:
            self.viewMakerAlg = viewMaker
        else:
            self.viewMakerAlg = EventViewCreatorAlgorithm("IM"+name,
                                                          ViewFallThrough = True,
                                                          RoIsLink        = 'initialRoI', # -||-
                                                          InViewRoIs      = 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 addRecoAlg( self, alg ):
        """Reconstruction alg to be run per view"""
        log.warning( "InViewReco.addRecoAlgo: consider using mergeReco that takes care of the CA accumulation and moving algorithms" )
        self.addEventAlgo( alg, self.viewsSeq.name() )

    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
            log.debug( "created reconstruction fragment using function: %s", creator.func_name )
            return recoFragment
        else:
            log.debug( "reconstruction fragment that would be created from %s is taken from the cache", creator.func_name )
            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