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