ATLAS Offline Software
ElectronAnalysisConfig.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 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 Campaigns.Utils import Campaign
10 from AthenaCommon.Logging import logging
11 
12 # E/gamma import(s).
13 from xAODEgamma.xAODEgammaParameters import xAOD
14 
16 
17 
18 class ElectronCalibrationConfig (ConfigBlock) :
19  """the ConfigBlock for the electron four-momentum correction"""
20 
21  def __init__ (self, containerName='') :
22  super (ElectronCalibrationConfig, self).__init__ ()
23  self.setBlockName('Electrons')
24  self.addOption ('containerName', containerName, type=str,
25  noneAction='error',
26  info="the name of the output container after calibration.")
27  self.addOption ('ESModel', '', type=str,
28  info="flag of egamma calibration recommendation.")
29  self.addOption ('decorrelationModel', '1NP_v1', type=str,
30  info="egamma energy scale decorrelationModel. The default is 1NP_v1. "
31  "Supported Model: 1NP_v1, FULL_v1.")
32  self.addOption ('postfix', '', type=str,
33  info="a postfix to apply to decorations and algorithm names. Typically "
34  "not needed here since the calibration is common to all electrons.")
35  self.addOption ('crackVeto', False, type=bool,
36  info="whether to perform LAr crack veto based on the cluster eta, "
37  "i.e. remove electrons within 1.37<|eta|<1.52. The default "
38  "is False.")
39  self.addOption ('isolationCorrection', False, type=bool,
40  info="whether or not to perform isolation corrections (leakage "
41  "corrections), i.e. set up an instance of "
42  "CP::EgammaIsolationCorrectionAlg.")
43  self.addOption ('recalibratePhyslite', True, type=bool,
44  info="whether to run the CP::EgammaCalibrationAndSmearingAlg on "
45  "PHYSLITE derivations. The default is True.")
46  self.addOption ('minPt', 4.5*GeV, type=float,
47  info="the minimum pT cut to apply to calibrated electrons. "
48  "The default is 4.5 GeV.")
49  self.addOption ('maxEta', 2.47, type=float,
50  info="maximum electron |eta| (float). The default is 2.47.")
51  self.addOption ('forceFullSimConfig', False, type=bool,
52  info="whether to force the tool to use the configuration meant for "
53  "full simulation samples. Only for testing purposes. The default "
54  "is False.")
55 
56  self.addOption ('splitCalibrationAndSmearing', False, type=bool,
57  info="EXPERIMENTAL: This splits the EgammaCalibrationAndSmearingTool "
58  " into two steps. The first step applies a baseline calibration that "
59  "is not affected by systematics. The second step then applies the "
60  "systematics dependent corrections. The net effect is that the "
61  "slower first step only has to be run once, while the second is run "
62  "once per systematic. ATLASG-2358")
63 
64  self.addOption ('decorateTruth', False, type=bool,
65  info="decorate truth particle information on the reconstructed one")
66 
67 
68  def makeCalibrationAndSmearingAlg (self, config, name) :
69  """Create the calibration and smearing algorithm
70 
71  Factoring this out into its own function, as we want to
72  instantiate it in multiple places"""
73  # Set up the calibration and smearing algorithm:
74  alg = config.createAlgorithm( 'CP::EgammaCalibrationAndSmearingAlg', name + self.postfix )
75  config.addPrivateTool( 'calibrationAndSmearingTool',
76  'CP::EgammaCalibrationAndSmearingTool' )
77  # Set default ESModel per period
78  if self.ESModel:
79  alg.calibrationAndSmearingTool.ESModel = self.ESModel
80  else:
81  if config.geometry() is LHCPeriod.Run2:
82  alg.calibrationAndSmearingTool.ESModel = 'es2023_R22_Run2_v0'
83  elif config.geometry() is LHCPeriod.Run3:
84  alg.calibrationAndSmearingTool.ESModel = 'es2022_R22_PRE'
85  elif config.geometry() is LHCPeriod.Run4:
86  logging.warning("No ESModel set for Run4, using Run 3 model instead")
87  alg.calibrationAndSmearingTool.ESModel = 'es2022_R22_PRE'
88  else:
89  raise ValueError (f"Can't set up the ElectronCalibrationConfig with {config.geometry().value}, "
90  "there must be something wrong!")
91 
92  alg.calibrationAndSmearingTool.decorrelationModel = self.decorrelationModel
93  alg.calibrationAndSmearingTool.useFastSim = (
94  0 if self.forceFullSimConfig
95  else int( config.dataType() is DataType.FastSim ))
96  alg.egammas = config.readName (self.containerName)
97  alg.egammasOut = config.copyName (self.containerName)
98  alg.preselection = config.getPreselection (self.containerName, '')
99  return alg
100 
101 
102  def makeAlgs (self, config) :
103 
104  log = logging.getLogger('ElectronCalibrationConfig')
105 
106  if self.forceFullSimConfig:
107  log.warning("You are running ElectronCalibrationConfig forcing full sim config")
108  log.warning(" This is only intended to be used for testing purposes")
109 
110  if config.isPhyslite() :
111  config.setSourceName (self.containerName, "AnalysisElectrons")
112  else :
113  config.setSourceName (self.containerName, "Electrons")
114 
115  # Set up a shallow copy to decorate
116  if config.wantCopy (self.containerName) :
117  alg = config.createAlgorithm( 'CP::AsgShallowCopyAlg', 'ElectronShallowCopyAlg' + self.postfix )
118  alg.input = config.readName (self.containerName)
119  alg.output = config.copyName (self.containerName)
120 
121 
122  # Set up the eta-cut on all electrons prior to everything else
123  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronEtaCutAlg' + self.postfix )
124  alg.selectionDecoration = 'selectEta' + self.postfix + ',as_bits'
125  config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
126  alg.selectionTool.maxEta = self.maxEta
127  if self.crackVeto:
128  alg.selectionTool.etaGapLow = 1.37
129  alg.selectionTool.etaGapHigh = 1.52
130  alg.selectionTool.useClusterEta = True
131  alg.particles = config.readName (self.containerName)
132  alg.preselection = config.getPreselection (self.containerName, '')
133  config.addSelection (self.containerName, '', alg.selectionDecoration)
134 
135  # Select electrons only with good object quality.
136  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronObjectQualityAlg' + self.postfix )
137  alg.selectionDecoration = 'goodOQ' + self.postfix + ',as_bits'
138  config.addPrivateTool( 'selectionTool', 'CP::EgammaIsGoodOQSelectionTool' )
139  alg.selectionTool.Mask = xAOD.EgammaParameters.BADCLUSELECTRON
140  alg.particles = config.readName (self.containerName)
141  alg.preselection = config.getPreselection (self.containerName, '')
142  config.addSelection (self.containerName, '', alg.selectionDecoration)
143 
144  if not self.splitCalibrationAndSmearing :
145  # Set up the calibration and smearing algorithm:
146  alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronCalibrationAndSmearingAlg')
147  if config.isPhyslite() and not self.recalibratePhyslite :
148  alg.skipNominal = True
149  else:
150  # This splits the EgammaCalibrationAndSmearingTool into two
151  # steps. The first step applies a baseline calibration that
152  # is not affected by systematics. The second step then
153  # applies the systematics dependent corrections. The net
154  # effect is that the slower first step only has to be run
155  # once, while the second is run once per systematic.
156  #
157  # For now (22 May 24) this has to happen in the same job, as
158  # the output of the first step is not part of PHYSLITE, and
159  # even for the nominal the output of the first and second
160  # step are different. In the future the plan is to put both
161  # the output of the first and second step into PHYSLITE,
162  # allowing to skip the first step when running on PHYSLITE.
163  #
164  # WARNING: All of this is experimental, see: ATLASG-2358
165 
166  # Set up the calibration algorithm:
167  alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronBaseCalibrationAlg')
168  # turn off systematics for the calibration step
169  alg.noToolSystematics = True
170  # turn off smearing for the calibration step
171  alg.calibrationAndSmearingTool.doSmearing = False
172 
173  # Set up the smearing algorithm:
174  alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronCalibrationSystematicsAlg')
175  # turn off scale corrections for the smearing step
176  alg.calibrationAndSmearingTool.doScaleCorrection = False
177  alg.calibrationAndSmearingTool.useMVACalibration = False
178 
179  if self.minPt > 0 :
180  # Set up the the pt selection
181  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronPtCutAlg' + self.postfix )
182  alg.selectionDecoration = 'selectPt' + self.postfix + ',as_bits'
183  config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
184  alg.selectionTool.minPt = self.minPt
185  alg.particles = config.readName (self.containerName)
186  alg.preselection = config.getPreselection (self.containerName, '')
187  config.addSelection (self.containerName, '', alg.selectionDecoration,
188  preselection=True)
189 
190  # Set up the isolation correction algorithm:
191  if self.isolationCorrection:
192  alg = config.createAlgorithm( 'CP::EgammaIsolationCorrectionAlg',
193  'ElectronIsolationCorrectionAlg' + self.postfix )
194  config.addPrivateTool( 'isolationCorrectionTool',
195  'CP::IsolationCorrectionTool' )
196  alg.isolationCorrectionTool.IsMC = config.dataType() is not DataType.Data
197  alg.isolationCorrectionTool.AFII_corr = (
198  0 if self.forceFullSimConfig
199  else config.dataType() is DataType.FastSim)
200  alg.egammas = config.readName (self.containerName)
201  alg.egammasOut = config.copyName (self.containerName)
202  alg.preselection = config.getPreselection (self.containerName, '')
203 
204  # Additional decorations
205  alg = config.createAlgorithm( 'CP::AsgEnergyDecoratorAlg', 'EnergyDecorator' + self.containerName + self.postfix )
206  alg.particles = config.readName(self.containerName)
207 
208  config.addOutputVar (self.containerName, 'pt', 'pt')
209  config.addOutputVar (self.containerName, 'eta', 'eta', noSys=True)
210  config.addOutputVar (self.containerName, 'phi', 'phi', noSys=True)
211  config.addOutputVar (self.containerName, 'e_%SYS%', 'e')
212  config.addOutputVar (self.containerName, 'charge', 'charge', noSys=True)
213 
214  # decorate truth information on the reconstructed object:
215  if self.decorateTruth and config.dataType() is not DataType.Data:
216  config.addOutputVar (self.containerName, "truthType", "truth_type", noSys=True)
217  config.addOutputVar (self.containerName, "truthOrigin", "truth_origin", noSys=True)
218 
219  config.addOutputVar (self.containerName, "firstEgMotherPdgId", "truth_firstEgMotherPdgId", noSys=True)
220  config.addOutputVar (self.containerName, "firstEgMotherTruthOrigin", "truth_firstEgMotherTruthOrigin", noSys=True)
221  config.addOutputVar (self.containerName, "firstEgMotherTruthType", "truth_firstEgMotherTruthType", noSys=True)
222 
223 
224 class ElectronWorkingPointConfig (ConfigBlock) :
225  """the ConfigBlock for the electron working point
226 
227  This may at some point be split into multiple blocks (29 Aug 22)."""
228 
229  def __init__ (self, containerName='', selectionName='') :
230  super (ElectronWorkingPointConfig, self).__init__ ()
231  self.addOption ('containerName', containerName, type=str,
232  noneAction='error',
233  info="the name of the input container.")
234  self.addOption ('selectionName', selectionName, type=str,
235  noneAction='error',
236  info="the name of the electron selection to define (e.g. tight or "
237  "loose).")
238  self.addOption ('postfix', None, type=str,
239  info="a postfix to apply to decorations and algorithm names. "
240  "Typically not needed here as selectionName is used internally.")
241  self.addOption ('trackSelection', True, type=bool,
242  info="whether or not to set up an instance of "
243  "CP::AsgLeptonTrackSelectionAlg, with the recommended d_0 and "
244  "z_0 sin(theta) cuts. The default is True.")
245  self.addOption ('maxD0Significance', 5, type=float,
246  info="maximum d0 significance used for the trackSelection"
247  "The default is 5")
248  self.addOption ('maxDeltaZ0SinTheta', 0.5, type=float,
249  info="maximum z0sinTheta in mm used for the trackSelection"
250  "The default is 0.5 mm")
251  self.addOption ('writeTrackD0Z0', False, type = bool,
252  info="save the d0 significance and z0sinTheta variables so they can be written out")
253  self.addOption ('identificationWP', None, type=str,
254  info="the ID WP (string) to use. Supported ID WPs: TightLH, "
255  "MediumLH, LooseBLayerLH, TightDNN, MediumDNN, LooseDNN, "
256  "TightDNNnoCF, MediumDNNnoCF, VeryLooseDNNnoCF97.")
257  self.addOption ('isolationWP', None, type=str,
258  info="the isolation WP (string) to use. Supported isolation WPs: "
259  "HighPtCaloOnly, Loose_VarRad, Tight_VarRad, TightTrackOnly_"
260  "VarRad, TightTrackOnly_FixedRad, NonIso.")
261  self.addOption ('addSelectionToPreselection', True, type=bool,
262  info="whether to retain only electrons satisfying the working point "
263  "requirements. The default is True.")
264  self.addOption ('closeByCorrection', False, type=bool,
265  info="whether to use close-by-corrected isolation working points")
266  self.addOption ('recomputeID', False, type=bool,
267  info="whether to rerun the ID LH/DNN. The default is False, i.e. to use "
268  "derivation flags.")
269  self.addOption ('chargeIDSelectionRun2', False, type=bool,
270  info="whether to run the ECIDS tool. Only available for run 2. "
271  "The default is False.")
272  self.addOption ('recomputeChargeID', False, type=bool,
273  info="whether to rerun the ECIDS. The default is False, i.e. to use "
274  "derivation flags.")
275  self.addOption ('doFSRSelection', False, type=bool,
276  info="whether to accept additional electrons close to muons for "
277  "the purpose of FSR corrections to these muons. Expert feature "
278  "requested by the H4l analysis running on PHYSLITE. "
279  "The default is False.")
280  self.addOption ('noEffSF', False, type=bool,
281  info="disables the calculation of efficiencies and scale factors. "
282  "Experimental! only useful to test a new WP for which scale "
283  "factors are not available. The default is False.")
284  self.addOption ('forceFullSimConfig', False, type=bool,
285  info="whether to force the tool to use the configuration meant for "
286  "full simulation samples. Only for testing purposes. "
287  "The default is False.")
288  self.addOption ('correlationModelId', 'SIMPLIFIED', type=str,
289  info="the correlation model (string) to use for ID scale factors "
290  "Supported models: SIMPLIFIED (default), FULL, TOTAL, TOYS")
291  self.addOption ('correlationModelIso', 'SIMPLIFIED', type=str,
292  info="the correlation model (string) to use for isolation scale factors "
293  "Supported models: SIMPLIFIED (default), FULL, TOTAL, TOYS")
294  self.addOption ('correlationModelReco', 'SIMPLIFIED', type=str,
295  info="the correlation model (string) to use for reconstruction scale factors "
296  "Supported models: SIMPLIFIED (default), FULL, TOTAL, TOYS")
297 
298 
299  def makeAlgs (self, config) :
300 
301  log = logging.getLogger('ElectronWorkingPointConfig')
302 
303  if self.forceFullSimConfig:
304  log.warning("You are running ElectronWorkingPointConfig forcing full sim config")
305  log.warning("This is only intended to be used for testing purposes")
306 
307  selectionPostfix = self.selectionName
308  if selectionPostfix != '' and selectionPostfix[0] != '_' :
309  selectionPostfix = '_' + selectionPostfix
310 
311  # The setup below is inappropriate for Run 1
312  if config.geometry() is LHCPeriod.Run1:
313  raise ValueError ("Can't set up the ElectronWorkingPointConfig with %s, there must be something wrong!" % config.geometry().value)
314 
315  postfix = self.postfix
316  if postfix is None :
317  postfix = self.selectionName
318  if postfix != '' and postfix[0] != '_' :
319  postfix = '_' + postfix
320 
321  # Set up the track selection algorithm:
322  if self.writeTrackD0Z0 or self.trackSelection :
323  alg = config.createAlgorithm( 'CP::AsgLeptonTrackSelectionAlg',
324  'ElectronTrackSelectionAlg' + postfix )
325  alg.selectionDecoration = 'trackSelection' + postfix + ',as_bits'
326  alg.maxD0Significance = self.maxD0Significance
327  alg.maxDeltaZ0SinTheta = self.maxDeltaZ0SinTheta
328  alg.decorateTTVAVars = self.writeTrackD0Z0
329  alg.particles = config.readName (self.containerName)
330  alg.preselection = config.getPreselection (self.containerName, '')
331  if self.trackSelection :
332  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
333  preselection=self.addSelectionToPreselection)
334  if self.writeTrackD0Z0 :
335  alg.d0sigDecoration = 'd0sig' + postfix
336  alg.z0sinthetaDecoration = 'z0sintheta' + postfix
337  config.addOutputVar (self.containerName, alg.d0sigDecoration, alg.d0sigDecoration,noSys=True)
338  config.addOutputVar (self.containerName, alg.z0sinthetaDecoration, alg.z0sinthetaDecoration,noSys=True)
339 
340  if 'LH' in self.identificationWP:
341  # Set up the likelihood ID selection algorithm
342  # It is safe to do this before calibration, as the cluster E is used
343  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' + postfix )
344  alg.selectionDecoration = 'selectLikelihood' + selectionPostfix + ',as_char'
345  if self.recomputeID:
346  # Rerun the likelihood ID
347  config.addPrivateTool( 'selectionTool', 'AsgElectronLikelihoodTool' )
348  alg.selectionTool.primaryVertexContainer = 'PrimaryVertices'
349  # Here we have to match the naming convention of EGSelectorConfigurationMapping.h
350  # which differ from the one used for scale factors
351  if config.geometry() >= LHCPeriod.Run3:
352  alg.selectionTool.WorkingPoint = self.identificationWP.replace("BLayer","BL") + 'Electron'
353  elif config.geometry() is LHCPeriod.Run2:
354  alg.selectionTool.WorkingPoint = self.identificationWP.replace("BLayer","BL") + 'Electron_Run2'
355  else:
356  # Select from Derivation Framework flags
357  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
358  dfFlag = "DFCommonElectronsLH" + self.identificationWP.split('LH')[0]
359  dfFlag = dfFlag.replace("BLayer","BL")
360  alg.selectionTool.selectionFlags = [dfFlag]
361  elif 'SiHit' in self.identificationWP:
362  # Only want SiHit electrons, so veto loose LH electrons
363  algVeto = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlgVeto' + postfix + 'Veto')
364  algVeto.selectionDecoration = 'selectLikelihoodVeto' + postfix + ',as_char'
365  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
366  algVeto.selectionTool.selectionFlags = ["DFCommonElectronsLHLoose"]
367  algVeto.selectionTool.invertFlags = [True]
368  algVeto.particles = config.readName (self.containerName)
369  algVeto.preselection = config.getPreselection (self.containerName, self.selectionName)
370  # add the veto as a selection
371  config.addSelection (self.containerName, self.selectionName, algVeto.selectionDecoration,
372  preselection=self.addSelectionToPreselection)
373 
374  # Select SiHit electrons using IsEM bits
375  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' + postfix )
376  alg.selectionDecoration = 'selectSiHit' + selectionPostfix + ',as_char'
377  # Select from Derivation Framework IsEM bits
378  config.addPrivateTool( 'selectionTool', 'CP::AsgMaskSelectionTool' )
379  dfVar = "DFCommonElectronsLHLooseBLIsEMValue"
380  alg.selectionTool.selectionVars = [dfVar]
381  mask = int( 0 | 0x1 << 1 | 0x1 << 2)
382  alg.selectionTool.selectionMasks = [mask]
383  elif 'DNN' in self.identificationWP:
384  if self.chargeIDSelectionRun2:
385  raise ValueError('DNN is not intended to be used with '
386  '`chargeIDSelectionRun2` option as there are '
387  'DNN WPs containing charge flip rejection.')
388  # Set up the DNN ID selection algorithm
389  alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDNNAlg' + postfix )
390  alg.selectionDecoration = 'selectDNN' + selectionPostfix + ',as_char'
391  if self.recomputeID:
392  # Rerun the DNN ID
393  config.addPrivateTool( 'selectionTool', 'AsgElectronSelectorTool' )
394  # Here we have to match the naming convention of EGSelectorConfigurationMapping.h
395  if config.geometry() is LHCPeriod.Run3:
396  raise ValueError ( "DNN working points are not available for Run 3 yet.")
397  else:
398  alg.selectionTool.WorkingPoint = self.identificationWP + 'Electron'
399  else:
400  # Select from Derivation Framework flags
401  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
402  dfFlag = "DFCommonElectronsDNN" + self.identificationWP.split('DNN')[0]
403  alg.selectionTool.selectionFlags = [dfFlag]
404 
405  alg.particles = config.readName (self.containerName)
406  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
407  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
408  preselection=self.addSelectionToPreselection)
409 
410  # maintain order of selections
411  if 'SiHit' in self.identificationWP:
412  # Set up the ElectronSiHitDecAlg algorithm to decorate SiHit electrons with a minimal amount of information:
413  algDec = config.createAlgorithm( 'CP::ElectronSiHitDecAlg', 'ElectronSiHitDecAlg' + postfix )
414  selDec = 'siHitEvtHasLeptonPair' + selectionPostfix + ',as_char'
415  algDec.selectionName = selDec.split(",")[0]
416  algDec.ElectronContainer = config.readName (self.containerName)
417  # Set flag to only collect SiHit electrons for events with an electron or muon pair to minimize size increase from SiHit electrons
418  algDec.RequireTwoLeptons = True
419  config.addSelection (self.containerName, self.selectionName, selDec,
420  preselection=self.addSelectionToPreselection)
421 
422  # Set up the FSR selection
423  if self.doFSRSelection :
424  # save the flag set for the WP
425  wpFlag = alg.selectionDecoration.split(",")[0]
426  alg = config.createAlgorithm( 'CP::EgammaFSRForMuonsCollectorAlg', 'EgammaFSRForMuonsCollectorAlg' + postfix )
427  alg.selectionDecoration = wpFlag
428  alg.ElectronOrPhotonContKey = config.readName (self.containerName)
429  # For SiHit electrons, set flag to remove FSR electrons.
430  # For standard electrons, FSR electrons need to be added as they may be missed by the standard selection.
431  # For SiHit electrons FSR electrons are generally always selected, so they should be removed since they will be in the standard electron container.
432  if 'SiHit' in self.identificationWP:
433  alg.vetoFSR = True
434 
435  # Set up the isolation selection algorithm:
436  if self.isolationWP != 'NonIso' :
437  alg = config.createAlgorithm( 'CP::EgammaIsolationSelectionAlg',
438  'ElectronIsolationSelectionAlg' + postfix )
439  alg.selectionDecoration = 'isolated' + selectionPostfix + ',as_char'
440  config.addPrivateTool( 'selectionTool', 'CP::IsolationSelectionTool' )
441  alg.selectionTool.ElectronWP = self.isolationWP
442  if self.closeByCorrection:
443  alg.selectionTool.IsoDecSuffix = "CloseByCorr"
444  alg.egammas = config.readName (self.containerName)
445  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
446  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
447  preselection=self.addSelectionToPreselection)
448 
449  if self.chargeIDSelectionRun2 and config.geometry() >= LHCPeriod.Run3:
450  log.warning("ECIDS is only available for Run 2 and will not have effect in run 3.")
451 
452  # Select electrons only if they don't appear to have flipped their charge.
453  if self.chargeIDSelectionRun2 and config.geometry() < LHCPeriod.Run3:
454  alg = config.createAlgorithm( 'CP::AsgSelectionAlg',
455  'ElectronChargeIDSelectionAlg' + postfix )
456  alg.selectionDecoration = 'chargeID' + selectionPostfix + ',as_char'
457  if self.recomputeChargeID:
458  # Rerun the ECIDS BDT
459  config.addPrivateTool( 'selectionTool',
460  'AsgElectronChargeIDSelectorTool' )
461  alg.selectionTool.TrainingFile = \
462  'ElectronPhotonSelectorTools/ChargeID/ECIDS_20180731rel21Summer2018.root'
463  alg.selectionTool.WorkingPoint = 'Loose'
464  alg.selectionTool.CutOnBDT = -0.337671 # Loose 97%
465  else:
466  # Select from Derivation Framework flags
467  config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
468  alg.selectionTool.selectionFlags = ["DFCommonElectronsECIDS"]
469 
470  alg.particles = config.readName (self.containerName)
471  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
472  config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration,
473  preselection=self.addSelectionToPreselection)
474 
475  correlationModels = ["SIMPLIFIED", "FULL", "TOTAL", "TOYS"]
476 
477  # Set up the RECO electron efficiency correction algorithm:
478  if config.dataType() is not DataType.Data and not self.noEffSF:
479 
480  if config.geometry() is LHCPeriod.Run2:
481  raise ValueError('Run 2 does not yet have efficiency correction, '
482  'please disable it by setting `noEffSF` to True.')
483  if 'DNN' in self.identificationWP:
484  raise ValueError('DNN does not yet have efficiency correction, '
485  'please disable it by setting `noEffSF` to True.')
486 
487  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
488  'ElectronEfficiencyCorrectionAlgReco' + postfix )
489  config.addPrivateTool( 'efficiencyCorrectionTool',
490  'AsgElectronEfficiencyCorrectionTool' )
491  alg.scaleFactorDecoration = 'el_reco_effSF' + selectionPostfix + '_%SYS%'
492  alg.efficiencyCorrectionTool.RecoKey = "Reconstruction"
493  if self.correlationModelReco not in correlationModels:
494  raise ValueError('Invalid correlation model for reconstruction efficiency, '
495  f'has to be one of: {", ".join(correlationModels)}')
496  if config.geometry() >= LHCPeriod.Run3 and self.correlationModelReco != "TOTAL":
497  log.warning("Only TOTAL correlation model is currently supported "
498  "for reconstruction efficiency correction in Run 3.")
499  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
500  else:
501  alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelReco
502  if config.dataType() is DataType.FastSim:
503  alg.efficiencyCorrectionTool.ForceDataType = (
504  PATCore.ParticleDataType.Full if self.forceFullSimConfig
505  else PATCore.ParticleDataType.Fast)
506  elif config.dataType() is DataType.FullSim:
507  alg.efficiencyCorrectionTool.ForceDataType = \
508  PATCore.ParticleDataType.Full
509  alg.outOfValidity = 2 #silent
510  alg.outOfValidityDeco = 'el_reco_bad_eff' + selectionPostfix
511  alg.electrons = config.readName (self.containerName)
512  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
513  config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'reco_effSF' + postfix)
514 
515  # Set up the ID electron efficiency correction algorithm:
516  if config.dataType() is not DataType.Data and not self.noEffSF:
517  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
518  'ElectronEfficiencyCorrectionAlgID' + postfix )
519  config.addPrivateTool( 'efficiencyCorrectionTool',
520  'AsgElectronEfficiencyCorrectionTool' )
521  alg.scaleFactorDecoration = 'el_id_effSF' + selectionPostfix + '_%SYS%'
522  alg.efficiencyCorrectionTool.IdKey = self.identificationWP.replace("LH","")
523  if self.correlationModelId not in correlationModels:
524  raise ValueError('Invalid correlation model for identification efficiency, '
525  f'has to be one of: {", ".join(correlationModels)}')
526  alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelId
527  if config.dataType() is DataType.FastSim:
528  alg.efficiencyCorrectionTool.ForceDataType = (
529  PATCore.ParticleDataType.Full if self.forceFullSimConfig
530  else PATCore.ParticleDataType.Fast)
531  elif config.dataType() is DataType.FullSim:
532  alg.efficiencyCorrectionTool.ForceDataType = \
533  PATCore.ParticleDataType.Full
534  alg.outOfValidity = 2 #silent
535  alg.outOfValidityDeco = 'el_id_bad_eff' + selectionPostfix
536  alg.electrons = config.readName (self.containerName)
537  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
538  config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'id_effSF' + postfix)
539 
540  # Set up the ISO electron efficiency correction algorithm:
541  if config.dataType() is not DataType.Data and self.isolationWP != 'NonIso' and not self.noEffSF:
542  alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
543  'ElectronEfficiencyCorrectionAlgIsol' + postfix )
544  config.addPrivateTool( 'efficiencyCorrectionTool',
545  'AsgElectronEfficiencyCorrectionTool' )
546  alg.scaleFactorDecoration = 'el_isol_effSF' + selectionPostfix + '_%SYS%'
547  alg.efficiencyCorrectionTool.IdKey = self.identificationWP.replace("LH","")
548  alg.efficiencyCorrectionTool.IsoKey = self.isolationWP
549  if self.correlationModelIso not in correlationModels:
550  raise ValueError('Invalid correlation model for isolation efficiency, '
551  f'has to be one of: {", ".join(correlationModels)}')
552  if config.geometry() >= LHCPeriod.Run3:
553  log.warning("Only TOTAL correlation model is currently supported "
554  "for isolation efficiency correction in Run 3.")
555  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
556  else:
557  alg.efficiencyCorrectionTool.CorrelationModel = self.correlationModelIso
558  if config.dataType() is DataType.FastSim:
559  alg.efficiencyCorrectionTool.ForceDataType = (
560  PATCore.ParticleDataType.Full if self.forceFullSimConfig
561  else PATCore.ParticleDataType.Fast)
562  elif config.dataType() is DataType.FullSim:
563  alg.efficiencyCorrectionTool.ForceDataType = \
564  PATCore.ParticleDataType.Full
565  alg.outOfValidity = 2 #silent
566  alg.outOfValidityDeco = 'el_isol_bad_eff' + selectionPostfix
567  alg.electrons = config.readName (self.containerName)
568  alg.preselection = config.getPreselection (self.containerName, self.selectionName)
569  config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'isol_effSF' + postfix)
570 
571  # TO-DO: add trigger SFs, for which we need ID key + ISO key + Trigger key !
572 
573  if self.chargeIDSelectionRun2:
574  # ECIDS is currently not supported in R22.
575  # SFs might become available or it will be part of the DNN ID.
576  pass
577 
578 
579 def makeElectronCalibrationConfig( seq, containerName, postfix = None,
580  crackVeto = None,
581  isolationCorrection = None,
582  forceFullSimConfig = None):
583  """Create electron calibration configuration blocks
584 
585  This makes all the algorithms that need to be run first befor
586  all working point specific algorithms and that can be shared
587  between the working points.
588 
589  Keyword arguments:
590  postfix -- a postfix to apply to decorations and algorithm
591  names. this is mostly used/needed when using this
592  sequence with multiple working points to ensure all
593  names are unique.
594  isolationCorrection -- Whether or not to perform isolation correction
595  forceFullSimConfig -- imposes full-sim config for FastSim for testing
596  """
597 
598  config = ElectronCalibrationConfig (containerName)
599  config.setOptionValue ('crackVeto', crackVeto)
600  config.setOptionValue ('isolationCorrection', isolationCorrection)
601  config.setOptionValue ('forceFullSimConfig', forceFullSimConfig)
602  seq.append (config)
603 
604 
605 
606 
607 
608 def makeElectronWorkingPointConfig( seq, containerName, workingPoint,
609  selectionName,
610  recomputeID = None,
611  chargeIDSelectionRun2 = None,
612  recomputeChargeID = None,
613  noEffSF = None,
614  forceFullSimConfig = None):
615  """Create electron analysis configuration blocks
616 
617  Keyword arguments:
618  workingPoint -- The working point to use
619  selectionName -- a postfix to apply to decorations and algorithm
620  names. this is mostly used/needed when using this
621  sequence with multiple working points to ensure all
622  names are unique.
623  recomputeID -- Whether to rerun the LH/DNN ID. If not, use derivation flags
624  chargeIDSelectionRun2 -- Whether or not to perform charge ID/flip selection
625  recomputeChargeID -- Whether to rerun the ECIDS. If not, use derivation flags
626  noEffSF -- Disables the calculation of efficiencies and scale factors
627  forceFullSimConfig -- imposes full-sim config for FastSim for testing
628  """
629 
630 
631  config = ElectronWorkingPointConfig (containerName, selectionName)
632  if workingPoint is not None :
633  splitWP = workingPoint.split ('.')
634  if len (splitWP) != 2 :
635  raise ValueError ('working point should be of format "likelihood.isolation", not ' + workingPoint)
636  config.setOptionValue ('identificationWP', splitWP[0])
637  config.setOptionValue ('isolationWP', splitWP[1])
638  config.setOptionValue ('recomputeID', recomputeID)
639  config.setOptionValue ('chargeIDSelectionRun2', chargeIDSelectionRun2)
640  config.setOptionValue ('recomputeChargeID', recomputeChargeID)
641  config.setOptionValue ('noEffSF', noEffSF)
642  config.setOptionValue ('forceFullSimConfig', forceFullSimConfig)
643  seq.append (config)
644 
645 
646 class ElectronTriggerAnalysisSFBlock (ConfigBlock):
647 
648  def __init__ (self, configName='') :
649  super (ElectronTriggerAnalysisSFBlock, self).__init__ ()
650 
651  self.addOption ('triggerChainsPerYear', {}, type=None,
652  info="a dictionary with key (string) the year and value (list of "
653  "strings) the trigger chains. The default is {} (empty dictionary).")
654  self.addOption ('electronID', '', type=str,
655  info="the electron ID WP (string) to use.")
656  self.addOption ('electronIsol', '', type=str,
657  info="the electron isolation WP (string) to use.")
658  self.addOption ('saveEff', False, type=bool,
659  info="define whether we decorate also the trigger scale efficiency "
660  "The default is false.")
661  self.addOption ('containerName', '', type=str,
662  info="the input electron container, with a possible selection, in "
663  "the format container or container.selection.")
664 
665  def makeAlgs (self, config) :
666 
667  if config.dataType() is not DataType.Data:
668 
669  # Dictionary from TrigGlobalEfficiencyCorrection/Triggers.cfg
670  # Key is trigger chain (w/o HLT prefix)
671  # Value is empty for single leg trigger or list of legs
672  triggerDict = TriggerDict()
673 
674  version = ("2015_2018/rel21.2/Precision_Summer2020_v1"
675  if config.geometry() is LHCPeriod.Run2 else
676  "2015_2025/rel22.2/2022_Summer_Prerecom_v1")
677  # Dictionary from TrigGlobalEfficiencyCorrection/MapKeys.cfg
678  # Key is year_leg
679  # Value is list of configs available, first one will be used
680  mapKeysDict = MapKeysDict(version)
681 
682  if config.campaign() is Campaign.MC20a:
683  years = [2015, 2016]
684  elif config.campaign() is Campaign.MC20d:
685  years = [2017]
686  elif config.campaign() is Campaign.MC20e:
687  years = [2018]
688  elif config.campaign() in [Campaign.MC21a, Campaign.MC23a]:
689  years = [2022]
690  elif config.campaign() in [Campaign.MC23c, Campaign.MC23d]:
691  years = [2023]
692 
693  triggerConfigs = {}
694  for year in years:
695  triggerChains = self.triggerChainsPerYear.get(int(year), self.triggerChainsPerYear.get(str(year), []))
696  for chain in triggerChains:
697  chain = chain.replace("HLT_", "").replace(" || ", "_OR_")
698  legs = triggerDict[chain]
699  if len(legs)==0:
700  if chain[0]=='e' and chain[1].isdigit:
701  triggerConfigs[chain] = mapKeysDict[year + '_' + chain]
702  else:
703  for leg in legs:
704  if leg[0]=='e' and leg[1].isdigit:
705  triggerConfigs[leg] = mapKeysDict[year + '_' + leg]
706 
707  decorations = ['EffSF']
708  if self.saveEff:
709  decorations += ['Eff']
710 
711  for trig, conf in triggerConfigs.items():
712  for deco in decorations:
713  alg = config.createAlgorithm('CP::ElectronEfficiencyCorrectionAlg',
714  'EleTrigEfficiencyCorrectionsAlg' + deco +
715  '_' + trig)
716  config.addPrivateTool( 'efficiencyCorrectionTool',
717  'AsgElectronEfficiencyCorrectionTool' )
718 
719  # Reproduce config from TrigGlobalEfficiencyAlg
720  alg.efficiencyCorrectionTool.MapFilePath = "ElectronEfficiencyCorrection/" + version + "/map4.txt"
721  alg.efficiencyCorrectionTool.IdKey = self.electronID.replace("LH","")
722  alg.efficiencyCorrectionTool.IsoKey = self.electronIsol
723  alg.efficiencyCorrectionTool.TriggerKey = (
724  ("Eff_" if "SF" not in deco else "") + conf[0])
725  alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
726  alg.efficiencyCorrectionTool.ForceDataType = \
727  PATCore.ParticleDataType.Full
728 
729  alg.scaleFactorDecoration = (
730  'el_trig' + deco + '_' + trig + '_%SYS%')
731  alg.outOfValidity = 2 #silent
732  alg.outOfValidityDeco = 'bad_eff_eletrig' + deco + '_' + trig
733  alg.electrons = config.readName (self.containerName)
734  alg.preselection = config.getPreselection (self.containerName, '')
735  config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'trig' + deco + '_' + trig)
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:307
SystemOfUnits
PATCore::ParticleDataType
Definition: PATCoreEnums.h:21
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock.__init__
def __init__(self, configName='')
Definition: ElectronAnalysisConfig.py:648
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.ElectronAnalysisConfig.ElectronWorkingPointConfig.makeAlgs
def makeAlgs(self, config)
Definition: ElectronAnalysisConfig.py:299
TriggerLeg_DictHelpers.MapKeysDict
def MapKeysDict(target_version)
Definition: TriggerLeg_DictHelpers.py:36
python.ElectronAnalysisConfig.ElectronCalibrationConfig
Definition: ElectronAnalysisConfig.py:18
python.ElectronAnalysisConfig.ElectronWorkingPointConfig.__init__
def __init__(self, containerName='', selectionName='')
Definition: ElectronAnalysisConfig.py:229
python.ElectronAnalysisConfig.ElectronCalibrationConfig.makeCalibrationAndSmearingAlg
def makeCalibrationAndSmearingAlg(self, config, name)
Definition: ElectronAnalysisConfig.py:68
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock.makeAlgs
def makeAlgs(self, config)
Definition: ElectronAnalysisConfig.py:665
python.ElectronAnalysisConfig.makeElectronCalibrationConfig
def makeElectronCalibrationConfig(seq, containerName, postfix=None, crackVeto=None, isolationCorrection=None, forceFullSimConfig=None)
Definition: ElectronAnalysisConfig.py:579
python.ElectronAnalysisConfig.ElectronTriggerAnalysisSFBlock
Definition: ElectronAnalysisConfig.py:646
python.ElectronAnalysisConfig.makeElectronWorkingPointConfig
def makeElectronWorkingPointConfig(seq, containerName, workingPoint, selectionName, recomputeID=None, chargeIDSelectionRun2=None, recomputeChargeID=None, noEffSF=None, forceFullSimConfig=None)
Definition: ElectronAnalysisConfig.py:608
python.ElectronAnalysisConfig.ElectronCalibrationConfig.__init__
def __init__(self, containerName='')
Definition: ElectronAnalysisConfig.py:21
python.ElectronAnalysisConfig.ElectronWorkingPointConfig
Definition: ElectronAnalysisConfig.py:224
python.ElectronAnalysisConfig.ElectronCalibrationConfig.makeAlgs
def makeAlgs(self, config)
Definition: ElectronAnalysisConfig.py:102
TriggerLeg_DictHelpers.TriggerDict
def TriggerDict()
Definition: TriggerLeg_DictHelpers.py:7
get
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition: hcg.cxx:127
str
Definition: BTagTrackIpAccessor.cxx:11
Trk::split
@ split
Definition: LayerMaterialProperties.h:38