# 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)