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