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 self.addOption ('multiTriggerChainsPerYear', {}, type=None,
32 info="a dictionary with key (string) a trigger set name and value a "
33 "`triggerChainsPerYear` dictionary, following the previous convention. "
34 "Relevant for analyses using different triggers in different categories, "
35 "where the trigger global scale factors shouldn't be combined.")
36 self.addOption ('triggerChainsForSelection', [], type=None,
37 info="a list of trigger chains (list of strings) to be used for "
38 "trigger selection. Only set it if you need a different setup "
39 "than for trigger SFs.")
40 self.addOption ('triggerChainsForDecoration', [], type=None,
41 info="a list of trigger chains (list of strings) to be used for "
42 "trigger decoration, if it needs to be different from the selection one.")
43 self.addOption ('prescaleDecoration', 'prescale', type=str,
44 info="name of the decoration for trigger prescales.")
45 self.addOption ('prescaleLumiCalcFiles', [], type=None,
46 info="a list of lumicalc files (list of strings) to calculate "
47 "trigger prescales. Mutually "
48 "exclusive with `prescaleLumiCalcFilesPerYear`.")
49 self.addOption ('prescaleLumiCalcFilesPerYear', {}, type=None,
50 info="a dicrionary with key (string) the year and value (list of "
51 "strings) the list of lumicalc files to calculate trigger prescales "
52 "for an individual data year. "
53 "Mutually exclusive with `prescaleLumiCalcFiles`.")
54 self.addOption ('prescaleTriggersFormula', '', type=str,
55 info="a formula used in (un)prescaling, producing overall prescale "
56 "factor instead of prescale per trigger.")
57 self.addOption ('prescaleMC', False, type=bool,
58 info="output trigger prescales when running on MC.")
59 self.addOption ('unprescaleData', False, type=bool,
60 info="output trigger prescales when running on Data.")
61 self.addOption ('prescaleIncludeAllYearsPerRun', False, type=bool,
62 info="trigger prescales will include all configured years "
63 "from `prescaleLumiCalcFilesPerYear` in all jobs.")
64 self.addOption ('splitPerLHCRun', False, type=bool,
65 info="trigger branches will only be processed for the current LHC run.")
66 self.addOption ('noFilter', False, type=bool,
67 info="do not apply an event filter (i.e. keep events not passing trigger selection or matching).")
68 self.addOption ('noL1', False, type=bool,
69 info="toggle off the L1 trigger decision. This flag is required e.g. for running the algorithm on TLA stream data.")
70
71 def instanceName (self) :
72 """Return the instance name for this block"""
73 return '' # no instance name, as this is a singleton block
74
75 @staticmethod
77 # Might have already been added
78 toolName = "TrigDecisionTool"
79 if toolName in config._algorithms:
80 return config._algorithms[toolName]
81
82 # Create public trigger tools
83 xAODConfTool = config.createPublicTool("TrigConf::xAODConfigTool", "xAODConfigTool")
84 decisionTool = config.createPublicTool("Trig::TrigDecisionTool", toolName)
85 decisionTool.ConfigTool = f"{xAODConfTool.getType()}/{xAODConfTool.getName()}"
86 decisionTool.HLTSummary = config.hltSummary()
87 if config.geometry() is LHCPeriod.Run3:
88 # Read Run 3 navigation
89 # (options are "TrigComposite" for R3 or "TriggElement" for R2,
90 # R2 navigation is not kept in most DAODs)
91 decisionTool.NavigationFormat = "TrigComposite"
92
93 return decisionTool
94
95 @staticmethod
96 def makeTriggerMatchingTool(config, decisionTool):
97 # Might have already been added
98 toolName = "TrigMatchingTool"
99 if toolName in config._algorithms:
100 return config._algorithms[toolName]
101
102 # Create public trigger tools
103 if config.geometry() is LHCPeriod.Run3:
104 drScoringTool = config.createPublicTool("Trig::DRScoringTool", "DRScoringTool")
105 matchingTool = config.createPublicTool("Trig::R3MatchingTool", toolName)
106 matchingTool.ScoringTool = f"{drScoringTool.getType()}/{drScoringTool.getName()}"
107 matchingTool.TrigDecisionTool = f"{decisionTool.getType()}/{decisionTool.getName()}"
108 else:
109 matchingTool = config.createPublicTool("Trig::MatchFromCompositeTool", toolName)
110 if config.isPhyslite():
111 matchingTool.InputPrefix = "AnalysisTrigMatch_"
112 return matchingTool
113
114 @staticmethod
115 def _get_lumicalc_triggers(lumicalc_files: list[str]) -> list[str]:
116 return [lumicalc.split(":")[-1] for lumicalc in lumicalc_files if ":" in lumicalc]
117
118 def makeTriggerSelectionAlg(self, config, decisionTool):
119
120 # Set up the trigger selection:
121 alg = config.createAlgorithm( 'CP::TrigEventSelectionAlg', 'TrigEventSelectionAlg' )
122 alg.tool = '%s/%s' % \
123 ( decisionTool.getType(), decisionTool.getName() )
124 alg.triggers = self.triggerChainsForSelection
125 alg.selectionDecoration = 'trigPassed'
126 alg.noFilter = self.noFilter
127 alg.noL1 = self.noL1
128
130 t = t.replace(".", "p").replace("-", "_")
131 config.addOutputVar ('EventInfo', 'trigPassed_' + t, 'trigPassed_' + t, noSys=True)
132
133 # for decoration, make sure we only get the triggers not already in the selection list
134 triggerChainsForDeco = list(set(self.triggerChainsForDecoration) - set(self.triggerChainsForSelection))
135 if triggerChainsForDeco :
136 alg = config.createAlgorithm( 'CP::TrigEventSelectionAlg', 'TrigEventSelectionAlgDeco' )
137 alg.tool = '%s/%s' % \
138 ( decisionTool.getType(), decisionTool.getName() )
139 alg.triggers = triggerChainsForDeco
140 alg.selectionDecoration = 'trigPassed'
141 alg.noFilter = True
142 alg.noL1 = self.noL1
143
144 for t in triggerChainsForDeco :
145 t = t.replace(".", "p").replace("-", "_")
146 config.addOutputVar ('EventInfo', 'trigPassed_' + t, 'trigPassed_' + t, noSys=True)
147
148 # Calculate trigger prescales
150 self.unprescaleData if config.dataType() is DataType.Data else self.prescaleMC
151 ):
152
153 lumicalc_files = []
155 lumicalc_files = self.prescaleLumiCalcFiles
157 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import get_input_years, get_year_data
158
159 years = get_input_years(config)
160 for year in years:
161 lumicalc_files.extend(get_year_data(self.prescaleLumiCalcFilesPerYear, year))
162
163 prescale_triggers = self._get_lumicalc_triggers(lumicalc_files)
164 prescale_triggersAll = self.triggerChainsForSelection
165
166 # Schedule trigger prescale output branches
167 prescale_triggers_output = set(prescale_triggers)
168 if self.prescaleIncludeAllYearsPerRun and self.prescaleLumiCalcFilesPerYear:
169 all_lumicalc_files = [
170 lumicalc
171 for lumicalc_year, lumicalc_values in self.prescaleLumiCalcFilesPerYear.items()
172 for lumicalc in lumicalc_values
173 if is_year_in_current_period(config, int(lumicalc_year))
174 ]
175 prescale_triggers_output.update(self._get_lumicalc_triggers(all_lumicalc_files))
176 prescale_triggersAll = list(prescale_triggers_output)
177
178 if not self.prescaleTriggersFormula and not prescale_triggersAll:
179 return
180
181 alg = config.createAlgorithm( 'CP::TrigPrescalesAlg', 'TrigPrescalesAlg' )
182 config.addPrivateTool( 'pileupReweightingTool', 'CP::PileupReweightingTool' )
183 alg.pileupReweightingTool.LumiCalcFiles = lumicalc_files
184 alg.selectionDecoration = 'trigPassed'
185 alg.prescaleMC = config.dataType() is not DataType.Data
186 alg.prescaleDecoration = self.prescaleDecoration
187 if self.prescaleTriggersFormula != '':
188 alg.prescaleTriggersFormula = self.prescaleTriggersFormula
189 config.addOutputVar("EventInfo", alg.prescaleDecoration, alg.prescaleDecoration, noSys=True)
190 else:
191 alg.triggers = prescale_triggers
192 alg.triggersAll = prescale_triggersAll
193
194 # Schedule trigger prescale output branches
195 for trigger in prescale_triggers_output:
196 trigger = trigger.replace("-", "_")
197 config.addOutputVar(
198 "EventInfo",
199 alg.prescaleDecoration + "_" + trigger,
200 alg.prescaleDecoration + "_" + trigger,
201 noSys=True,
202 )
203
204 return
205
206
207 def makeAlgs (self, config) :
208
209 if (self.multiTriggerChainsPerYear and self.triggerChainsPerYear and
210 self.triggerChainsPerYear is not self.multiTriggerChainsPerYear.get('')):
211 raise Exception('multiTriggerChainsPerYear and triggerChainsPerYear cannot be configured at the same time!')
212
214 raise Exception('prescaleLumiCalcFiles and prescaleLumiCalcFilesPerYear cannot be configured at the same time!')
215
216 if self.prescaleIncludeAllYearsPerRun and not self.prescaleLumiCalcFilesPerYear:
217 raise Exception('prescaleIncludeAllYearsPerRun requires prescaleLumiCalcFilesPerYear to be configured!')
218
219 if (self.prescaleLumiCalcFiles or self.prescaleLumiCalcFilesPerYear) and not (
220 self.unprescaleData or self.prescaleMC
221 ):
222 raise Exception('Lumicalc files are provided but no trigger prescale output is configured! Specify output with "unprescaleData" and/or "prescaleMC".')
223
224 if self.triggerChainsPerYear and not self.multiTriggerChainsPerYear:
225 self.multiTriggerChainsPerYear = {'': self.triggerChainsPerYear}
226
227 # if we are only given the trigger dictionary, we fill the selection list automatically
228 if self.triggerChainsPerYear and not self.triggerChainsForSelection:
229 triggers_for_selection = set()
230 triggers_for_decoration = set()
231 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import get_input_years
232 years = get_input_years(config)
233 for trigger_chains in self.multiTriggerChainsPerYear.values():
234 for year, chain_list in trigger_chains.items():
235 if self.splitPerLHCRun and not is_year_in_current_period(config, year):
236 continue
237 target_triggers = triggers_for_selection if int(year) in years else triggers_for_decoration
238 for chain in chain_list:
239 if '||' in chain:
240 chains = chain.split('||')
241 target_triggers.update(map(str.strip, chains))
242 else:
243 target_triggers.add(chain.strip())
244 self.triggerChainsForSelection = list(triggers_for_selection)
245 self.triggerChainsForDecoration += list(triggers_for_decoration)
246
247 # Create the decision algorithm, keeping track of the decision tool for later
248 decisionTool = self.makeTriggerDecisionTool(config)
249
251 self.makeTriggerSelectionAlg(config, decisionTool)
252
253 return
254
255
256
257@groupBlocks
258def Trigger(seq):
259 seq.append(TriggerAnalysisBlock())
260 from TriggerAnalysisAlgorithms.TriggerAnalysisSFConfig import TriggerAnalysisSFBlock
261 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)