JetChainConfiguration.py 17.8 KB
Newer Older
1
# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
Catrin Bernius's avatar
Catrin Bernius committed
2
3
4

from AthenaCommon.Logging import logging
logging.getLogger().info("Importing %s",__name__)
5
log = logging.getLogger(__name__)
Catrin Bernius's avatar
Catrin Bernius committed
6
7
8

from TriggerMenuMT.HLTMenuConfig.Menu.ChainConfigurationBase import ChainConfigurationBase
from TriggerMenuMT.HLTMenuConfig.Menu.MenuComponents import ChainStep, RecoFragmentsPool
9
from .JetRecoConfiguration import jetRecoDictToString
Catrin Bernius's avatar
Catrin Bernius committed
10

11
12
13
14
15
16
17
18
19
import copy

def jetChainParts(chainParts):
    jChainParts = []
    for p in chainParts:
        if p['trigType'] == 'j':
            jChainParts.append(p)
    return jChainParts

Catrin Bernius's avatar
Catrin Bernius committed
20
21
22
23
24
25
#----------------------------------------------------------------
# Class to configure chain
#----------------------------------------------------------------
class JetChainConfiguration(ChainConfigurationBase):

    def __init__(self, chainDict):
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
        # we deliberately don't call base class constructore, since this assumes a single chain part
        # which is not the case for jets

        self.dict = copy.deepcopy(chainDict)
        
        self.chainName = self.dict['chainName']
        self.chainL1Item = self.dict['L1item']

        self.chainPart = self.dict['chainParts']
        self.L1Threshold = ''
        self.mult = 1 # from the framework point of view I think the multiplicity is 1, internally the jet hypo has to figure out what to actually do

        # these properties are in the base class, but I don't think we need them for jets
        #self.chainPartName = ''
        #self.chainPartNameNoMult = ''
        #self.chainPartNameNoMultwL1 = ''

        # expect that the L1 seed is the same for all jet parts, otherwise we have a problem
        jChainParts = jetChainParts(self.chainPart)
45
46
        # Register if this is a performance chain, in which case the HLT should be exactly j0_perf
        self.isPerf = False
47
48
        # Exotic hypo (emerging-jets, trackless jets)
        self.exotHypo = ''
49
50
51
        # Check if we intend to preselect events with calo jets in step 1
        self.trkpresel = "nopresel"
        for ip,p in enumerate(jChainParts):
52
53
54
55
56
57
58

            # Check if there is exactly one exotic hypothesis defined
            if len(p['exotHypo']) > 1:
                raise RuntimeError(f'Exotic chains currently not configurable with more than one exotic selection!')
            if p['exotHypo']:
                self.exotHypo = p['exotHypo'][0]

59
60
61
62
63
64
            if p['addInfo'] == 'perf':
                # Slightly awkward check but we want to permit any L1, while
                # restricting HLT to have exactly this form and nothing else
                if self.chainName != 'HLT_j0_perf_'+self.chainL1Item:
                        raise RuntimeError(f'Invalid jet \'perf\' chain "{self.chainName}": Only "HLT_j0_perf_[L1]" is permitted!')
                self.isPerf = True
65
66
            l1th = p['L1threshold']
            if self.L1Threshold != '' and self.L1Threshold != l1th:
67
                raise RuntimeError('Cannot configure a jet chain with different L1 thresholds')
68
            self.L1Threshold = l1th
69
70
71
72
73
74
            # We require that if there is any preselection it is only written
            # in the last chainPart to avoid inconsistencies -- chain should
            # look like HLT_jA_jB_jC_preselNjX_L1BLAH
            if p["trkpresel"]!="nopresel":
                if ip+1==len(jChainParts): # Last jet chainPart, presel should go here
                    self.trkpresel=p["trkpresel"]
75
                    self.trkpresel_parsed_reco = {key:p[key] for key in ['recoAlg']} #Storing here the reco options from last chain part that we want to propagate to preselection (e.g. jet radius)
76
77
78
                else:
                    log.error("Likely inconsistency encountered in preselection specification for %s",self.chainName)
                    raise RuntimeError("Preselection %s specified earlier than in the last chainPart!",p["trkpresel"])
79

Teng Jian Khoo's avatar
Teng Jian Khoo committed
80
        from TriggerMenuMT.HLTMenuConfig.Jet.JetRecoConfiguration import extractRecoDict
81
        self.recoDict = extractRecoDict(jChainParts)
Teng Jian Khoo's avatar
Teng Jian Khoo committed
82

83
84
85
        self._setJetName()


86
87
88
89
    # ----------------------
    # Assemble jet collection name based on reco dictionary
    # ----------------------
    def _setJetName(self):
90
91
92
        from ..Menu.ChainDictTools import splitChainDict
        from .JetRecoSequences import JetRecoConfiguration
        from JetRecConfig.JetDefinition import buildJetAlgName, xAODType
93
94
95
        try:
            subChainDict = splitChainDict(self.dict)[0]
        except IndexError:
96
            raise ValueError("Chain dictionary is empty. Cannot define jet collection name on empty dictionary")
97
        jetRecoDict = JetRecoConfiguration.extractRecoDict(subChainDict["chainParts"])
98
        clustersKey = JetRecoConfiguration.getClustersKey(jetRecoDict)
99
100
        prefix = JetRecoConfiguration.getHLTPrefix()
        suffix = "_"+jetRecoDict["jetCalib"]
101
102
103
        if JetRecoConfiguration.jetDefNeedsTracks(jetRecoDict):
            suffix += "_{}".format(jetRecoDict["trkopt"])
        inputDef = JetRecoConfiguration.defineJetConstit(jetRecoDict, clustersKey = clustersKey, pfoPrefix=prefix+jetRecoDict["trkopt"])
104
105
106
107
108
109
110
111
112
113
        jetalg, jetradius, jetextra = JetRecoConfiguration.interpretRecoAlg(jetRecoDict["recoAlg"])
        actualradius = float(jetradius)/10
        self.jetName = prefix+buildJetAlgName("AntiKt", actualradius)+inputDef.label+"Jets"+suffix
        if inputDef.basetype == xAODType.CaloCluster:
             # Omit cluster origin correction from jet name
             # Keep the origin correction explicit because sometimes we may not
             # wish to apply it, whereas PFlow corrections are applied implicitly
             self.jetName = self.jetName.replace("Origin","")
        

Catrin Bernius's avatar
Catrin Bernius committed
114
115
116
    # ----------------------
    # Assemble the chain depending on information from chainName
    # ----------------------
117
    def assembleChainImpl(self):                            
118
        log.debug("Assembling chain %s", self.chainName)
Catrin Bernius's avatar
Catrin Bernius committed
119
120
121
122

        # --------------------
        # define here the names of the steps and obtain the chainStep configuration 
        # --------------------
123
124
        # Only one step for now, but we might consider adding steps for
        # reclustering and trimming workflows
Catrin Bernius's avatar
Catrin Bernius committed
125
        chainSteps = []
126
127
128
129
        if self.recoDict["ionopt"]=="ion":
            jetCollectionName, jetDef, jetHICaloHypoStep = self.getJetHICaloHypoChainStep()
            chainSteps.append( jetHICaloHypoStep )
        elif self.recoDict["trkopt"]=="ftf":
130
            if self.trkpresel=="nopresel":
131
132
                clustersKey, caloRecoStep = self.getJetCaloRecoChainStep()
                chainSteps.append( caloRecoStep )
133
            else:
134
135
                clustersKey, jetPreselStep = self.getJetCaloPreselChainStep()
                chainSteps.append( jetPreselStep )
136
            jetCollectionName, jetDef, jetTrackingHypoStep = self.getJetTrackingHypoChainStep(clustersKey)
137
138
            chainSteps.append( jetTrackingHypoStep )
        else:
139
            jetCollectionName, jetDef, jetCaloHypoStep = self.getJetCaloHypoChainStep()
140
141
            chainSteps.append( jetCaloHypoStep )

142
        if self.dict["eventBuildType"]=="PhysicsTLA":
143
144
145
146
147
            # Select the TLA jets from the full jet container
            # rather than the filtered one seen by the hypo
            # (No diff in practice if the TLA cut is higher than the hypo filter)
            TLAStep = self.getJetTLAChainStep(jetDef.fullname())
            chainSteps+= [TLAStep]
148
149
150
151
152

        # Add exotic jets hypo
        if self.exotHypo != '' and ("Exotic" in self.exotHypo or "Trackless" in self.exotHypo):
            EJsStep = self.getJetEJsChainStep(jetCollectionName, self.chainName, self.exotHypo)
            chainSteps+= [EJsStep]
153
        
Catrin Bernius's avatar
Catrin Bernius committed
154
        myChain = self.buildChain(chainSteps)
155

Catrin Bernius's avatar
Catrin Bernius committed
156
157
158
159
160
161
        return myChain
        

    # --------------------
    # Configuration of steps
    # --------------------
162
163
164
165
166
167
    def getJetCaloHypoChainStep(self):
        jetDefStr = jetRecoDictToString(self.recoDict)

        stepName = "MainStep_jet_"+jetDefStr
        from AthenaConfiguration.AllConfigFlags import ConfigFlags
        from TriggerMenuMT.HLTMenuConfig.Jet.JetMenuSequences import jetCaloHypoMenuSequence
168
169
        jetSeq, jetDef = RecoFragmentsPool.retrieve( jetCaloHypoMenuSequence, 
                                                     ConfigFlags, isPerf=self.isPerf, **self.recoDict )
170
        jetCollectionName = str(jetSeq.hypo.Alg.Jets)
171

172
        return jetCollectionName, jetDef ,ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[self.dict])
173

174
175
176
177
178
179
180
181
182
183
    def getJetHICaloHypoChainStep(self):
        stepName = "MainStep_HIjet"
        from AthenaConfiguration.AllConfigFlags import ConfigFlags
        from TriggerMenuMT.HLTMenuConfig.Jet.JetMenuSequences import jetHICaloHypoMenuSequence
        jetSeq, jetDef = RecoFragmentsPool.retrieve( jetHICaloHypoMenuSequence,
                                                     ConfigFlags, isPerf=self.isPerf, **self.recoDict )
        jetCollectionName = str(jetSeq.hypo.Alg.Jets)

        return jetCollectionName, jetDef ,ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[self.dict])

184
    def getJetTrackingHypoChainStep(self, clustersKey):
185
186
        jetDefStr = jetRecoDictToString(self.recoDict)

187
        stepName = "MainStep_jet_"+jetDefStr
188
        from AthenaConfiguration.AllConfigFlags import ConfigFlags
189
        from TriggerMenuMT.HLTMenuConfig.Jet.JetMenuSequences import jetTrackingHypoMenuSequence
190
191
192
        jetSeq, jetDef = RecoFragmentsPool.retrieve( jetTrackingHypoMenuSequence,
                                                     ConfigFlags, clustersKey=clustersKey,
                                                     isPerf=self.isPerf, **self.recoDict )
193
        jetCollectionName = str(jetSeq.hypo.Alg.Jets)
194
        return jetCollectionName, jetDef, ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[self.dict])
195
196

    def getJetCaloRecoChainStep(self):
197
        stepName = "CaloRecoPTStep_jet_"+self.recoDict["clusterCalib"]
198
199
200
        from AthenaConfiguration.AllConfigFlags import ConfigFlags
        from TriggerMenuMT.HLTMenuConfig.Jet.JetMenuSequences import jetCaloRecoMenuSequence
        jetSeq, clustersKey = RecoFragmentsPool.retrieve( jetCaloRecoMenuSequence,
201
                                                          ConfigFlags, clusterCalib=self.recoDict["clusterCalib"] )
202

203
        return str(clustersKey), ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[self.dict])
204
205

    def getJetCaloPreselChainStep(self):
206
207
208
209
210
        #Find if a a4 or a10 calo jet needs to be used in the pre-selection from the last chain dict
        import re
        assert 'recoAlg' in self.trkpresel_parsed_reco.keys(), "Impossible to find \'recoAlg\' key in last chain dictionary for preselection"
        #Want to match now only a4 and a10 in the original reco algorithm. We don't want to use a10sd or a10t in the preselection
        matched_reco = re.match(r'^a\d?\d?',self.trkpresel_parsed_reco['recoAlg'])
211
        assert matched_reco is not None, "Impossible to get matched reco algorithm for jet trigger preselection The reco expression {0} seems to be impossible to be parsed.".format(self.trkpresel_parsed_reco['recoAlg'])
212

213
214
        # Define a fixed preselection dictionary for prototyping -- we may expand the options
        preselRecoDict = {
215
            'recoAlg':matched_reco.group(), #Getting the outcome of the regex reco option (it should correspond to a4 or a10 depending by which chain you are configuring)
216
217
218
            'constitType':'tc',
            'clusterCalib':'em',
            'constitMod':'',
219
            'trkopt':'notrk',
220
            'ionopt':'noion',
221
        }
222
223
        ''' #Here you can set custom calibrations for large-R preselections. If you set to LCW you'll get an issue though, as the trigger expects the *same* topocluster collection to be used in the preselection and in the PFlow stage with tracking. Therefore this would need to be adapted, but it might not be so easy...
         
224
225
        if preselRecoDict['recoAlg']=='a10': #Setting LC calibrations for large-R jets
            preselRecoDict['clusterCalib']='lcw'
226
        '''
227
        from .JetRecoConfiguration import interpretJetCalibDefault
228
        preselRecoDict.update({'jetCalib':interpretJetCalibDefault(preselRecoDict) if preselRecoDict['recoAlg']=='a4' else 'nojcalib'}) #Adding default calibration for corresponding chain
229
        from ..Menu.SignatureDicts import JetChainParts_Default
230
231
232
        preselCommonJetParts = dict(JetChainParts_Default)
        preselCommonJetParts.update(preselRecoDict)

233
        preselChainDict = dict(self.dict)
234
235
236
237
        preselChainDict['chainParts']=[]

        # Get from the last chainPart in order to avoid to specify preselection for every leg
        #TODO: add protection for cases where the preselection is not specified in the last chainPart
238
        presel_matched = re.match(r'presel(?P<cut>\d?\d?[jacf][\d\D]+)', self.trkpresel)
239
240
241
242
        assert presel_matched is not None, "Impossible to match preselection pattern for self.trkpresel=\'{0}\'.".format(self.trkpresel)
        presel_cut_str = presel_matched.groupdict()['cut'] #This is the cut string you want to parse. For example 'presel2j50XXj40'

        for p in presel_cut_str.split('XX'):
243
            matched = re.match(r'(?P<mult>\d?\d?)(?P<region>[jacf])(?P<cut>\d+)', p)
244
245
            assert matched is not None, "Impossible to extract preselection cut for \'{0}\' substring. Please investigate.".format(p)
            cut_dict = matched.groupdict()
246
            mult,region,cut=cut_dict['mult'],cut_dict['region'],cut_dict['cut']
247
248
            chainPartName=f'{mult}j{cut}'
            if mult=='': mult='1'
249
250
251
252
253
            etarange = {
                "j":"0eta320", # default
                "a":"0eta490",
                "c":"0eta240",
                "f":"320eta490"}[region]
254
255
256
257
258
259
260

            tmpChainDict = dict(preselCommonJetParts) 
            tmpChainDict.update(
                {'L1threshold': 'FSNOSEED',
                 'chainPartName': chainPartName,
                 'multiplicity': mult,
                 'threshold': cut,
261
                 'etaRange':etarange,
262
263
264
265
                 'jvt':'',
                 }
            )
            preselChainDict['chainParts'] += [tmpChainDict]
266

267
268
269
270
271
272
        # We need to pad by the legs not in the preselection expression
        # otherwise the ComboHypo does not find the corresponding
        # legs in the DecisionObject and kills the event
        jetlegs = sum([p['signature'] in ["Jet","Bjet"] for p in self.chainPart])
        padding = jetlegs-len(preselChainDict['chainParts'])
        if padding>0:
