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