ATLAS Offline Software
MenuAlignmentTools.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
2 import numpy as np
3 from collections import OrderedDict
4 from AthenaCommon.Logging import logging
5 import itertools
6 
7 log = logging.getLogger( __name__ )
8 
9 # Warning: If the grouping of signatures changes in *one* of these functions,
10 # it needs to change in ALL of them
11 # They should all group using the same dictionary
12 
13 # The ordering here is VERY important - a set made from the keys will define the order the
14 # chains will run in!
15 # have to use OrderedDict because ordering is not preserved in a standard dictionary!
16 the_signature_grouping = OrderedDict([
17  ('Electron', 'AllTag'),
18  ('Photon' , 'AllTag'),
19  ('Muon' , 'AllTag'),
20  ('Bphysics', 'AllTag'),
21  ('Tau','AllTag'),
22  ('Jet','JetMET'),
23  ('MET','JetMET'),
24  ('UnconventionalTracking','JetMET'),
25  ('Bjet','JetMET'),
26  ('MinBias', 'MinBias'),
27  ('Beamspot', 'Beamspot'),
28  ('MuonnoL1', 'MuonnoL1'),
29  ('Electronprobe', 'AllProbe'),
30  ('Photonprobe' , 'AllProbe'),
31  ('Tauprobe', 'AllProbe'),
32  ('Muonprobe' , 'AllProbe'),
33  ('HeavyIon' , 'MinBias'),
34 ])
35 
36 
38  seen = set()
39  return [v for v in the_signature_grouping.values() if not (v in seen or seen.add(v))]
40 
41 def get_alignment_group_from_pattern(signature, extra):
42 
43  signature_for_alignment = signature + extra
44 
45  log.debug("[get_alignment_group_from_pattern] Searching for alignment group for %s",signature_for_alignment)
46 
47  if signature_for_alignment in the_signature_grouping.keys():
48  return the_signature_grouping[signature_for_alignment]
49  elif signature in the_signature_grouping.keys():
50  log.debug("[get_alignment_group_from_pattern] Falling back to signature alignment grouping for %s (%s)",signature, extra)
51  return the_signature_grouping[signature]
52  else:
53  log.debug("[get_alignment_group_from_pattern] No dedicated alignment grouping for signature %s (%s)",signature, extra)
54  return signature
55 
56 def remove_duplicates(config_tuples):
57  # move a list like this [(5, 'Tau'), (6, 'Tau'), (1, 'JetMET')]
58  # .... to this [(6, 'Tau'), (1, 'JetMET')]
59  # one per group, and keep the max length
60  list_of_groups = [x[1] for x in config_tuples]
61 
62  if len(list_of_groups) == len(set(list_of_groups)):
63  return config_tuples
64 
65  else:
66  unique_list = OrderedDict()
67  for ag_length, ag in config_tuples:
68  if ag in unique_list:
69  if ag_length > unique_list[ag]:
70  unique_list[ag] = ag_length
71  else:
72  unique_list[ag] = ag_length
73 
74  unique_config_tuples = []
75  for ag, ag_length in unique_list.items():
76  unique_config_tuples += [(ag_length, ag)]
77  return unique_config_tuples
78 
79 class MenuAlignment():
80  """ Class to hold/calculate chain alignment """
81  def __init__(self, combinations_in_menu, groups_to_align, length_of_configs):
82 
83  self.combinations_in_menu = combinations_in_menu
84  self.length_of_configs = length_of_configs
85  self.groups_to_align = groups_to_align
86 
87  self.signature_dict = {}
89 
90  self.sets_to_align = {}
91 
92  # first we make a dictionary of the ordering, based on the_signature_grouping
93  # e.g. 1 electron+photon, 2 muon, 3 tau, 4 jet/met/b-jet, 5 noL1 muons
94  igrp = 0
95  for value in the_signature_grouping.values():
96  if value not in self.signature_dict:
97  self.signature_dict[value] = igrp
98  igrp += 1
99 
100  self.inverse_sig_dict = {v: k for k, v in self.signature_dict.items()}
101 
102  # Chains can be grouped together, e.g. e chains, mu chains, gamma chains.
103  # Chains are grouped if we want their steps to run in parallel, e.g. if they have shared
104  # sequences, like e+gamma chains use the same calo clustering step.
105  # Each group is called alignment group. These can also split signatures: muons have
106  # two alignment groups, Muon and MuonnoL1 (chainDict['signature']+chainDict['extra'])
107 
108  # With combined chains, e.g. e+mu, we can define in the_signature_grouping if we want
109  # the two legs to run in parallel (put them in the same alignment group, e.g. EgammaMuon),
110  # or in series (to save CPU - don't run slow muons until the electron decision has been made)
111 
112  # The alignment groups have a global ordering defined in the_signature_grouping
113  # But we only want to make all these triggers run in series if the combinations exist
114  # in the menu - i.e. do not put Tau after Egamma and Muon unless there are combined
115  # Tau chains that necessitate this ordering.
116 
117  # Here, we use a list of all the combined chain signature combinations in the menu and the signature
118  # groupings/orderings we've defined in the_signature_grouping to calculate which signatures
119  # need to be ordered compared to which others.
120  # The function returns a dictionary of an ordered list of signatures each signature belongs to
121  # e.g. 'Muon' : ['Egamma','Muon']
122 
123  def analyse_combinations(self): #combinations_in_menu, alignmentGroups_in_combinations):
124 
125  # need to find out of if an alignment group, or anything in combination with that
126  # aligment group, is in combination with any other alignment group.
127 
128  the_matrix = np.eye((len(self.signature_dict)))
129 
130  for comb in self.combinations_in_menu:
131  for comb_pair in list(itertools.combinations(comb,2)):
132  the_matrix[self.signature_dict[comb_pair[0]]][self.signature_dict[comb_pair[1]]] = 1
133  the_matrix[self.signature_dict[comb_pair[1]]][self.signature_dict[comb_pair[0]]] = 1
134 
135  _,eigenvecs = np.linalg.eig(the_matrix)
136  # eigenvecs: The normalized (unit length) eigenvectors, such that the column v[:,i]
137  # is the eigenvector corresponding to the eigenvalue w[i].
138  # so we do the transpose!
139  eigenvecs = np.transpose(eigenvecs)
140 
141  # find the indices filled by each eigenvector, and make a unique list of these
142  pre_unique_sets = list(set([tuple(np.nonzero(eigenvec)[0]) for eigenvec in eigenvecs]))
143 
144  # remove any that are subsets of another (e.g. because something like (1,1,1), (1,-1,1),(0,1,1)
145  # could be an answer but (0,1,1) clearly is not spanning a unique subspace from (1,1,1))
146  unique_sets = []
147  for aset in pre_unique_sets:
148  if len(unique_sets) == 0 :
149  unique_sets +=[aset]
150  else:
151  inlist = True
152  for ibset,bset in enumerate(unique_sets):
153  if aset == bset:
154  inlist = True
155  break
156  elif set(aset).issubset(set(bset)):
157  inlist = True
158  break
159  elif set(bset).issubset(set(aset)):
160  unique_sets.pop(ibset)
161  unique_sets += [aset]
162  inlist = True
163  break
164  else:
165  inlist = False
166  if not inlist:
167  unique_sets +=[aset]
168 
169  # convert the indices back to actual signature names
170  unique_by_sig = [[ self.inverse_sig_dict[sig_int] for sig_int in setlist ] for setlist in unique_sets]
171 
172  sig_to_set = {}
173  for sig in self.groups_to_align:
174  for aset in unique_by_sig:
175  if sig in aset:
176  sig_to_set[sig] = aset
177 
178  self.sets_to_align = sig_to_set
179 
180  return
181 
182  def alignment_group_is_alone(self, alignment_grp):
183  # no need to align lonely alignment groups
184  # the latter condition should never happen, because we only put in alignment groups that are
185  # in combined chains, and thus will need *some* aligning. but good to check in any case.
186  if alignment_grp not in self.sets_to_align:
187  return True
188  elif len(self.sets_to_align[alignment_grp]) == 1:
189  return True
190  return False
191 
192  def alignment_group_is_first(self, alignment_grp):
193 
194  # if it's the first chain in the set to be aligned, again - nothing to do here.
195  if alignment_grp == self.sets_to_align[alignment_grp][0]:
196  return True
197  return False
198 
199  def get_groups_to_align(self, alignment_grp):
200 
201  all_groups_to_align = self.sets_to_align[alignment_grp]
202  index_of_chain_group = self.sets_to_align[alignment_grp].index(alignment_grp)
203 
204  # we don't need to add empty steps for alignment groups that happen *after* the
205  # one our chain belongs to - so just cut off at the chain's alignment group here.
206  return all_groups_to_align[:index_of_chain_group]
207 
208 
209  def single_align(self, chainDict, chainConfig):
210 
211  if len(set(chainDict['alignmentGroups'])) != 1:
212  log.error("Cannot call single_align on chain %s with alignment groups %s",
213  chainDict['chainName'], ",".join(chainDict['alignmentGroups']))
214  raise Exception("Will not proceed, the chain is not suitable for single alignment.")
215 
216  alignment_grp = chainDict['alignmentGroups'][0]
217 
218  if self.alignment_group_is_alone(alignment_grp):
219  log.debug("Finished with retrieving chain configuration for chain %s", chainDict['chainName'])
220  chainConfig.numberAllSteps()
221  elif self.alignment_group_is_first(alignment_grp):
222  # if it's the first chain in the set to be aligned, again - nothing to do here,
223  # since this is single_align
224  log.debug("Finished with retrieving chain configuration for chain %s", chainDict['chainName'])
225  chainConfig.numberAllSteps()
226  else:
227  # now we know that empty steps are necessary before this chain. we can loop through and add accordingly
228  aligngroups_set = self.get_groups_to_align(alignment_grp)
229  # but we want to do this in reverse
230  aligngroups_set.reverse()
231 
232  for align_grp_to_align in aligngroups_set:
233  chainConfig.insertEmptySteps('Empty'+align_grp_to_align+'Align',self.length_of_configs[align_grp_to_align],0)
234  log.debug("Finished with retrieving chain configuration for chain %s", chainDict['chainName'])
235  chainConfig.numberAllSteps()
236 
237  return chainConfig
238 
239  def multi_align(self, chainDict, chainConfig, lengthOfChainConfigs):
240 
241  lengthOfChainConfigs = remove_duplicates(lengthOfChainConfigs)
242 
243  alignment_grps = chainDict['alignmentGroups']
244 
245  #check for a few bad conditions first:
246  if not set(alignment_grps).issubset(self.sets_to_align):
247  log.error(" one of the alignmentGroups in %s is not available in the sets to align dictionary!", alignment_grps)
248  raise Exception("MenuAlignment.analyse_combinations() needs checking, this should never happen.")
249  elif len(set([tuple(self.sets_to_align[x]) for x in alignment_grps])) != 1:
250  log.error(" the alignmentGroups %s point to different sets in the sets to align dictionary", alignment_grps)
251  for x in alignment_grps:
252  log.error(" Set: %s, group %s", self.sets_to_align[x] , x)
253  raise Exception("MenuAlignment.analyse_combinations() needs checking, this should never happen.")
254 
255  #now we know that all alignmentGroups points to the same set, so just use the first entry
256  if len(self.sets_to_align[alignment_grps[0]]) == 2:
257 
258  # if the pair is on its own, then we just make sure the first signature's number
259  # of steps is equal to the max in that signature (so the next signature starts at the right step)
260 
261  # not a dictionary because we could have a chain mu_jet_munoL1? Not sure. But don't want to
262  # overwrite duplicates yet.
263  # probably, at some point, will need to divide this beyond signature but instead as unique sequence within a signature.
264  # munoL1 is already one case...
265  length_firstgrp = 0
266  max_length_firstgrp = self.length_of_configs[self.sets_to_align[alignment_grps[0]][0]]
267 
268  for config_length,config_grp in lengthOfChainConfigs:
269  if config_grp == self.sets_to_align[alignment_grps[0]][0]:
270  length_firstgrp = config_length
271 
272  if length_firstgrp < max_length_firstgrp:
273  #too short! need to add padding steps between two alignment groups...
274  needed_steps = max_length_firstgrp - length_firstgrp
275  chainConfig.insertEmptySteps('Empty'+self.sets_to_align[alignment_grps[0]][0]+'Align',needed_steps,length_firstgrp)
276 
277  elif length_firstgrp > max_length_firstgrp:
278  log.error("%s first signature length %d is greater than the max calculated, %d",chainDict.name,length_firstgrp, max_length_firstgrp)
279  raise Exception("Probably something went wrong in GenerateMenuMT.generateChains()!")
280 
281  #this should probably work for signatures > 2, but might be a few gotchas (and errors need updating)
282  #Can't properly test until ATR-22206 is resolved.
283  elif len(self.sets_to_align[alignment_grps[0]]) > 2:
284  if not set(alignment_grps).issubset(self.sets_to_align):
285  log.error(" one of the alignmentGroups in %s is not available in the sets to align dictionary!", alignment_grps)
286  raise Exception("MenuAlignment.analyse_combinations() needs checking, this should never happen.")
287  elif len(set([tuple(self.sets_to_align[x]) for x in alignment_grps])) != 1:
288  log.error(" the alignmentGroups %s point to different sets in the sets to align dictionary", alignment_grps)
289  for x in alignment_grps:
290  log.error(" Set: %s, group %s", self.sets_to_align[x] , x)
291  raise Exception("MenuAlignment.analyse_combinations() needs checking, this should never happen.")
292 
293  # first, we need to convert alignment_grps from the order it is based on the chain naming
294  # convention into the order it needs to be for the alignment
295  # these are not always the same thing!
296 
297  # takes the values of the ordering dictionary and creates a list of unique values
298  # so it isn't just e.g. [egamma, egamma, egamma, JetMET, JetMET] but actually
299  # [egamma, JetMET]
300  alignment_grp_ordering = get_alignment_group_ordering()
301  alignment_grps_ordered = [x for x in alignment_grp_ordering if x in alignment_grps]
302 
303  # we need to know which alignment_grps are in the chain in which order. Assume this is always stored correctly.
304  # (this should be true, once it is sorted! If not, there is a bug.)
305  # never need to add empty steps to the last leg - it can end at a different
306  # time (be a different number of steps) - no problem.
307  # ignore any signatures after the end of those in this chain
308  aligngroups_set = self.get_groups_to_align(alignment_grps_ordered[-1])
309  aligngroups_set.reverse()
310  grp_masks = [x in alignment_grps_ordered for x in aligngroups_set]
311  grp_lengths = []
312  for align_grp,grp_in_chain in zip(aligngroups_set,grp_masks):
313  if grp_in_chain:
314  for config_length,config_grp in lengthOfChainConfigs:
315  if config_grp == align_grp:
316  grp_lengths += [config_length]
317  else:
318  grp_lengths += [0]
319 
320  for istep,(align_grp,grp_in_chain,length_in_chain) in enumerate(zip(aligngroups_set,grp_masks,grp_lengths)):
321  # We're working our way backwards through the chain
322  # need to know how many steps are already before us!
323  n_steps_before_grp = 0
324  if istep < len(grp_lengths)-1:
325  n_steps_before_grp = sum(grp_lengths[istep+1:])
326  max_length_grp = self.length_of_configs[align_grp]
327  if grp_in_chain:
328  if length_in_chain < max_length_grp:
329  #too short! gotta add padding steps between two alignmentGroups...
330  needed_steps = max_length_grp - length_in_chain
331  start_step = n_steps_before_grp + length_in_chain
332  chainConfig.insertEmptySteps('Empty'+align_grp+'Align',needed_steps,start_step)
333  else:
334  # this sig isn't in the chain, but we still will need empty steps for it
335  # always add them to the start, because we're running in reverse order
336  chainConfig.insertEmptySteps('Empty'+align_grp+'Align',self.length_of_configs[align_grp],n_steps_before_grp)
337  else:
338  log.error("Should never reach this point. Ordered alignmentGroups: %s, sets_to_align: %s",alignment_grps_ordered,self.sets_to_align)
339  raise Exception("MenuAlignment.multi_align() needs checking, this should never happen.")
340 
341  log.debug("Finished with retrieving chain configuration for chain %s", chainDict['chainName'])
342  chainConfig.numberAllSteps()
343 
344  return chainConfig
index
Definition: index.py:1
MenuAlignmentTools.MenuAlignment.analyse_combinations
def analyse_combinations(self)
Definition: MenuAlignmentTools.py:123
MenuAlignmentTools.MenuAlignment.sets_to_align
sets_to_align
Definition: MenuAlignmentTools.py:90
MenuAlignmentTools.MenuAlignment.signature_dict
signature_dict
Definition: MenuAlignmentTools.py:87
MenuAlignmentTools.MenuAlignment.alignment_group_is_alone
def alignment_group_is_alone(self, alignment_grp)
Definition: MenuAlignmentTools.py:182
MenuAlignmentTools.MenuAlignment
Definition: MenuAlignmentTools.py:79
MenuAlignmentTools.MenuAlignment.__init__
def __init__(self, combinations_in_menu, groups_to_align, length_of_configs)
Definition: MenuAlignmentTools.py:81
MenuAlignmentTools.MenuAlignment.single_align
def single_align(self, chainDict, chainConfig)
Definition: MenuAlignmentTools.py:209
convertTimingResiduals.sum
sum
Definition: convertTimingResiduals.py:55
MenuAlignmentTools.remove_duplicates
def remove_duplicates(config_tuples)
Definition: MenuAlignmentTools.py:56
MenuAlignmentTools.get_alignment_group_ordering
def get_alignment_group_ordering()
Definition: MenuAlignmentTools.py:37
MenuAlignmentTools.MenuAlignment.get_groups_to_align
def get_groups_to_align(self, alignment_grp)
Definition: MenuAlignmentTools.py:199
MenuAlignmentTools.MenuAlignment.multi_align
def multi_align(self, chainDict, chainConfig, lengthOfChainConfigs)
Definition: MenuAlignmentTools.py:239
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
MenuAlignmentTools.MenuAlignment.groups_to_align
groups_to_align
Definition: MenuAlignmentTools.py:85
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:224
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
MenuAlignmentTools.MenuAlignment.length_of_configs
length_of_configs
Definition: MenuAlignmentTools.py:84
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
MenuAlignmentTools.get_alignment_group_from_pattern
def get_alignment_group_from_pattern(signature, extra)
Definition: MenuAlignmentTools.py:41
MenuAlignmentTools.MenuAlignment.combinations_in_menu
combinations_in_menu
Definition: MenuAlignmentTools.py:83
MenuAlignmentTools.MenuAlignment.alignment_group_is_first
def alignment_group_is_first(self, alignment_grp)
Definition: MenuAlignmentTools.py:192
MenuAlignmentTools.MenuAlignment.inverse_sig_dict
inverse_sig_dict
Definition: MenuAlignmentTools.py:88