-
Added MC23d support for electron trigger SF config
Added MC23d support for electron trigger SF config
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ElectronAnalysisConfig.py 35.02 KiB
# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
# AnaAlgorithm import(s):
from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
from AthenaConfiguration.Enums import LHCPeriod
from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType
from TrigGlobalEfficiencyCorrection.TriggerLeg_DictHelpers import TriggerDict, MapKeysDict
from Campaigns.Utils import Campaign
# E/gamma import(s).
from xAODEgamma.xAODEgammaParameters import xAOD
import PATCore.ParticleDataType
class ElectronCalibrationConfig (ConfigBlock) :
"""the ConfigBlock for the electron four-momentum correction"""
def __init__ (self, containerName='') :
super (ElectronCalibrationConfig, self).__init__ ()
self.setBlockName('Electrons')
self.addOption ('containerName', containerName, type=str,
noneAction='error',
info="the name of the output container after calibration.")
self.addOption ('postfix', '', type=str,
info="a postfix to apply to decorations and algorithm names. Typically "
"not needed here since the calibration is common to all electrons.")
self.addOption ('crackVeto', False, type=bool,
info="whether to perform LAr crack veto based on the cluster eta, "
"i.e. remove electrons within 1.37<|eta|<1.52. The default "
"is False.")
self.addOption ('isolationCorrection', False, type=bool,
info="whether or not to perform isolation corrections (leakage "
"corrections), i.e. set up an instance of "
"CP::EgammaIsolationCorrectionAlg.")
self.addOption ('recalibratePhyslite', True, type=bool,
info="whether to run the CP::EgammaCalibrationAndSmearingAlg on "
"PHYSLITE derivations. The default is True.")
self.addOption ('minPt', 4.5e3, type=float,
info="the minimum pT cut to apply to calibrated electrons. "
"The default is 4.5 GeV.")
self.addOption ('forceFullSimConfig', False, type=bool,
info="whether to force the tool to use the configuration meant for "
"full simulation samples. Only for testing purposes. The default "
"is False.")
self.addOption ('splitCalibrationAndSmearing', False, type=bool,
info="EXPERIMENTAL: This splits the EgammaCalibrationAndSmearingTool "
" into two steps. The first step applies a baseline calibration that "
"is not affected by systematics. The second step then applies the "
"systematics dependent corrections. The net effect is that the "
"slower first step only has to be run once, while the second is run "
"once per systematic. ATLASG-2358")
def makeCalibrationAndSmearingAlg (self, config, name) :
"""Create the calibration and smearing algorithm
Factoring this out into its own function, as we want to
instantiate it in multiple places"""
# Set up the calibration and smearing algorithm:
alg = config.createAlgorithm( 'CP::EgammaCalibrationAndSmearingAlg', name + self.postfix )
config.addPrivateTool( 'calibrationAndSmearingTool',
'CP::EgammaCalibrationAndSmearingTool' )
alg.calibrationAndSmearingTool.ESModel = 'es2022_R22_PRE'
alg.calibrationAndSmearingTool.decorrelationModel = '1NP_v1'
alg.calibrationAndSmearingTool.useFastSim = (
0 if self.forceFullSimConfig
else int( config.dataType() is DataType.FastSim ))
alg.egammas = config.readName (self.containerName)
alg.egammasOut = config.copyName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
return alg
def makeAlgs (self, config) :
if self.forceFullSimConfig:
print("WARNING! You are running ElectronCalibrationConfig forcing full sim config")
print("WARNING! This is only intended to be used for testing purposes")
if config.isPhyslite() :
config.setSourceName (self.containerName, "AnalysisElectrons")
else :
config.setSourceName (self.containerName, "Electrons")
# Set up a shallow copy to decorate
if config.wantCopy (self.containerName) :
alg = config.createAlgorithm( 'CP::AsgShallowCopyAlg', 'ElectronShallowCopyAlg' + self.postfix )
alg.input = config.readName (self.containerName)
alg.output = config.copyName (self.containerName)
# Set up the eta-cut on all electrons prior to everything else
alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronEtaCutAlg' + self.postfix )
alg.selectionDecoration = 'selectEta' + self.postfix + ',as_bits'
config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
alg.selectionTool.maxEta = 2.47
if self.crackVeto:
alg.selectionTool.etaGapLow = 1.37
alg.selectionTool.etaGapHigh = 1.52
alg.selectionTool.useClusterEta = True
alg.particles = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
config.addSelection (self.containerName, '', alg.selectionDecoration)
# Select electrons only with good object quality.
alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronObjectQualityAlg' + self.postfix )
alg.selectionDecoration = 'goodOQ' + self.postfix + ',as_bits'
config.addPrivateTool( 'selectionTool', 'CP::EgammaIsGoodOQSelectionTool' )
alg.selectionTool.Mask = xAOD.EgammaParameters.BADCLUSELECTRON
alg.particles = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
config.addSelection (self.containerName, '', alg.selectionDecoration)
if not self.splitCalibrationAndSmearing :
# Set up the calibration and smearing algorithm:
alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronCalibrationAndSmearingAlg')
if config.isPhyslite() and not self.recalibratePhyslite :
alg.skipNominal = True
else:
# This splits the EgammaCalibrationAndSmearingTool into two
# steps. The first step applies a baseline calibration that
# is not affected by systematics. The second step then
# applies the systematics dependent corrections. The net
# effect is that the slower first step only has to be run
# once, while the second is run once per systematic.
#
# For now (22 May 24) this has to happen in the same job, as
# the output of the first step is not part of PHYSLITE, and
# even for the nominal the output of the first and second
# step are different. In the future the plan is to put both
# the output of the first and second step into PHYSLITE,
# allowing to skip the first step when running on PHYSLITE.
#
# WARNING: All of this is experimental, see: ATLASG-2358
# Set up the calibration algorithm:
alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronBaseCalibrationAlg')
# turn off systematics for the calibration step
alg.noToolSystematics = True
# turn off smearing for the calibration step
alg.calibrationAndSmearingTool.doSmearing = False
# Set up the smearing algorithm:
alg = self.makeCalibrationAndSmearingAlg (config, 'ElectronCalibrationSystematicsAlg')
# turn off scale corrections for the smearing step
alg.calibrationAndSmearingTool.doScaleCorrection = False
alg.calibrationAndSmearingTool.useMVACalibration = False
if self.minPt > 0 :
# Set up the the pt selection
alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronPtCutAlg' + self.postfix )
alg.selectionDecoration = 'selectPt' + self.postfix + ',as_bits'
config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
alg.selectionTool.minPt = self.minPt
alg.particles = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
config.addSelection (self.containerName, '', alg.selectionDecoration,
preselection=True)
# Set up the isolation correction algorithm:
if self.isolationCorrection:
alg = config.createAlgorithm( 'CP::EgammaIsolationCorrectionAlg',
'ElectronIsolationCorrectionAlg' + self.postfix )
config.addPrivateTool( 'isolationCorrectionTool',
'CP::IsolationCorrectionTool' )
alg.isolationCorrectionTool.IsMC = config.dataType() is not DataType.Data
alg.isolationCorrectionTool.AFII_corr = (
0 if self.forceFullSimConfig
else config.dataType() is DataType.FastSim)
alg.egammas = config.readName (self.containerName)
alg.egammasOut = config.copyName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
# Additional decorations
alg = config.createAlgorithm( 'CP::AsgEnergyDecoratorAlg', 'EnergyDecorator' + self.containerName + self.postfix )
alg.particles = config.readName(self.containerName)
config.addOutputVar (self.containerName, 'pt', 'pt')
config.addOutputVar (self.containerName, 'eta', 'eta', noSys=True)
config.addOutputVar (self.containerName, 'phi', 'phi', noSys=True)
config.addOutputVar (self.containerName, 'e_%SYS%', 'e')
config.addOutputVar (self.containerName, 'charge', 'charge', noSys=True)
class ElectronWorkingPointConfig (ConfigBlock) :
"""the ConfigBlock for the electron working point
This may at some point be split into multiple blocks (29 Aug 22)."""
def __init__ (self, containerName='', selectionName='') :
super (ElectronWorkingPointConfig, self).__init__ ()
self.addOption ('containerName', containerName, type=str,
noneAction='error',
info="the name of the input container.")
self.addOption ('selectionName', selectionName, type=str,
noneAction='error',
info="the name of the electron selection to define (e.g. tight or "
"loose).")
self.addOption ('postfix', None, type=str,
info="a postfix to apply to decorations and algorithm names. "
"Typically not needed here as selectionName is used internally.")
self.addOption ('trackSelection', True, type=bool,
info="whether or not to set up an instance of "
"CP::AsgLeptonTrackSelectionAlg, with the recommended d_0 and "
"z_0 sin(theta) cuts. The default is True.")
self.addOption ('maxD0Significance', 5, type=float,
info="maximum d0 significance used for the trackSelection"
"The default is 5")
self.addOption ('maxDeltaZ0SinTheta', 0.5, type=float,
info="maximum z0sinTheta in mm used for the trackSelection"
"The default is 0.5 mm")
self.addOption ('writeTrackD0Z0', False, type = bool,
info="save the d0 significance and z0sinTheta variables so they can be written out")
self.addOption ('likelihoodWP', None, type=str,
info="the ID WP (string) to use. Supported ID WPs: TightLH, "
"MediumLH, LooseBLayerLH. ")
self.addOption ('isolationWP', None, type=str,
info="the isolation WP (string) to use. Supported isolation WPs: "
"HighPtCaloOnly, Loose_VarRad, Tight_VarRad, TightTrackOnly_"
"VarRad, TightTrackOnly_FixedRad, NonIso.")
self.addOption ('recomputeLikelihood', False, type=bool,
info="whether to rerun the LH. The default is False, i.e. to use "
"derivation flags.")
self.addOption ('chargeIDSelection', False, type=bool,
info="whether to run the ECIDS tool. The default is False.")
self.addOption ('doFSRSelection', False, type=bool,
info="whether to accept additional electrons close to muons for "
"the purpose of FSR corrections to these muons. Expert feature "
"requested by the H4l analysis running on PHYSLITE. "
"The default is False.")
self.addOption ('noEffSF', False, type=bool,
info="disables the calculation of efficiencies and scale factors. "
"Experimental! only useful to test a new WP for which scale "
"factors are not available. The default is False.")
self.addOption ('forceFullSimConfig', False, type=bool,
info="whether to force the tool to use the configuration meant for "
"full simulation samples. Only for testing purposes. "
"The default is False.")
def makeAlgs (self, config) :
if self.forceFullSimConfig:
print("WARNING! You are running ElectronWorkingPointConfig forcing full sim config")
print("WARNING! This is only intended to be used for testing purposes")
selectionPostfix = self.selectionName
if selectionPostfix != '' and selectionPostfix[0] != '_' :
selectionPostfix = '_' + selectionPostfix
# The setup below is inappropriate for Run 1
if config.geometry() is LHCPeriod.Run1:
raise ValueError ("Can't set up the ElectronWorkingPointConfig with %s, there must be something wrong!" % config.geometry().value)
postfix = self.postfix
if postfix is None :
postfix = self.selectionName
if postfix != '' and postfix[0] != '_' :
postfix = '_' + postfix
# Set up the track selection algorithm:
if self.writeTrackD0Z0 or self.trackSelection :
alg = config.createAlgorithm( 'CP::AsgLeptonTrackSelectionAlg',
'ElectronTrackSelectionAlg' + postfix )
alg.selectionDecoration = 'trackSelection' + postfix + ',as_bits'
alg.maxD0Significance = self.maxD0Significance
alg.maxDeltaZ0SinTheta = self.maxDeltaZ0SinTheta
alg.decorateTTVAVars = self.writeTrackD0Z0
alg.particles = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
if self.trackSelection :
config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration)
if self.writeTrackD0Z0 :
alg.d0sigDecoration = 'd0sig' + postfix
alg.z0sinthetaDecoration = 'z0sintheta' + postfix
config.addOutputVar (self.containerName, alg.d0sigDecoration, alg.d0sigDecoration,noSys=True)
config.addOutputVar (self.containerName, alg.z0sinthetaDecoration, alg.z0sinthetaDecoration,noSys=True)
if 'LH' in self.likelihoodWP:
# Set up the likelihood ID selection algorithm
# It is safe to do this before calibration, as the cluster E is used
alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' + postfix )
alg.selectionDecoration = 'selectLikelihood' + selectionPostfix + ',as_bits'
if self.recomputeLikelihood:
# Rerun the likelihood ID
config.addPrivateTool( 'selectionTool', 'AsgElectronLikelihoodTool' )
alg.selectionTool.primaryVertexContainer = 'PrimaryVertices'
# Here we have to match the naming convention of EGSelectorConfigurationMapping.h
# which differ from the one used for scale factors
if config.geometry() >= LHCPeriod.Run3:
alg.selectionTool.WorkingPoint = self.likelihoodWP.replace("BLayer","BL") + 'Electron'
elif config.geometry() is LHCPeriod.Run2:
alg.selectionTool.WorkingPoint = self.likelihoodWP.replace("BLayer","BL") + 'Electron_Run2'
else:
# Select from Derivation Framework flags
config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
dfFlag = "DFCommonElectronsLH" + self.likelihoodWP.split('LH')[0]
dfFlag = dfFlag.replace("BLayer","BL")
alg.selectionTool.selectionFlags = [dfFlag]
elif 'SiHit' in self.likelihoodWP:
# Only want SiHit electrons, so veto loose LH electrons
algVeto = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlgVeto' + postfix + 'Veto')
algVeto.selectionDecoration = 'selectLikelihoodVeto' + postfix + ',as_bits'
config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' )
algVeto.selectionTool.selectionFlags = ["DFCommonElectronsLHLoose"]
algVeto.selectionTool.invertFlags = [True]
algVeto.particles = config.readName (self.containerName)
algVeto.preselection = config.getPreselection (self.containerName, self.selectionName)
# add in as preselection a veto
config.addSelection (self.containerName, self.selectionName, algVeto.selectionDecoration)
# Select SiHit electrons using IsEM bits
alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronLikelihoodAlg' + postfix )
alg.selectionDecoration = 'selectSiHit' + selectionPostfix + ',as_bits'
# Select from Derivation Framework IsEM bits
config.addPrivateTool( 'selectionTool', 'CP::AsgMaskSelectionTool' )
dfVar = "DFCommonElectronsLHLooseBLIsEMValue"
alg.selectionTool.selectionVars = [dfVar]
mask = int( 0 | 0x1 << 1 | 0x1 << 2)
alg.selectionTool.selectionMasks = [mask]
# Set up the ElectronSiHitDecAlg algorithm to decorate SiHit electrons with a minimal amount of information:
algDec = config.createAlgorithm( 'CP::ElectronSiHitDecAlg', 'ElectronSiHitDecAlg' + postfix )
selDec = 'siHitEvtHasLeptonPair' + selectionPostfix + ',as_bits'
algDec.selectionName = selDec.split(",")[0]
algDec.ElectronContainer = config.readName (self.containerName)
# Set flag to only collect SiHit electrons for events with an electron or muon pair to minimize size increase from SiHit electrons
algDec.RequireTwoLeptons = True
config.addSelection (self.containerName, self.selectionName, selDec)
elif 'DNN' in self.likelihoodWP:
# Set up the DNN ID selection algorithm
alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDNNAlg' + postfix )
alg.selectionDecoration = 'selectDNN' + selectionPostfix + ',as_bits'
if self.recomputeLikelihood:
# Rerun the DNN ID
config.addPrivateTool( 'selectionTool', 'AsgElectronSelectorTool' )
# Here we have to match the naming convention of EGSelectorConfigurationMapping.h
if config.geometry() is LHCPeriod.Run3:
raise ValueError ( "DNN working points are not available for Run 3 yet.")
else:
alg.selectionTool.WorkingPoint = self.likelihoodWP + 'Electron'
else:
# Select from Derivation Framework flags
raise ValueError ( "DNN working points are not available in derivations yet.")
alg.particles = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, self.selectionName)
config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration)
# Set up the FSR selection
if self.doFSRSelection :
# save the flag set for the WP
wpFlag = alg.selectionDecoration.split(",")[0]
alg = config.createAlgorithm( 'CP::EgammaFSRForMuonsCollectorAlg', 'EgammaFSRForMuonsCollectorAlg' + postfix )
alg.selectionDecoration = wpFlag
alg.ElectronOrPhotonContKey = config.readName (self.containerName)
# For SiHit electrons, set flag to remove FSR electrons.
# For standard electrons, FSR electrons need to be added as they may be missed by the standard selection.
# For SiHit electrons FSR electrons are generally always selected, so they should be removed since they will be in the standard electron container.
if 'SiHit' in self.likelihoodWP:
alg.vetoFSR = True
# Set up the isolation selection algorithm:
if self.isolationWP != 'NonIso' :
alg = config.createAlgorithm( 'CP::EgammaIsolationSelectionAlg',
'ElectronIsolationSelectionAlg' + postfix )
alg.selectionDecoration = 'isolated' + selectionPostfix + ',as_bits'
config.addPrivateTool( 'selectionTool', 'CP::IsolationSelectionTool' )
alg.selectionTool.ElectronWP = self.isolationWP
alg.egammas = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, self.selectionName)
config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration)
# Select electrons only if they don't appear to have flipped their charge.
if self.chargeIDSelection:
alg = config.createAlgorithm( 'CP::AsgSelectionAlg',
'ElectronChargeIDSelectionAlg' + postfix )
alg.selectionDecoration = 'chargeID' + selectionPostfix + ',as_bits'
config.addPrivateTool( 'selectionTool',
'AsgElectronChargeIDSelectorTool' )
alg.selectionTool.TrainingFile = \
'ElectronPhotonSelectorTools/ChargeID/ECIDS_20180731rel21Summer2018.root'
alg.selectionTool.WorkingPoint = 'Loose'
alg.selectionTool.CutOnBDT = -0.337671 # Loose 97%
alg.particles = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, self.selectionName)
config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration)
# Set up the RECO electron efficiency correction algorithm:
if config.dataType() is not DataType.Data and not self.noEffSF:
alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
'ElectronEfficiencyCorrectionAlgReco' + postfix )
config.addPrivateTool( 'efficiencyCorrectionTool',
'AsgElectronEfficiencyCorrectionTool' )
alg.scaleFactorDecoration = 'el_reco_effSF' + selectionPostfix + '_%SYS%'
alg.efficiencyCorrectionTool.RecoKey = "Reconstruction"
alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
if config.dataType() is DataType.FastSim:
alg.efficiencyCorrectionTool.ForceDataType = (
PATCore.ParticleDataType.Full if self.forceFullSimConfig
else PATCore.ParticleDataType.Fast)
elif config.dataType() is DataType.FullSim:
alg.efficiencyCorrectionTool.ForceDataType = \
PATCore.ParticleDataType.Full
if config.geometry() is LHCPeriod.Run2:
alg.efficiencyCorrectionTool.MapFilePath = "ElectronEfficiencyCorrection/2015_2018/rel21.2/Precision_Summer2020_v1/map4.txt"
alg.outOfValidity = 2 #silent
alg.outOfValidityDeco = 'el_reco_bad_eff' + selectionPostfix
alg.electrons = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, self.selectionName)
config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'reco_effSF' + postfix)
# Set up the ID electron efficiency correction algorithm:
if config.dataType() is not DataType.Data and not self.noEffSF:
alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
'ElectronEfficiencyCorrectionAlgID' + postfix )
config.addPrivateTool( 'efficiencyCorrectionTool',
'AsgElectronEfficiencyCorrectionTool' )
alg.scaleFactorDecoration = 'el_id_effSF' + selectionPostfix + '_%SYS%'
alg.efficiencyCorrectionTool.IdKey = self.likelihoodWP.replace("LH","")
alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
if config.dataType() is DataType.FastSim:
alg.efficiencyCorrectionTool.ForceDataType = (
PATCore.ParticleDataType.Full if self.forceFullSimConfig
else PATCore.ParticleDataType.Fast)
elif config.dataType() is DataType.FullSim:
alg.efficiencyCorrectionTool.ForceDataType = \
PATCore.ParticleDataType.Full
if config.geometry() is LHCPeriod.Run2:
alg.efficiencyCorrectionTool.MapFilePath = "ElectronEfficiencyCorrection/2015_2018/rel21.2/Precision_Summer2020_v1/map4.txt"
alg.outOfValidity = 2 #silent
alg.outOfValidityDeco = 'el_id_bad_eff' + selectionPostfix
alg.electrons = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, self.selectionName)
config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'id_effSF' + postfix)
# Set up the ISO electron efficiency correction algorithm:
if config.dataType() is not DataType.Data and self.isolationWP != 'NonIso' and not self.noEffSF:
alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg',
'ElectronEfficiencyCorrectionAlgIsol' + postfix )
config.addPrivateTool( 'efficiencyCorrectionTool',
'AsgElectronEfficiencyCorrectionTool' )
alg.scaleFactorDecoration = 'el_isol_effSF' + selectionPostfix + '_%SYS%'
alg.efficiencyCorrectionTool.IdKey = self.likelihoodWP.replace("LH","")
alg.efficiencyCorrectionTool.IsoKey = self.isolationWP
alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
if config.dataType() is DataType.FastSim:
alg.efficiencyCorrectionTool.ForceDataType = (
PATCore.ParticleDataType.Full if self.forceFullSimConfig
else PATCore.ParticleDataType.Fast)
elif config.dataType() is DataType.FullSim:
alg.efficiencyCorrectionTool.ForceDataType = \
PATCore.ParticleDataType.Full
if config.geometry() is LHCPeriod.Run2:
alg.efficiencyCorrectionTool.MapFilePath = "ElectronEfficiencyCorrection/2015_2018/rel21.2/Precision_Summer2020_v1/map4.txt"
alg.efficiencyCorrectionTool.CorrelationModel = "SIMPLIFIED" # remove when Run 2 R25 recommendations are available!
alg.outOfValidity = 2 #silent
alg.outOfValidityDeco = 'el_isol_bad_eff' + selectionPostfix
alg.electrons = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, self.selectionName)
config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'isol_effSF' + postfix)
# TO-DO: add trigger SFs, for which we need ID key + ISO key + Trigger key !
if self.chargeIDSelection:
# ECIDS is currently not supported in R22.
# SFs might become available or it will be part of the DNN ID.
pass
def makeElectronCalibrationConfig( seq, containerName, postfix = None,
crackVeto = None,
isolationCorrection = None,
forceFullSimConfig = None):
"""Create electron calibration configuration blocks
This makes all the algorithms that need to be run first befor
all working point specific algorithms and that can be shared
between the working points.
Keyword arguments:
postfix -- a postfix to apply to decorations and algorithm
names. this is mostly used/needed when using this
sequence with multiple working points to ensure all
names are unique.
isolationCorrection -- Whether or not to perform isolation correction
forceFullSimConfig -- imposes full-sim config for FastSim for testing
"""
config = ElectronCalibrationConfig (containerName)
config.setOptionValue ('crackVeto', crackVeto)
config.setOptionValue ('isolationCorrection', isolationCorrection)
config.setOptionValue ('forceFullSimConfig', forceFullSimConfig)
seq.append (config)
def makeElectronWorkingPointConfig( seq, containerName, workingPoint,
selectionName,
recomputeLikelihood = None,
chargeIDSelection = None,
noEffSF = None,
forceFullSimConfig = None):
"""Create electron analysis configuration blocks
Keyword arguments:
workingPoint -- The working point to use
selectionName -- a postfix to apply to decorations and algorithm
names. this is mostly used/needed when using this
sequence with multiple working points to ensure all
names are unique.
recomputeLikelihood -- Whether to rerun the LH. If not, use derivation flags
chargeIDSelection -- Whether or not to perform charge ID/flip selection
noEffSF -- Disables the calculation of efficiencies and scale factors
forceFullSimConfig -- imposes full-sim config for FastSim for testing
"""
config = ElectronWorkingPointConfig (containerName, selectionName)
if workingPoint is not None :
splitWP = workingPoint.split ('.')
if len (splitWP) != 2 :
raise ValueError ('working point should be of format "likelihood.isolation", not ' + workingPoint)
config.setOptionValue ('likelihoodWP', splitWP[0])
config.setOptionValue ('isolationWP', splitWP[1])
config.setOptionValue ('recomputeLikelihood', recomputeLikelihood)
config.setOptionValue ('chargeIDSelection', chargeIDSelection)
config.setOptionValue ('noEffSF', noEffSF)
config.setOptionValue ('forceFullSimConfig', forceFullSimConfig)
seq.append (config)
class ElectronTriggerAnalysisSFBlock (ConfigBlock):
def __init__ (self, configName='') :
super (ElectronTriggerAnalysisSFBlock, self).__init__ ()
self.addOption ('triggerChainsPerYear', {}, type=None,
info="a dictionary with key (string) the year and value (list of "
"strings) the trigger chains. The default is {} (empty dictionary).")
self.addOption ('electronID', '', type=str,
info="the electron ID WP (string) to use.")
self.addOption ('electronIsol', '', type=str,
info="the electron isolation WP (string) to use.")
self.addOption ('saveEff', False, type=bool,
info="define whether we decorate also the trigger scale efficiency "
"The default is false.")
self.addOption ('containerName', '', type=str,
info="the input electron container, with a possible selection, in "
"the format container or container.selection.")
def makeAlgs (self, config) :
if config.dataType() is not DataType.Data:
# Dictionary from TrigGlobalEfficiencyCorrection/Triggers.cfg
# Key is trigger chain (w/o HLT prefix)
# Value is empty for single leg trigger or list of legs
triggerDict = TriggerDict()
version = ("2015_2018/rel21.2/Precision_Summer2020_v1"
if config.geometry() is LHCPeriod.Run2 else
"2015_2025/rel22.2/2022_Summer_Prerecom_v1")
# Dictionary from TrigGlobalEfficiencyCorrection/MapKeys.cfg
# Key is year_leg
# Value is list of configs available, first one will be used
mapKeysDict = MapKeysDict(version)
if config.campaign() is Campaign.MC20a:
years = ['2015', '2016']
elif config.campaign() is Campaign.MC20d:
years = ['2017']
elif config.campaign() is Campaign.MC20e:
years = ['2018']
elif config.campaign() in [Campaign.MC21a, Campaign.MC23a]:
years = ['2022']
elif config.campaign() in [Campaign.MC23c, Campaign.MC23d]:
years = ['2023']
triggerConfigs = {}
for year in years:
triggerChains = self.triggerChainsPerYear.get(year,[])
for chain in triggerChains:
chain = chain.replace("HLT_", "").replace(" || ", "_OR_")
legs = triggerDict[chain]
if len(legs)==0:
if chain[0]=='e' and chain[1].isdigit:
triggerConfigs[chain] = mapKeysDict[year + '_' + chain]
else:
for leg in legs:
if leg[0]=='e' and leg[1].isdigit:
triggerConfigs[leg] = mapKeysDict[year + '_' + leg]
decorations = ['EffSF']
if self.saveEff:
decorations += ['Eff']
for trig, conf in triggerConfigs.items():
for deco in decorations:
alg = config.createAlgorithm('CP::ElectronEfficiencyCorrectionAlg',
'EleTrigEfficiencyCorrectionsAlg' + deco +
'_' + trig)
config.addPrivateTool( 'efficiencyCorrectionTool',
'AsgElectronEfficiencyCorrectionTool' )
# Reproduce config from TrigGlobalEfficiencyAlg
alg.efficiencyCorrectionTool.MapFilePath = "ElectronEfficiencyCorrection/" + version + "/map4.txt"
alg.efficiencyCorrectionTool.IdKey = self.electronID.replace("LH","")
alg.efficiencyCorrectionTool.IsoKey = self.electronIsol
alg.efficiencyCorrectionTool.TriggerKey = (
("Eff_" if "SF" not in deco else "") + conf[0])
alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL"
alg.efficiencyCorrectionTool.ForceDataType = \
PATCore.ParticleDataType.Full
alg.scaleFactorDecoration = (
'el_trig' + deco + '_' + trig + '_%SYS%')
alg.outOfValidity = 2 #silent
alg.outOfValidityDeco = 'bad_eff_eletrig' + deco + '_' + trig
alg.electrons = config.readName (self.containerName)
alg.preselection = config.getPreselection (self.containerName, '')
config.addOutputVar (self.containerName, alg.scaleFactorDecoration, 'trig' + deco + '_' + trig)