ATLAS Offline Software
Loading...
Searching...
No Matches
TriggerAnalysisConfig.py
Go to the documentation of this file.
1# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2
3from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
4from AnalysisAlgorithmsConfig.ConfigSequence import groupBlocks
5from AnalysisAlgorithmsConfig.ConfigAccumulator import DataType, ConfigAccumulator
6from AthenaConfiguration.Enums import LHCPeriod
7
8
9def is_year_in_current_period(config: ConfigAccumulator, year: int | str) -> bool:
10 """
11 Utility function to check whether the year is valid for the current configuration
12 """
13 if config.geometry() is LHCPeriod.Run2 and year >= 2022:
14 return False
15 if config.geometry() is LHCPeriod.Run3 and year < 2022:
16 return False
17
18 return True
19
20
21class TriggerAnalysisBlock (ConfigBlock):
22 """the ConfigBlock for trigger analysis"""
23
24 def __init__ (self) :
25 super (TriggerAnalysisBlock, self).__init__ ()
26 self.addOption ('triggerChainsPerYear', {}, type=None,
27 info="a dictionary with key (string) the year and value (list of "
28 "strings) the trigger chains. You can also use || within a string "
29 "to enforce an OR of triggers without looking up the individual "
30 "triggers. Used for both trigger selection and SFs. "
31 "The default is {} (empty dictionary).")
32 self.addOption ('multiTriggerChainsPerYear', {}, type=None,
33 info="a dictionary with key (string) a trigger set name and value a "
34 "triggerChainsPerYear dictionary, following the previous convention. "
35 "Relevant for analyses using different triggers in different categories, "
36 "where the trigger global scale factors shouldn't be combined. "
37 "The default is {} (empty dictionary).")
38 self.addOption ('triggerChainsForSelection', [], type=None,
39 info="a list of trigger chains (list of strings) to be used for "
40 "trigger selection. Only set it if you need a different setup "
41 "than for trigger SFs. The default is [] (empty list).")
42 self.addOption ('triggerChainsForDecoration', [], type=None,
43 info="a list of trigger chains (list of strings) to be used for "
44 "trigger decoration, if it needs to be different from the selection one. "
45 "The default is [] (empty list).")
46 self.addOption ('prescaleDecoration', 'prescale', type=str,
47 info="name (prefix) of decoration for trigger prescales.")
48 self.addOption ('prescaleLumiCalcFiles', [], type=None,
49 info="a list of lumical files (list of strings) to calculate "
50 "trigger prescales. The default is [] (empty list). Mutually "
51 "exclusive with prescaleLumiCalcFilesPerYear")
52 self.addOption ('prescaleLumiCalcFilesPerYear', {}, type=None,
53 info="a dicrionary with key (string) the year and value (list of "
54 "strings) the list of lumicalc files to calculate trigger prescales "
55 "for an individual data year. The default is {} (empty dictionary). "
56 "Mutually exclusive with prescaleLumiCalcFiles")
57 self.addOption ('prescaleTriggersFormula', '', type=str,
58 info="a formula used in (un)prescaling, producing overall prescale "
59 "factor instead of prescale per trigger.")
60 self.addOption ('prescaleMC', False, type=bool,
61 info="ouput trigger prescales when running on MC. The default is False.")
62 self.addOption ('unprescaleData', False, type=bool,
63 info="ouput trigger prescales when running on Data. The default is False.")
64 self.addOption ('prescaleIncludeAllYearsPerRun', False, type=bool,
65 info="if True, trigger prescales will include all configured years "
66 "from prescaleLumiCalcFilesPerYear in all jobs. The default is False.")
67 self.addOption ('splitPerLHCRun', False, type=bool,
68 info="if True, trigger branches will only be processed for the current LHC run.")
69 self.addOption ('noFilter', False, type=bool,
70 info="do not apply an event filter. The default is False, i.e. "
71 "remove events not passing trigger selection and matching.")
72 # TODO: add info string
73 self.addOption ('noL1', False, type=bool,
74 info="")
75
76 def instanceName (self) :
77 """Return the instance name for this block"""
78 return '' # no instance name, as this is a singleton block
79
80 @staticmethod
82 # Might have already been added
83 toolName = "TrigDecisionTool"
84 if toolName in config._algorithms:
85 return config._algorithms[toolName]
86
87 # Create public trigger tools
88 xAODConfTool = config.createPublicTool("TrigConf::xAODConfigTool", "xAODConfigTool")
89 decisionTool = config.createPublicTool("Trig::TrigDecisionTool", toolName)
90 decisionTool.ConfigTool = f"{xAODConfTool.getType()}/{xAODConfTool.getName()}"
91 decisionTool.HLTSummary = config.hltSummary()
92 if config.geometry() is LHCPeriod.Run3:
93 # Read Run 3 navigation
94 # (options are "TrigComposite" for R3 or "TriggElement" for R2,
95 # R2 navigation is not kept in most DAODs)
96 decisionTool.NavigationFormat = "TrigComposite"
97
98 return decisionTool
99
100 @staticmethod
101 def makeTriggerMatchingTool(config, decisionTool):
102 # Might have already been added
103 toolName = "TrigMatchingTool"
104 if toolName in config._algorithms:
105 return config._algorithms[toolName]
106
107 # Create public trigger tools
108 if config.geometry() is LHCPeriod.Run3:
109 drScoringTool = config.createPublicTool("Trig::DRScoringTool", "DRScoringTool")
110 matchingTool = config.createPublicTool("Trig::R3MatchingTool", toolName)
111 matchingTool.ScoringTool = f"{drScoringTool.getType()}/{drScoringTool.getName()}"
112 matchingTool.TrigDecisionTool = f"{decisionTool.getType()}/{decisionTool.getName()}"
113 else:
114 matchingTool = config.createPublicTool("Trig::MatchFromCompositeTool", toolName)
115 if config.isPhyslite():
116 matchingTool.InputPrefix = "AnalysisTrigMatch_"
117 return matchingTool
118
119 @staticmethod
120 def _get_lumicalc_triggers(lumicalc_files: list[str]) -> list[str]:
121 return [lumicalc.split(":")[-1] for lumicalc in lumicalc_files if ":" in lumicalc]
122
123 def makeTriggerSelectionAlg(self, config, decisionTool):
124
125 # Set up the trigger selection:
126 alg = config.createAlgorithm( 'CP::TrigEventSelectionAlg', 'TrigEventSelectionAlg' )
127 alg.tool = '%s/%s' % \
128 ( decisionTool.getType(), decisionTool.getName() )
129 alg.triggers = self.triggerChainsForSelection
130 alg.selectionDecoration = 'trigPassed'
131 alg.noFilter = self.noFilter
132 alg.noL1 = self.noL1
133
135 t = t.replace(".", "p").replace("-", "_")
136 config.addOutputVar ('EventInfo', 'trigPassed_' + t, 'trigPassed_' + t, noSys=True)
137
138 # for decoration, make sure we only get the triggers not already in the selection list
139 triggerChainsForDeco = list(set(self.triggerChainsForDecoration) - set(self.triggerChainsForSelection))
140 if triggerChainsForDeco :
141 alg = config.createAlgorithm( 'CP::TrigEventSelectionAlg', 'TrigEventSelectionAlgDeco' )
142 alg.tool = '%s/%s' % \
143 ( decisionTool.getType(), decisionTool.getName() )
144 alg.triggers = triggerChainsForDeco
145 alg.selectionDecoration = 'trigPassed'
146 alg.noFilter = True
147 alg.noL1 = self.noL1
148
149 for t in triggerChainsForDeco :
150 t = t.replace(".", "p").replace("-", "_")
151 config.addOutputVar ('EventInfo', 'trigPassed_' + t, 'trigPassed_' + t, noSys=True)
152
153 # Calculate trigger prescales
155 self.unprescaleData if config.dataType() is DataType.Data else self.prescaleMC
156 ):
157
158 lumicalc_files = []
160 lumicalc_files = self.prescaleLumiCalcFiles
162 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import get_input_years, get_year_data
163
164 years = get_input_years(config)
165 for year in years:
166 lumicalc_files.extend(get_year_data(self.prescaleLumiCalcFilesPerYear, year))
167
168 prescale_triggers = self._get_lumicalc_triggers(lumicalc_files)
169 prescale_triggersAll = self.triggerChainsForSelection
170
171 # Schedule trigger prescale output branches
172 prescale_triggers_output = set(prescale_triggers)
173 if self.prescaleIncludeAllYearsPerRun and self.prescaleLumiCalcFilesPerYear:
174 all_lumicalc_files = [
175 lumicalc
176 for lumicalc_year, lumicalc_values in self.prescaleLumiCalcFilesPerYear.items()
177 for lumicalc in lumicalc_values
178 if is_year_in_current_period(config, int(lumicalc_year))
179 ]
180 prescale_triggers_output.update(self._get_lumicalc_triggers(all_lumicalc_files))
181 prescale_triggersAll = list(prescale_triggers_output)
182
183 if not self.prescaleTriggersFormula and not prescale_triggersAll:
184 return
185
186 alg = config.createAlgorithm( 'CP::TrigPrescalesAlg', 'TrigPrescalesAlg' )
187 config.addPrivateTool( 'pileupReweightingTool', 'CP::PileupReweightingTool' )
188 alg.pileupReweightingTool.LumiCalcFiles = lumicalc_files
189 alg.selectionDecoration = 'trigPassed'
190 alg.prescaleMC = config.dataType() is not DataType.Data
191 alg.prescaleDecoration = self.prescaleDecoration
192 if self.prescaleTriggersFormula != '':
193 alg.prescaleTriggersFormula = self.prescaleTriggersFormula
194 config.addOutputVar("EventInfo", alg.prescaleDecoration, alg.prescaleDecoration, noSys=True)
195 else:
196 alg.triggers = prescale_triggers
197 alg.triggersAll = prescale_triggersAll
198
199 # Schedule trigger prescale output branches
200 for trigger in prescale_triggers_output:
201 trigger = trigger.replace("-", "_")
202 config.addOutputVar(
203 "EventInfo",
204 alg.prescaleDecoration + "_" + trigger,
205 alg.prescaleDecoration + "_" + trigger,
206 noSys=True,
207 )
208
209 return
210
211
212 def makeAlgs (self, config) :
213
214 if (self.multiTriggerChainsPerYear and self.triggerChainsPerYear and
215 self.triggerChainsPerYear is not self.multiTriggerChainsPerYear.get('')):
216 raise Exception('multiTriggerChainsPerYear and triggerChainsPerYear cannot be configured at the same time!')
217
219 raise Exception('prescaleLumiCalcFiles and prescaleLumiCalcFilesPerYear cannot be configured at the same time!')
220
221 if self.prescaleIncludeAllYearsPerRun and not self.prescaleLumiCalcFilesPerYear:
222 raise Exception('prescaleIncludeAllYearsPerRun requires prescaleLumiCalcFilesPerYear to be configured!')
223
224 if (self.prescaleLumiCalcFiles or self.prescaleLumiCalcFilesPerYear) and not (
225 self.unprescaleData or self.prescaleMC
226 ):
227 raise Exception('Lumicalc files are provided but no trigger prescale output is configured! Specify output with "unprescaleData" and/or "prescaleMC".')
228
229 if self.triggerChainsPerYear and not self.multiTriggerChainsPerYear:
230 self.multiTriggerChainsPerYear = {'': self.triggerChainsPerYear}
231
232 # if we are only given the trigger dictionary, we fill the selection list automatically
233 if self.triggerChainsPerYear and not self.triggerChainsForSelection:
234 triggers_for_selection = set()
235 triggers_for_decoration = set()
236 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import get_input_years
237 years = get_input_years(config)
238 for trigger_chains in self.multiTriggerChainsPerYear.values():
239 for year, chain_list in trigger_chains.items():
240 if self.splitPerLHCRun and not is_year_in_current_period(config, year):
241 continue
242 target_triggers = triggers_for_selection if int(year) in years else triggers_for_decoration
243 for chain in chain_list:
244 if '||' in chain:
245 chains = chain.split('||')
246 target_triggers.update(map(str.strip, chains))
247 else:
248 target_triggers.add(chain.strip())
249 self.triggerChainsForSelection = list(triggers_for_selection)
250 self.triggerChainsForDecoration += list(triggers_for_decoration)
251
252 # Create the decision algorithm, keeping track of the decision tool for later
253 decisionTool = self.makeTriggerDecisionTool(config)
254
256 self.makeTriggerSelectionAlg(config, decisionTool)
257
258 return
259
260
261
262@groupBlocks
263def Trigger(seq):
264 seq.append(TriggerAnalysisBlock())
265 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import TriggerAnalysisSFBlock
266 seq.append(TriggerAnalysisSFBlock())
STL class.
list[str] _get_lumicalc_triggers(list[str] lumicalc_files)
STL class.
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition hcg.cxx:310
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
bool is_year_in_current_period(ConfigAccumulator config, int|str year)