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, is_year_in_current_period
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 def get_input_years(config: ConfigAccumulator) -> list[int]:
36  """
37  Utility function to get the list of years that the input corresponds to
38  """
39  years = []
40  if config.campaign() is Campaign.MC20a:
41  years = [2015, 2016]
42  elif is_data_from(config, 2015):
43  years = [2015]
44  elif is_data_from(config, 2016):
45  years = [2016]
46  elif config.campaign() is Campaign.MC20d or is_data_from(config, 2017):
47  years = [2017]
48  elif config.campaign() is Campaign.MC20e or is_data_from(config, 2018):
49  years = [2018]
50  elif config.campaign() is Campaign.MC23a or is_data_from(config, 2022):
51  years = [2022]
52  elif config.campaign() is Campaign.MC23d or is_data_from(config, 2023):
53  years = [2023]
54  elif config.campaign() is Campaign.MC23e or is_data_from(config, 2024):
55  years = [2024]
56  elif config.campaign() is Campaign.MC23g or is_data_from(config, 2025):
57  years = [2025]
58  else:
59  raise ValueError('TriggerAnalysisConfig: unable to deduce data taking year for input file')
60 
61  return years
62 
63 
64 def trigger_set(config, triggerChainsPerYear, includeAllYearsPerRun):
65  triggers = set()
66  if includeAllYearsPerRun:
67  for year in triggerChainsPerYear:
68  if not is_year_in_current_period(config, year):
69  continue
70  triggers.update(get_year_data(triggerChainsPerYear, year))
71  else:
72  for year in get_input_years(config):
73  triggers.update(get_year_data(triggerChainsPerYear, year))
74  return triggers
75 
76 
77 class TriggerAnalysisSFBlock(ConfigBlock):
78  """the ConfigBlock for trigger analysis"""
79  def __init__(self):
80  super(TriggerAnalysisSFBlock, self).__init__()
81  self.addDependency('Electrons', required=False)
82  self.addDependency('Photons', required=False)
83  self.addDependency('Muons', required=False)
84  self.addDependency('Taus', required=False)
85  self.addDependency('OverlapRemoval', required=False)
86 
87  self.addOption ('triggerChainsPerYear', {}, type=None,
88  info="a dictionary with key (string) the year and value (list of "
89  "strings) the trigger chains. You can also use || within a string "
90  "to enforce an OR of triggers without looking up the individual "
91  "triggers. Used for both trigger selection and SFs. "
92  "The default is {} (empty dictionary).")
93  self.addOption ('multiTriggerChainsPerYear', {}, type=None,
94  info="a dictionary with key (string) a trigger set name and value a "
95  "triggerChainsPerYear dictionary, following the previous convention. "
96  "Relevant for analyses using different triggers in different categories, "
97  "where the trigger global scale factors shouldn't be combined. "
98  "The default is {} (empty dictionary).")
99  self.addOption ('noFilter', False, type=bool,
100  info="do not apply an event filter. The default is False, i.e. "
101  "remove events not passing trigger selection and matching.")
102  self.addOption ('electronID', '', type=str,
103  info="the electron ID WP (string) to use.")
104  self.addOption ('electronIsol', '', type=str,
105  info="the electron isolation WP (string) to use.")
106  self.addOption ('photonIsol', '', type=str,
107  info="the photon isolation WP (string) to use.")
108  self.addOption ('muonID', '', type=str,
109  info="the muon quality WP (string) to use.")
110  self.addOption ('electrons', '', type=str,
111  info="the input electron container, with a possible selection, in "
112  "the format container or container.selection.")
113  self.addOption ('muons', '', type=str,
114  info="the input muon container, with a possible selection, in the "
115  "format container or container.selection.")
116  self.addOption ('photons', '', type=str,
117  info="the input photon container, with a possible selection, in "
118  "the format container or container.selection.")
119  self.addOption ('taus', '', type=str,
120  info="the input tau container, with a possible selection, in "
121  "the format container or container.selection.")
122  self.addOption ('numberOfToys', 0, type=int,
123  info="Number of toy experiments to run to estimate the trigger efficiencies, "
124  "instead of using explicit formulas. The default is 0 (not using toys).")
125  self.addOption ('noEffSF', False, type=bool,
126  info="disables the calculation of efficiencies and scale factors. "
127  "Experimental! only useful to test a new WP for which scale "
128  "factors are not available. Still performs the global trigger "
129  "matching (same behaviour as on data). The default is False.",
130  expertMode=True)
131  self.addOption ('noGlobalTriggerEff', False, type=bool,
132  info="disables the global trigger efficiency tool (including "
133  "matching), which is only suited for electron/muon/photon "
134  "trigger legs. The default is False.")
135  self.addOption ('separateChainMatching', False, type=bool,
136  info="Store the matching status for each trigger separately")
137  self.addOption ('triggerMatchingChainsPerYear', {}, type=None,
138  info="a dictionary with key (string) the year and value (list of "
139  "strings) the trigger chains. The default is {} (empty dictionary).")
140  self.addOption("includeAllYearsPerRun", False, type=bool,
141  info="if True, trigger matching will include all configured years "
142  "in the LHC run in all jobs. The default is False.")
143  self.addOption ('postfix', '', type=str,
144  info="a unique identifier for the trigger matching decorations. Only "
145  "useful when defining multiple setups. The default is '' (empty string).")
146 
148  self,
149  config: ConfigAccumulator,
150  matchingTool,
151  noSF: bool,
152  triggerSuffix: str = ''
153  ) -> None:
154  alg = config.createAlgorithm( 'CP::TrigGlobalEfficiencyAlg', 'TrigGlobalSFAlg' + triggerSuffix + self.postfix)
155  if config.geometry() is LHCPeriod.Run3:
156  alg.triggers_2022 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2022)]
157  alg.triggers_2023 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2023)]
158  alg.triggers_2024 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2024)]
159  alg.triggers_2025 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2025)]
160  if is_mc_from(config, Campaign.MC23a) or is_data_from(config, 2022):
161  if not alg.triggers_2022:
162  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2022!')
163  elif is_mc_from(config, Campaign.MC23d) or is_data_from(config, 2023):
164  if not alg.triggers_2023:
165  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2023!')
166  else:
167  alg.triggers_2015 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2015)]
168  alg.triggers_2016 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2016)]
169  alg.triggers_2017 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2017)]
170  alg.triggers_2018 = [trig.replace("HLT_","").replace(" || ", "_OR_") for trig in get_year_data(self.triggerChainsPerYear, 2018)]
171  if is_mc_from(config, Campaign.MC20a):
172  if not (alg.triggers_2015 and alg.triggers_2016):
173  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the years 2015 and 2016!')
174  elif is_data_from(config, 2015):
175  if not alg.triggers_2015:
176  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2015!')
177  elif is_data_from(config, 2016):
178  if not alg.triggers_2016:
179  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2016!')
180  elif is_mc_from(config, Campaign.MC20d) or is_data_from(config, 2017):
181  if not alg.triggers_2017:
182  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2017!')
183  elif is_mc_from(config, Campaign.MC20e) or is_data_from(config, 2018):
184  if not alg.triggers_2018:
185  raise ValueError('TriggerAnalysisConfig: you must provide a set of triggers for the year 2018!')
186 
187  triggerMatchingChains = set()
188  if self.separateChainMatching:
189  for year in get_input_years(config):
190  for trig in get_year_data(self.triggerChainsPerYear, year):
191  triggerMatchingChains.update(trig.replace(' || ', '_OR_').split('_OR_'))
192  alg.separateMatchingTriggers = list(triggerMatchingChains)
193  alg.separateMatchingDecorationSuffix = triggerSuffix + self.postfix
194 
195  alg.matchingTool = '%s/%s' % ( matchingTool.getType(), matchingTool.getName() )
196  alg.isRun3Geo = config.geometry() is LHCPeriod.Run3
197  alg.numberOfToys = self.numberOfToys
198  alg.scaleFactorDecoration = 'globalTriggerEffSF' + triggerSuffix + self.postfix + '_%SYS%'
199  alg.matchingDecoration = 'globalTriggerMatch' + triggerSuffix + self.postfix + '_%SYS%'
200  alg.eventDecisionOutputDecoration = 'globalTriggerMatch' + triggerSuffix + self.postfix + '_dontsave_%SYS%'
201  alg.doMatchingOnly = config.dataType() is DataType.Data or noSF
202  alg.noFilter = self.noFilter
203  alg.electronID = self.electronID
204  alg.electronIsol = self.electronIsol
205  alg.photonIsol = self.photonIsol
206  alg.muonID = self.muonID
207  if self.electrons:
208  alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
209  if self.muons:
210  alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
211  if self.photons:
212  alg.photons, alg.photonSelection = config.readNameAndSelection(self.photons)
213  if not (self.electrons or self.muons or self.photons):
214  raise ValueError('TriggerAnalysisConfig: at least one object collection must be provided! (electrons, muons, photons)' )
215 
216  if config.dataType() is not DataType.Data and not alg.doMatchingOnly:
217  config.addOutputVar('EventInfo', alg.scaleFactorDecoration, 'globalTriggerEffSF' + triggerSuffix + self.postfix)
218  config.addOutputVar('EventInfo', alg.matchingDecoration, 'globalTriggerMatch' + triggerSuffix + self.postfix, noSys=False)
219 
220  for trig in triggerMatchingChains:
221  var = f'triggerMatch_{trig.replace("-", "_").replace(".", "p")}{alg.separateMatchingDecorationSuffix}'
222  config.addOutputVar('EventInfo', f'{var}_%SYS%', var, noSys=False)
223 
224  return
225 
227  self,
228  config: ConfigAccumulator,
229  matchingTool,
230  particles,
231  trig_string: str = "",
232  trig_chains: set = None,
233  trig_chains_dummy: set = None,
234  ) -> None:
235  if not particles or (not trig_chains and not trig_chains_dummy):
236  return
237 
238  if not any(trig_string in trig for trig in trig_chains) \
239  and not any(trig_string in trig for trig in trig_chains_dummy):
240  return
241 
242  alg = config.createAlgorithm("CP::TrigMatchingAlg", f"TrigMatchingAlg_{trig_string}{self.postfix}")
243  alg.matchingTool = f"{matchingTool.getType()}/{matchingTool.getName()}"
244  alg.matchingDecoration = f"trigMatched{self.postfix}"
245  alg.trigSingleMatchingList = [trig for trig in trig_chains if trig_string in trig]
246  alg.trigSingleMatchingListDummy = [trig for trig in trig_chains_dummy if trig_string in trig]
247  alg.particles, alg.particleSelection = config.readNameAndSelection(particles)
248 
249  for trig in list(alg.trigSingleMatchingList) + list(alg.trigSingleMatchingListDummy):
250  trig = trig.replace(".", "p").replace("-", "_").replace(" ", "")
251  if trig_string in trig:
252  config.addOutputVar(particles.split(".")[0], f"trigMatched_{self.postfix}{trig}", f"trigMatched_{self.postfix}{trig}", noSys=True)
253 
255  self,
256  config: ConfigAccumulator,
257  matchingTool,
258  ) -> None:
259  years = get_input_years(config)
260 
261  triggerMatchingChains = set()
262  triggerMatchingChainsDummy = set()
263  for year in years:
264  for trig in get_year_data(self.triggerMatchingChainsPerYear, year):
265  trig = trig.replace(' || ', '_OR_')
266  triggerMatchingChains.update(trig.split('_OR_'))
267  if self.includeAllYearsPerRun:
268  triggerMatchingChainsAll = set()
269  for year in self.triggerMatchingChainsPerYear:
270  if not is_year_in_current_period(config, year):
271  continue
272  for trig in get_year_data(self.triggerMatchingChainsPerYear, year):
273  trig = trig.replace(' || ', '_OR_')
274  triggerMatchingChainsAll.update(trig.split('_OR_'))
275  triggerMatchingChainsDummy = triggerMatchingChainsAll - triggerMatchingChains
276 
277  self.createTrigMatching(config, matchingTool, self.electrons, 'HLT_e', triggerMatchingChains, triggerMatchingChainsDummy)
278  self.createTrigMatching(config, matchingTool, self.muons, 'HLT_mu', triggerMatchingChains, triggerMatchingChainsDummy)
279  self.createTrigMatching(config, matchingTool, self.photons, 'HLT_g', triggerMatchingChains, triggerMatchingChainsDummy)
280  self.createTrigMatching(config, matchingTool, self.taus, 'HLT_tau', triggerMatchingChains, triggerMatchingChainsDummy)
281 
282  return
283 
284  def makeAlgs(self, config: ConfigAccumulator) -> None:
285  if (
287  self.triggerChainsPerYear and
289  ):
290  raise Exception('multiTriggerChainsPerYear and triggerChainsPerYear cannot be configured at the same time!')
291 
292  if self.triggerChainsPerYear and not self.multiTriggerChainsPerYear:
294 
295  # Create the decision algorithm, keeping track of the decision tool for later
296  decisionTool = TriggerAnalysisBlock.makeTriggerDecisionTool(config)
297 
298  # Now pass it to the matching algorithm, keeping track of the matching tool for later
299  matchingTool = TriggerAnalysisBlock.makeTriggerMatchingTool(config, decisionTool)
300 
301  # Calculate multi-lepton (electron/muon/photon) trigger efficiencies and SFs
302  if self.multiTriggerChainsPerYear and not self.noGlobalTriggerEff:
303  for suffix, trigger_chains in self.multiTriggerChainsPerYear.items():
304  self.triggerChainsPerYear = trigger_chains
305  self.makeTriggerGlobalEffCorrAlg(config, matchingTool, self.noEffSF, suffix)
306 
307  # Save trigger matching information (currently only single leg trigger are supported)
308  if self.triggerMatchingChainsPerYear:
309  self.makeTrigMatchingAlg(config, matchingTool)
310 
311  return
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:310
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock
Definition: TriggerAnalysisSFConfig.py:77
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.multiTriggerChainsPerYear
multiTriggerChainsPerYear
Definition: TriggerAnalysisSFConfig.py:293
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.__init__
def __init__(self)
Definition: TriggerAnalysisSFConfig.py:79
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:147
python.Bindings.values
values
Definition: Control/AthenaPython/python/Bindings.py:808
python.TriggerAnalysisConfig.is_year_in_current_period
bool is_year_in_current_period(ConfigAccumulator config, int|str year)
Definition: TriggerAnalysisConfig.py:9
python.TriggerAnalysisSFConfig.trigger_set
def trigger_set(config, triggerChainsPerYear, includeAllYearsPerRun)
Definition: TriggerAnalysisSFConfig.py:64
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:284
python.TriggerAnalysisSFConfig.get_input_years
list[int] get_input_years(ConfigAccumulator config)
Definition: TriggerAnalysisSFConfig.py:35
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.triggerChainsPerYear
triggerChainsPerYear
Definition: TriggerAnalysisSFConfig.py:304
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:226
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:71
python.CaloAddPedShiftConfig.int
int
Definition: CaloAddPedShiftConfig.py:45
python.TriggerAnalysisSFConfig.TriggerAnalysisSFBlock.makeTrigMatchingAlg
None makeTrigMatchingAlg(self, ConfigAccumulator config, matchingTool)
Definition: TriggerAnalysisSFConfig.py:254
str
Definition: BTagTrackIpAccessor.cxx:11
Trk::split
@ split
Definition: LayerMaterialProperties.h:38