273
            preselChainDict['chainParts'][-1]['chainPartName']+='_SHARED'
274
275
276
277
            preselChainDict['chainParts'][-1]['tboundary']='SHARED'
            dummyLegPart = dict(preselCommonJetParts)
            dummyLegPart.update(
                {'L1threshold': 'FSNOSEED',
278
                 'chainPartName': 'j0_SHARED',
279
280
281
                 'multiplicity': '1',
                 'threshold': '0',
                 'jvt':'',
282
                 'tboundary': 'SHARED'
283
284
                 }
            )
285
            preselChainDict['chainParts'] += [dict(dummyLegPart) for i in range(padding)]
286
287
288
            # Last one is not permitted to be shared as there is nothing following
            preselChainDict['chainParts'][-1]['chainPartName']='j0'
            preselChainDict['chainParts'][-1]['tboundary']=''
289

290
        assert(len(preselChainDict['chainParts'])==len(self.chainPart))
291
292
293
294
295
        jetDefStr = jetRecoDictToString(preselRecoDict)

        stepName = "PreselStep_jet_"+jetDefStr
        from AthenaConfiguration.AllConfigFlags import ConfigFlags
        from TriggerMenuMT.HLTMenuConfig.Jet.JetMenuSequences import jetCaloPreselMenuSequence
296
297
        jetSeq, jetDef, clustersKey = RecoFragmentsPool.retrieve( jetCaloPreselMenuSequence,
                                                                  ConfigFlags, **preselRecoDict )
298

299
        return str(clustersKey), ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[preselChainDict])
300

301
    def getJetTLAChainStep(self, jetCollectionName):
302
        from TriggerMenuMT.HLTMenuConfig.Jet.JetTLASequences import jetTLAMenuSequence
303

304
        stepName = "TLAStep_"+jetCollectionName
305
        jetSeq = RecoFragmentsPool.retrieve( jetTLAMenuSequence, None, jetsin=jetCollectionName )
306
        chainStep = ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[self.dict])
Catrin Bernius's avatar
Catrin Bernius committed
307

308
        return chainStep
Catrin Bernius's avatar
Catrin Bernius committed
309

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343

    def getJetEJsChainStep(self, jetCollectionName, thresh, exotdictstring):
        from TriggerMenuMT.HLTMenuConfig.Jet.ExoticJetSequences import jetEJsMenuSequence

        # Must be configured similar to : ExoticPTF0p0dR1p2 or TracklessdR1p2
        if 'Exotic' in exotdictstring and ('dR' not in exotdictstring \
           or 'PTF' not in exotdictstring):
            log.error('Misconfiguration of exotic jet chain - need dR and PTF options')
            exit(1)
        if 'Trackless' in exotdictstring and 'dR' not in exotdictstring:
            log.error('Misconfiguration of trackless exotic jet chain - need dR option')
            exit(1)

        trackless = int(0)
        if 'Exotic' in exotdictstring:
            ptf = float(exotdictstring.split('PTF')[1].split('dR')[0].replace('p', '.'))
            dr  = float(exotdictstring.split('dR')[1].split('_')[0].replace('p', '.'))
        elif 'Trackless' in exotdictstring:
            trackless = int(1)
            ptf = 0.0
            dr = float(exotdictstring.split('dR')[1].split('_')[0].replace('p', '.'))
        else:
            log.error('Misconfiguration of trackless exotic jet chain - need Exotic or Trackless selection')
            exit(1)

        log.debug("Running exotic jets with ptf: " + str(ptf) + "\tdR: " + str(dr) + "\ttrackless: " + str(trackless) + "\thypo: " + exotdictstring)

        stepName = "EJsStep_"+self.chainName
        jetSeq = RecoFragmentsPool.retrieve( jetEJsMenuSequence, None, jetsin=jetCollectionName, name=thresh)
        #from TrigGenericAlgs.TrigGenericAlgsConfig import PassthroughComboHypoCfg
        chainStep = ChainStep(stepName, [jetSeq], multiplicity=[1], chainDicts=[self.dict])#, comboHypoCfg=PassthroughComboHypoCfg)

        return chainStep