2 from typing
import Iterable, Union
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
11 def is_mc_from(config: ConfigAccumulator, campaign_list: Union[Campaign, Iterable[Campaign]]) -> bool:
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
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
20 def is_data_from(config, data_year_list: Union[int, Iterable[int]]) -> bool:
22 Utility function to check whether the data type is data and whether the year is in desired list of years
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
30 Utility function to get the data for a specific year from a dictionary
32 return dictionary.get(
int(year), dictionary.get(
str(year), []))
37 Utility function to get the list of years that the input corresponds to
40 if config.campaign()
is Campaign.MC20a:
46 elif config.campaign()
is Campaign.MC20d
or is_data_from(config, 2017):
48 elif config.campaign()
is Campaign.MC20e
or is_data_from(config, 2018):
50 elif config.campaign()
is Campaign.MC23a
or is_data_from(config, 2022):
52 elif config.campaign()
is Campaign.MC23d
or is_data_from(config, 2023):
54 elif config.campaign()
is Campaign.MC23e
or is_data_from(config, 2024):
56 elif config.campaign()
is Campaign.MC23g
or is_data_from(config, 2025):
59 raise ValueError(
'TriggerAnalysisConfig: unable to deduce data taking year for input file')
64 def trigger_set(config, triggerChainsPerYear, includeAllYearsPerRun):
66 if includeAllYearsPerRun:
67 for year
in triggerChainsPerYear:
78 """the ConfigBlock for trigger analysis"""
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)
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.",
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).")
149 config: ConfigAccumulator,
152 triggerSuffix: str =
''
154 alg = config.createAlgorithm(
'CP::TrigGlobalEfficiencyAlg',
'TrigGlobalSFAlg' + triggerSuffix + self.postfix)
155 if config.geometry()
is LHCPeriod.Run3:
161 if not alg.triggers_2022:
162 raise ValueError(
'TriggerAnalysisConfig: you must provide a set of triggers for the year 2022!')
164 if not alg.triggers_2023:
165 raise ValueError(
'TriggerAnalysisConfig: you must provide a set of triggers for the year 2023!')
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!')
175 if not alg.triggers_2015:
176 raise ValueError(
'TriggerAnalysisConfig: you must provide a set of triggers for the year 2015!')
178 if not alg.triggers_2016:
179 raise ValueError(
'TriggerAnalysisConfig: you must provide a set of triggers for the year 2016!')
181 if not alg.triggers_2017:
182 raise ValueError(
'TriggerAnalysisConfig: you must provide a set of triggers for the year 2017!')
184 if not alg.triggers_2018:
185 raise ValueError(
'TriggerAnalysisConfig: you must provide a set of triggers for the year 2018!')
187 triggerMatchingChains =
set()
188 if self.separateChainMatching:
191 triggerMatchingChains.update(trig.replace(
' || ',
'_OR_').
split(
'_OR_'))
192 alg.separateMatchingTriggers =
list(triggerMatchingChains)
193 alg.separateMatchingDecorationSuffix = triggerSuffix + self.postfix
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
208 alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
210 alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
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)' )
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)
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)
228 config: ConfigAccumulator,
231 trig_string: str =
"",
232 trig_chains: set =
None,
233 trig_chains_dummy: set =
None,
235 if not particles
or (
not trig_chains
and not trig_chains_dummy):
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):
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)
249 for trig
in list(alg.trigSingleMatchingList) +
list(alg.trigSingleMatchingListDummy):
251 if trig_string
in trig:
252 config.addOutputVar(particles.split(
".")[0], f
"trigMatched_{self.postfix}{trig}", f
"trigMatched_{self.postfix}{trig}", noSys=
True)
256 config: ConfigAccumulator,
261 triggerMatchingChains =
set()
262 triggerMatchingChainsDummy =
set()
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:
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
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)
284 def makeAlgs(self, config: ConfigAccumulator) ->
None:
290 raise Exception(
'multiTriggerChainsPerYear and triggerChainsPerYear cannot be configured at the same time!')
296 decisionTool = TriggerAnalysisBlock.makeTriggerDecisionTool(config)
299 matchingTool = TriggerAnalysisBlock.makeTriggerMatchingTool(config, decisionTool)
308 if self.triggerMatchingChainsPerYear: