ATLAS Offline Software
Loading...
Searching...
No Matches
MuonAnalysisConfig.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3# AnaAlgorithm import(s):
4from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
5from AnalysisAlgorithmsConfig.ConfigSequence import groupBlocks
6from AthenaCommon.SystemOfUnits import GeV
7from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType
8from TrackingAnalysisAlgorithms.TrackingAnalysisConfig import InDetTrackCalibrationConfig
9from AthenaConfiguration.Enums import LHCPeriod
10from TrigGlobalEfficiencyCorrection.TriggerLeg_DictHelpers import TriggerDict
11from Campaigns.Utils import Campaign
12from AthenaCommon.Logging import logging
13
14
16 """the ConfigBlock for the muon four-momentum correction"""
17
18 def __init__ (self) :
19 super (MuonMomentumCalibrationConfig, self).__init__ ()
20 self.setBlockName('Muons')
21 self.addOption ('inputContainer', '', type=str,
22 info="the name of the input muon container. If left empty, automatically defaults "
23 "to `AnalysisMuons` for PHYSLITE or `Muons` otherwise.")
24 self.addOption ('containerName', '', type=str,
25 noneAction='error',
26 info="the name of the output container after calibration.")
27 self.addOption ('postfix', "", type=str,
28 info="a postfix to apply to decorations and algorithm names. "
29 "Typically not needed here since the calibration is common to "
30 "all muons.")
31 self.addOption ('minPt', 3.0*GeV, type=float,
32 info=r"$p_\mathrm{T}$ cut (in MeV) to apply to calibrated muons.")
33 self.addOption ('recalibratePhyslite', True, type=bool,
34 info="whether to run the `CP::MuonCalibrationAndSmearingAlg` on "
35 "PHYSLITE derivations.")
36 self.addOption ('maxEta', 2.7, type=float,
37 info=r"maximum muon $\vert\eta\vert$.")
38 self.addOption ('excludeNSWFromPrecisionLayers', False, type=bool,
39 info="only for testing purposes, turn on to ignore NSW hits and "
40 "fix a crash with older derivations (p-tag <p5834).")
41 self.addOption ('calibMode', 'correctData_CB', type=str, info='calibration mode of the `MuonCalibTool` needed to turn on the sagitta bias corrections and to select the muon track calibration type (CB or ID+MS), see https://atlas-mcp.docs.cern.ch/guidelines/muonmomentumcorrections/index.html#cpmuoncalibtool-tool.')
42 self.addOption ('useZeroPixMuons', False, type=bool, info='if True, a second `MuonCalibTool` instance with calibMode=correctData_MSonly is scheduled and applied only to ZeroPixelHit muons.')
43 self.addOption ('zeroPixMuonType', None, type=int, info='muonType value used for the ZeroPix calibration tool. If left as None, the default xAOD::Muon::MuonType::ZeroPixelHit is used.')
44 self.addOption ('decorateTruth', False, type=bool,
45 info="decorate truth particle information on the reconstructed one.")
46 self.addOption ('writeColumnarToolVariables', False, type=bool,
47 info="whether to add variables needed for running the columnar muon tool(s) on the output n-tuple (EXPERIMENTAL).",
48 expertMode=True)
49 self.addOption ('addGlobalFELinksDep', False, type=bool,
50 info="whether to add dependencies for the global FE links (needed for PHYSLITE production)",
51 expertMode=True)
52
53 def instanceName (self) :
54 if self.postfix != "":
55 return self.postfix
56 else :
57 return self.containerName
58
59 def makeAlgs (self, config) :
60
61 log = logging.getLogger('MuonCalibrationConfig')
62
63 #make sure that this is sync with
64 #PhysicsAnalysis/MuonID/MuonIDAnalysis/MuonMomentumCorrections/MuonMomentumCorrections/MuonCalibTool.h#L31-37
65 if self.calibMode == 'correctData_CB':
66 calibMode = 0
67 elif self.calibMode == 'correctData_IDMS':
68 calibMode = 1
69 elif self.calibMode == 'notCorrectData_IDMS':
70 calibMode = 2
71 elif self.calibMode == 'notCorrectData_CB':
72 calibMode = 3
73 elif self.calibMode == 'correctData_IDonly':
74 calibMode = 4
75 elif self.calibMode == 'correctData_MSonly':
76 calibMode = 5
77 else :
78 raise ValueError ("invalid calibMode: \"" + self.calibMode + "\". Allowed values are correctData_CB, correctData_IDMS, notCorrectData_IDMS, notCorrectData_CB, correctData_IDonly, correctData_MSonly")
79
80 inputContainer = "AnalysisMuons" if config.isPhyslite() else "Muons"
82 inputContainer = self.inputContainer
83 config.setSourceName (self.containerName, inputContainer)
84 config.setContainerMeta (self.containerName, 'calibMode', calibMode)
85
86 # Set up a shallow copy to decorate
87 if config.wantCopy (self.containerName) :
88 alg = config.createAlgorithm( 'CP::AsgShallowCopyAlg', 'MuonShallowCopyAlg' )
89 alg.input = config.readName (self.containerName)
90 alg.output = config.copyName (self.containerName)
91 alg.outputType = 'xAOD::MuonContainer'
92 decorationList = ['DFCommonJetDr',
93 'DFCommonMuonPassIDCuts',
94 'DFCommonMuonPassPreselection',
95 'neflowisol20_CloseByCorr',
96 'ptvarcone30_Nonprompt_All_MaxWeightTTVA_pt1000_CloseByCorr',
97 'ptvarcone30_Nonprompt_All_MaxWeightTTVA_pt500_CloseByCorr',
98 'topoetcone20_CloseByCorr']
100 decorationList += ['neutralGlobalFELinks', 'chargedGlobalFELinks']
101 if config.dataType() is not DataType.Data:
102 decorationList += ['TruthLink']
103 if self.addGlobalFELinksDep or config.dataType() is not DataType.Data:
104 alg.declareDecorations = decorationList
105
106 # Set up the eta-cut on all muons prior to everything else
107 alg = config.createAlgorithm( 'CP::AsgSelectionAlg',
108 'MuonEtaCutAlg' )
109 config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
110 alg.selectionTool.maxEta = self.maxEta
111 alg.selectionDecoration = 'selectEta' + self.postfix + ',as_bits'
112 alg.particles = config.readName (self.containerName)
113 alg.preselection = config.getPreselection (self.containerName, '')
114 config.addSelection (self.containerName, '', alg.selectionDecoration)
115
116 # Set up the muon calibration and smearing algorithm:
117 alg = config.createAlgorithm( 'CP::MuonCalibrationAndSmearingAlg',
118 'MuonCalibrationAndSmearingAlg' )
119 config.addPrivateTool( 'calibrationAndSmearingTool',
120 'CP::MuonCalibTool' )
121
122 alg.calibrationAndSmearingTool.IsRun3Geo = config.geometry() >= LHCPeriod.Run3
123 alg.calibrationAndSmearingTool.calibMode = calibMode
124 if config.geometry() is LHCPeriod.Run4:
125 log.warning("Disabling NSW hits for Run4 geometry")
126 alg.calibrationAndSmearingTool.ExcludeNSWFromPrecisionLayers = True
127 else:
128 alg.calibrationAndSmearingTool.ExcludeNSWFromPrecisionLayers = self.excludeNSWFromPrecisionLayers and (config.geometry() >= LHCPeriod.Run3)
129
130 # Optionally set up a second calibration tool applied only to ZPH muons
131 # The calibMode is set to 'correctData_MSonly'.
133 config.addPrivateTool( 'calibrationAndSmearingTool_ZeroPix',
134 'CP::MuonCalibTool' )
135 alg.calibrationAndSmearingTool_ZeroPix.IsRun3Geo = config.geometry() >= LHCPeriod.Run3
136 alg.calibrationAndSmearingTool_ZeroPix.calibMode = 5 # correctData_MSonly
137 alg.calibrationAndSmearingTool_ZeroPix.ExcludeNSWFromPrecisionLayers = alg.calibrationAndSmearingTool.ExcludeNSWFromPrecisionLayers
138 if self.zeroPixMuonType is not None:
139 alg.zeroPixMuonType = self.zeroPixMuonType
140
141 alg.muons = config.readName (self.containerName)
142 alg.muonsOut = config.copyName (self.containerName)
143 alg.preselection = config.getPreselection (self.containerName, '')
144 if config.isPhyslite() and not self.recalibratePhyslite :
145 alg.skipNominal = True
146
147 # Set up the the pt selection
148 if self.minPt > 0:
149 alg = config.createAlgorithm( 'CP::AsgSelectionAlg', 'MuonPtCutAlg' )
150 alg.selectionDecoration = 'selectPt' + self.postfix + ',as_bits'
151 config.addPrivateTool( 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
152 alg.particles = config.readName (self.containerName)
153 alg.selectionTool.minPt = self.minPt
154 alg.preselection = config.getPreselection (self.containerName, '')
155 config.addSelection (self.containerName, '', alg.selectionDecoration,
156 preselection = True)
157
158 alg = config.createAlgorithm( 'CP::AsgEnergyDecoratorAlg', 'EnergyDecorator' )
159 alg.particles = config.readName (self.containerName)
160
161 config.addOutputVar (self.containerName, 'pt', 'pt')
162 config.addOutputVar (self.containerName, 'eta', 'eta', noSys=True)
163 config.addOutputVar (self.containerName, 'phi', 'phi', noSys=True)
164 config.addOutputVar (self.containerName, 'e_%SYS%', 'e')
165 config.addOutputVar (self.containerName, 'charge', 'charge', noSys=True)
166
167 # decorate truth information on the reconstructed object:
168 if self.decorateTruth and config.dataType() is not DataType.Data:
169 config.addOutputVar (self.containerName, "truthType", "truth_type", noSys=True)
170 config.addOutputVar (self.containerName, "truthOrigin", "truth_origin", noSys=True)
171
172 config.addOutputVar (self.containerName, 'muonType', 'muonType', noSys=True, enabled=self.writeColumnarToolVariables)
173
174
175class MuonIPCalibrationConfig (ConfigBlock) :
176 """the ConfigBlock for the muon impact parameter correction"""
177
178 def __init__ (self) :
179 super (MuonIPCalibrationConfig, self).__init__ ()
180 self.setBlockName('MuonIPCalibration')
181 self.addDependency('Muons', required=True)
182 self.addDependency('MuonWorkingPointSelection', required=False)
183 self.addOption ('containerName', '', type=str,
184 noneAction='error',
185 info="the name of the output container after calibration.")
186 self.addOption ('postfix', "", type=str,
187 info="a postfix to apply to decorations and algorithm names. "
188 "Typically not needed here since the calibration is common to "
189 "all muons.")
190 self.addOption ('writeTrackD0Z0', False, type = bool,
191 info=r"save the $d_0$ significance and $z_0\sin\theta$ variables.")
192 self.addOption ('runTrackBiasing', False, type=bool,
193 info="This enables the `InDetTrackBiasingTool`, for tracks "
194 "associated to muons")
195
196 def instanceName (self) :
197 return self.containerName + self.postfix
198
199 def makeAlgs (self, config) :
200
201 # Additional decorations
203 alg = config.createAlgorithm( 'CP::AsgLeptonTrackDecorationAlg',
204 'LeptonTrackDecorator' )
205 if config.dataType() is not DataType.Data:
207 InDetTrackCalibrationConfig.makeTrackBiasingTool(config, alg)
208 InDetTrackCalibrationConfig.makeTrackSmearingTool(config, alg)
209 alg.particles = config.readName (self.containerName)
210
211 config.addOutputVar (self.containerName, 'd0_%SYS%', 'd0')
212 config.addOutputVar (self.containerName, 'd0sig_%SYS%', 'd0sig')
213 config.addOutputVar (self.containerName, 'z0_%SYS%', 'z0')
214 config.addOutputVar (self.containerName, 'z0sintheta_%SYS%', 'z0sintheta')
215 config.addOutputVar (self.containerName, 'z0sinthetasig_%SYS%', 'z0sinthetasig')
216
217
219 """the ConfigBlock for the muon working point selection"""
220
221 def __init__ (self) :
222 super (MuonWorkingPointSelectionConfig, self).__init__ ()
223 self.setBlockName('MuonWorkingPointSelection')
224 self.addOption ('containerName', '', type=str,
225 noneAction='error',
226 info="the name of the input container.")
227 self.addOption ('selectionName', '', type=str,
228 noneAction='error',
229 info="the name of the muon selection to define (e.g. `tight` or `loose`).")
230 self.addOption ('postfix', None, type=str,
231 info="a postfix to apply to decorations and algorithm names. "
232 "Typically not needed here as `selectionName` is used internally.")
233 self.addOption ('trackSelection', True, type=bool,
234 info="whether or not to set up an instance of "
235 "`CP::AsgLeptonTrackSelectionAlg`, with the recommended $d_0$ and "
236 r"$z_0\sin\theta$ cuts.")
237 self.addOption ('maxD0Significance', 3, type=float,
238 info="maximum $d_0$ significance used for the track selection.")
239 self.addOption ('maxDeltaZ0SinTheta', 0.5, type=float,
240 info=r"maximum $\Delta z_0\sin\theta$ (in mm) used for the track selection.")
241 self.addOption ('quality', None, type=str,
242 info="the ID WP to use. Supported ID WPs: `Tight`, `Medium`, "
243 "`Loose`, `LowPt`, `HighPt`.")
244 self.addOption ('isolation', None, type=str,
245 info="the isolation WP to use. Supported isolation WPs: "
246 "`PflowLoose_VarRad`, `PflowTight_VarRad`, `Loose_VarRad`, "
247 "`Tight_VarRad`, `NonIso`.")
248 self.addOption ('addSelectionToPreselection', True, type=bool,
249 info="whether to retain only muons satisfying the working point "
250 "requirements.")
251 self.addOption ('isoDecSuffix', '', type=str,
252 info="the `isoDecSuffix` name if using close-by-corrected isolation working points.")
253 self.addOption ('excludeNSWFromPrecisionLayers', False, type=bool,
254 info="only for testing purposes, turn on to ignore NSW hits and "
255 "fix a crash with older derivations (p-tag <p5834).")
256 self.addOption('useLRT', False, type=bool,
257 info="whether to enable LRT handling in CP::MuonSelectionTool")
258
259 def instanceName (self) :
260 if self.postfix is not None:
261 return self.containerName + '_' + self.postfix
262 else:
263 return self.containerName + '_' + self.selectionName
264
265 def makeAlgs (self, config) :
266 log = logging.getLogger('MuonWorkingPointSelectionConfig')
267
268 from xAODMuon.xAODMuonEnums import xAODMuonEnums
269 if self.quality == 'Tight' :
270 quality = xAODMuonEnums.Quality.Tight
271 elif self.quality == 'Medium' :
272 quality = xAODMuonEnums.Quality.Medium
273 elif self.quality == 'Loose' :
274 quality = xAODMuonEnums.Quality.Loose
275 elif self.quality == 'VeryLoose' :
276 quality = xAODMuonEnums.Quality.VeryLoose
277 elif self.quality == 'HighPt' :
278 quality = 4
279 elif self.quality == 'LowPt' :
280 quality = 5
281 else :
282 raise ValueError ("invalid muon quality: \"" + self.quality +
283 "\", allowed values are Tight, Medium, Loose, " +
284 "VeryLoose, HighPt, LowPt")
285
286 # The setup below is inappropriate for Run 1
287 if config.geometry() is LHCPeriod.Run1:
288 raise ValueError ("Can't set up the MuonWorkingPointSelectionConfig with %s, there must be something wrong!" % config.geometry().value)
289
290 postfix = self.postfix
291 if postfix is None :
292 postfix = self.selectionName
293 if postfix != '' and postfix[0] != '_' :
294 postfix = '_' + postfix
295
296 # Set up the track selection algorithm:
298 alg = config.createAlgorithm( 'CP::AsgLeptonTrackSelectionAlg',
299 'MuonTrackSelectionAlg',
300 reentrant=True )
301 alg.selectionDecoration = 'trackSelection' + postfix + ',as_bits'
302 alg.maxD0Significance = self.maxD0Significance
303 alg.maxDeltaZ0SinTheta = self.maxDeltaZ0SinTheta
304 alg.particles = config.readName (self.containerName)
305 alg.preselection = config.getPreselection (self.containerName, '')
306 config.addSelection (self.containerName, self.selectionName, alg.selectionDecoration, preselection=self.addSelectionToPreselection)
307
308 # Setup the muon quality selection
309 alg = config.createAlgorithm( 'CP::MuonSelectionAlgV2',
310 'MuonSelectionAlg' )
311 config.addPrivateTool( 'selectionTool', 'CP::MuonSelectionTool' )
312 alg.selectionTool.MuQuality = quality
313 alg.selectionTool.IsRun3Geo = config.geometry() >= LHCPeriod.Run3
314 alg.selectionTool.UseLRT = self.useLRT
315 if config.geometry() is LHCPeriod.Run4:
316 log.warning("Disabling NSW hits for Run4 geometry")
317 alg.selectionTool.ExcludeNSWFromPrecisionLayers = True
318 else:
319 alg.selectionTool.ExcludeNSWFromPrecisionLayers = self.excludeNSWFromPrecisionLayers and (config.geometry() >= LHCPeriod.Run3)
320 alg.selectionDecoration = 'good_muon' + postfix + ',as_char'
321 alg.badMuonVetoDecoration = 'is_bad' + postfix + ',as_char'
322 alg.muons = config.readName (self.containerName)
323 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
324 config.addSelection (self.containerName, self.selectionName,
325 alg.selectionDecoration,
326 preselection=self.addSelectionToPreselection)
327 if self.quality == 'HighPt':
328 config.addOutputVar (self.containerName, 'is_bad' + postfix, 'is_bad' + postfix)
329
330 # Set up the isolation calculation algorithm:
331 if self.isolation != 'NonIso' :
332 alg = config.createAlgorithm( 'CP::MuonIsolationAlg',
333 'MuonIsolationAlg' )
334 config.addPrivateTool( 'isolationTool', 'CP::IsolationSelectionTool' )
335 alg.isolationTool.MuonWP = self.isolation
336 alg.isolationTool.IsoDecSuffix = self.isoDecSuffix
337 alg.isolationDecoration = 'isolated_muon' + postfix + ',as_char'
338 alg.muons = config.readName (self.containerName)
339 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
340 config.addSelection (self.containerName, self.selectionName,
341 alg.isolationDecoration,
342 preselection=self.addSelectionToPreselection)
343
344
346 """the ConfigBlock for the muon working point efficiency computation"""
347
348 def __init__ (self) :
349 super (MuonWorkingPointEfficiencyConfig, self).__init__ ()
350 self.setBlockName('MuonWorkingPointEfficiency')
351 self.addDependency('MuonWorkingPointSelection', required=True)
352 self.addDependency('EventSelection', required=False)
353 self.addDependency('EventSelectionMerger', required=False)
354 self.addOption ('containerName', '', type=str,
355 noneAction='error',
356 info="the name of the input container.")
357 self.addOption ('selectionName', '', type=str,
358 noneAction='error',
359 info="the name of the muon selection to define (e.g. `tight` or `loose`).")
360 self.addOption ('postfix', None, type=str,
361 info="a postfix to apply to decorations and algorithm names. "
362 "Typically not needed here as `selectionName` is used internally.")
363 self.addOption ('trackSelection', True, type=bool,
364 info="whether or not to set up an instance of "
365 "`CP::AsgLeptonTrackSelectionAlg`, with the recommended $d_0$ and "
366 r"$z_0\sin\theta$ cuts.")
367 self.addOption ('quality', None, type=str,
368 info="the ID WP to use. Supported ID WPs: `Tight`, `Medium`, "
369 "`Loose`, `LowPt`, `HighPt`.")
370 self.addOption ('isolation', None, type=str,
371 info="the isolation WP to use. Supported isolation WPs: "
372 "`PflowLoose_VarRad`, `PflowTight_VarRad`, `Loose_VarRad`, "
373 "`Tight_VarRad`, `NonIso`.")
374 self.addOption ('systematicBreakdown', False, type=bool,
375 info="enables the full breakdown of efficiency SF systematics "
376 "(1 NP per uncertainty source, instead of 1 NP in total).")
377 self.addOption ('noEffSF', False, type=bool,
378 info="disables the calculation of efficiencies and scale factors. "
379 "Experimental! Only useful to test a new WP for which scale "
380 "factors are not available.",
381 expertMode=True)
382 self.addOption ('saveDetailedSF', True, type=bool,
383 info="save all the independent detailed object scale factors.")
384 self.addOption ('saveCombinedSF', False, type=bool,
385 info="save the combined object scale factor.")
386 self.addOption('useLRT', False, type=bool,
387 info="apply the LRT-specific reco/ID efficiency SF treatment. When "
388 "set (and quality is Medium, the only WP supported for LRT muons), "
389 "the reco SF tool routes per-muon via the isLRT flag and uses the "
390 "LRT-specific CalibrationRelease.")
391
392 def instanceName (self) :
393 if self.postfix is not None:
394 return self.containerName + '_' + self.postfix
395 else:
396 return self.containerName + '_' + self.selectionName
397
398 def makeAlgs (self, config) :
399
400 # The setup below is inappropriate for Run 1
401 if config.geometry() is LHCPeriod.Run1:
402 raise ValueError ("Can't set up the MuonWorkingPointEfficiencyConfig with %s, there must be something wrong!" % config.geometry().value)
403
404 postfix = self.postfix
405 if postfix is None :
406 postfix = self.selectionName
407 if postfix != '' and postfix[0] != '_' :
408 postfix = '_' + postfix
409
410 sfList = []
411 # Set up the reco/ID efficiency scale factor calculation algorithm:
412 if config.dataType() is not DataType.Data and not self.noEffSF:
413 alg = config.createAlgorithm( 'CP::MuonEfficiencyScaleFactorAlg',
414 'MuonEfficiencyScaleFactorAlgReco' )
415 config.addPrivateTool( 'efficiencyScaleFactorTool',
416 'CP::MuonEfficiencyScaleFactors' )
417 config.setExtraInputs ({('xAOD::EventInfo', 'EventInfo.RandomRunNumber')})
418 alg.scaleFactorDecoration = 'muon_reco_effSF' + postfix + "_%SYS%"
419 alg.outOfValidity = 2 #silent
420 alg.outOfValidityDeco = 'muon_reco_bad_eff' + postfix
421 alg.efficiencyScaleFactorTool.WorkingPoint = self.quality
422 # LRT muons: MCP supports only Medium WP. Enable per-muon isLRT flag and use dedicated LRT reco-sf release.
423 if self.useLRT and self.quality != 'Medium':
424 raise ValueError ("useLRT is only supported with the Medium quality working point, not '%s'" % self.quality)
425 if config.geometry() >= LHCPeriod.Run3:
426 alg.efficiencyScaleFactorTool.CalibrationRelease = '250418_Preliminary_r24run3' if self.useLRT else '251211_Preliminary_r24run3'
427 else:
428 alg.efficiencyScaleFactorTool.CalibrationRelease = '240620_LRT_r22run2' if self.useLRT else '230213_Preliminary_r22run2_loosefix'
429 alg.efficiencyScaleFactorTool.BreakDownSystematics = self.systematicBreakdown
430 alg.muons = config.readName (self.containerName)
431 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
433 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
434 'reco_effSF' + postfix)
435 sfList += [alg.scaleFactorDecoration]
436
437 # Set up the HighPt-specific BadMuonVeto efficiency scale factor calculation algorithm:
438 if config.dataType() is not DataType.Data and self.quality == 'HighPt' and not self.noEffSF:
439 alg = config.createAlgorithm( 'CP::MuonEfficiencyScaleFactorAlg',
440 'MuonEfficiencyScaleFactorAlgBMVHighPt' )
441 config.addPrivateTool( 'efficiencyScaleFactorTool',
442 'CP::MuonEfficiencyScaleFactors' )
443 alg.scaleFactorDecoration = 'muon_BadMuonVeto_effSF' + postfix + "_%SYS%"
444 alg.outOfValidity = 2 #silent
445 alg.outOfValidityDeco = 'muon_BadMuonVeto_bad_eff' + postfix
446 alg.efficiencyScaleFactorTool.WorkingPoint = 'BadMuonVeto_HighPt'
447 if config.geometry() >= LHCPeriod.Run3:
448 alg.efficiencyScaleFactorTool.CalibrationRelease = '220817_Preliminary_r22run3' # not available as part of '230123_Preliminary_r22run3'!
449 else:
450 alg.efficiencyScaleFactorTool.CalibrationRelease = '230213_Preliminary_r22run2_loosefix'
451 alg.efficiencyScaleFactorTool.BreakDownSystematics = self.systematicBreakdown
452 alg.muons = config.readName (self.containerName)
453 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
454 if self.saveDetailedSF:
455 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
456 'BadMuonVeto_effSF' + postfix)
457 sfList += [alg.scaleFactorDecoration]
458
459 # Set up the isolation efficiency scale factor calculation algorithm:
460 if config.dataType() is not DataType.Data and self.isolation != 'NonIso' and not self.noEffSF:
461 alg = config.createAlgorithm( 'CP::MuonEfficiencyScaleFactorAlg',
462 'MuonEfficiencyScaleFactorAlgIsol' )
463 config.addPrivateTool( 'efficiencyScaleFactorTool',
464 'CP::MuonEfficiencyScaleFactors' )
465 alg.scaleFactorDecoration = 'muon_isol_effSF' + postfix + "_%SYS%"
466 alg.outOfValidity = 2 #silent
467 alg.outOfValidityDeco = 'muon_isol_bad_eff' + postfix
468 alg.efficiencyScaleFactorTool.WorkingPoint = self.isolation + 'Iso'
469 if config.geometry() >= LHCPeriod.Run3:
470 alg.efficiencyScaleFactorTool.CalibrationRelease = '251211_Preliminary_r24run3'
471 else:
472 alg.efficiencyScaleFactorTool.CalibrationRelease = '230213_Preliminary_r22run2_loosefix'
473 alg.efficiencyScaleFactorTool.BreakDownSystematics = self.systematicBreakdown
474 alg.muons = config.readName (self.containerName)
475 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
476 if self.saveDetailedSF:
477 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
478 'isol_effSF' + postfix)
479 sfList += [alg.scaleFactorDecoration]
480
481 # Set up the TTVA scale factor calculation algorithm:
482 if config.dataType() is not DataType.Data and self.trackSelection and not self.noEffSF:
483 alg = config.createAlgorithm( 'CP::MuonEfficiencyScaleFactorAlg',
484 'MuonEfficiencyScaleFactorAlgTTVA' )
485 config.addPrivateTool( 'efficiencyScaleFactorTool',
486 'CP::MuonEfficiencyScaleFactors' )
487 alg.scaleFactorDecoration = 'muon_TTVA_effSF' + postfix + "_%SYS%"
488 alg.outOfValidity = 2 #silent
489 alg.outOfValidityDeco = 'muon_TTVA_bad_eff' + postfix
490 alg.efficiencyScaleFactorTool.WorkingPoint = 'TTVA'
491 if config.geometry() >= LHCPeriod.Run3:
492 alg.efficiencyScaleFactorTool.CalibrationRelease = '251211_Preliminary_r24run3'
493 else:
494 alg.efficiencyScaleFactorTool.CalibrationRelease = '230213_Preliminary_r22run2_loosefix'
495 alg.efficiencyScaleFactorTool.BreakDownSystematics = self.systematicBreakdown
496 alg.muons = config.readName (self.containerName)
497 alg.preselection = config.getPreselection (self.containerName, self.selectionName)
498 if self.saveDetailedSF:
499 config.addOutputVar (self.containerName, alg.scaleFactorDecoration,
500 'TTVA_effSF' + postfix)
501 sfList += [alg.scaleFactorDecoration]
502
503 if config.dataType() is not DataType.Data and not self.noEffSF and self.saveCombinedSF:
504 alg = config.createAlgorithm( 'CP::AsgObjectScaleFactorAlg',
505 'MuonCombinedEfficiencyScaleFactorAlg' )
506 alg.particles = config.readName (self.containerName)
507 alg.inScaleFactors = sfList
508 alg.outScaleFactor = 'effSF' + postfix + '_%SYS%'
509 config.addOutputVar (self.containerName, alg.outScaleFactor, 'effSF' + postfix)
510
511class MuonTriggerAnalysisSFBlock (ConfigBlock):
512
513 def __init__ (self) :
514 super (MuonTriggerAnalysisSFBlock, self).__init__ ()
515 self.addDependency('EventSelection', required=False)
516 self.addDependency('EventSelectionMerger', required=False)
517 self.addOption ('triggerChainsPerYear', {}, type=dict,
518 info="a dictionary with key (string) the year and value (list of "
519 "strings) the trigger chains.")
520 self.addOption ('muonID', '', type=str,
521 info="the muon quality WP to use.")
522 self.addOption ('saveSF', True, type=bool,
523 info="whether to decorate the trigger scale factor.")
524 self.addOption ('saveEff', False, type=bool,
525 info="whether to decorate the trigger MC efficiencies.")
526 self.addOption ('saveEffData', False, type=bool,
527 info="whether to decorate the trigger data efficiencies.")
528 self.addOption ('prefixSF', 'trigEffSF', type=str,
529 info="the decoration prefix for trigger scale factors.")
530 self.addOption ('prefixEff', 'trigEff', type=str,
531 info="the decoration prefix for MC trigger efficiencies.")
532 self.addOption ('prefixEffData', 'trigEffData', type=str,
533 info="the decoration prefix for data trigger efficiencies.")
534 self.addOption ('includeAllYearsPerRun', False, type=bool,
535 info="all configured years in the LHC run will "
536 "be included in all jobs.")
537 self.addOption ('removeHLTPrefix', True, type=bool,
538 info="remove the HLT prefix from trigger chain names.")
539 self.addOption ('containerName', '', type=str,
540 info="the input muon container, with a possible selection, in "
541 "the format `container` or `container.selection`.")
542 self.addOption ('customToolSuffix', '', type=str,
543 expertMode=True, info="EXPERIMENTAL: specify custom suffix for the public tool name")
544 self.addOption ('customInputFolder', '', type=str,
545 expertMode=True, info="EXPERIMENTAL: specify custom input folder")
546 self.addOption ('customInputFilePerYear', {}, type=dict,
547 expertMode=True, info="EXPERIMENTAL: specify custom input file per year")
548
549 def instanceName (self) :
550 return self.containerName + '_' + self.muonID
551
552 def makeAlgs (self, config) :
553
554 if config.dataType() is not DataType.Data:
555
556 # Dictionary from TrigGlobalEfficiencyCorrection/Triggers.cfg
557 # Key is trigger chain (w/o HLT prefix)
558 # Value is empty for single leg trigger or list of legs
559 triggerDict = TriggerDict()
560
562 years = [int(year) for year in self.triggerChainsPerYear.keys()]
563 else:
564 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import (
565 get_input_years)
566 years = get_input_years(config)
567
568 triggerYearStartBoundaries = {
569 2015: 260000,
570 2016: 290000,
571 2017: 324000,
572 2018: 348000,
573 2022: 410000,
574 2023: 450000,
575 2024: 470000,
576 2025: 495000,
577 2026: 516000,
578 }
579
580 triggerConfigs = {}
581 triggerConfigYears = {}
582 from TriggerAnalysisAlgorithms.TriggerAnalysisConfig import is_year_in_current_period
583 for year in years:
584 if not is_year_in_current_period(config, year):
585 continue
586
587 triggerChains = self.triggerChainsPerYear.get(int(year), self.triggerChainsPerYear.get(str(year), []))
588 for chain in triggerChains:
589 chain = chain.replace(" || ", "_OR_")
590 chain_noHLT = chain.replace("HLT_", "")
591 chain_out = chain_noHLT if self.removeHLTPrefix else chain
592 legs = triggerDict[chain_noHLT]
593 if not legs:
594 if chain_noHLT.startswith('mu') and chain_noHLT[2].isdigit:
595 # Need to support HLT_mu26_ivarmedium_OR_HLT_mu50
596 triggerConfigs[chain_out] = chain
597 if chain_out in triggerConfigYears.keys():
598 triggerConfigYears[chain_out].append(year)
599 else:
600 triggerConfigYears[chain_out] = [year]
601 else:
602 for leg in legs:
603 if leg.startswith('mu') and leg[2].isdigit:
604 # Need to support HLT_mu14_ivarloose
605 leg_out = leg if self.removeHLTPrefix else f"HLT_{leg}"
606 triggerConfigs[leg_out] = f"HLT_{leg}"
607 if leg_out in triggerConfigYears.keys():
608 triggerConfigYears[leg_out].append(year)
609 else:
610 triggerConfigYears[leg_out] = [year]
611
612 if not triggerConfigs:
613 return
614
615 # Make the public tool for this configuration
616 sfTool = config.createPublicTool("CP::MuonTriggerScaleFactors", f"{self.instanceName()}_SFTool{self.customToolSuffix}")
617 # Reproduce config from TrigGlobalEfficiencyAlg
618 sfTool.MuonQuality = self.muonID
619 sfTool.AllowZeroSF = True
620 sfTool.CustomInputFolder = self.customInputFolder
621 sfTool.CustomInputFilePerYear = self.customInputFilePerYear
622 sfTool.Campaign = config.campaign().value
623
624 for trig_short, trig in triggerConfigs.items():
625 alg = config.createAlgorithm('CP::MuonTriggerEfficiencyScaleFactorAlg',
626 'MuonTrigEfficiencyCorrectionsAlg_' + trig_short)
627 alg.efficiencyScaleFactorTool = f"{sfTool.getType()}/{sfTool.getName()}"
628
629 # Avoid warnings for missing triggers
630 if self.includeAllYearsPerRun:
631 alg.minRunNumber = 0
632 alg.maxRunNumber = 999999
633
634 if triggerConfigYears[trig_short][0] != years[0]:
635 alg.minRunNumber = triggerYearStartBoundaries.get(triggerConfigYears[trig_short][0], 999999)
636 if triggerConfigYears[trig_short][-1] != years[-1]:
637 alg.maxRunNumber = triggerYearStartBoundaries.get(triggerConfigYears[trig_short][-1] + 1, 999999)
638 elif config.campaign() is Campaign.MC20a: # to avoid potential corner-cases keep the default config unchanged
639 if triggerConfigYears[trig_short] == [2015]:
640 alg.maxRunNumber = 290000
641 elif triggerConfigYears[trig_short] == [2016]:
642 alg.minRunNumber = 290000
643
644 alg.trigger = trig
645
646 # Some triggers in `250731_SummerUpdate` recommendations are not supported in 2022 period F
647 if config.campaign() is Campaign.MC23a and (trig_short == "HLT_mu8noL1_FSNOSEED" or trig_short == "HLT_mu22_L1MU14FCH"):
648 alg.minRunNumber = 435816 # Start of 2022 period H
649
650 if self.saveSF:
651 alg.scaleFactorDecoration = f"muon_{self.prefixSF}_{trig_short}_%SYS%"
652 if self.saveEff:
653 alg.mcEfficiencyDecoration = f"muon_{self.prefixEff}_{trig_short}_%SYS%"
654 if self.saveEffData:
655 alg.dataEfficiencyDecoration = f"muon_{self.prefixEffData}_{trig_short}_%SYS%"
656 alg.outOfValidity = 2 #silent
657 alg.outOfValidityDeco = f"bad_eff_muontrig_{trig_short}"
658 alg.muons = config.readName (self.containerName)
659 alg.preselection = config.getPreselection (self.containerName, '')
660 if self.saveSF:
661 config.addOutputVar (self.containerName, alg.scaleFactorDecoration, f"{self.prefixSF}_{trig_short}")
662 if self.saveEff:
663 config.addOutputVar (self.containerName, alg.mcEfficiencyDecoration, f"{self.prefixEff}_{trig_short}")
664 if self.saveEffData:
665 config.addOutputVar (self.containerName, alg.dataEfficiencyDecoration, f"{self.prefixEffData}_{trig_short}")
666
667
668class MuonLRTMergedConfig (ConfigBlock) :
669 def __init__ (self) :
670 super (MuonLRTMergedConfig, self).__init__ ()
671 self.addOption (
672 'inputMuons', 'Muons', type=str,
673 noneAction='error',
674 info="the name of the input muon container."
675 )
676 self.addOption (
677 'inputLRTMuons', 'MuonsLRT', type=str,
678 noneAction='error',
679 info="the name of the input LRT muon container."
680 )
681 self.addOption (
682 'containerName', 'Muons_LRTMerged', type=str,
683 noneAction='error',
684 info="the name of the output container after LRT merging."
685 )
686
687 def instanceName (self) :
688 return self.containerName
689
690 def makeAlgs (self, config) :
691
692 if config.isPhyslite() :
693 raise(RuntimeError("Muon LRT merging is not available in Physlite mode"))
694
695 alg = config.createAlgorithm( "CP::MuonLRTMergingAlg", "MuonLRTMergingAlg" )
696 alg.PromptMuonLocation = self.inputMuons
697 alg.LRTMuonLocation = self.inputLRTMuons
698 alg.OutputMuonLocation = self.containerName
699 alg.UseRun3WP = config.geometry() >= LHCPeriod.Run3
700 alg.CreateViewCollection = False
701
702class MuonContainerMergingConfig (ConfigBlock) :
703 def __init__ (self) :
704 super (MuonContainerMergingConfig, self).__init__ ()
705 self.addOption (
706 'inputMuonContainers', [], type=list,
707 noneAction='error',
708 info="list of container names to be merged (of type `xAOD::MuonContainer`)."
709 )
710 self.addOption (
711 'outputMuonLocation', 'MuonsMerged', type=str,
712 noneAction='error',
713 info="the name of the output muon container."
714 )
715 self.addOption (
716 'createViewCollection', True, type=bool,
717 info="whether the output container should be a view container rather than a deep copy."
718 )
719
720 def instanceName (self) :
721 return self.outputMuonLocation
722
723 def makeAlgs (self, config) :
724 alg = config.createAlgorithm( "CP::MuonContainerMergingAlg", "MuonContainerMergingAlg" )
725 alg.InputMuonContainers = self.inputMuonContainers
726 alg.OutputMuonLocation = self.outputMuonLocation
727 alg.CreateViewCollection = self.createViewCollection
728
729@groupBlocks
732 seq.append(MuonIPCalibrationConfig())
733
734@groupBlocks
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:132