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