diff --git a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py index 8bcfc7d5a589020f1e95ac2d61f39da7fef037df..2aae666d7d4056f5fd5599c47923451546ad65cd 100644 --- a/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py +++ b/PhysicsAnalysis/Algorithms/AnalysisAlgorithmsConfig/python/FullCPAlgorithmsTest.py @@ -15,6 +15,8 @@ triggerChains = [ 'HLT_2e17_lhvloose_nod0' ] +electronMinPt = 10e3 +electronMaxEta = None muonMinPt = None muonMaxEta = None @@ -141,11 +143,12 @@ def makeSequenceOld (dataType, algSeq, vars, forCompare) : electronSequence.configure( inputName = 'Electrons', outputName = 'AnaElectrons_%SYS%' ) algSeq += electronSequence - if not forCompare : - vars += [ 'OutElectrons_%SYS%.pt -> el_pt_%SYS%', - 'OutElectrons_NOSYS.phi -> el_phi', - 'OutElectrons_NOSYS.eta -> el_eta', - 'OutElectrons_%SYS%.baselineSelection_loose -> el_select_loose_%SYS%', ] + vars += [ 'OutElectrons_%SYS%.pt -> el_pt_%SYS%', + 'OutElectrons_NOSYS.phi -> el_phi', + 'OutElectrons_NOSYS.eta -> el_eta', + 'OutElectrons_%SYS%.baselineSelection_loose -> el_select_loose_%SYS%', ] + if dataType != 'data': + vars += [ 'OutElectrons_%SYS%.effSF_loose_%SYS% -> el_effSF_loose_%SYS%', ] # Include, and then set up the photon analysis sequence: @@ -195,8 +198,10 @@ def makeSequenceOld (dataType, algSeq, vars, forCompare) : selalg = createAlgorithm( 'CP::AsgSelectionAlg', 'UserElectronsSelectionAlg' ) addPrivateTool( selalg, 'selectionTool', 'CP::AsgPtEtaSelectionTool' ) - selalg.selectionTool.minPt = 10e3 - #selalg.selectionTool.maxEta = 2.47 + if electronMinPt : + selalg.selectionTool.minPt = electronMinPt + if electronMaxEta : + selalg.selectionTool.maxEta = electronMaxEta selalg.selectionDecoration = 'selectPtEta' selalg.particles = 'AnaElectrons_%SYS%' algSeq += selalg @@ -407,11 +412,32 @@ def makeSequenceBlocks (dataType, algSeq, vars, forCompare) : 'EventInfo.eventNumber -> eventNumber', ] + configSeq = ConfigSequence () + + + # Include, and then set up the electron analysis algorithm sequence: + from EgammaAnalysisAlgorithms.ElectronAnalysisConfig import makeElectronCalibrationConfig, makeElectronWorkingPointConfig + + likelihood = True + recomputeLikelihood=False + if likelihood: + workingpoint = 'LooseLHElectron.Loose_VarRad' + else: + workingpoint = 'LooseDNNElectron.Loose_VarRad' + makeElectronCalibrationConfig (configSeq, 'AnaElectrons') + makeElectronWorkingPointConfig (configSeq, 'AnaElectrons', workingpoint, postfix = 'loose', + recomputeLikelihood=recomputeLikelihood) + vars += [ 'OutElectrons_NOSYS.eta -> el_eta', + 'OutElectrons_NOSYS.phi -> el_phi', + 'OutElectrons_%SYS%.pt -> el_pt_%SYS%', + 'OutElectrons_%SYS%.baselineSelection_loose -> el_select_loose_%SYS%', ] + if dataType != 'data': + vars += [ 'OutElectrons_%SYS%.effSF_loose_%SYS% -> el_effSF_loose_%SYS%', ] + + # Include, and then set up the muon analysis algorithm sequence: from MuonAnalysisAlgorithms.MuonAnalysisConfig import makeMuonCalibrationConfig, makeMuonWorkingPointConfig - configSeq = ConfigSequence () - makeMuonCalibrationConfig (configSeq, 'AnaMuons') makeMuonWorkingPointConfig (configSeq, 'AnaMuons', workingPoint='Medium.Iso', postfix='medium') makeMuonWorkingPointConfig (configSeq, 'AnaMuons', workingPoint='Tight.Iso', postfix='tight') @@ -427,6 +453,17 @@ def makeSequenceBlocks (dataType, algSeq, vars, forCompare) : configAccumulator = ConfigAccumulator (dataType, algSeq) configSeq.fullConfigure (configAccumulator) + selalg = createAlgorithm( 'CP::AsgSelectionAlg', 'UserElectronsSelectionAlg' ) + addPrivateTool( selalg, 'selectionTool', 'CP::AsgPtEtaSelectionTool' ) + if electronMinPt : + selalg.selectionTool.minPt = electronMinPt + if electronMaxEta : + selalg.selectionTool.maxEta = electronMaxEta + selalg.selectionDecoration = 'selectPtEta' + selalg.particles = 'AnaElectrons_%SYS%' + algSeq += selalg + addOutputCopyAlgorithms (algSeq, 'Electrons', 'AnaElectrons_%SYS%', 'OutElectrons_%SYS%', + 'selectPtEta&&baselineSelection_loose,as_char') selalg = createAlgorithm( 'CP::AsgSelectionAlg', 'UserMuonsSelectionAlg' ) addPrivateTool( selalg, 'selectionTool', 'CP::AsgPtEtaSelectionTool' ) diff --git a/PhysicsAnalysis/Algorithms/EgammaAnalysisAlgorithms/python/ElectronAnalysisConfig.py b/PhysicsAnalysis/Algorithms/EgammaAnalysisAlgorithms/python/ElectronAnalysisConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..00132f0d32847e236c5e56f7c9cdfc5febc87517 --- /dev/null +++ b/PhysicsAnalysis/Algorithms/EgammaAnalysisAlgorithms/python/ElectronAnalysisConfig.py @@ -0,0 +1,273 @@ +# Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration + +# AnaAlgorithm import(s): +from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock + +# 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, postfix) : + super (ElectronCalibrationConfig, self).__init__ () + self.containerName = containerName + self.postfix = postfix + self.crackVeto = False + self.ptSelectionOutput = False + self.isolationCorrection = False + + + def makeAlgs (self, config) : + + # Set up a shallow copy to decorate + if config.wantCopy (self.containerName, 'Electrons') : + 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, + bits=(5 if self.crackVeto else 4)) + + # Set up the track selection algorithm: + alg = config.createAlgorithm( 'CP::AsgLeptonTrackSelectionAlg', + 'ElectronTrackSelectionAlg' + self.postfix ) + alg.selectionDecoration = 'trackSelection' + self.postfix + ',as_bits' + alg.maxD0Significance = 5 + alg.maxDeltaZ0SinTheta = 0.5 + alg.particles = config.readName (self.containerName) + alg.preselection = config.getPreselection (self.containerName, '') + config.addSelection (self.containerName, '', alg.selectionDecoration, + bits=3) + + # 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, + bits=1) + + # Set up the calibration and smearing algorithm: + alg = config.createAlgorithm( 'CP::EgammaCalibrationAndSmearingAlg', + 'ElectronCalibrationAndSmearingAlg' + self.postfix ) + config.addPrivateTool( 'calibrationAndSmearingTool', + 'CP::EgammaCalibrationAndSmearingTool' ) + alg.calibrationAndSmearingTool.ESModel = 'es2022_R22_PRE' + alg.calibrationAndSmearingTool.decorrelationModel = '1NP_v1' + if config.dataType() == 'afii': + alg.calibrationAndSmearingTool.useAFII = 1 + else: + alg.calibrationAndSmearingTool.useAFII = 0 + alg.egammas = config.readName (self.containerName) + alg.egammasOut = config.copyName (self.containerName) + alg.preselection = config.getPreselection (self.containerName, '') + + # 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 = 4.5e3 + alg.particles = config.readName (self.containerName) + alg.preselection = config.getPreselection (self.containerName, '') + config.addSelection (self.containerName, '', alg.selectionDecoration, + bits=2, preselection=self.ptSelectionOutput) + + # Set up the isolation correction algorithm: + if self.isolationCorrection: + alg = config.createAlgorithm( 'CP::EgammaIsolationCorrectionAlg', + 'ElectronIsolationCorrectionAlg' + self.postfix ) + config.addPrivateTool( 'isolationCorrectionTool', + 'CP::IsolationCorrectionTool' ) + if config.dataType() == 'data': + alg.isolationCorrectionTool.IsMC = 0 + else: + alg.isolationCorrectionTool.IsMC = 1 + alg.egammas = config.readName (self.containerName) + alg.egammasOut = config.readName (self.containerName) + alg.preselection = config.getPreselection (self.containerName, '') + + + +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, postfix, likelihoodWP, isolationWP) : + super (ElectronWorkingPointConfig, self).__init__ () + self.containerName = containerName + self.selectionName = postfix + self.postfix = postfix + if self.postfix != '' and self.postfix[0] != '_' : + self.postfix = '_' + self.postfix + self.likelihoodWP = likelihoodWP + self.isolationWP = isolationWP + self.recomputeLikelihood = False + self.chargeIDSelection = False + + def makeAlgs (self, config) : + + 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' + self.postfix ) + alg.selectionDecoration = 'selectLikelihood' + self.postfix + ',as_bits' + if self.recomputeLikelihood: + # Rerun the likelihood ID + config.addPrivateTool( 'selectionTool', 'AsgElectronLikelihoodTool' ) + alg.selectionTool.primaryVertexContainer = 'PrimaryVertices' + alg.selectionTool.WorkingPoint = self.likelihoodWP + algDecorCount = 7 + else: + # Select from Derivation Framework flags + config.addPrivateTool( 'selectionTool', 'CP::AsgFlagSelectionTool' ) + dfFlag = "DFCommonElectronsLH" + self.likelihoodWP.split('LH')[0] + alg.selectionTool.selectionFlags = [dfFlag] + algDecorCount = 1 + else: + # Set up the DNN ID selection algorithm + alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'ElectronDNNAlg' + self.postfix ) + alg.selectionDecoration = 'selectDNN' + self.postfix + ',as_bits' + if self.recomputeLikelihood: + # Rerun the DNN ID + config.addPrivateTool( 'selectionTool', 'AsgElectronSelectorTool' ) + alg.selectionTool.WorkingPoint = self.likelihoodWP + algDecorCount = 6 + 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, + bits=algDecorCount) + + # Set up the isolation selection algorithm: + if self.isolationWP != 'NonIso' : + alg = config.createAlgorithm( 'CP::EgammaIsolationSelectionAlg', + 'ElectronIsolationSelectionAlg' + self.postfix ) + alg.selectionDecoration = 'isolated' + self.postfix + ',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, + bits=1) + + # Select electrons only if they don't appear to have flipped their charge. + if self.chargeIDSelection: + alg = config.createAlgorithm( 'CP::AsgSelectionAlg', + 'ElectronChargeIDSelectionAlg' + self.postfix ) + alg.selectionDecoration = 'chargeID' + self.postfix + ',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, + bits=1) + + # Set up an algorithm used for decorating baseline electron selection: + alg = config.createAlgorithm( 'CP::AsgSelectionAlg', + 'ElectronSelectionSummary' + self.postfix ) + alg.selectionDecoration = 'baselineSelection' + self.postfix + ',as_char' + alg.particles = config.readName (self.containerName) + alg.preselection = config.getFullSelection (self.containerName, self.selectionName) + + # Set up the electron efficiency correction algorithm: + if config.dataType() != 'data': + alg = config.createAlgorithm( 'CP::ElectronEfficiencyCorrectionAlg', + 'ElectronEfficiencyCorrectionAlg' + self.postfix ) + config.addPrivateTool( 'efficiencyCorrectionTool', + 'AsgElectronEfficiencyCorrectionTool' ) + alg.scaleFactorDecoration = 'effSF' + self.postfix + '_%SYS%' + alg.efficiencyCorrectionTool.RecoKey = "Reconstruction" + alg.efficiencyCorrectionTool.CorrelationModel = "TOTAL" + if config.dataType() == 'afii': + alg.efficiencyCorrectionTool.ForceDataType = \ + PATCore.ParticleDataType.Fast + elif config.dataType() == 'mc': + alg.efficiencyCorrectionTool.ForceDataType = \ + PATCore.ParticleDataType.Full + alg.outOfValidity = 2 #silent + alg.outOfValidityDeco = 'bad_eff' + self.postfix + alg.electrons = config.readName (self.containerName) + alg.preselection = config.getPreselection (self.containerName, self.selectionName) + + + + +def makeElectronCalibrationConfig( seq, containerName, postfix = '', + crackVeto = False, + ptSelectionOutput = False, + isolationCorrection = False): + """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 + ptSelectionOutput -- Whether or not to apply pt selection when creating + output containers. + """ + + config = ElectronCalibrationConfig (containerName, postfix) + config.crackVeto = crackVeto + config.ptSelectionOutput = ptSelectionOutput + config.isolationCorrection = isolationCorrection + seq.append (config) + + + + + +def makeElectronWorkingPointConfig( seq, containerName, workingPoint, + postfix = '', + recomputeLikelihood = False, + chargeIDSelection = False ): + """Create electron analysis configuration blocks + + Keyword arguments: + workingPoint -- The working point to use + 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. + recomputeLikelihood -- Whether to rerun the LH. If not, use derivation flags + chargeIDSelection -- Whether or not to perform charge ID/flip selection + """ + + splitWP = workingPoint.split ('.') + if len (splitWP) != 2 : + raise ValueError ('working point should be of format "likelihood.isolation", not ' + workingPoint) + + config = ElectronWorkingPointConfig (containerName, postfix, splitWP[0], splitWP[1]) + config.recomputeLikelihood = False + config.chargeIDSelection = False + seq.append (config)