ATLAS Offline Software
TriggerAnalysisSFConfig.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 from typing import Iterable, Union
3 
4 from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
5 from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType, ConfigAccumulator
6 from AthenaConfiguration.Enums import LHCPeriod
7 from Campaigns.Utils import Campaign
8 from TriggerAnalysisAlgorithms.TriggerAnalysisConfig import TriggerAnalysisBlock
9 
10 
11 def is_mc_from(config: ConfigAccumulator, campaign_list: Union[Campaign, Iterable[Campaign]]) -> bool:
12  """
13  Utility function to check whether the data type is mc and whether the campaign is in desired list of campaigns
14  without causing an invalid FlugEnum comparison
15  """
16  campaign_list = [campaign_list] if isinstance(campaign_list, Campaign) else campaign_list
17  return config.dataType() is not DataType.Data and config.campaign() in campaign_list
18 
19 
20 def is_data_from(config, data_year_list: Union[int, Iterable[int]]) -> bool:
21  """
22  Utility function to check whether the data type is data and whether the year is in desired list of years
23  """
24  data_year_list = [data_year_list] if isinstance(data_year_list, int) else data_year_list
25  return config.dataType() is DataType.Data and config.dataYear() in data_year_list
26 
27 
28 def get_year_data(dictionary: dict, year: int | str) -> list:
29  """
30  Utility function to get the data for a specific year from a dictionary
31  """
32  return dictionary.get(int(year), dictionary.get(str(year), []))
33 
34 
35 class TriggerAnalysisSFBlock(ConfigBlock):
36  """the ConfigBlock for trigger analysis"""
37  def __init__(self):
38  super(TriggerAnalysisSFBlock, self).__init__()
39  self.addDependency('Electrons', required=False)
40  self.addDependency('Photons', required=False)
41  self.addDependency('Muons', required=False)
42  self.addDependency('Taus', required=False)
43  self.addDependency('OverlapRemoval', required=False)
44 
45  self.addOption ('triggerChainsPerYear', {}, type=None,
46  info="a dictionary with key (string) the year and value (list of "
47  "strings) the trigger chains. You can also use || within a string "
48  "to enforce an OR of triggers without looking up the individual "
49  "triggers. Used for both trigger selection and SFs. "
50  "The default is {} (empty dictionary).")
51  self.addOption ('multiTriggerChainsPerYear', {}, type=None,
52  info="a dictionary with key (string) a trigger set name and value a "
53  "triggerChainsPerYear dictionary, following the previous convention. "
54  "Relevant for analyses using different triggers in different categories, "
55  "where the trigger global scale factors shouldn't be combined. "
56  "The default is {} (empty dictionary).")
57  self.addOption ('noFilter', False, type=bool,
58  info="do not apply an event filter. The default is False, i.e. "
59  "remove events not passing trigger selection and matching.")
60  self.addOption ('electronID', '', type=str,
61  info="the electron ID WP (string) to use.")
62  self.addOption ('electronIsol', '', type=str,
63  info="the electron isolation WP (string) to use.")
64  self.addOption ('photonIsol', '', type=str,
65  info="the photon isolation WP (string) to use.")
66  self.addOption ('muonID', '', type=str,
67  info="the muon quality WP (string) to use.")
68  self.addOption ('electrons', '', type=str,
69  info="the input electron container, with a possible selection, in "
70  "the format container or container.selection.")
71  self.addOption ('muons', '', type=str,
72  info="the input muon container, with a possible selection, in the "
73  "format container or container.selection.")
74  self.addOption ('photons', '', type=str,
75  info="the input photon container, with a possible selection, in "
76  "the format container or container.selection.")
77  self.addOption ('taus', '', type=str,
78  info="the input tau container, with a possible selection, in "
79  "the format container or container.selection.")
80  self.addOption ('noEffSF', False, type=bool,
81  info="disables the calculation of efficiencies and scale factors. "
82  "Experimental! only useful to test a new WP for which scale "
83  "factors are not available. Still performs the global trigger "
84  "matching (same behaviour as on data). The default is False.")
85  self.addOption ('noGlobalTriggerEff', False, type=bool,
86  info="disables the global trigger efficiency tool (including "
87  "matching), which is only suited for electron/muon/photon "
88  "trigger legs. The default is False.")
89  self.addOption ('triggerMatchingChainsPerYear', {}, type=None,
90  info="a dictionary with key (string) the year and value (list of "
91  "strings) the trigger chains. The default is {} (empty dictionary).")
92  self.addOption("includeAllYears", False, type=bool,
93  info="if True, trigger matching will include all configured years "
94  "in all jobs. The default is False.")
95  self.addOption ('postfix', '', type=str,
96  info="a unique identifier for the trigger matching decorations. Only "
97  "useful when defining multiple setups. The default is '' (empty string).")
98 
100  self,
101  config: ConfigAccumulator,
102  matchingTool,
103  noSF: bool,
104  triggerSuffix: str = ''
105  ) -> None:
106  alg = config.createAlgorithm( 'CP::TrigGlobalEfficiencyAlg', 'TrigGlobalSFAlg' + triggerSuffix + self.postfix)
107  if config.geometry() is LHCPeriod.Run3:
108  alg.triggers_2022 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2022)]
109  alg.triggers_2023 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2023)]
110  alg.triggers_2024 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2024)]
111  alg.triggers_2025 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2025)]
112  if is_mc_from(config, [Campaign.MC21a, Campaign.MC23a]) or is_data_from(config, 2022):
113  if not alg.triggers_2022:
114  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2022!')
115  elif is_mc_from(config, [Campaign.MC23c, Campaign.MC23d]) or is_data_from(config, 2023):
116  if not alg.triggers_2023:
117  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2023!')
118  else:
119  alg.triggers_2015 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2015)]
120  alg.triggers_2016 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2016)]
121  alg.triggers_2017 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2017)]
122  alg.triggers_2018 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2018)]
123  if is_mc_from(config, Campaign.MC20a):
124  if not (alg.triggers_2015 and alg.triggers_2016):
125  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the years 2015 and 2016!')
126  elif is_data_from(config, 2015):
127  if not alg.triggers_2015:
128  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2015!')
129  elif is_data_from(config, 2016):
130  if not alg.triggers_2016:
131  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2016!')
132  elif is_mc_from(config, Campaign.MC20d) or is_data_from(config, 2017):
133  if not alg.triggers_2017:
134  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2017!')
135  elif is_mc_from(config, Campaign.MC20e) or is_data_from(config, 2018):
136  if not alg.triggers_2018:
137  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2018!')
138 
139  alg.matchingTool = '%s/%s' % ( matchingTool.getType(), matchingTool.getName() )
140  alg.isRun3Geo = config.geometry() is LHCPeriod.Run3
141  alg.scaleFactorDecoration = 'globalTriggerEffSF' + triggerSuffix + self.postfix + '_%SYS%'
142  alg.matchingDecoration = 'globalTriggerMatch' + triggerSuffix + self.postfix + '_%SYS%'
143  alg.eventDecisionOutputDecoration = 'globalTriggerMatch' + triggerSuffix + self.postfix + '_dontsave_%SYS%'
144  alg.doMatchingOnly = config.dataType() is DataType.Data or noSF
145  alg.noFilter = self.noFilter
146  alg.electronID = self.electronID
147  alg.electronIsol = self.electronIsol
148  alg.photonIsol = self.photonIsol
149  alg.muonID = self.muonID
150  if self.electrons:
151  alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
152  if self.muons:
153  alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
154  if self.photons:
155  alg.photons, alg.photonSelection = config.readNameAndSelection(self.photons)
156  if not (self.electrons or self.muons or self.photons):
157  raise ValueError('TriggerAnalysisConfig: at least one object collection must be provided! (electrons, muons, photons)' )
158 
159  if config.dataType() is not DataType.Data and not alg.doMatchingOnly:
160  config.addOutputVar('EventInfo', alg.scaleFactorDecoration, 'globalTriggerEffSF' + triggerSuffix + self.postfix)
161  config.addOutputVar('EventInfo', alg.matchingDecoration, 'globalTriggerMatch' + triggerSuffix + self.postfix, noSys=False)
162 
163  return
164 
166  self,
167  config: ConfigAccumulator,
168  matchingTool,
169  particles,
170  trig_string: str = "",
171  trig_chains: set = None,
172  trig_chains_dummy: set = None,
173  ) -> None:
174  if not particles or (not trig_chains and not trig_chains_dummy):
175  return
176 
177  if not any(trig_string in trig for trig in trig_chains) \
178  and not any(trig_string in trig for trig in trig_chains_dummy):
179  return
180 
181  alg = config.createAlgorithm("CP::TrigMatchingAlg", f"TrigMatchingAlg_{trig_string}{self.postfix}")
182  alg.matchingTool = f"{matchingTool.getType()}/{matchingTool.getName()}"
183  alg.matchingDecoration = f"trigMatched{self.postfix}"
184  alg.trigSingleMatchingList = [trig for trig in trig_chains if trig_string in trig]
185  alg.trigSingleMatchingListDummy = [trig for trig in trig_chains_dummy if trig_string in trig]
186  alg.particles, alg.particleSelection = config.readNameAndSelection(particles)
187 
188  for trig in list(alg.trigSingleMatchingList) + list(alg.trigSingleMatchingListDummy):
189  trig = trig.replace(".", "p").replace("-", "_").replace(" ", "")
190  if trig_string in trig:
191  config.addOutputVar(particles.split(".")[0], f"trigMatched_{self.postfix}{trig}", f"trigMatched_{self.postfix}{trig}", noSys=True)
192 
194  self,
195  config: ConfigAccumulator,
196  matchingTool,
197  ) -> None:
198  years = []
199  if config.campaign() is Campaign.MC20a: years = [2015, 2016]
200  elif is_data_from(config, 2015): years = [2015]
201  elif is_data_from(config, 2016): years = [2016]
202  elif config.campaign() is Campaign.MC20d or is_data_from(config, 2017): years = [2017]
203  elif config.campaign() is Campaign.MC20e or is_data_from(config, 2018): years = [2018]
204  elif config.campaign() in [Campaign.MC21a, Campaign.MC23a] or is_data_from(config, 2022): years = [2022]
205  elif config.campaign() in [Campaign.MC23c, Campaign.MC23d] or is_data_from(config, 2023): years = [2023]
206 
207  triggerMatchingChains = set()
208  triggerMatchingChainsDummy = set()
209  for year in years:
210  for trig in get_year_data(self.triggerMatchingChainsPerYear, year):
211  trig = trig.replace(' || ', '_OR_')
212  triggerMatchingChains.update(trig.split('_OR_'))
213  if self.includeAllYears:
214  triggerMatchingChainsAll = set()
215  for year in self.triggerMatchingChainsPerYear:
216  for trig in get_year_data(self.triggerMatchingChainsPerYear, year):
217  trig = trig.replace(' || ', '_OR_')
218  triggerMatchingChainsAll.update(trig.split('_OR_'))
219  triggerMatchingChainsDummy = triggerMatchingChainsAll - triggerMatchingChains
220 
221  self.createTrigMatching(config, matchingTool, self.electrons, 'HLT_e', triggerMatchingChains, triggerMatchingChainsDummy)
222  self.createTrigMatching(config, matchingTool, self.muons, 'HLT_mu', triggerMatchingChains, triggerMatchingChainsDummy)
223  self.createTrigMatching(config, matchingTool, self.photons, 'HLT_g', triggerMatchingChains, triggerMatchingChainsDummy)
224  self.createTrigMatching(config, matchingTool, self.taus, 'HLT_tau', triggerMatchingChains, triggerMatchingChainsDummy)
225 
226  return
227 
228  def makeAlgs(self, config: ConfigAccumulator) -> None:
229  if (
231  self.triggerChainsPerYear and
233  ):
234  raise Exception('multiTriggerChainsPerYear and triggerChainsPerYear cannot be configured at the same time!')
235 
236  if self.triggerChainsPerYear and not self.multiTriggerChainsPerYear:
238 
239  # Create the decision algorithm, keeping track of the decision tool for later
240  decisionTool = TriggerAnalysisBlock.makeTriggerDecisionTool(config)
241 
242  # Now pass it to the matching algorithm, keeping track of the matching tool for later
243  matchingTool = TriggerAnalysisBlock.makeTriggerMatchingTool(config, decisionTool)
244 
245  # Calculate multi-lepton (electron/muon/photon) trigger efficiencies and SFs
246  if self.multiTriggerChainsPerYear and not self.noGlobalTriggerEff:
247  for suffix, trigger_chains in self.multiTriggerChainsPerYear.items():
248  self.triggerChainsPerYear = trigger_chains
249  self.makeTriggerGlobalEffCorrAlg(config, matchingTool, self.noEffSF, suffix)
250 
251  # Save trigger matching information (currently only single leg trigger are supported)
252  if self.triggerMatchingChainsPerYear:
253  self.makeTrigMatchingAlg(config, matchingTool)
254 
255  return
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:307
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock
Definition: TriggerAnalysisSFConfig.py:35
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.multiTriggerChainsPerYear
multiTriggerChainsPerYear
Definition: TriggerAnalysisSFConfig.py:237
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.__init__
def __init__(self)
Definition: TriggerAnalysisSFConfig.py:37
python.TriggerAnalysisSFConfig.is_mc_from
bool is_mc_from(ConfigAccumulator config, Union[Campaign, Iterable[Campaign]] campaign_list)
Definition: TriggerAnalysisSFConfig.py:11
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.makeTriggerGlobalEffCorrAlg
None makeTriggerGlobalEffCorrAlg(self, ConfigAccumulator config, matchingTool, bool noSF, str triggerSuffix='')
Definition: TriggerAnalysisSFConfig.py:99
python.Bindings.values
values
Definition: Control/AthenaPython/python/Bindings.py:805
python.TriggerAnalysisSFConfig.get_year_data
list get_year_data(dict dictionary, int|str year)
Definition: TriggerAnalysisSFConfig.py:28
python.TriggerAnalysisSFConfig.is_data_from
bool is_data_from(config, Union[int, Iterable[int]] data_year_list)
Definition: TriggerAnalysisSFConfig.py:20
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.makeAlgs
None makeAlgs(self, ConfigAccumulator config)
Definition: TriggerAnalysisSFConfig.py:228
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.triggerChainsPerYear
triggerChainsPerYear
Definition: TriggerAnalysisSFConfig.py:248
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:232
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.createTrigMatching
None createTrigMatching(self, ConfigAccumulator config, matchingTool, particles, str trig_string="", set trig_chains=None, set trig_chains_dummy=None)
Definition: TriggerAnalysisSFConfig.py:165
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.makeTrigMatchingAlg
None makeTrigMatchingAlg(self, ConfigAccumulator config, matchingTool)
Definition: TriggerAnalysisSFConfig.py:193
str
Definition: BTagTrackIpAccessor.cxx:11