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.FixTimingIssueInCore = True
232  alg.isolationCorrectionTool.ToolVer = "REL22"
233  alg.isolationCorrectionTool.CorrFile = "IsolationCorrections/v6/isolation_ptcorrections_rel22_mc20.root"
234  alg.egammas = config.readName (self.containerName)
235  alg.egammasOut = config.copyName (self.containerName)
236  alg.preselection = config.getPreselection (self.containerName, '')
237  else:
238  log.warning("You are not applying the isolation corrections")
239  log.warning("This is only intended to be used for testing purposes")
240 
241  # Additional decorations
242  if self.writeTrackD0Z0:
243  alg = config.createAlgorithm( 'CP::AsgLeptonTrackDecorationAlg',
244  'LeptonTrackDecorator',
245  reentrant=True )
246  alg.particles = config.readName (self.containerName)
247 
248  alg = config.createAlgorithm( 'CP::AsgEnergyDecoratorAlg', 'EnergyDecorator' )
249  alg.particles = config.readName(self.containerName)
250 
251  config.addOutputVar (self.containerName, 'pt', 'pt')
252  config.addOutputVar (self.containerName, 'eta', 'eta', noSys=True)
253  config.addOutputVar (self.containerName, 'phi', 'phi', noSys=True)
254  config.addOutputVar (self.containerName, 'e_%SYS%', 'e')
255  config.addOutputVar (self.containerName, 'charge', 'charge', noSys=True)
256 
257  if self.writeTrackD0Z0:
258  config.addOutputVar (self.containerName, 'd0_%SYS%', 'd0', noSys=True)
259  config.addOutputVar (self.containerName, 'd0sig_%SYS%', 'd0sig', noSys=True)
260  config.addOutputVar (self.containerName, 'z0sintheta_%SYS%', 'z0sintheta', noSys=True)
261  config.addOutputVar (self.containerName, 'z0sinthetasig_%SYS%', 'z0sinthetasig', noSys=True)
262 
263  # decorate truth information on the reconstructed object:
264  if self.decorateTruth and config.dataType() is not DataType.Data:
265  config.addOutputVar (self.containerName, "truthType", "truth_type", noSys=True)
266  config.addOutputVar (self.containerName, "truthOrigin", "truth_origin", noSys=True)
267 
268  config.addOutputVar (self.containerName, "firstEgMotherPdgId", "truth_firstEgMotherPdgId", noSys=True)
269  config.addOutputVar (self.containerName, "firstEgMotherTruthOrigin", "truth_firstEgMotherTruthOrigin", noSys=True)
270  config.addOutputVar (self.containerName, "firstEgMotherTruthType", "truth_firstEgMotherTruthType", noSys=True)
271 
272 
273 class ElectronWorkingPointConfig (ConfigBlock) :
274  """the ConfigBlock for the electron working point
275 
276  This may at some point be split into multiple blocks (29 Aug 22)."""
277 
278  def __init__ (self) :
279  super (ElectronWorkingPointConfig, self).__init__ ()
280  self.addOption ('containerName', '', type=str,
281  noneAction='error',
282  info="the name of the input container.")
283  self.addOption ('selectionName', '', type=str,
284  noneAction='error',
285  info="the name of the electron selection to define (e.g. tight or "
286  "loose).")
287  self.addOption ('postfix', None, type=str,
288  info="a postfix to apply to decorations and algorithm names. "
289  "Typically not needed here as selectionName is used internally.")
290  self.addOption ('trackSelection', True, type=bool,
291  info="whether or not to set up an instance of "
292  "CP::AsgLeptonTrackSelectionAlg, with the recommended d_0 and "
293  "z_0 sin(theta) cuts. The default is True.")
294  self.addOption ('maxD0Significance', 5, type=float,
295  info="maximum d0 significance used for the trackSelection"
296  "The default is 5")
297  self.addOption ('maxDeltaZ0SinTheta', 0.5, type=float,
298  info="maximum z0sinTheta in mm used for the trackSelection"
299  "The default is 0.5 mm")
300  self.addOption ('identificationWP', None, type=str,
301  info="the ID WP (string) to use. Supported ID WPs: TightLH, "
302  "MediumLH, LooseBLayerLH, TightDNN, MediumDNN, LooseDNN, "
303  "TightNoCFDNN, MediumNoCFDNN, VeryLooseNoCF97DNN, NoID.",
304  expertMode=["NoID"])
305  self.addOption ('isolationWP', None, type=str,
306  info="the isolation WP (string) to use. Supported isolation WPs: "
307  "HighPtCaloOnly, Loose_VarRad, Tight_VarRad, TightTrackOnly_"
308  "VarRad, TightTrackOnly_FixedRad, NonIso.")
309  self.addOption ('convSelection', None, type=str,
310  info="enter additional selection (string) to use. To be used with "
311  "TightLH or will crash. Supported keywords:"
312  "Veto, MatConv, GammaStar.")
313  self.addOption ('addSelectionToPreselection', True, type=bool,
314  info="whether to retain only electrons satisfying the working point "
315  "requirements. The default is True.")
316  self.addOption ('closeByCorrection', False, type=bool,
317  info="whether to use close-by-corrected isolation working points")
318  self.addOption ('recomputeID', False, type=bool,
319  info="whether to rerun the ID LH/DNN. The default is False, i.e. to use "
320  "derivation flags.")
321  self.addOption ('chargeIDSelectionRun2', False, type=bool,
322  info="whether to run the ECIDS tool. Only available for run 2. "
323  "The default is False.")
324  self.addOption ('recomputeChargeID', False, type=bool,
325  info="whether to rerun the ECIDS. The default is False, i.e. to use "
326  "derivation flags.")
327  self.addOption ('doFSRSelection', False, type=bool,
328  info="whether to accept additional electrons close to muons for "
329  "the purpose of FSR corrections to these muons. Expert feature "
330  "requested by the H4l analysis running on PHYSLITE. "
331  "The default is False.",
332  expertMode=True)
333  self.addOption ('noEffSF', False, type=bool,
334  info="disables the calculation of efficiencies and scale factors. "
335  "Experimental! only useful to test a new WP for which scale "
336  "factors are not available. The default is False.",
337  expertMode=True)
338  self.addOption ('saveDetailedSF', True, type=bool,
339  info="save all the independent detailed object scale factors. "
340  "The default is True.")
341  self.addOption ('saveCombinedSF', False, type=bool,
342  info="save the combined object scale factor. "
343  "The default is False.")
344  self.addOption ('forceFullSimConfig', False, type=bool,
345  info="whether to force the tool to use the configuration meant for "
346  "full simulation samples. Only for testing purposes. "
347  "The default is False.")
348  self.addOption ('correlationModelId', 'SIMPLIFIED', type=str,
349  info="the correlation model (string) to use for ID scale factors "
350  "Supported models: SIMPLIFIED (default), FULL, TOTAL, TOYS")
351  self.addOption ('correlationModelIso', 'SIMPLIFIED', type=str,
352  info="the correlation model (string) to use for isolation scale factors "
353  "Supported models: SIMPLIFIED (default), FULL, TOTAL, TOYS")
354  self.addOption ('correlationModelReco', 'SIMPLIFIED', type=str,
355  info="the correlation model (string) to use for reconstruction scale factors "
356  "Supported models: SIMPLIFIED (default), FULL, TOTAL, TOYS")
357  self.addOption('addChargeMisIDSF', False, type=bool,
358  info="Adds scale factors for charge-misID.")
359 
360  def instanceName (self) :
361  """Return the instance name for this block"""
362  if self.postfix is not None :
363  return self.containerName + '_' + self.selectionName + self.postfix
364  return self.containerName + '_' + self.selectionName
365 
366  def makeAlgs (self, config) :
367 
368  log = logging.getLogger('ElectronWorkingPointConfig')
369 
370  if self.forceFullSimConfig:
371  log.warning("You are running ElectronWorkingPointConfig forcing full sim config")
372  log.warning("This is only intended to be used for testing purposes")
373 
374  selectionPostfix = self.selectionName
375  if selectionPostfix != '' and selectionPostfix[0] != '_' :
376  selectionPostfix = '_' + selectionPostfix
377 
378  # The setup below is inappropriate for Run 1
379  if config.geometry() is LHCPeriod.Run1:
380  raise ValueError ("Can't set up the ElectronWorkingPointConfig with %s, there must be something wrong!" % config.geometry().value)
381 
382  postfix = self.postfix
383  if postfix is None :
384  postfix = self.selectionName
385  if postfix != '' and postfix[0] != '_' :
386  postfix = '_' + postfix
387 
388  # Set up the track selection algorithm:
389  if self.trackSelection :
390  alg = config.createAlgorithm( 'CP::AsgLeptonTrackSelectionAlg',
391  'ElectronTrackSelectionAlg',
392  reentrant=True )
393  alg.selectionDecoration = 'trackSelection' + postfix + ',as_bits'
394  alg.maxD0Significance = self.maxD0Significance
395  alg.maxDeltaZ0SinTheta = self.maxDeltaZ0SinTheta
396  alg.particles = config.readName (self.containerName)
397  alg.preselection = config.getPreselection (self.containerName, '')
398  if self.trackSelection :
399  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
400  preselection=self.addSelectionToPreselection)
401 
402  if 'LH' in self.identificationWP:
403  # Set up the likelihood ID selection algorithm
404  # It is safe to do this before calibration, as the cluster E is used
405  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' )
406  alg.selectionDecoration = 'selectLikelihood' + selectionPostfix + ',as_char'
407  if self.recomputeID:
408  # Rerun the likelihood ID
409  config.addPrivateTool( 'selectionTool', 'AsgElectronLikelihoodTool' )
410  alg.selectionTool.primaryVertexContainer = 'PrimaryVertices'
411  # Here we have to match the naming convention of EGSelectorConfigurationMapping.h
412  # which differ from the one used for scale factors
413  if config.geometry() >= LHCPeriod.Run3:
414  if 'HI' not in self.identificationWP:
415  alg.selectionTool.WorkingPoint = self.identificationWP.replace("BLayer","BL") + 'Electron'
416  else:
417  alg.selectionTool.WorkingPoint = self.identificationWP.replace('_HI', 'Electron_HI')
418  elif config.geometry() is LHCPeriod.Run2:
419  alg.selectionTool.WorkingPoint = self.identificationWP.replace("BLayer","BL") + 'Electron_Run2'
420  else:
421  # Select from Derivation Framework flags
422  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
423  dfFlag = "DFCommonElectronsLH" + self.identificationWP.split('LH')[0]
424  dfFlag = dfFlag.replace("BLayer","BL")
425  alg.selectionTool.selectionFlags = [dfFlag]
426  elif 'SiHit' in self.identificationWP:
427  # Only want SiHit electrons, so veto loose LH electrons
428  algVeto = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlgVeto')
429  algVeto.selectionDecoration = 'selectLikelihoodVeto' + postfix + ',as_char'
430  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
431  algVeto.selectionTool.selectionFlags = ["DFCommonElectronsLHLoose"]
432  algVeto.selectionTool.invertFlags = [True]
433  algVeto.particles = config.readName (self.containerName)
434  algVeto.preselection = config.getPreselection (self.containerName, self.selectionName)
435  # add the veto as a selection
436  config.addSelection (self.containerName, self.selectionName, algVeto.selectionDecoration,
437  preselection=self.addSelectionToPreselection)
438 
439  # Select SiHit electrons using IsEM bits
440  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' )
441  alg.selectionDecoration = 'selectSiHit' + selectionPostfix + ',as_char'
442  # Select from Derivation Framework IsEM bits
443  config.addPrivateTool( 'selectionTool', 'CP::AsgMaskSelectionTool' )
444  dfVar = "DFCommonElectronsLHLooseBLIsEMValue"
445  alg.selectionTool.selectionVars = [dfVar]
446  mask = int( 0 | 0x1 << 1 | 0x1 << 2)
447  alg.selectionTool.selectionMasks = [mask]
448  elif 'DNN' in self.identificationWP:
449  if self.chargeIDSelectionRun2:
450  raise ValueError('DNN is not intended to be used with '
451  '`chargeIDSelectionRun2` option as there are '
452  'DNN WPs containing charge flip rejection.')
453  # Set up the DNN ID selection algorithm
454  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDNNAlg' )
455  alg.selectionDecoration = 'selectDNN' + selectionPostfix + ',as_char'
456  if self.recomputeID:
457  # Rerun the DNN ID
458  config.addPrivateTool( 'selectionTool', 'AsgElectronSelectorTool' )
459  # Here we have to match the naming convention of EGSelectorConfigurationMapping.h
460  if config.geometry() is LHCPeriod.Run3:
461  raise ValueError ( "DNN working points are not available for Run 3 yet.")
462  else:
463  alg.selectionTool.WorkingPoint = self.identificationWP + 'Electron'
464  else:
465  # Select from Derivation Framework flags
466  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
467  dfFlag = "DFCommonElectronsDNN" + self.identificationWP.split('DNN')[0]
468  alg.selectionTool.selectionFlags = [dfFlag]
469  elif self.identificationWP == 'NoID':
470  alg = None
471  else:
472  raise ValueError (f"Electron ID working point '{self.identificationWP}' is not recognised!")
473 
474  if alg is not None:
475  alg.particles = config.readName (self.containerName)
476  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
477  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
478  preselection=self.addSelectionToPreselection)
479 
480  # maintain order of selections
481  if 'SiHit' in self.identificationWP:
482  # Set up the ElectronSiHitDecAlg algorithm to decorate SiHit electrons with a minimal amount of information:
483  algDec = config.createAlgorithm( 'CP::ElectronSiHitDecAlg', 'ElectronSiHitDecAlg' )
484  selDec = 'siHitEvtHasLeptonPair' + selectionPostfix + ',as_char'
485  algDec.selectionName = selDec.split(",")[0]
486  algDec.ElectronContainer = config.readName (self.containerName)
487  # Set flag to only collect SiHit electrons for events with an electron or muon pair to minimize size increase from SiHit electrons
488  algDec.RequireTwoLeptons = True
489  config.addSelection (self.containerName, self.selectionName, selDec,
490  preselection=self.addSelectionToPreselection)
491 
492  # Additional selection for conversions and gamma*
493  if self.convSelection is not None:
494  # skip if not applied together with TightLH
495  if self.identificationWP != 'TightLH':
496  raise ValueError(f"convSelection can only be used with TightLH ID, "
497  f"whereas {self.identificationWP} has been selected. convSelection option will be ignored.")
498  # check if allowed value
499  allowedValues = ["Veto", "GammaStar", "MatConv"]
500  if self.convSelection not in allowedValues:
501  raise ValueError(f"convSelection has been set to {self.convSelection}, which is not a valid option. "
502  f"convSelection option must be one of {allowedValues}.")
503 
504  # ambiguityType == 0
505  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronAmbiguityTypeAlg' )
506  alg.selectionDecoration = 'selectAmbiguityType' + selectionPostfix + ',as_char'
507  config.addPrivateTool( 'selectionTool', 'CP::AsgNumDecorationSelectionToolUInt8' )
508  alg.selectionTool.decorationName = "ambiguityType"
509  alg.selectionTool.doEqual = True
510  alg.selectionTool.equal = 0
511  alg.particles = config.readName (self.containerName)
512  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
513  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
514  preselection=self.addSelectionToPreselection)
515 
516  # DFCommonAddAmbiguity selection
517  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDFCommonAddAmbiguityAlg' )
518  alg.selectionDecoration = 'selectDFCommonAddAmbiguity' + selectionPostfix + ',as_char'
519  config.addPrivateTool( 'selectionTool', 'CP::AsgNumDecorationSelectionToolInt' )
520  alg.selectionTool.decorationName = "DFCommonAddAmbiguity"
521  if self.convSelection == "Veto":
522  alg.selectionTool.doMax = True
523  alg.selectionTool.max = 1
524  elif self.convSelection == "GammaStar":
525  alg.selectionTool.doEqual = True
526  alg.selectionTool.equal = 1
527  elif self.convSelection == "MatConv":
528  alg.selectionTool.doEqual = True
529  alg.selectionTool.equal = 2
530  alg.particles = config.readName (self.containerName)
531  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
532  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
533  preselection=self.addSelectionToPreselection)
534 
535  # Set up the FSR selection
536  if self.doFSRSelection :
537  # save the flag set for the WP
538  wpFlag = alg.selectionDecoration.split(",")[0]
539  alg = config.createAlgorithm( 'CP::EgammaFSRForMuonsCollectorAlg', 'EgammaFSRForMuonsCollectorAlg' )
540  alg.selectionDecoration = wpFlag
541  alg.ElectronOrPhotonContKey = config.readName (self.containerName)
542  # For SiHit electrons, set flag to remove FSR electrons.
543  # For standard electrons, FSR electrons need to be added as they may be missed by the standard selection.
544  # For SiHit electrons FSR electrons are generally always selected, so they should be removed since they will be in the standard electron container.
545  if 'SiHit' in self.identificationWP:
546  alg.vetoFSR = True
547 
548  # Set up the isolation selection algorithm:
549  if self.isolationWP != 'NonIso' :
550  alg = config.createAlgorithm( 'CP::EgammaIsolationSelectionAlg',
551  'ElectronIsolationSelectionAlg' )
552  alg.selectionDecoration = 'isolated' + selectionPostfix + ',as_char'
553  config.addPrivateTool( 'selectionTool', 'CP::IsolationSelectionTool' )
554  alg.selectionTool.ElectronWP = self.isolationWP
555  if self.closeByCorrection:
556  alg.selectionTool.IsoDecSuffix = "CloseByCorr"
557  alg.egammas = config.readName (self.containerName)
558  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
559  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
560  preselection=self.addSelectionToPreselection)
561 
562  if self.chargeIDSelectionRun2 and config.geometry() >= LHCPeriod.Run3:
563  log.warning("ECIDS is only available for Run 2 and will not have any effect in Run 3.")
564 
565  # Select electrons only if they don't appear to have flipped their charge.
566  if self.chargeIDSelectionRun2 and config.geometry() < LHCPeriod.Run3:
567  alg = config.createAlgorithm( 'CP::AsgSelectionAlg',
568  'ElectronChargeIDSelectionAlg' )
569  alg.selectionDecoration = 'chargeID' + selectionPostfix + ',as_char'
570  if self.recomputeChargeID:
571  # Rerun the ECIDS BDT
572  config.addPrivateTool( 'selectionTool',
573  'AsgElectronChargeIDSelectorTool' )
574  alg.selectionTool.TrainingFile = \
575  'ElectronPhotonSelectorTools/ChargeID/ECIDS_20180731rel21Summer2018.root'
576  alg.selectionTool.WorkingPoint = 'Loose'
577  alg.selectionTool.CutOnBDT = -0.337671 # Loose 97%
578  else:
579  # Select from Derivation Framework flags
580  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
581  alg.selectionTool.selectionFlags = ["DFCommonElectronsECIDS"]
582 
583  alg.particles = config.readName (self.containerName)
584  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
585  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
586  preselection=self.addSelectionToPreselection)
587 
588  correlationModels = ["SIMPLIFIED", "FULL", "TOTAL", "TOYS"]
589  map_file = 'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v3/map0.txt' \
590  if config.geometry() is LHCPeriod.Run2 else \
591  'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run3_Consolidated_Prerecom_v3/map1.txt'
592  sfList = []
593  # Set up the RECO electron efficiency correction algorithm:
594  if config.dataType() is not DataType.Data and not self.noEffSF:
595  if 'DNN' in self.identificationWP:
596  raise ValueError('DNN does not yet have efficiency correction, '
597  'please disable it by setting `noEffSF` to True.')
598 
599  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
600  'ElectronEfficiencyCorrectionAlgReco' )
601  config.addPrivateTool( 'efficiencyCorrectionTool',
602  'AsgElectronEfficiencyCorrectionTool' )
603  alg.scaleFactorDecoration = 'el_reco_effSF' + selectionPostfix + '_%SYS%'
604  alg.efficiencyCorrectionTool.MapFilePath = map_file
605  alg.efficiencyCorrectionTool.RecoKey = "Reconstruction"
606  if self.correlationModelReco not in correlationModels:
607  raise ValueError('Invalid correlation model for reconstruction efficiency, '
608  f'has to be one of: {", ".join(correlationModels)}')
609  if config.geometry() >= LHCPeriod.Run3 and self.correlationModelReco != "TOTAL":
610  log.warning("Only TOTAL correlation model is currently supported "
611  "for reconstruction efficiency correction in Run 3.")
612  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
613  else:
614  alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelReco
615  if config.dataType() is DataType.FastSim:
616  alg.efficiencyCorrectionTool.ForceDataType = (
617  PATCore.ParticleDataType.Full if self.forceFullSimConfig
618  else PATCore.ParticleDataType.Fast)
619  elif config.dataType() is DataType.FullSim:
620  alg.efficiencyCorrectionTool.ForceDataType = \
621  PATCore.ParticleDataType.Full
622  alg.outOfValidity = 2 #silent
623  alg.outOfValidityDeco = 'el_reco_bad_eff' + selectionPostfix
624  alg.electrons = config.readName (self.containerName)
625  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
626  if self.saveDetailedSF:
627  config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
628  'reco_effSF' + postfix)
629  sfList += [alg.scaleFactorDecoration]
630 
631  # Set up the ID electron efficiency correction algorithm:
632  if config.dataType() is not DataType.Data and not self.noEffSF and self.identificationWP != 'NoID':
633 
634  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
635  'ElectronEfficiencyCorrectionAlgID' )
636  config.addPrivateTool( 'efficiencyCorrectionTool',
637  'AsgElectronEfficiencyCorrectionTool' )
638  alg.scaleFactorDecoration = 'el_id_effSF' + selectionPostfix + '_%SYS%'
639  alg.efficiencyCorrectionTool.MapFilePath = map_file
640  alg.efficiencyCorrectionTool.IdKey = self.identificationWP.replace("LH","")
641  if self.correlationModelId not in correlationModels:
642  raise ValueError('Invalid correlation model for identification efficiency, '
643  f'has to be one of: {", ".join(correlationModels)}')
644  alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelId
645  if config.dataType() is DataType.FastSim:
646  alg.efficiencyCorrectionTool.ForceDataType = (
647  PATCore.ParticleDataType.Full if self.forceFullSimConfig
648  else PATCore.ParticleDataType.Fast)
649  elif config.dataType() is DataType.FullSim:
650  alg.efficiencyCorrectionTool.ForceDataType = \
651  PATCore.ParticleDataType.Full
652  alg.outOfValidity = 2 #silent
653  alg.outOfValidityDeco = 'el_id_bad_eff' + selectionPostfix
654  alg.electrons = config.readName (self.containerName)
655  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
656  if self.saveDetailedSF:
657  config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
658  'id_effSF' + postfix)
659  sfList += [alg.scaleFactorDecoration]
660 
661  # Set up the ISO electron efficiency correction algorithm:
662  if config.dataType() is not DataType.Data and self.isolationWP != 'NonIso' and not self.noEffSF:
663  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
664  'ElectronEfficiencyCorrectionAlgIsol' )
665  config.addPrivateTool( 'efficiencyCorrectionTool',
666  'AsgElectronEfficiencyCorrectionTool' )
667  alg.scaleFactorDecoration = 'el_isol_effSF' + selectionPostfix + '_%SYS%'
668  alg.efficiencyCorrectionTool.MapFilePath = map_file
669  alg.efficiencyCorrectionTool.IdKey = self.identificationWP.replace("LH","")
670  alg.efficiencyCorrectionTool.IsoKey = self.isolationWP
671  if self.correlationModelIso not in correlationModels:
672  raise ValueError('Invalid correlation model for isolation efficiency, '
673  f'has to be one of: {", ".join(correlationModels)}')
674  if self.correlationModelIso != 'TOTAL':
675  log.warning("Only TOTAL correlation model is currently supported "
676  "for isolation efficiency correction in Run 3.")
677  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
678  if config.dataType() is DataType.FastSim:
679  alg.efficiencyCorrectionTool.ForceDataType = (
680  PATCore.ParticleDataType.Full if self.forceFullSimConfig
681  else PATCore.ParticleDataType.Fast)
682  elif config.dataType() is DataType.FullSim:
683  alg.efficiencyCorrectionTool.ForceDataType = \
684  PATCore.ParticleDataType.Full
685  alg.outOfValidity = 2 #silent
686  alg.outOfValidityDeco = 'el_isol_bad_eff' + selectionPostfix
687  alg.electrons = config.readName (self.containerName)
688  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
689  if self.saveDetailedSF:
690  config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
691  'isol_effSF' + postfix)
692  sfList += [alg.scaleFactorDecoration]
693 
694  if (self.chargeIDSelectionRun2 and config.geometry() < LHCPeriod.Run3 and
695  config.dataType() is not DataType.Data and not self.noEffSF):
696  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
697  'ElectronEfficiencyCorrectionAlgEcids' )
698  config.addPrivateTool( 'efficiencyCorrectionTool',
699  'AsgElectronEfficiencyCorrectionTool' )
700  alg.scaleFactorDecoration = 'el_ecids_effSF' + selectionPostfix + '_%SYS%'
701  if self.isolationWP != 'Tight_VarRad':
702  raise ValueError('ECIDS SFs are supported only for Tight_VarRad isolation.')
703  if self.identificationWP == 'LooseBLayerLH':
704  ecids_lh = 'loose'
705  elif self.identificationWP == 'MediumLH':
706  ecids_lh = 'medium'
707  elif self.identificationWP == 'TightLH':
708  ecids_lh = 'tight'
709  else:
710  raise ValueError('ECIDS SFs are supported only for ID LooseBLayerLH, MediumLH, or TightLH')
711 
712  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
713  alg.efficiencyCorrectionTool.CorrectionFileNameList = \
714  [f'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v2/ecids/efficiencySF.ChargeID.{ecids_lh}_ECIDS_Tight_VarRad.root']
715  if config.dataType() is DataType.FastSim:
716  alg.efficiencyCorrectionTool.ForceDataType = (
717  PATCore.ParticleDataType.Full if self.forceFullSimConfig
718  else PATCore.ParticleDataType.Fast)
719  elif config.dataType() is DataType.FullSim:
720  alg.efficiencyCorrectionTool.ForceDataType = \
721  PATCore.ParticleDataType.Full
722  alg.outOfValidity = 2 #silent
723  alg.outOfValidityDeco = 'el_ecids_bad_eff' + selectionPostfix
724  alg.electrons = config.readName (self.containerName)
725  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
726  if self.saveDetailedSF:
727  config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
728  'ecids_effSF' + postfix)
729  sfList += [alg.scaleFactorDecoration]
730 
731  if self.addChargeMisIDSF and config.dataType() is not DataType.Data and not self.noEffSF:
732  if config.geometry() >= LHCPeriod.Run3:
733  raise ValueError('Run 3 does not yet have charge mis-ID correction, '
734  'please disable it by setting `noEffSF` to False.')
735 
736  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
737  'ElectronEfficiencyCorrectionAlgMisid' )
738  config.addPrivateTool( 'efficiencyCorrectionTool',
739  'CP::ElectronChargeEfficiencyCorrectionTool' )
740  alg.scaleFactorDecoration = 'el_charge_misid_effSF' + selectionPostfix + '_%SYS%'
741  if self.isolationWP != 'Tight_VarRad':
742  raise ValueError('Charge mis-ID SFs are supported only for Tight_VarRad isolation.')
743  if self.identificationWP == 'LooseBLayerLH':
744  misid_lh = 'LooseAndBLayerLLH'
745  elif self.identificationWP == 'MediumLH':
746  misid_lh = 'MediumLLH'
747  elif self.identificationWP == 'TightLH':
748  misid_lh = 'TightLLH'
749  else:
750  raise ValueError('Charge mis-ID SFs are supported only for ID LooseBLayerLH, MediumLH, or TightLH')
751  misid_suffix = '_ECIDSloose' if self.chargeIDSelectionRun2 else ''
752 
753  alg.efficiencyCorrectionTool.CorrectionFileName = \
754  f'ElectronEfficiencyCorrection/2015_2025/rel22.2/2025_Run2Rel22_Recommendation_v2/charge_misID/chargeEfficiencySF.{misid_lh}_d0z0_TightVarRad{misid_suffix}.root'
755  if config.dataType() is DataType.FastSim:
756  alg.efficiencyCorrectionTool.ForceDataType = (
757  PATCore.ParticleDataType.Full if self.forceFullSimConfig
758  else PATCore.ParticleDataType.Fast)
759  elif config.dataType() is DataType.FullSim:
760  alg.efficiencyCorrectionTool.ForceDataType = \
761  PATCore.ParticleDataType.Full
762  alg.outOfValidity = 2 #silent
763  alg.outOfValidityDeco = 'el_misid_bad_eff' + selectionPostfix
764  alg.electrons = config.readName (self.containerName)
765  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
766  if self.saveDetailedSF:
767  config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
768  'charge_misid_effSF' + postfix)
769  sfList += [alg.scaleFactorDecoration]
770 
771  if config.dataType() is not DataType.Data and not self.noEffSF and self.saveCombinedSF:
772  alg = config.createAlgorithm( 'CP::AsgObjectScaleFactorAlg',
773  'ElectronCombinedEfficiencyScaleFactorAlg' )
774  alg.particles = config.readName (self.containerName)
775  alg.inScaleFactors = sfList
776  alg.outScaleFactor = 'effSF' + postfix + '_%SYS%'
777  config.addOutputVar (self.containerName, alg.outScaleFactor, 'effSF' + postfix)
778 
779 
780 
781 class ElectronTriggerAnalysisSFBlock (ConfigBlock):
782 
783  def __init__ (self) :
784  super (ElectronTriggerAnalysisSFBlock, self).__init__ ()
785 
786  self.addOption ('triggerChainsPerYear', {}, type=None,
787  info="a dictionary with key (string) the year and value (list of "
788  "strings) the trigger chains. The default is {} (empty dictionary).")
789  self.addOption ('electronID', '', type=str,
790  info="the electron ID WP (string) to use.")
791  self.addOption ('electronIsol', '', type=str,
792  info="the electron isolation WP (string) to use.")
793  self.addOption ('saveEff', False, type=bool,
794  info="define whether we decorate also the trigger scale efficiency "
795  "The default is false.")
796  self.addOption ('prefixSF', 'trigEffSF', type=str,
797  info="the decoration prefix for trigger scale factors, "
798  "the default is 'trigEffSF'")
799  self.addOption ('prefixEff', 'trigEff', type=str,
800  info="the decoration prefix for MC trigger efficiencies, "
801  "the default is 'trigEff'")
802  self.addOption ('includeAllYearsPerRun', False, type=bool,
803  info="if True, all configured years in the LHC run will "
804  "be included in all jobs. The default is False.")
805  self.addOption ('removeHLTPrefix', True, type=bool,
806  info="remove the HLT prefix from trigger chain names, "
807  "The default is True.")
808  self.addOption ('useToolKeyAsOutput', False, type=bool,
809  info="use tool trigger key as output, "
810  "The default is False.")
811  self.addOption ('containerName', '', type=str,
812  info="the input electron container, with a possible selection, in "
813  "the format container or container.selection.")
814 
815  def instanceName (self) :
816  """Return the instance name for this block"""
817  return self.containerName
818 
819  def makeAlgs (self, config) :
820 
821  if config.dataType() is not DataType.Data:
822  log = logging.getLogger('ElectronTriggerSFConfig')
823 
824  if self.includeAllYearsPerRun and not self.useToolKeyAsOutput:
825  log.warning('`includeAllYearsPerRun` is set to True, but `useToolKeyAsOutput` is set to False. '
826  'This will cause multiple branches to be written out with the same content.')
827 
828  # Dictionary from TrigGlobalEfficiencyCorrection/Triggers.cfg
829  # Key is trigger chain (w/o HLT prefix)
830  # Value is empty for single leg trigger or list of legs
831  triggerDict = TriggerDict()
832 
833  # currently recommended versions
834  version_Run2 = "2015_2018/rel21.2/Precision_Summer2020_v1"
835  map_Run2 = f"{version_Run2}/map4.txt"
836  version_Run3 = "2015_2025/rel22.2/2025_Run3_Consolidated_Recommendation_v4"
837  map_Run3 = "2015_2025/rel22.2/2025_Run3_Consolidated_Recommendation_v4/map2.txt"
838 
839  version = version_Run2 if config.geometry() is LHCPeriod.Run2 else version_Run3
840  # Dictionary from TrigGlobalEfficiencyCorrection/MapKeys.cfg
841  # Key is year_leg
842  # Value is list of configs available, first one will be used
843  mapKeysDict = MapKeysDict(version)
844 
845  # helper function for leg filtering, very hardcoded but allows autoconfiguration
846  def filterConfFromMap(conf, electronMapKeys):
847  if not conf:
848  raise ValueError("No configuration found for trigger chain.")
849  if len(conf) == 1:
850  return conf[0]
851 
852  for c in conf:
853  if c in electronMapKeys:
854  return c
855 
856  return conf[0]
857 
858  if self.includeAllYearsPerRun:
859  years = [int(year) for year in self.triggerChainsPerYear.keys()]
860  else:
861  from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import (
862  get_input_years)
863  years = get_input_years(config)
864 
865  # prepare keys
866  import ROOT
867  triggerChainsPerYear_Run2 = {}
868  triggerChainsPerYear_Run3 = {}
869  for year, chains in self.triggerChainsPerYear.items():
870  if not chains:
871  log.warning("No trigger chains configured for year %s. "
872  "Assuming this is intended, no Electron trigger SF will be computed.", year)
873  continue
874 
875  chains_split = [chain.replace("HLT_", "").replace(" || ", "_OR_") for chain in chains]
876  if int(year) >= 2022:
877  triggerChainsPerYear_Run3[str(year)] = ' || '.join(chains_split)
878  else:
879  triggerChainsPerYear_Run2[str(year)] = ' || '.join(chains_split)
880  electronMapKeys_Run2 = ROOT.std.map("string", "string")()
881  electronMapKeys_Run3 = ROOT.std.map("string", "string")()
882 
883  sc_Run2 = ROOT.TrigGlobalEfficiencyCorrectionTool.suggestElectronMapKeys(triggerChainsPerYear_Run2, version_Run2, electronMapKeys_Run2)
884  sc_Run3 = ROOT.TrigGlobalEfficiencyCorrectionTool.suggestElectronMapKeys(triggerChainsPerYear_Run3, version_Run3, electronMapKeys_Run3)
885  if sc_Run2.code() != 2 or sc_Run3.code() != 2:
886  raise RuntimeError("Failed to suggest electron map keys")
887  electronMapKeys = dict(electronMapKeys_Run2) | dict(electronMapKeys_Run3)
888 
889  # collect configurations
890  from TriggerAnalysisAlgorithms.TriggerAnalysisConfig import is_year_in_current_period
891  triggerConfigs = {}
892  for year in years:
893  if not is_year_in_current_period(config, year):
894  continue
895 
896  triggerChains = self.triggerChainsPerYear.get(int(year), self.triggerChainsPerYear.get(str(year), []))
897  for chain in triggerChains:
898  chain = chain.replace(" || ", "_OR_")
899  chain_noHLT = chain.replace("HLT_", "")
900  chain_out = chain_noHLT if self.removeHLTPrefix else chain
901  legs = triggerDict[chain_noHLT]
902  if not legs:
903  if chain_noHLT[0] == 'e' and chain_noHLT[1].isdigit:
904  chain_key = f"{year}_{chain_noHLT}"
905  chain_conf = mapKeysDict[chain_key][0]
906  triggerConfigs[chain_conf if self.useToolKeyAsOutput else chain_out] = chain_conf
907  else:
908  for leg in legs:
909  if leg[0] == 'e' and leg[1].isdigit:
910  leg_out = leg if self.removeHLTPrefix else f"HLT_{leg}"
911  leg_key = f"{year}_{leg}"
912  leg_conf = filterConfFromMap(mapKeysDict[leg_key], electronMapKeys)
913  triggerConfigs[leg_conf if self.useToolKeyAsOutput else leg_out] = leg_conf
914 
915  decorations = [self.prefixSF]
916  if self.saveEff:
917  decorations += [self.prefixEff]
918 
919  for label, conf in triggerConfigs.items():
920  for deco in decorations:
921  alg = config.createAlgorithm('CP::ElectronEfficiencyCorrectionAlg',
922  'EleTrigEfficiencyCorrectionsAlg' + deco +
923  '_' + label)
924  config.addPrivateTool( 'efficiencyCorrectionTool',
925  'AsgElectronEfficiencyCorrectionTool' )
926 
927  # Reproduce config from TrigGlobalEfficiencyAlg
928  alg.efficiencyCorrectionTool.MapFilePath = "ElectronEfficiencyCorrection/" + (map_Run3 if config.geometry() is LHCPeriod.Run3 else map_Run2)
929  alg.efficiencyCorrectionTool.IdKey = self.electronID.replace("LH","")
930  alg.efficiencyCorrectionTool.IsoKey = self.electronIsol
931  alg.efficiencyCorrectionTool.TriggerKey = (
932  ("Eff_" if deco == self.prefixEff else "") + conf)
933  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
934  alg.efficiencyCorrectionTool.ForceDataType = \
935  PATCore.ParticleDataType.Full
936 
937  alg.scaleFactorDecoration = f"el_{deco}_{label}_%SYS%"
938 
939  alg.outOfValidity = 2 #silent
940  alg.outOfValidityDeco = f"bad_eff_ele{deco}_{label}"
941  alg.electrons = config.readName (self.containerName)
942  alg.preselection = config.getPreselection (self.containerName, "")
943  config.addOutputVar (self.containerName, alg.scaleFactorDecoration, f"{deco}_{label}")
944 
945 
946 class ElectronLRTMergedConfig (ConfigBlock) :
947  def __init__ (self) :
948  super (ElectronLRTMergedConfig, self).__init__ ()
949  self.addOption (
950  'inputElectrons', 'Electrons', type=str,
951  noneAction='error',
952  info="the name of the input electron container."
953  )
954  self.addOption (
955  'inputLRTElectrons', 'LRTElectrons', type=str,
956  noneAction='error',
957  info="the name of the input LRT electron container."
958  )
959  self.addOption (
960  'containerName', 'Electrons_LRTMerged', type=str,
961  noneAction='error',
962  info="the name of the output container after LRT merging."
963  )
964 
965  def instanceName (self) :
966  """Return the instance name for this block"""
967  return self.containerName
968 
969  def makeAlgs (self, config) :
970 
971  if config.isPhyslite() :
972  raise(RuntimeError("Electron LRT merging is not available in Physlite mode"))
973 
974  alg = config.createAlgorithm( "CP::ElectronLRTMergingAlg", "ElectronLRTMergingAlg" )
975  alg.PromptElectronLocation = self.inputElectrons
976  alg.LRTElectronLocation = self.inputLRTElectrons
977  alg.OutputCollectionName = self.containerName
978  alg.CreateViewCollection = False
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:310
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock.__init__
def __init__(self)
Definition: ElectronAnalysisConfig.py:783
python.ElectronAnalysisConfig.ElectronLRTMergedConfig.instanceName
def instanceName(self)
Definition: ElectronAnalysisConfig.py:965
SystemOfUnits
PATCore::ParticleDataType
Definition: PATCoreEnums.h:21
python.ElectronAnalysisConfig.ElectronWorkingPointConfig.makeAlgs
def makeAlgs(self, config)
Definition: ElectronAnalysisConfig.py:366
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:360
python.ElectronAnalysisConfig.ElectronLRTMergedConfig.makeAlgs
def makeAlgs(self, config)
Definition: ElectronAnalysisConfig.py:969
python.ElectronAnalysisConfig.ElectronCalibrationConfig.makeCalibrationAndSmearingAlg
def makeCalibrationAndSmearingAlg(self, config, name)
Definition: ElectronAnalysisConfig.py:85
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock.makeAlgs
def makeAlgs(self, config)
Definition: ElectronAnalysisConfig.py:819
python.ElectronAnalysisConfig.ElectronWorkingPointConfig.__init__
def __init__(self)
Definition: ElectronAnalysisConfig.py:278
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:946
python.ElectronAnalysisConfig.ElectronLRTMergedConfig.__init__
def __init__(self)
Definition: ElectronAnalysisConfig.py:947
python.TriggerAnalysisSFConfig.get_input_years
list[int] get_input_years(ConfigAccumulator config)
Definition: TriggerAnalysisSFConfig.py:35
python.ElectronAnalysisConfig.ElectronWorkingPointConfig.convSelection
convSelection
Definition: ElectronAnalysisConfig.py:521
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock
Definition: ElectronAnalysisConfig.py:781
python.ElectronAnalysisConfig.ElectronWorkingPointConfig.identificationWP
identificationWP
Definition: ElectronAnalysisConfig.py:469
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:273
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:130
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock.instanceName
def instanceName(self)
Definition: ElectronAnalysisConfig.py:815
str
Definition: BTagTrackIpAccessor.cxx:11
python.Bindings.keys
keys
Definition: Control/AthenaPython/python/Bindings.py:801
Trk::split
@ split
Definition: LayerMaterialProperties.h:38