ATLAS Offline Software
Loading...
Searching...
No Matches
ConfigHelpers.py
Go to the documentation of this file.
1# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2
3""" Helper functions for configuring MET chains"""
4
5from __future__ import annotations
6from typing import Any, Optional
7
8from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
9from AthenaConfiguration.ComponentFactory import CompFactory
10from ..Menu.SignatureDicts import METChainParts_Default, METChainParts
11from ..Config.MenuComponents import (
12 ChainStep,
13 InEventRecoCA,
14 SelectionCA,
15 MenuSequence,
16)
17from .StepOutput import StepOutput
18from copy import copy
19from ..CommonSequences.FullScanDefs import trkFSRoI
20from AthenaCommon.Logging import logging
21from TrigEFMissingET.TrigEFMissingETConfig import getMETMonTool
22from abc import ABC, abstractmethod
23from string import ascii_uppercase
24from TrigMissingETHypo.TrigMissingETHypoConfig import TrigMETHypoToolFromDict
25import functools
26
27def streamer_hypo_tool(flags, chainDict):
28 return CompFactory.TrigStreamerHypoTool(chainDict["chainName"])
29
30
31log = logging.getLogger(__name__)
32
33# The keys from the MET chain dict that directly affect reconstruction
34# The order here is important as it also controls the dict -> string conversion
35
36recoKeys = [
37 "EFrecoAlg",
38 "calib",
39 "constitType",
40 "constitmod",
41 "jetCalib",
42 "nSigma",
43 "addInfo",
44]
45metFSRoIs: list[str | None] = ["", None, trkFSRoI]
46
47
48def metRecoDictToString(recoDict: dict[str, str], skipDefaults: bool = True) -> str:
49 """Convert a dictionary containing reconstruction keys to a string
50
51 Any key (from recoKeys) missing will just be skipped.
52
53 Parameters
54 ----------
55 recoDict : dict[str, str]
56 The reconstruction dictionary
57 skipDefaults : bool, optional
58 If true, skip any values that match the default, by default True
59
60 Returns
61 -------
62 str
63 The fixed string representation
64 """
65 return "_".join(
66 recoDict[k]
67 for k in recoKeys
68 if k in recoDict
69 and (not skipDefaults or recoDict[k] != METChainParts_Default[k])
70 )
71
72
73def stringToMETRecoDict(string: str, onlyRecoKeys: bool = False) -> dict[str, Any]:
74 """Convert a string to a MET reco dict. Optionally only keep entries contained in recoKeys.
75 Necessary for correctly configuring algorithms as inputs to NN.
76 """
77 defaults = copy(METChainParts_Default)
78 if onlyRecoKeys:
79 defaults = {key: defaults[key] for key in recoKeys}
80 # Now go through the parts of the string and fill in the dict
81 for part in string.split("_"):
82 for key, values in METChainParts.items():
83 if not isinstance(values, list):
84 continue
85 if part in values:
86 if isinstance(defaults[key], list):
87 defaults[key].append(part) # type: ignore[attr-defined]
88 else:
89 defaults[key] = part
90 return defaults
91
92
93class AlgConfig(ABC):
94 """Base class to describe algorithm configurations
95
96 Each individual 'EFrecoAlg' should be described by *one* AlgConfig subclass.
97 It must provide its list of required inputs to the constructor and override
98 the make_fex method
99
100 The name of fexAlg *must* be self.fexName and the METContainerKey property
101 *must* be set to self.outputKey (but this class usually should take care of
102 this).
103
104 The subclass must also implement the @classmethod 'algType' which returns
105 the EFrecoAlg string that it describes.
106 """
107
108 @classmethod
109 def algType(cls) -> str:
110 """The algorithm that this object configures - this corresponds to the
111 EFrecoAlg in the METChainParts dictionary
112
113 Note that no two subclasses of AlgConfig can describe the same algorithm
114 (as identified by this string).
115 """
116 raise NotImplementedError("algType not implemented by subclass!")
117
118 def __init__(self, **recoDict: str):
119 """Initialise the base class
120
121 =========
122 Arguments
123 =========
124 recoDict: Pass *all* the keys required for the recoDict
125 """
126
127 # Make sure that we got *all* the keys (i.e. the subclass didn't
128 # inadvertently steal one of them from us)
129 alg_type = self.algType()
130 assert all(k in recoDict for k in recoKeys), (
131 f"AlgConfig.__init__ for {alg_type} did not receive all the recoKeys"
132 " - this suggests a problem in the subclass __init__ method!"
133 )
134 self.recoDict = copy(recoDict)
136
137 @property
138 def outputKey(self) -> str:
139 """The MET container object produced by this algorithm"""
140 from TrigEDMConfig.TriggerEDM import recordable
141
142 return recordable("HLT_MET_{}".format(self._suffix))
143
144 @property
145 def fexName(self) -> str:
146 """The name of the algorithm made by this configuration"""
147 return "EFMET_{}".format(self._suffix)
148
149 def interpret_reco_dict(self) -> dict[str, Any]:
150 """Return a version of the reco dict with any necessary post-processing"""
151 return self.recoDict
152
153 def getMonTool(self, flags):
154 """Create the monitoring tool"""
155 return getMETMonTool(flags)
156
157 def name_step(self, idx) -> str:
158 return f"step{ascii_uppercase[idx]}_{self._suffix}"
159
160 @abstractmethod
161 def make_reco_algs(self, flags, **recoDict) -> StepOutput:
162 """Create the reconstruction sequences including the FEX
163
164 Returns a list of CAs split by step
165 """
166
167 def _append_fex(self, flags, fex, inputs: Optional[StepOutput] = None) -> None:
168 """Append the FEX to the output object
169
170 Finalizes the FEX by setting the monitoring tool and output name.
171 """
172 fex.MonTool = self.getMonTool(flags)
173 fex.METContainerKey = self.outputKey
174 acc = ComponentAccumulator()
175 acc.addEventAlgo(fex, primary=True)
176 return StepOutput.create(acc, inputs, MET=self.outputKey)
177
178 def make_reco_ca(self, flags):
179 """Make the reconstruction sequences for the new JO style"""
180 # Retrieve the inputs
181 log.verbose("Create inputs for %s", self._suffix)
182 steps, inputs = self.inputRegistry.build_steps(
183 flags, self._inputs, metFSRoIs, self.recoDict, return_ca=True
184 )
185 # Create the FEX and add it to the last input sequence
186 fex = self.make_fex_accumulator(flags, self.fexName, inputs)
187 fex.MonTool = self.getMonTool(flags)
188 fex.METContainerKey = self.outputKey
189 steps[-1].addEventAlgo(fex)
190 return steps
191
192 def make_accumulator_steps(self, flags, chainDict):
193 """Make the full accumulator steps"""
194
195 # Get the reco sequences
196 reco_sequences = self.make_reco_algs(flags, **self.interpret_reco_dict()).steps
197 output_steps = []
198 # build up the output steps
199 # We have to merge together the CAs containing the reconstruction sequences
200 # and the InEventRecoCAs that contain the input makers
201 for step_idx, reco_sequence in enumerate(reco_sequences):
202 reco_acc = self.inputMakerCA(step_idx)
203 sel_acc = None
204 step_name = f"Empty_METStep{step_idx}"
205 if (
206 reco_acc is not None
207 ): # Define reco and hypo algorithms only if reco_acc is not None
208 if step_idx == len(reco_sequences) - 1:
209 # If this is the last step we have to add the hypo alg
210 hypo_alg = self.make_hypo_alg()
211 hypo_tool = TrigMETHypoToolFromDict
212 else:
213 # Otherwise, we add a passthrough hypo
214 hypo_alg = self.make_passthrough_hypo_alg(step_idx)
215 hypo_tool = streamer_hypo_tool
216
217 # Now merge the reco sequence into the InEventRecoCA
218 reco_acc.mergeReco(reco_sequence)
219
220 # Create a selection CA
221 sel_acc = SelectionCA("METAthSeq_" + self.name_step(step_idx))
222 # Merge in the reconstruction sequence
223 sel_acc.mergeReco(reco_acc)
224 # Add its hypo alg
225 sel_acc.addHypoAlgo(hypo_alg)
226
227 # FIXME: We need to mark the CA as merged because during the fast menu generation
228 # the merging is only done once for all chains, which is not compatible with the MET
229 # menu generation code. Remove this once ATR-29211 is fixed.
230 sel_acc.wasMerged()
231
232 step_name = sel_acc.name
233
234 # Build the menu sequence and create the actual chainStep
235 output_steps.append(
236 ChainStep(
237 name=step_name,
238 chainDicts=[chainDict],
239 SequenceGens=[]
240 if sel_acc is None
241 else [
242 functools.partial(make_MET_menu_sequenceGenCfg, flags, sel_acc, hypo_tool)
243 ],
244 isEmpty=True if sel_acc is None else False
245 )
246 )
247
248 return output_steps
249
250
251 def make_hypo_alg(self):
252 """The hypo alg used for this configuration"""
253 return CompFactory.TrigMissingETHypoAlg(
254 f"METHypoAlg_{self._suffix}", METContainerKey=self.outputKey
255 )
256
258 return CompFactory.TrigStreamerHypoAlg(
259 "METPassThroughHypo_" + self.name_step(step)
260 )
261
262 def inputMakerCA(self, idx):
263 """Get the InEventRecoCA for the given step index"""
264 from TrigT2CaloCommon.CaloDef import clusterFSInputMaker
265 from AthenaConfiguration.ComponentFactory import CompFactory
266 from ..CommonSequences.FullScanDefs import trkFSRoI
267
268 name = self.name_step(idx) + "Reco"
269 if idx == 0:
270 return InEventRecoCA(name, inputMaker=clusterFSInputMaker())
271 elif idx == 1:
272 return None
273 elif idx == 2:
274 return InEventRecoCA(
275 name,
276 inputMaker=CompFactory.InputMakerForRoI(
277 "IM_Jet_TrackingStep",
278 RoITool=CompFactory.ViewCreatorInitialROITool(),
279 RoIs=trkFSRoI,
280 ),
281 )
282 else:
283 raise KeyError(f"No input maker for step {idx}")
284
285 @classmethod
287 """Provides a way to iterate over all subclasses of this class"""
288 for subcls in cls.__subclasses__():
289 for subsubcls in subcls.__subclasses__():
290 yield subsubcls
291 yield subcls
292
293 @classmethod
294 def fromRecoDict(cls, EFrecoAlg, **recoDict) -> AlgConfig:
295 for subcls in cls._get_subclasses():
296 if subcls.algType() == EFrecoAlg:
297 return subcls(EFrecoAlg=EFrecoAlg, **recoDict)
298
299 raise ValueError("Unknown EFrecoAlg '{}' requested".format(EFrecoAlg))
300
301def make_MET_menu_sequenceGenCfg(flags, sel_acc, hypo_tool):
302 return MenuSequence(flags, selectionCA=sel_acc, HypoToolGen=hypo_tool)
303
304# Load all the defined configurations
305from . import AlgConfigs
306
307# Make sure that there is an AlgConfig for every EFrecoAlg
308AlgConfigs.test_configs()
make_accumulator_steps(self, flags, chainDict)
None _append_fex(self, flags, fex, Optional[StepOutput] inputs=None)
AlgConfig fromRecoDict(cls, EFrecoAlg, **recoDict)
StepOutput make_reco_algs(self, flags, **recoDict)
make_MET_menu_sequenceGenCfg(flags, sel_acc, hypo_tool)
streamer_hypo_tool(flags, chainDict)
str metRecoDictToString(dict[str, str] recoDict, bool skipDefaults=True)
dict[str, Any] stringToMETRecoDict(str string, bool onlyRecoKeys=False)