ATLAS Offline Software
Loading...
Searching...
No Matches
ElectronAnalysisConfig.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3# AnaAlgorithm import(s):
4from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
5from AthenaCommon.SystemOfUnits import GeV
6from AthenaConfiguration.Enums import LHCPeriod
7from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType
8from TrackingAnalysisAlgorithms.TrackingAnalysisConfig import InDetTrackCalibrationConfig
9from TrigGlobalEfficiencyCorrection.TriggerLeg_DictHelpers import TriggerDict, MapKeysDict
10from AthenaCommon.Logging import logging
11
12# E/gamma import(s).
13from xAODEgamma.xAODEgammaParameters import xAOD
14
16
17
18class ElectronCalibrationConfig (ConfigBlock) :
19 """the ConfigBlock for the electron four-momentum correction"""
20
21 def __init__ (self) :
22 super (ElectronCalibrationConfig, self).__init__ ()
23 self.setBlockName('Electrons')
24 self.addOption ('inputContainer', '', type=str,
25 info="the name of the input electron container. If left empty, automatically defaults "
26 "to `AnalysisElectrons` for PHYSLITE or `Electrons` otherwise.")
27 self.addOption ('containerName', '', type=str,
28 noneAction='error',
29 info="the name of the output container after calibration.")
30 self.addOption ('ESModel', '', type=str,
31 info="flag for Egamma calibration. If left empty, use the current recommendations.")
32 self.addOption ('decorrelationModel', '1NP_v1', type=str,
33 info="decorrelation model for the EGamma energy scale. Supported choices are: `1NP_v1`, `FULL_v1`.")
34 self.addOption ('postfix', '', type=str,
35 info="a postfix to apply to decorations and algorithm names. Typically "
36 "not needed here since the calibration is common to all electrons.")
37 self.addOption ('crackVeto', False, type=bool,
38 info=r"whether to perform LAr crack veto based on the cluster $\eta$, "
39 r"i.e. remove electrons within $1.37<\vert\eta\vert<1.52$.")
40 self.addOption ('isolationCorrection', True, type=bool,
41 info="whether or not to perform isolation corrections (leakage "
42 "corrections), i.e. set up an instance of "
43 "`CP::EgammaIsolationCorrectionAlg`.",
44 expertMode=True)
45 self.addOption ('recalibratePhyslite', True, type=bool,
46 info="whether to run the `CP::EgammaCalibrationAndSmearingAlg` on "
47 "PHYSLITE derivations")
48 self.addOption ('minPt', 4.5*GeV, type=float,
49 info="the minimum pT cut to apply to calibrated electrons.")
50 self.addOption ('maxEta', 2.47, type=float,
51 info=r"maximum electron $\vert\eta\vert$.")
52 self.addOption ('forceFullSimConfigForP4', False, type=bool,
53 info="whether to force the tool to use the configuration meant for "
54 "full simulation samples for 4-vector corrections. Only for testing purposes.")
55 self.addOption ('forceFullSimConfigForIso', False, type=bool,
56 info="whether to force the tool to use the configuration meant for "
57 "full simulation samples for isolation corrections. Only for testing purposes.")
58 self.addOption ('splitCalibrationAndSmearing', False, type=bool,
59 info="EXPERIMENTAL: This splits the `EgammaCalibrationAndSmearingTool` "
60 " into two steps. The first step applies a baseline calibration that "
61 "is not affected by systematics. The second step then applies the "
62 "systematics-dependent corrections. The net effect is that the "
63 "slower first step only has to be run once, while the second is run "
64 "once per systematic. ATLASG-2358.",
65 expertMode=True)
66 self.addOption ('runTrackBiasing', False, type=bool,
67 info="EXPERIMENTAL: This enables the `InDetTrackBiasingTool`, for "
68 "tracks associated to electrons. The tool does not have Run 3 "
69 "recommendations yet.",
70 expertMode=True)
71 self.addOption ('decorateTruth', False, type=bool,
72 info="decorate truth particle information on the reconstructed one.")
73 self.addOption ('decorateCaloClusterEta', False, type=bool,
74 info=r"decorate the calo cluster $\eta$.")
75 self.addOption ('writeTrackD0Z0', False, type = bool,
76 info=r"save the $d_0$ significance and $z_0\sin\theta$ variables.")
77 self.addOption ('decorateEmva', False, type=bool,
78 info="decorate `E_mva_only` on the electrons (needed for columnar tools/PHYSLITE).")
79 self.addOption ('decorateSamplingPattern', False, type=bool,
80 info="decorate `samplingPattern` on the clusters (meant for PHYSLITE).")
81
82 def instanceName (self) :
83 """Return the instance name for this block"""
84 return self.containerName + self.postfix
85
86 def makeCalibrationAndSmearingAlg (self, config, name) :
87 """Create the calibration and smearing algorithm
88
89 Factoring this out into its own function, as we want to
90 instantiate it in multiple places"""
91 log = logging.getLogger('ElectronCalibrationConfig')
92
93 # Set up the calibration and smearing algorithm:
94 alg = config.createAlgorithm( 'CP::EgammaCalibrationAndSmearingAlg', name )
95 config.addPrivateTool( 'calibrationAndSmearingTool',
96 'CP::EgammaCalibrationAndSmearingTool' )
97 # Set default ESModel per period
98 if self.ESModel:
99 alg.calibrationAndSmearingTool.ESModel = self.ESModel
100 else:
101 if config.geometry() is LHCPeriod.Run2:
102 alg.calibrationAndSmearingTool.ESModel = 'es2023_R22_Run2_v1'
103 elif config.geometry() is LHCPeriod.Run3:
104 alg.calibrationAndSmearingTool.ESModel = 'es2024_Run3_v0'
105 elif config.geometry() is LHCPeriod.Run4:
106 log.warning("No ESModel set for Run4, using Run 3 model instead")
107 alg.calibrationAndSmearingTool.ESModel = 'es2024_Run3_v0'
108 else:
109 raise ValueError (f"Can't set up the ElectronCalibrationConfig with {config.geometry().value}, "
110 "there must be something wrong!")
111
112 alg.calibrationAndSmearingTool.decorrelationModel = self.decorrelationModel
113 alg.calibrationAndSmearingTool.useFastSim = (
115 else int( config.dataType() is DataType.FastSim ))
116 alg.calibrationAndSmearingTool.decorateEmva = self.decorateEmva
117 alg.egammas = config.readName (self.containerName)
118 alg.egammasOut = config.copyName (self.containerName)
119 alg.preselection = config.getPreselection (self.containerName, '')
120 return alg
121
122
123 def makeAlgs (self, config) :
124
125 log = logging.getLogger('ElectronCalibrationConfig')
126
128 log.warning("You are running ElectronCalibrationConfig forcing full sim config")
129 log.warning(" This is only intended to be used for testing purposes")
130
131 inputContainer = "AnalysisElectrons" if config.isPhyslite() else "Electrons"
133 inputContainer = self.inputContainer
134 config.setSourceName (self.containerName, inputContainer)
135
136 # Decorate calo cluster eta if required
138 alg = config.createAlgorithm( 'CP::EgammaCaloClusterEtaAlg',
139 'ElectronEgammaCaloClusterEtaAlg',
140 reentrant=True )
141 alg.particles = config.readName(self.containerName)
142 config.addOutputVar (self.containerName, 'caloEta2', 'caloEta2', noSys=True)
143
145 config.createAlgorithm( 'CP::EgammaSamplingPatternDecoratorAlg', 'EgammaSamplingPatternDecoratorAlg' )
146
147 # Set up a shallow copy to decorate
148 if config.wantCopy (self.containerName) :
149 alg = config.createAlgorithm( 'CP::AsgShallowCopyAlg', 'ElectronShallowCopyAlg' )
150 alg.input = config.readName (self.containerName)
151 alg.output = config.copyName (self.containerName)
152
153 # Set up the eta-cut on all electrons prior to everything else
154 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronEtaCutAlg' )
155 alg.selectionDecoration = 'selectEta' + self.postfix + ',as_bits'
156 config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
157 alg.selectionTool.maxEta = self.maxEta
158 if self.crackVeto:
159 alg.selectionTool.etaGapLow = 1.37
160 alg.selectionTool.etaGapHigh = 1.52
161 alg.selectionTool.useClusterEta = True
162 alg.particles = config.readName (self.containerName)
163 alg.preselection = config.getPreselection (self.containerName, '')
164 config.addSelection (self.containerName, '', alg.selectionDecoration)
165
166 # Select electrons only with good object quality.
167 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronObjectQualityAlg' )
168 alg.selectionDecoration = 'goodOQ' + self.postfix + ',as_bits'
169 config.addPrivateTool( 'selectionTool', 'CP::EgammaIsGoodOQSelectionTool' )
170 alg.selectionTool.Mask = xAOD.EgammaParameters.BADCLUSELECTRON
171 alg.particles = config.readName (self.containerName)
172 alg.preselection = config.getPreselection (self.containerName, '')
173 config.addSelection (self.containerName, '', alg.selectionDecoration)
174
176 # Set up the calibration and smearing algorithm:
177 alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronCalibrationAndSmearingAlg')
178 if config.isPhyslite() and not self.recalibratePhyslite :
179 alg.skipNominal = True
180 else:
181 # This splits the EgammaCalibrationAndSmearingTool into two
182 # steps. The first step applies a baseline calibration that
183 # is not affected by systematics. The second step then
184 # applies the systematics dependent corrections. The net
185 # effect is that the slower first step only has to be run
186 # once, while the second is run once per systematic.
187 #
188 # For now (22 May 24) this has to happen in the same job, as
189 # the output of the first step is not part of PHYSLITE, and
190 # even for the nominal the output of the first and second
191 # step are different. In the future the plan is to put both
192 # the output of the first and second step into PHYSLITE,
193 # allowing to skip the first step when running on PHYSLITE.
194 #
195 # WARNING: All of this is experimental, see: ATLASG-2358
196
197 # Set up the calibration algorithm:
198 alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronBaseCalibrationAlg')
199 # turn off systematics for the calibration step
200 alg.noToolSystematics = True
201 # turn off smearing for the calibration step
202 alg.calibrationAndSmearingTool.doSmearing = False
203
204 # Set up the smearing algorithm:
205 alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronCalibrationSystematicsAlg')
206 # turn off scale corrections for the smearing step
207 alg.calibrationAndSmearingTool.doScaleCorrection = False
208 alg.calibrationAndSmearingTool.useMVACalibration = False
209 alg.calibrationAndSmearingTool.decorateEmva = False
210
211 if self.minPt > 0 :
212 # Set up the the pt selection
213 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronPtCutAlg' )
214 alg.selectionDecoration = 'selectPt' + self.postfix + ',as_bits'
215 config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
216 alg.selectionTool.minPt = self.minPt
217 alg.particles = config.readName (self.containerName)
218 alg.preselection = config.getPreselection (self.containerName, '')
219 config.addSelection (self.containerName, '', alg.selectionDecoration,
220 preselection=True)
221
222 # Set up the isolation correction algorithm:
224 alg = config.createAlgorithm( 'CP::EgammaIsolationCorrectionAlg',
225 'ElectronIsolationCorrectionAlg' )
226 config.addPrivateTool( 'isolationCorrectionTool',
227 'CP::IsolationCorrectionTool' )
228 alg.isolationCorrectionTool.IsMC = config.dataType() is not DataType.Data
229 alg.isolationCorrectionTool.AFII_corr = (
230 0 if self.forceFullSimConfigForIso
231 else config.dataType() is DataType.FastSim)
232 alg.isolationCorrectionTool.FixTimingIssueInCore = True
233 alg.isolationCorrectionTool.ToolVer = "REL22"
234 alg.isolationCorrectionTool.CorrFile = "IsolationCorrections/v6/isolation_ptcorrections_rel22_mc20.root"
235 alg.egammas = config.readName (self.containerName)
236 alg.egammasOut = config.copyName (self.containerName)
237 alg.preselection = config.getPreselection (self.containerName, '')
238 else:
239 log.warning("You are not applying the isolation corrections")
240 log.warning("This is only intended to be used for testing purposes")
241
242 # Additional decorations
244 alg = config.createAlgorithm( 'CP::AsgLeptonTrackDecorationAlg',
245 'LeptonTrackDecorator' )
246 if config.dataType() is not DataType.Data:
248 InDetTrackCalibrationConfig.makeTrackBiasingTool(config, alg)
249 InDetTrackCalibrationConfig.makeTrackSmearingTool(config, alg)
250 alg.particles = config.readName (self.containerName)
251
252 alg = config.createAlgorithm( 'CP::AsgEnergyDecoratorAlg', 'EnergyDecorator' )
253 alg.particles = config.readName(self.containerName)
254
255 config.addOutputVar (self.containerName, 'pt', 'pt')
256 config.addOutputVar (self.containerName, 'eta', 'eta', noSys=True)
257 config.addOutputVar (self.containerName, 'phi', 'phi', noSys=True)
258 config.addOutputVar (self.containerName, 'e_%SYS%', 'e')
259 config.addOutputVar (self.containerName, 'charge', 'charge', noSys=True)
260 config.addOutputVar (self.containerName, 'caloClusterEnergyReso_%SYS%', 'caloClusterEnergyReso', noSys=True)
261
262 if self.writeTrackD0Z0:
263 config.addOutputVar (self.containerName, 'd0_%SYS%', 'd0')
264 config.addOutputVar (self.containerName, 'd0sig_%SYS%', 'd0sig')
265 config.addOutputVar (self.containerName, 'z0_%SYS%', 'z0')
266 config.addOutputVar (self.containerName, 'z0sintheta_%SYS%', 'z0sintheta')
267 config.addOutputVar (self.containerName, 'z0sinthetasig_%SYS%', 'z0sinthetasig')
268
269 # decorate truth information on the reconstructed object:
270 if self.decorateTruth and config.dataType() is not DataType.Data:
271 config.addOutputVar (self.containerName, "truthType", "truth_type", noSys=True, auxType='int')
272 config.addOutputVar (self.containerName, "truthOrigin", "truth_origin", noSys=True, auxType='int')
273
274 config.addOutputVar (self.containerName, "firstEgMotherPdgId", "truth_firstEgMotherPdgId", noSys=True, auxType='int')
275 config.addOutputVar (self.containerName, "firstEgMotherTruthOrigin", "truth_firstEgMotherTruthOrigin", noSys=True, auxType='int')
276 config.addOutputVar (self.containerName, "firstEgMotherTruthType", "truth_firstEgMotherTruthType", noSys=True, auxType='int')
277
278
279class ElectronWorkingPointConfig (ConfigBlock) :
280 """the ConfigBlock for the electron working point
281
282 This may at some point be split into multiple blocks (29 Aug 22)."""
283
284 def __init__ (self) :
285 super (ElectronWorkingPointConfig, self).__init__ ()
286 self.addOption ('containerName', '', type=str,
287 noneAction='error',
288 info="the name of the input container.")
289 self.addOption ('selectionName', '', type=str,
290 noneAction='error',
291 info="the name of the electron selection to define (e.g. `tight` or "
292 "`loose`).")
293 self.addOption ('postfix', None, type=str,
294 info="a postfix to apply to decorations and algorithm names. "
295 "Typically not needed here as `selectionName` is used internally.")
296 self.addOption ('trackSelection', True, type=bool,
297 info="whether or not to set up an instance of "
298 "`CP::AsgLeptonTrackSelectionAlg`, with the recommended $d_0$ and "
299 r"$z_0\sin\theta$ cuts")
300 self.addOption ('maxD0Significance', 5, type=float,
301 info="maximum $d_0$ significance used for the track selection.")
302 self.addOption ('maxDeltaZ0SinTheta', 0.5, type=float,
303 info=r"maximum $z_0\sin\theta$ in mm used for the track selection.")
304 self.addOption ('identificationWP', None, type=str,
305 info="the ID WP to use. Supported ID WPs: `TightLH`, "
306 "`MediumLH`, `LooseBLayerLH`, `TightDNN`, `MediumDNN`, `LooseDNN`, "
307 "`TightNoCFDNN`, `MediumNoCFDNN`, `VeryLooseNoCF97DNN`, `NoID`.",
308 expertMode=["NoID"])
309 self.addOption ('isolationWP', None, type=str,
310 info="the isolation WP to use. Supported isolation WPs: "
311 "`HighPtCaloOnly`, `Loose_VarRad`, `Tight_VarRad`, `TightTrackOnly_"
312 "VarRad`, `TightTrackOnly_FixedRad`, `NonIso`.")
313 self.addOption ('convSelection', None, type=str,
314 info="enter additional selection to use for conversions. To be used with "
315 "`TightLH` or will crash. Supported keywords:"
316 "`Veto`, `MatConv`, `GammaStar`.")
317 self.addOption ('addSelectionToPreselection', True, type=bool,
318 info="whether to retain only electrons satisfying the working point "
319 "requirements")
320 self.addOption ('closeByCorrection', False, type=bool,
321 info="whether to use close-by-corrected isolation working points.")
322 self.addOption ('recomputeID', False, type=bool,
323 info="whether to rerun the ID LH/DNN, or rely on derivation flags.")
324 self.addOption ('chargeIDSelectionRun2', False, type=bool,
325 info="whether to run the ECIDS tool. Only available for Run 2.")
326 self.addOption ('recomputeChargeID', False, type=bool,
327 info="whether to rerun the ECIDS, or rely on derivation flags.")
328 self.addOption ('doFSRSelection', False, type=bool,
329 info="whether to accept additional electrons close to muons for "
330 "the purpose of FSR corrections to these muons. Expert feature "
331 "requested by the H4l analysis running on PHYSLITE.",
332 expertMode=True)
333 self.addOption ('noEffSF', False, type=bool,
334 info="disables the calculation of efficiencies and scale factors. "
335 "Experimental! only useful to test a new WP for which scale "
336 "factors are not available.",
337 expertMode=True)
338 self.addOption ('saveDetailedSF', True, type=bool,
339 info="save all the independent detailed object scale factors.")
340 self.addOption ('saveCombinedSF', False, type=bool,
341 info="save the combined object scale factor.")
342 self.addOption ('forceFullSimConfig', False, type=bool,
343 info="whether to force the tool to use the configuration meant for "
344 "full simulation samples. Only for testing purposes.")
345 self.addOption ('correlationModelId', 'SIMPLIFIED', type=str,
346 info="the correlation model to use for ID scale factors. "
347 "Supported models: `SIMPLIFIED`, `FULL`, `TOTAL`, `TOYS`.")
348 self.addOption ('correlationModelIso', 'SIMPLIFIED', type=str,
349 info="the correlation model to use for isolation scale factors, "
350 "Supported models: `SIMPLIFIED`, `FULL`, `TOTAL`, `TOYS`.")
351 self.addOption ('correlationModelReco', 'SIMPLIFIED', type=str,
352 info="the correlation model to use for reconstruction scale factors. "
353 "Supported models: `SIMPLIFIED`, `FULL`, `TOTAL`, `TOYS`.")
354 self.addOption('addChargeMisIDSF', False, type=bool,
355 info="adds scale factors for charge-misID.")
356
357 def instanceName (self) :
358 """Return the instance name for this block"""
359 if self.postfix is not None :
360 return self.containerName + '_' + self.selectionName + self.postfix
361 return self.containerName + '_' + self.selectionName
362
363 def makeAlgs (self, config) :
364
365 log = logging.getLogger('ElectronWorkingPointConfig')
366
368 log.warning("You are running ElectronWorkingPointConfig forcing full sim config")
369 log.warning("This is only intended to be used for testing purposes")
370
371 selectionPostfix = self.selectionName
372 if selectionPostfix != '' and selectionPostfix[0] != '_' :
373 selectionPostfix = '_' + selectionPostfix
374
375 # The setup below is inappropriate for Run 1
376 if config.geometry() is LHCPeriod.Run1:
377 raise ValueError ("Can't set up the ElectronWorkingPointConfig with %s, there must be something wrong!" % config.geometry().value)
378
379 postfix = self.postfix
380 if postfix is None :
381 postfix = self.selectionName
382 if postfix != '' and postfix[0] != '_' :
383 postfix = '_' + postfix
384
385 # Set up the track selection algorithm:
387 alg = config.createAlgorithm( 'CP::AsgLeptonTrackSelectionAlg',
388 'ElectronTrackSelectionAlg',
389 reentrant=True )
390 alg.selectionDecoration = 'trackSelection' + postfix + ',as_bits'
391 alg.maxD0Significance = self.maxD0Significance
392 alg.maxDeltaZ0SinTheta = self.maxDeltaZ0SinTheta
393 alg.particles = config.readName (self.containerName)
394 alg.preselection = config.getPreselection (self.containerName, '')
395 if self.trackSelection :
396 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
397 preselection=self.addSelectionToPreselection)
398
399 if 'LH' in self.identificationWP:
400 # Set up the likelihood ID selection algorithm
401 # It is safe to do this before calibration, as the cluster E is used
402 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' )
403 alg.selectionDecoration = 'selectLikelihood' + selectionPostfix + ',as_char'
404 if self.recomputeID:
405 # Rerun the likelihood ID
406 config.addPrivateTool( 'selectionTool', 'AsgElectronLikelihoodTool' )
407 alg.selectionTool.primaryVertexContainer = 'PrimaryVertices'
408 # Here we have to match the naming convention of EGSelectorConfigurationMapping.h
409 # which differ from the one used for scale factors
410 if config.geometry() >= LHCPeriod.Run3:
411 if 'HI' not in self.identificationWP:
412 alg.selectionTool.WorkingPoint = self.identificationWP.replace("BLayer","BL") + 'Electron'
413 else:
414 alg.selectionTool.WorkingPoint = self.identificationWP.replace('_HI', 'Electron_HI')
415 elif config.geometry() is LHCPeriod.Run2:
416 alg.selectionTool.WorkingPoint = self.identificationWP.replace("BLayer","BL") + 'Electron_Run2'
417 else:
418 # Select from Derivation Framework flags
419 config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
420 dfFlag = "DFCommonElectronsLH" + self.identificationWP.split('LH')[0]
421 dfFlag = dfFlag.replace("BLayer","BL")
422 alg.selectionTool.selectionFlags = [dfFlag]
423 elif 'SiHit' in self.identificationWP:
424 # Only want SiHit electrons, so veto loose LH electrons
425 algVeto = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlgVeto')
426 algVeto.selectionDecoration = 'selectLikelihoodVeto' + postfix + ',as_char'
427 config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
428 algVeto.selectionTool.selectionFlags = ["DFCommonElectronsLHLoose"]
429 algVeto.selectionTool.invertFlags = [True]
430 algVeto.particles = config.readName (self.containerName)
431 algVeto.preselection = config.getPreselection (self.containerName, self.selectionName)
432 # add the veto as a selection
433 config.addSelection (self.containerName, self.selectionName, algVeto.selectionDecoration,
434 preselection=self.addSelectionToPreselection)
435
436 # Select SiHit electrons using IsEM bits
437 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' )
438 alg.selectionDecoration = 'selectSiHit' + selectionPostfix + ',as_char'
439 # Select from Derivation Framework IsEM bits
440 config.addPrivateTool( 'selectionTool', 'CP::AsgMaskSelectionTool' )
441 dfVar = "DFCommonElectronsLHLooseBLIsEMValue"
442 alg.selectionTool.selectionVars = [dfVar]
443 mask = int( 0 | 0x1 << 1 | 0x1 << 2)
444 alg.selectionTool.selectionMasks = [mask]
445 elif 'DNN' in self.identificationWP:
447 raise ValueError('DNN is not intended to be used with '
448 '`chargeIDSelectionRun2` option as there are '
449 'DNN WPs containing charge flip rejection.')
450 # Set up the DNN ID selection algorithm
451 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDNNAlg' )
452 alg.selectionDecoration = 'selectDNN' + selectionPostfix + ',as_char'
453 if self.recomputeID:
454 # Rerun the DNN ID
455 config.addPrivateTool( 'selectionTool', 'AsgElectronSelectorTool' )
456 # Here we have to match the naming convention of EGSelectorConfigurationMapping.h
457 if config.geometry() is LHCPeriod.Run3:
458 raise ValueError ( "DNN working points are not available for Run 3 yet.")
459 else:
460 alg.selectionTool.WorkingPoint = self.identificationWP + 'Electron'
461 else:
462 # Select from Derivation Framework flags
463 config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
464 dfFlag = "DFCommonElectronsDNN" + self.identificationWP.split('DNN')[0]
465 alg.selectionTool.selectionFlags = [dfFlag]
466 elif self.identificationWP == 'NoID':
467 alg = None
468 else:
469 raise ValueError (f"Electron ID working point '{self.identificationWP}' is not recognised!")
470
471 if alg is not None:
472 alg.particles = config.readName (self.containerName)
473 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
474 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
475 preselection=self.addSelectionToPreselection)
476
477 # maintain order of selections
478 if 'SiHit' in self.identificationWP:
479 # Set up the ElectronSiHitDecAlg algorithm to decorate SiHit electrons with a minimal amount of information:
480 algDec = config.createAlgorithm( 'CP::ElectronSiHitDecAlg', 'ElectronSiHitDecAlg' )
481 selDec = 'siHitEvtHasLeptonPair' + selectionPostfix + ',as_char'
482 algDec.selectionName = selDec.split(",")[0]
483 algDec.ElectronContainer = config.readName (self.containerName)
484 # Set flag to only collect SiHit electrons for events with an electron or muon pair to minimize size increase from SiHit electrons
485 algDec.RequireTwoLeptons = True
486 config.addSelection (self.containerName, self.selectionName, selDec,
487 preselection=self.addSelectionToPreselection)
488
489 # Additional selection for conversions and gamma*
490 if self.convSelection is not None:
491 # skip if not applied together with TightLH
492 if self.identificationWP != 'TightLH':
493 raise ValueError(f"convSelection can only be used with TightLH ID, "
494 f"whereas {self.identificationWP} has been selected. convSelection option will be ignored.")
495 # check if allowed value
496 allowedValues = ["Veto", "GammaStar", "MatConv"]
497 if self.convSelection not in allowedValues:
498 raise ValueError(f"convSelection has been set to {self.convSelection}, which is not a valid option. "
499 f"convSelection option must be one of {allowedValues}.")
500
501 # ambiguityType == 0
502 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronAmbiguityTypeAlg' )
503 alg.selectionDecoration = 'selectAmbiguityType' + selectionPostfix + ',as_char'
504 config.addPrivateTool( 'selectionTool', 'CP::AsgNumDecorationSelectionToolUInt8' )
505 alg.selectionTool.decorationName = "ambiguityType"
506 alg.selectionTool.doEqual = True
507 alg.selectionTool.equal = 0
508 alg.particles = config.readName (self.containerName)
509 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
510 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
511 preselection=self.addSelectionToPreselection)
512
513 # DFCommonAddAmbiguity selection
514 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDFCommonAddAmbiguityAlg' )
515 alg.selectionDecoration = 'selectDFCommonAddAmbiguity' + selectionPostfix + ',as_char'
516 config.addPrivateTool( 'selectionTool', 'CP::AsgNumDecorationSelectionToolInt' )
517 alg.selectionTool.decorationName = "DFCommonAddAmbiguity"
518 if self.convSelection == "Veto":
519 alg.selectionTool.doMax = True
520 alg.selectionTool.max = 1
521 elif self.convSelection == "GammaStar":
522 alg.selectionTool.doEqual = True
523 alg.selectionTool.equal = 1
524 elif self.convSelection == "MatConv":
525 alg.selectionTool.doEqual = True
526 alg.selectionTool.equal = 2
527 alg.particles = config.readName (self.containerName)
528 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
529 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
530 preselection=self.addSelectionToPreselection)
531
532 # Set up the FSR selection
534 # save the flag set for the WP
535 wpFlag = alg.selectionDecoration.split(",")[0]
536 alg = config.createAlgorithm( 'CP::EgammaFSRForMuonsCollectorAlg', 'EgammaFSRForMuonsCollectorAlg' )
537 alg.selectionDecoration = wpFlag
538 alg.ElectronOrPhotonContKey = config.readName (self.containerName)
539 # For SiHit electrons, set flag to remove FSR electrons.
540 # For standard electrons, FSR electrons need to be added as they may be missed by the standard selection.
541 # For SiHit electrons FSR electrons are generally always selected, so they should be removed since they will be in the standard electron container.
542 if 'SiHit' in self.identificationWP:
543 alg.vetoFSR = True
544
545 # Set up the isolation selection algorithm:
546 if self.isolationWP != 'NonIso' :
547 alg = config.createAlgorithm( 'CP::EgammaIsolationSelectionAlg',
548 'ElectronIsolationSelectionAlg' )
549 alg.selectionDecoration = 'isolated' + selectionPostfix + ',as_char'
550 config.addPrivateTool( 'selectionTool', 'CP::IsolationSelectionTool' )
551 alg.selectionTool.ElectronWP = self.isolationWP
553 alg.selectionTool.IsoDecSuffix = "CloseByCorr"
554 alg.egammas = config.readName (self.containerName)
555 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
556 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
557 preselection=self.addSelectionToPreselection)
558
559 if self.chargeIDSelectionRun2 and config.geometry() >= LHCPeriod.Run3:
560 log.warning("ECIDS is only available for Run 2 and will not have any effect in Run 3.")
561
562 # Select electrons only if they don't appear to have flipped their charge.
563 if self.chargeIDSelectionRun2 and config.geometry() < LHCPeriod.Run3:
564 alg = config.createAlgorithm( 'CP::AsgSelectionAlg',
565 'ElectronChargeIDSelectionAlg' )
566 alg.selectionDecoration = 'chargeID' + selectionPostfix + ',as_char'
568 # Rerun the ECIDS BDT
569 config.addPrivateTool( 'selectionTool',
570 'AsgElectronChargeIDSelectorTool' )
571 alg.selectionTool.TrainingFile = \
572 'ElectronPhotonSelectorTools/ChargeID/ECIDS_20180731rel21Summer2018.root'
573 alg.selectionTool.WorkingPoint = 'Loose'
574 alg.selectionTool.CutOnBDT = -0.337671 # Loose 97%
575 else:
576 # Select from Derivation Framework flags
577 config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
578 alg.selectionTool.selectionFlags = ["DFCommonElectronsECIDS"]
579
580 alg.particles = config.readName (self.containerName)
581 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
582 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
583 preselection=self.addSelectionToPreselection)
584
585 correlationModels = ["SIMPLIFIED", "FULL", "TOTAL", "TOYS"]
586 map_file = 'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v3/map1.txt' \
587 if config.geometry() is LHCPeriod.Run2 else \
588 'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run3_Consolidated_Recommendation_v4/map2.txt'
589 sfList = []
590 # Set up the RECO electron efficiency correction algorithm:
591 if config.dataType() is not DataType.Data and not self.noEffSF:
592 if 'DNN' in self.identificationWP:
593 raise ValueError('DNN does not yet have efficiency correction, '
594 'please disable it by setting `noEffSF` to True.')
595
596 alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
597 'ElectronEfficiencyCorrectionAlgReco' )
598 config.addPrivateTool( 'efficiencyCorrectionTool',
599 'AsgElectronEfficiencyCorrectionTool' )
600 alg.scaleFactorDecoration = 'el_reco_effSF' + selectionPostfix + '_%SYS%'
601 alg.efficiencyCorrectionTool.MapFilePath = map_file
602 alg.efficiencyCorrectionTool.RecoKey = "Reconstruction"
603 if self.correlationModelReco not in correlationModels:
604 raise ValueError('Invalid correlation model for reconstruction efficiency, '
605 f'has to be one of: {", ".join(correlationModels)}')
606 if config.geometry() >= LHCPeriod.Run3 and self.correlationModelReco != "TOTAL":
607 log.warning("Only TOTAL correlation model is currently supported "
608 "for reconstruction efficiency correction in Run 3.")
609 alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
610 else:
611 alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelReco
612 if config.dataType() is DataType.FastSim:
613 alg.efficiencyCorrectionTool.ForceDataType = (
614 PATCore.ParticleDataType.Full if self.forceFullSimConfig
615 else PATCore.ParticleDataType.Fast)
616 elif config.dataType() is DataType.FullSim:
617 alg.efficiencyCorrectionTool.ForceDataType = \
618 PATCore.ParticleDataType.Full
619 alg.outOfValidity = 2 #silent
620 alg.outOfValidityDeco = 'el_reco_bad_eff' + selectionPostfix
621 alg.electrons = config.readName (self.containerName)
622 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
624 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
625 'reco_effSF' + postfix)
626 sfList += [alg.scaleFactorDecoration]
627
628 # Set up the ID electron efficiency correction algorithm:
629 if config.dataType() is not DataType.Data and not self.noEffSF and self.identificationWP != 'NoID':
630
631 alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
632 'ElectronEfficiencyCorrectionAlgID' )
633 config.addPrivateTool( 'efficiencyCorrectionTool',
634 'AsgElectronEfficiencyCorrectionTool' )
635 alg.scaleFactorDecoration = 'el_id_effSF' + selectionPostfix + '_%SYS%'
636 alg.efficiencyCorrectionTool.MapFilePath = map_file
637 alg.efficiencyCorrectionTool.IdKey = self.identificationWP.replace("LH","")
638 if self.correlationModelId not in correlationModels:
639 raise ValueError('Invalid correlation model for identification efficiency, '
640 f'has to be one of: {", ".join(correlationModels)}')
641 alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelId
642 if config.dataType() is DataType.FastSim:
643 alg.efficiencyCorrectionTool.ForceDataType = (
644 PATCore.ParticleDataType.Full if self.forceFullSimConfig
645 else PATCore.ParticleDataType.Fast)
646 elif config.dataType() is DataType.FullSim:
647 alg.efficiencyCorrectionTool.ForceDataType = \
648 PATCore.ParticleDataType.Full
649 alg.outOfValidity = 2 #silent
650 alg.outOfValidityDeco = 'el_id_bad_eff' + selectionPostfix
651 alg.electrons = config.readName (self.containerName)
652 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
653 if self.saveDetailedSF:
654 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
655 'id_effSF' + postfix)
656 sfList += [alg.scaleFactorDecoration]
657
658 # Set up the ISO electron efficiency correction algorithm:
659 if config.dataType() is not DataType.Data and self.isolationWP != 'NonIso' and not self.noEffSF:
660 alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
661 'ElectronEfficiencyCorrectionAlgIsol' )
662 config.addPrivateTool( 'efficiencyCorrectionTool',
663 'AsgElectronEfficiencyCorrectionTool' )
664 alg.scaleFactorDecoration = 'el_isol_effSF' + selectionPostfix + '_%SYS%'
665 alg.efficiencyCorrectionTool.MapFilePath = map_file
666 alg.efficiencyCorrectionTool.IdKey = self.identificationWP.replace("LH","")
667 alg.efficiencyCorrectionTool.IsoKey = self.isolationWP
668 if self.correlationModelIso not in correlationModels:
669 raise ValueError('Invalid correlation model for isolation efficiency, '
670 f'has to be one of: {", ".join(correlationModels)}')
671 if self.correlationModelIso != 'TOTAL':
672 log.warning("Only TOTAL correlation model is currently supported "
673 "for isolation efficiency correction in Run 3.")
674 alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
675 if config.dataType() is DataType.FastSim:
676 alg.efficiencyCorrectionTool.ForceDataType = (
677 PATCore.ParticleDataType.Full if self.forceFullSimConfig
678 else PATCore.ParticleDataType.Fast)
679 elif config.dataType() is DataType.FullSim:
680 alg.efficiencyCorrectionTool.ForceDataType = \
681 PATCore.ParticleDataType.Full
682 alg.outOfValidity = 2 #silent
683 alg.outOfValidityDeco = 'el_isol_bad_eff' + selectionPostfix
684 alg.electrons = config.readName (self.containerName)
685 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
686 if self.saveDetailedSF:
687 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
688 'isol_effSF' + postfix)
689 sfList += [alg.scaleFactorDecoration]
690
691 if (self.chargeIDSelectionRun2 and config.geometry() < LHCPeriod.Run3 and
692 config.dataType() is not DataType.Data and not self.noEffSF):
693 alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
694 'ElectronEfficiencyCorrectionAlgEcids' )
695 config.addPrivateTool( 'efficiencyCorrectionTool',
696 'AsgElectronEfficiencyCorrectionTool' )
697 alg.scaleFactorDecoration = 'el_ecids_effSF' + selectionPostfix + '_%SYS%'
698 if self.isolationWP != 'Tight_VarRad':
699 raise ValueError('ECIDS SFs are supported only for Tight_VarRad isolation.')
700 if self.identificationWP == 'LooseBLayerLH':
701 ecids_lh = 'loose'
702 elif self.identificationWP == 'MediumLH':
703 ecids_lh = 'medium'
704 elif self.identificationWP == 'TightLH':
705 ecids_lh = 'tight'
706 else:
707 raise ValueError('ECIDS SFs are supported only for ID LooseBLayerLH, MediumLH, or TightLH')
708
709 alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
710 alg.efficiencyCorrectionTool.CorrectionFileNameList = \
711 [f'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v2/ecids/efficiencySF.ChargeID.{ecids_lh}_ECIDS_Tight_VarRad.root']
712 if config.dataType() is DataType.FastSim:
713 alg.efficiencyCorrectionTool.ForceDataType = (
714 PATCore.ParticleDataType.Full if self.forceFullSimConfig
715 else PATCore.ParticleDataType.Fast)
716 elif config.dataType() is DataType.FullSim:
717 alg.efficiencyCorrectionTool.ForceDataType = \
718 PATCore.ParticleDataType.Full
719 alg.outOfValidity = 2 #silent
720 alg.outOfValidityDeco = 'el_ecids_bad_eff' + selectionPostfix
721 alg.electrons = config.readName (self.containerName)
722 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
723 if self.saveDetailedSF:
724 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
725 'ecids_effSF' + postfix)
726 sfList += [alg.scaleFactorDecoration]
727
728 if self.addChargeMisIDSF and config.dataType() is not DataType.Data and not self.noEffSF:
729 if config.geometry() >= LHCPeriod.Run3:
730 raise ValueError('Run 3 does not yet have charge mis-ID correction, '
731 'please disable it by setting `noEffSF` to False.')
732
733 alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
734 'ElectronEfficiencyCorrectionAlgMisid' )
735 config.addPrivateTool( 'efficiencyCorrectionTool',
736 'CP::ElectronChargeEfficiencyCorrectionTool' )
737 alg.scaleFactorDecoration = 'el_charge_misid_effSF' + selectionPostfix + '_%SYS%'
738 if self.isolationWP != 'Tight_VarRad':
739 raise ValueError('Charge mis-ID SFs are supported only for Tight_VarRad isolation.')
740 if self.identificationWP == 'LooseBLayerLH':
741 misid_lh = 'LooseAndBLayerLLH'
742 elif self.identificationWP == 'MediumLH':
743 misid_lh = 'MediumLLH'
744 elif self.identificationWP == 'TightLH':
745 misid_lh = 'TightLLH'
746 else:
747 raise ValueError('Charge mis-ID SFs are supported only for ID LooseBLayerLH, MediumLH, or TightLH')
748 misid_suffix = '_ECIDSloose' if self.chargeIDSelectionRun2 else ''
749
750 alg.efficiencyCorrectionTool.CorrectionFileName = \
751 f'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v2/charge_misID/chargeEfficiencySF.{misid_lh}_d0z0_TightVarRad{misid_suffix}.root'
752 if config.dataType() is DataType.FastSim:
753 alg.efficiencyCorrectionTool.ForceDataType = (
754 PATCore.ParticleDataType.Full if self.forceFullSimConfig
755 else PATCore.ParticleDataType.Fast)
756 elif config.dataType() is DataType.FullSim:
757 alg.efficiencyCorrectionTool.ForceDataType = \
758 PATCore.ParticleDataType.Full
759 alg.outOfValidity = 2 #silent
760 alg.outOfValidityDeco = 'el_misid_bad_eff' + selectionPostfix
761 alg.electrons = config.readName (self.containerName)
762 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
763 if self.saveDetailedSF:
764 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
765 'charge_misid_effSF' + postfix)
766 sfList += [alg.scaleFactorDecoration]
767
768 if config.dataType() is not DataType.Data and not self.noEffSF and self.saveCombinedSF:
769 alg = config.createAlgorithm( 'CP::AsgObjectScaleFactorAlg',
770 'ElectronCombinedEfficiencyScaleFactorAlg' )
771 alg.particles = config.readName (self.containerName)
772 alg.inScaleFactors = sfList
773 alg.outScaleFactor = 'effSF' + postfix + '_%SYS%'
774 config.addOutputVar (self.containerName, alg.outScaleFactor, 'effSF' + postfix)
775
776
777
779
780 def __init__ (self) :
781 super (ElectronTriggerAnalysisSFBlock, self).__init__ ()
782
783 self.addOption ('triggerChainsPerYear', {}, type=None,
784 info="a dictionary with key (string) the year and value (list of "
785 "strings) the trigger chains.")
786 self.addOption ('electronID', '', type=str,
787 info="the electron ID WP to use.")
788 self.addOption ('electronIsol', '', type=str,
789 info="the electron isolation WP to use.")
790 self.addOption ('saveEff', False, type=bool,
791 info="define whether we decorate also the trigger scale efficiency.")
792 self.addOption ('prefixSF', 'trigEffSF', type=str,
793 info="the decoration prefix for trigger scale factors.")
794 self.addOption ('prefixEff', 'trigEff', type=str,
795 info="the decoration prefix for MC trigger efficiencies.")
796 self.addOption ('includeAllYearsPerRun', False, type=bool,
797 info="all configured years in the LHC run will "
798 "be included in all jobs.")
799 self.addOption ('removeHLTPrefix', True, type=bool,
800 info="remove the HLT prefix from trigger chain names.")
801 self.addOption ('useToolKeyAsOutput', False, type=bool,
802 info="use the tool trigger key as output.")
803 self.addOption ('containerName', '', type=str,
804 info="the input electron container, with a possible selection, in "
805 "the format `container` or `container.selection`.")
806
807 def instanceName (self) :
808 """Return the instance name for this block"""
809 return self.containerName
810
811 def makeAlgs (self, config) :
812
813 if config.dataType() is not DataType.Data:
814 log = logging.getLogger('ElectronTriggerSFConfig')
815
817 log.warning('`includeAllYearsPerRun` is set to True, but `useToolKeyAsOutput` is set to False. '
818 'This will cause multiple branches to be written out with the same content.')
819
820 # Dictionary from TrigGlobalEfficiencyCorrection/Triggers.cfg
821 # Key is trigger chain (w/o HLT prefix)
822 # Value is empty for single leg trigger or list of legs
823 triggerDict = TriggerDict()
824
825 # currently recommended versions
826 version_Run2 = "2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v3"
827 map_Run2 = f"ElectronEfficiencyCorrection/{version_Run2}/map1.txt"
828 version_Run3 = "2015_2025/rel22.2/2025_Run3_Consolidated_Recommendation_v4"
829 map_Run3 = f"ElectronEfficiencyCorrection/{version_Run3}/map2.txt"
830
831 version = version_Run2 if config.geometry() is LHCPeriod.Run2 else version_Run3
832 # Dictionary from TrigGlobalEfficiencyCorrection/MapKeys.cfg
833 # Key is year_leg
834 # Value is list of configs available, first one will be used
835 mapKeysDict = MapKeysDict(version)
836
837 # helper function for leg filtering, very hardcoded but allows autoconfiguration
838 def filterConfFromMap(conf, electronMapKeys):
839 if not conf:
840 raise ValueError("No configuration found for trigger chain.")
841 if len(conf) == 1:
842 return conf[0]
843
844 for c in conf:
845 if c in electronMapKeys:
846 return c
847
848 return conf[0]
849
851 years = [int(year) for year in self.triggerChainsPerYear.keys()]
852 else:
853 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import (
854 get_input_years)
855 years = get_input_years(config)
856
857 # prepare keys
858 import ROOT
859 triggerChainsPerYear_Run2 = {}
860 triggerChainsPerYear_Run3 = {}
861 for year, chains in self.triggerChainsPerYear.items():
862 if not chains:
863 log.warning("No trigger chains configured for year %s. "
864 "Assuming this is intended, no Electron trigger SF will be computed.", year)
865 continue
866
867 chains_split = [chain.replace("HLT_", "").replace(" || ", "_OR_") for chain in chains]
868 if int(year) >= 2022:
869 triggerChainsPerYear_Run3[str(year)] = ' || '.join(chains_split)
870 else:
871 triggerChainsPerYear_Run2[str(year)] = ' || '.join(chains_split)
872 electronMapKeys_Run2 = ROOT.std.map("string", "string")()
873 electronMapKeys_Run3 = ROOT.std.map("string", "string")()
874
875 sc_Run2 = ROOT.TrigGlobalEfficiencyCorrectionTool.suggestElectronMapKeys(triggerChainsPerYear_Run2, version_Run2, electronMapKeys_Run2)
876 sc_Run3 = ROOT.TrigGlobalEfficiencyCorrectionTool.suggestElectronMapKeys(triggerChainsPerYear_Run3, version_Run3, electronMapKeys_Run3)
877 if sc_Run2.code() != 2 or sc_Run3.code() != 2:
878 raise RuntimeError("Failed to suggest electron map keys")
879 electronMapKeys = dict(electronMapKeys_Run2) | dict(electronMapKeys_Run3)
880
881 # collect configurations
882 from TriggerAnalysisAlgorithms.TriggerAnalysisConfig import is_year_in_current_period
883 triggerConfigs = {}
884 for year in years:
885 if not is_year_in_current_period(config, year):
886 continue
887
888 triggerChains = self.triggerChainsPerYear.get(int(year), self.triggerChainsPerYear.get(str(year), []))
889 for chain in triggerChains:
890 chain = chain.replace(" || ", "_OR_")
891 chain_noHLT = chain.replace("HLT_", "")
892 chain_out = chain_noHLT if self.removeHLTPrefix else chain
893 legs = triggerDict[chain_noHLT]
894 if not legs:
895 if chain_noHLT[0] == 'e' and chain_noHLT[1].isdigit:
896 chain_key = f"{year}_{chain_noHLT}"
897 chain_conf = mapKeysDict[chain_key][0]
898 triggerConfigs[chain_conf if self.useToolKeyAsOutput else chain_out] = chain_conf
899 else:
900 for leg in legs:
901 if leg[0] == 'e' and leg[1].isdigit:
902 leg_out = leg if self.removeHLTPrefix else f"HLT_{leg}"
903 leg_key = f"{year}_{leg}"
904 leg_conf = filterConfFromMap(mapKeysDict[leg_key], electronMapKeys)
905 triggerConfigs[leg_conf if self.useToolKeyAsOutput else leg_out] = leg_conf
906
907 decorations = [self.prefixSF]
908 if self.saveEff:
909 decorations += [self.prefixEff]
910
911 for label, conf in triggerConfigs.items():
912 for deco in decorations:
913 alg = config.createAlgorithm('CP::ElectronEfficiencyCorrectionAlg',
914 'EleTrigEfficiencyCorrectionsAlg' + deco +
915 '_' + label)
916 config.addPrivateTool( 'efficiencyCorrectionTool',
917 'AsgElectronEfficiencyCorrectionTool' )
918
919 # Reproduce config from TrigGlobalEfficiencyAlg
920 alg.efficiencyCorrectionTool.MapFilePath = map_Run3 if config.geometry() is LHCPeriod.Run3 else map_Run2
921 alg.efficiencyCorrectionTool.IdKey = self.electronID.replace("LH","")
922 alg.efficiencyCorrectionTool.IsoKey = self.electronIsol
923 alg.efficiencyCorrectionTool.TriggerKey = (
924 ("Eff_" if deco == self.prefixEff else "") + conf)
925 alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
926 alg.efficiencyCorrectionTool.ForceDataType = \
927 PATCore.ParticleDataType.Full
928
929 alg.scaleFactorDecoration = f"el_{deco}_{label}_%SYS%"
930
931 alg.outOfValidity = 2 #silent
932 alg.outOfValidityDeco = f"bad_eff_ele{deco}_{label}"
933 alg.electrons = config.readName (self.containerName)
934 alg.preselection = config.getPreselection (self.containerName, "")
935 config.addOutputVar (self.containerName, alg.scaleFactorDecoration, f"{deco}_{label}")
936
937
938class ElectronLRTMergedConfig (ConfigBlock) :
939 def __init__ (self) :
940 super (ElectronLRTMergedConfig, self).__init__ ()
941 self.addOption (
942 'inputElectrons', 'Electrons', type=str,
943 noneAction='error',
944 info="the name of the input electron container."
945 )
946 self.addOption (
947 'inputLRTElectrons', 'LRTElectrons', type=str,
948 noneAction='error',
949 info="the name of the input LRT electron container."
950 )
951 self.addOption (
952 'containerName', 'Electrons_LRTMerged', type=str,
953 noneAction='error',
954 info="the name of the output container after LRT merging."
955 )
956
957 def instanceName (self) :
958 """Return the instance name for this block"""
959 return self.containerName
960
961 def makeAlgs (self, config) :
962
963 if config.isPhyslite() :
964 raise(RuntimeError("Electron LRT merging is not available in Physlite mode"))
965
966 alg = config.createAlgorithm( "CP::ElectronLRTMergingAlg", "ElectronLRTMergingAlg" )
967 alg.PromptElectronLocation = self.inputElectrons
968 alg.LRTElectronLocation = self.inputLRTElectrons
969 alg.OutputCollectionName = self.containerName
970 alg.CreateViewCollection = False
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition hcg.cxx:310
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177