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