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