ATLAS Offline Software
Loading...
Searching...
No Matches
TriggerAnalysisConfig.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 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 sanitizeTriggerChainName(chain: str) -> str:
10 """Convert a trigger chain name for use in decoration/branch names; keep in sync with TriggerAnalysisAlgorithms/TrigChainNameHelpers.h"""
11 return chain.replace(".", "p").replace("-", "_")
12
13
14def is_year_in_current_period(config: ConfigAccumulator, year: int | str) -> bool:
15 """
16 Utility function to check whether the year is valid for the current configuration
17 """
18 year = int(year)
19 if config.geometry() is LHCPeriod.Run2 and year >= 2022:
20 return False
21 if config.geometry() is LHCPeriod.Run3 and year < 2022:
22 return False
23
24 return True
25
26
27class TriggerAnalysisBlock (ConfigBlock):
28 """the ConfigBlock for trigger analysis"""
29
30 def __init__ (self) :
31 super ().__init__ ()
32 self.addOption ('triggerChainsPerYear', {}, type=dict,
33 info="a dictionary with key (string) the year and value (list of "
34 "strings) the trigger chains. You can also use `||` within a string "
35 "to enforce an OR of triggers without looking up the individual "
36 "triggers. Used for both trigger selection and SFs.")
37 self.addOption ('multiTriggerChainsPerYear', {}, type=dict,
38 info="a dictionary with key (string) a trigger set name and value a "
39 "`triggerChainsPerYear` dictionary, following the previous convention. "
40 "Relevant for analyses using different triggers in different categories, "
41 "where the trigger global scale factors shouldn't be combined.")
42 self.addOption ('triggerChainsForSelection', [], type=list,
43 info="a list of trigger chains (list of strings) to be used for "
44 "trigger selection. Only set it if you need a different setup "
45 "than for trigger SFs.")
46 self.addOption ('triggerChainsForDecoration', [], type=list,
47 info="a list of trigger chains (list of strings) to be used for "
48 "trigger decoration, if it needs to be different from the selection one.")
49 self.addOption ('prescaleDecoration', 'prescale', type=str,
50 info="name of the decoration for trigger prescales.")
51 self.addOption ('prescaleLumiCalcFiles', [], type=list,
52 info="a list of lumicalc files (list of strings) to calculate "
53 "trigger prescales. Mutually "
54 "exclusive with `prescaleLumiCalcFilesPerYear`.")
55 self.addOption ('prescaleLumiCalcFilesPerYear', {}, type=dict,
56 info="a dicrionary with key (string) the year and value (list of "
57 "strings) the list of lumicalc files to calculate trigger prescales "
58 "for an individual data year. "
59 "Mutually exclusive with `prescaleLumiCalcFiles`.")
60 self.addOption ('prescaleTriggersFormula', '', type=str,
61 info="a formula used in (un)prescaling, producing overall prescale "
62 "factor instead of prescale per trigger.")
63 self.addOption ('prescaleMC', False, type=bool,
64 info="output trigger prescales when running on MC.")
65 self.addOption ('unprescaleData', False, type=bool,
66 info="output trigger prescales when running on Data.")
67 self.addOption ('prescaleIncludeAllYearsPerRun', False, type=bool,
68 info="trigger prescales will include all configured years "
69 "from `prescaleLumiCalcFilesPerYear` in all jobs.")
70 self.addOption ('splitPerLHCRun', False, type=bool,
71 info="trigger branches will only be processed for the current LHC run.")
72 self.addOption ('noFilter', False, type=bool,
73 info="do not apply an event filter (i.e. keep events not passing trigger selection or matching).")
74 self.addOption ('noL1', False, type=bool,
75 info="toggle off the L1 trigger decision. This flag is required e.g. for running the algorithm on TLA stream data.")
76
77 def instanceName (self) :
78 """Return the instance name for this block"""
79 return '' # no instance name, as this is a singleton block
80
81 @staticmethod
83 # Might have already been added
84 toolName = "TrigDecisionTool"
85 if toolName in config._algorithms:
86 return config._algorithms[toolName]
87
88 # Create public trigger tools
89 xAODConfTool = config.createPublicTool("TrigConf::xAODConfigTool", "xAODConfigTool")
90 decisionTool = config.createPublicTool("Trig::TrigDecisionTool", toolName)
91 decisionTool.ConfigTool = f"{xAODConfTool.getType()}/{xAODConfTool.getName()}"
92 decisionTool.HLTSummary = config.hltSummary()
93 if config.geometry() is LHCPeriod.Run3:
94 # Read Run 3 navigation
95 # (options are "TrigComposite" for R3 or "TriggElement" for R2,
96 # R2 navigation is not kept in most DAODs)
97 decisionTool.NavigationFormat = "TrigComposite"
98
99 return decisionTool
100
101 @staticmethod
102 def makeTriggerMatchingTool(config, decisionTool):
103 # Might have already been added
104 toolName = "TrigMatchingTool"
105 if toolName in config._algorithms:
106 return config._algorithms[toolName]
107
108 # Create public trigger tools
109 if config.geometry() is LHCPeriod.Run3:
110 drScoringTool = config.createPublicTool("Trig::DRScoringTool", "DRScoringTool")
111 matchingTool = config.createPublicTool("Trig::R3MatchingTool", toolName)
112 matchingTool.ScoringTool = f"{drScoringTool.getType()}/{drScoringTool.getName()}"
113 matchingTool.TrigDecisionTool = f"{decisionTool.getType()}/{decisionTool.getName()}"
114 else:
115 matchingTool = config.createPublicTool("Trig::MatchFromCompositeTool", toolName)
116 if config.isPhyslite():
117 matchingTool.InputPrefix = "AnalysisTrigMatch_"
118 return matchingTool
119
120 @staticmethod
121 def _get_lumicalc_triggers(lumicalc_files: list[str]) -> list[str]:
122 return [lumicalc.split(":")[-1] for lumicalc in lumicalc_files if ":" in lumicalc]
123
124 def makeTriggerSelectionAlg(self, config, decisionTool):
125
126 # Set up the trigger selection:
127 alg = config.createAlgorithm( 'CP::TrigEventSelectionAlg', 'TrigEventSelectionAlg' )
128 alg.tool = f'{decisionTool.getType()}/{decisionTool.getName()}'
129 alg.triggers = self.triggerChainsForSelection
130 alg.selectionDecoration = 'trigPassed'
131 alg.noFilter = self.noFilter
132 alg.noL1 = self.noL1
133
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 = f'{decisionTool.getType()}/{decisionTool.getName()}'
143 alg.triggers = triggerChainsForDeco
144 alg.selectionDecoration = 'trigPassed'
145 alg.noFilter = True
146 alg.noL1 = self.noL1
147
148 for t in triggerChainsForDeco :
150 config.addOutputVar ('EventInfo', 'trigPassed_' + t, 'trigPassed_' + t, noSys=True)
151
152 # Calculate trigger prescales
154 self.unprescaleData if config.dataType() is DataType.Data else self.prescaleMC
155 ):
156
157 lumicalc_files = []
159 lumicalc_files = self.prescaleLumiCalcFiles
161 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import get_input_years, get_year_data
162
163 years = get_input_years(config)
164 for year in years:
165 lumicalc_files.extend(get_year_data(self.prescaleLumiCalcFilesPerYear, year))
166
167 prescale_triggers = self._get_lumicalc_triggers(lumicalc_files)
168 prescale_triggersAll = self.triggerChainsForSelection
169
170 # Schedule trigger prescale output branches
171 prescale_triggers_output = set(prescale_triggers)
172 if self.prescaleIncludeAllYearsPerRun and self.prescaleLumiCalcFilesPerYear:
173 all_lumicalc_files = [
174 lumicalc
175 for lumicalc_year, lumicalc_values in self.prescaleLumiCalcFilesPerYear.items()
176 for lumicalc in lumicalc_values
177 if is_year_in_current_period(config, int(lumicalc_year))
178 ]
179 prescale_triggers_output.update(self._get_lumicalc_triggers(all_lumicalc_files))
180 prescale_triggersAll = list(prescale_triggers_output)
181
182 if not self.prescaleTriggersFormula and not prescale_triggersAll:
183 return
184
185 alg = config.createAlgorithm( 'CP::TrigPrescalesAlg', 'TrigPrescalesAlg' )
186 config.addPrivateTool( 'pileupReweightingTool', 'CP::PileupReweightingTool' )
187 alg.pileupReweightingTool.LumiCalcFiles = lumicalc_files
188 alg.selectionDecoration = 'trigPassed'
189 alg.prescaleMC = config.dataType() is not DataType.Data
190 alg.prescaleDecoration = self.prescaleDecoration
191 if self.prescaleTriggersFormula != '':
192 alg.triggersFormula = self.prescaleTriggersFormula
193 config.addOutputVar("EventInfo", alg.prescaleDecoration, alg.prescaleDecoration, noSys=True)
194 else:
195 alg.triggers = prescale_triggers
196 alg.triggersAll = prescale_triggersAll
197
198 # Schedule trigger prescale output branches
199 for trigger in prescale_triggers_output & set(prescale_triggersAll):
200 trigger = sanitizeTriggerChainName(trigger)
201 config.addOutputVar(
202 "EventInfo",
203 alg.prescaleDecoration + "_" + trigger,
204 alg.prescaleDecoration + "_" + trigger,
205 noSys=True,
206 )
207
208 return
209
210
211 def makeAlgs (self, config) :
212
213 if (self.multiTriggerChainsPerYear and self.triggerChainsPerYear and
214 self.triggerChainsPerYear is not self.multiTriggerChainsPerYear.get('')):
215 raise Exception('multiTriggerChainsPerYear and triggerChainsPerYear cannot be configured at the same time!')
216
218 raise Exception('prescaleLumiCalcFiles and prescaleLumiCalcFilesPerYear cannot be configured at the same time!')
219
220 if self.prescaleIncludeAllYearsPerRun and not self.prescaleLumiCalcFilesPerYear:
221 raise Exception('prescaleIncludeAllYearsPerRun requires prescaleLumiCalcFilesPerYear to be configured!')
222
223 if (self.prescaleLumiCalcFiles or self.prescaleLumiCalcFilesPerYear) and not (
224 self.unprescaleData or self.prescaleMC
225 ):
226 raise Exception('Lumicalc files are provided but no trigger prescale output is configured! Specify output with "unprescaleData" and/or "prescaleMC".')
227
228 if self.triggerChainsPerYear and not self.multiTriggerChainsPerYear:
229 self.multiTriggerChainsPerYear = {'': self.triggerChainsPerYear}
230
231 # if we are only given the trigger dictionary, we fill the selection list automatically
232 # (configuring only multiTriggerChainsPerYear intentionally does not auto-fill triggerChainsForSelection)
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:312
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:132
bool is_year_in_current_period(ConfigAccumulator config, int|str year)