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