Loading [MathJax]/extensions/tex2jax.js
ATLAS Offline Software
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
ChainMerging.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
2 
3 from TriggerMenuMT.HLT.Config.Utility.MenuAlignmentTools import get_alignment_group_ordering as getAlignmentGroupOrdering
4 from TriggerMenuMT.HLT.Config.MenuComponents import Chain, ChainStep, isEmptySequenceCfg, createEmptyMenuSequenceCfg
5 
6 from AthenaCommon.Logging import logging
7 from TrigCompositeUtils.TrigCompositeUtils import legName
8 import functools
9 from itertools import repeat
10 from copy import deepcopy
11 import re
12 
13 log = logging.getLogger( __name__ )
14 
15 def mergeChainDefs(listOfChainDefs, chainDict, perSig_lengthOfChainConfigs = None):
16  """ function to merge chain definitions for all legs, used also by signature code
17  chainDefList is a list of Chain() objects
18  one for each part (leg) in the chain """
19 
20  strategy = chainDict["mergingStrategy"]
21  offset = chainDict["mergingOffset"]
22  log.debug("[mergeChainDefs] %s: Combine by using %s merging with %d chain defs (legs)", chainDict['chainName'], strategy, len(listOfChainDefs))
23 
24  leg_numbering = []
25 
26  if 'Bjet' in chainDict['signatures'] and 'Jet' in chainDict['signatures']:#and chainDict['Signature'] == 'Bjet':
27  leg_numbering = [it for it,s in enumerate(chainDict['signatures'])]# if s != 'Jet']
28 
29  if strategy=="parallel":
30  return mergeParallel(listOfChainDefs, offset)
31  elif strategy=="serial":
32  return mergeSerial(listOfChainDefs)
33 
34  elif strategy=="auto":
35  ordering = getAlignmentGroupOrdering()
36  merging_dict = {} # maps the chain alignment group with the alignment order dict
37  for ich,cConfig in enumerate(listOfChainDefs):
38  chain_ag = cConfig.alignmentGroups[0]
39  if chain_ag not in ordering:
40  log.error("[mergeChainDefs] Alignment group %s can't be auto-merged because it's not in the grouping list!",chain_ag)
41  if chain_ag in merging_dict:
42  merging_dict[chain_ag] += [ich]
43  else:
44  merging_dict[chain_ag] = [ich]
45 
46  tmp_merged = []
47  tmp_merged_ordering = []
48  for ag in merging_dict:
49  if len(merging_dict[ag]) > 1:
50  log.debug("[mergeChainDefs] parallel merging")
51  new_chain_defs, perSig_lengthOfChainConfigs = mergeParallel(list( listOfChainDefs[i] for i in merging_dict[ag] ), offset, leg_numbering, perSig_lengthOfChainConfigs)
52  tmp_merged += [new_chain_defs]
53  tmp_merged_ordering += [ordering.index(ag)]
54  else:
55  log.debug("[mergeChainDefs] don't need to parallel merge")
56  tmp_merged += [listOfChainDefs[merging_dict[ag][0]]]
57  tmp_merged_ordering += [ordering.index(ag)]
58 
59  #reset the ordering to index from zero (padding comes later!)
60  merged_ordering = [-1]*len(tmp_merged_ordering)
61  copy_ordering = tmp_merged_ordering.copy()
62  tmp_val = 0
63  while len(copy_ordering) > 0:
64  min_index = tmp_merged_ordering.index(min(copy_ordering))
65  copy_ordering.pop(copy_ordering.index(min(copy_ordering)))
66  merged_ordering[min_index] = tmp_val
67  tmp_val += 1
68 
69  # only serial merge if necessary
70  if len(tmp_merged) == 1:
71  if perSig_lengthOfChainConfigs is None:
72  log.debug("[mergeChainDefs] tmp merged has length 1, returning 0th element")
73  return tmp_merged[0]
74  else:
75  log.debug("[mergeChainDefs] tmp merged has length 1, returning 0th element and perSig list")
76  return tmp_merged[0], perSig_lengthOfChainConfigs
77 
78  if perSig_lengthOfChainConfigs is None:
79  log.debug("[mergeChainDefs] serial merging first")
80  return mergeSerial(tmp_merged, merged_ordering) #shouldn't need to modify it here!
81  else:
82  log.debug("[mergeChainDefs] returning mergeSerial result and perSig_lengthOfChainConfigs %s",perSig_lengthOfChainConfigs)
83  return mergeSerial(tmp_merged, merged_ordering), perSig_lengthOfChainConfigs #shouldn't need to modify it here!
84 
85  else:
86  log.error("[mergeChainDefs] Merging failed for %s. Merging strategy '%s' not known.", (listOfChainDefs, strategy))
87  return -1
88 
89 
90 
91 def check_leg_lengths(perSig_lengthOfChainConfigs):
92  if not perSig_lengthOfChainConfigs: #default is None
93  return "", -1
94  leg_length_dict = {}
95  for leg_lengths, leg_grps in perSig_lengthOfChainConfigs:
96  for grp, length in zip(leg_grps,leg_lengths):
97  if grp in leg_length_dict:
98  leg_length_dict[grp] += [length]
99  else:
100  leg_length_dict[grp] = [length]
101  found_mismatch = False
102  max_length = -1
103  mismatched_ag = ""
104  log.debug("[check_leg_lengths] leg lengths: %s",leg_length_dict)
105  for grp,lengths in leg_length_dict.items():
106  if len(set(lengths)) > 1: #a mismatch!
107  log.debug("[check_leg_lengths] found mismatch for %s given %s", grp, lengths)
108  if found_mismatch:
109  log.error("[check_leg_lengths] Second mismatch in the same chain! I don't know how to deal with this, please resolve. Chain leg lengths: %s",perSig_lengthOfChainConfigs)
110  log.error("[check_leg_lengths] Second mismatch in the same chain! lengths,grp: %s,%s",lengths, grp)
111  raise Exception("[are_lengths_mismatched] Cannot merge chain, exiting.")
112  found_mismatch = True
113  max_length = max(lengths)
114  mismatched_ag = grp
115 
116  return mismatched_ag, max_length
117 
118 
119 def mergeParallel(chainDefList, offset, leg_numbering = None, perSig_lengthOfChainConfigs = None):
120  ''' Performs merging of steps with the same step number '''
121  # default mutable values must be initialized to None
122  if leg_numbering is None: leg_numbering = []
123 
124  if offset != -1:
125  log.error("[mergeParallel] Offset for parallel merging not implemented.")
126  raise Exception("[mergeParallel] Cannot merge this chain, exiting.")
127 
128  log.debug(f"[mergeParallel] Parallel merging for chain alignments {[cConfig.alignmentGroups for cConfig in chainDefList]}")
129  allSteps = []
130  allStepsMult = []
131  nSteps = []
132  chainName = ''
133  l1Decisions = []
134  alignmentGroups = []
135  vertical_alignment_groups = []
136 
137  for iConfig, cConfig in enumerate(chainDefList):
138  if chainName == '':
139  chainName = cConfig.name
140  elif chainName != cConfig.name:
141  log.error("[mergeParallel] Something is wrong with the combined chain name: cConfig.name = %s while chainName = %s", cConfig.name, chainName)
142  raise Exception("[mergeParallel] Cannot merge this chain, exiting.")
143 
144  if len(set(cConfig.alignmentGroups)) == 1:
145  alignmentGroups.append(cConfig.alignmentGroups[0])
146  elif len(cConfig.alignmentGroups) > 1:
147  log.debug("[mergeParallel] Parallel merging an already merged chain with different alignment groups? This is odd! %s",cConfig.alignmentGroups)
148  log.debug("...let's look at the config: %s", perSig_lengthOfChainConfigs)
149  # if the length the matching group in the pre-merged part is shorter than the full one,
150  # we need to patch it up to the full length by adding empty steps so that when
151  # we merge, the longer leg doesn't merge onto the second alignment group
152  align_grp_to_lengthen, max_length = check_leg_lengths(perSig_lengthOfChainConfigs)
153  if max_length > -1:
154  current_leg_ag_length = -1
155  index_modified_leg = -1
156  leg_lengths, leg_ags = perSig_lengthOfChainConfigs[iConfig]
157  for ileg, (length, ag) in enumerate(zip(leg_lengths, leg_ags)):
158  if ag == align_grp_to_lengthen:
159  current_leg_ag_length = length
160  index_modified_leg = ileg
161  log.debug("[mergeParallel] ileg %s, length %s, ag %s: ",ileg, length, ag)
162  break
163  # it's already merged so even if there is more than one in this chain
164  # they had better be the same length already
165 
166  n_new_steps = max_length - current_leg_ag_length
167 
168  previous_step_dicts = cConfig.steps[current_leg_ag_length-1].stepDicts
169  for i in range(1,n_new_steps+1):
170  step_mult = []
171  sigNames = []
172 
173  for ileg,stepDict in enumerate(previous_step_dicts):
174  is_fs_string = 'FS' if isFullScanRoI(cConfig.L1decisions[ileg]) else ''
175  sigNames += [stepDict['chainParts'][0]['signature'] + is_fs_string]
176 
177  seqMultName = '_'.join([sigName for sigName in sigNames])
178  nLegs = 1 # TODO, make it follow the real multiplicity of the step
179  seqStepName = getMergedEmptyStepName(align_grp_to_lengthen, current_leg_ag_length+i, nLegs, seqMultName)
180  seqNames = [getEmptySeqName(previous_step_dicts[iSeq]['signature'], current_leg_ag_length+i, align_grp_to_lengthen,i) for iSeq in range(len(sigNames))]
181 
182  emptySequences = build_empty_sequences(previous_step_dicts, step_mult, 'mergeParallel', cConfig.L1decisions, seqNames, chainName)
183  # insert a step with an empty sequence
184  cConfig.steps.insert(current_leg_ag_length + i - 1, #-1 to go to indexed from zero
185  ChainStep( seqStepName, SequenceGens = emptySequences,
186  chainDicts = previous_step_dicts)
187  )
188 
189 
190  # edited the lengths, so need to update the leg length dict the code we did so!
191  perSig_lengthOfChainConfigs[iConfig][0][index_modified_leg] = max_length
192  else:
193  log.debug("[mergeParallel] Alignment groups are empty for this combined chain")
194 
195  allSteps.append(cConfig.steps)
196  #TODO: instead of the real step multiplicy (len(cConfig.steps[0].multiplicity))), we set allStepsMult=[1] because the zip doesn't need it: when a step is missing in one leg, one None step is added, not multiple steps. I think we can remove the allStepsMult in the zip_longest below
197  allStepsMult.append(1)
198  nSteps.append(len(cConfig.steps))
199  l1Decisions.extend(cConfig.L1decisions)
200 
201  # Use zip_longest_parallel so that we get None in case one chain has more steps than the other
202  orderedSteps = list(zip_longest_parallel(allSteps, allStepsMult))
203 
204  if perSig_lengthOfChainConfigs is not None and len(perSig_lengthOfChainConfigs) > 0:
205  in_chain_ag_lengths = {}
206  ag_ordering = getAlignmentGroupOrdering()
207 
208  for ag in ag_ordering:
209  for ag_lengths, sig_ags in perSig_lengthOfChainConfigs:
210  for ag_length, sig_ag in zip(ag_lengths, sig_ags):
211  if (sig_ag in in_chain_ag_lengths and in_chain_ag_lengths[sig_ag] < ag_length) or sig_ag not in in_chain_ag_lengths:
212  in_chain_ag_lengths[sig_ag] = ag_length
213  for ag, ag_length in in_chain_ag_lengths.items():
214  vertical_alignment_groups += [ag]*ag_length
215  else:
216  #it's all one alignment group in this case
217  vertical_alignment_groups = [alignmentGroups[0]]*len(orderedSteps)
218 
219 
220  log.debug("[mergeParallel] alignment groups horizontal: %s", alignmentGroups)
221  log.debug("[mergeParallel] alignment groups vertical: %s", vertical_alignment_groups)
222 
223  combChainSteps =[]
224  log.debug("[mergeParallel] len(orderedSteps): %d", len(orderedSteps))
225  for chain_index in range(len(chainDefList)):
226  log.debug('[mergeParallel] Chain object to merge (i.e. chainDef) %s', chainDefList[chain_index])
227 
228  for step_index, (steps, step_ag) in enumerate(zip(orderedSteps,vertical_alignment_groups)):
229  mySteps = list(steps)
230  log.debug(f"[mergeParallel] Merging step {step_index+1} with alignment group = {step_ag}")
231 
232  combStep = makeCombinedStep(mySteps, step_index+1, chainDefList, orderedSteps, combChainSteps, leg_numbering, step_ag)
233  combChainSteps.append(combStep)
234 
235 
236  combinedChainDef = Chain(chainName, ChainSteps=combChainSteps, L1decisions=l1Decisions,
237  nSteps = nSteps, alignmentGroups = alignmentGroups)
238 
239  log.debug("[mergeParallel] Parallel merged chain %s with these steps:", chainName)
240  for step in combinedChainDef.steps:
241  log.debug('\n %s', step)
242 
243  return combinedChainDef, perSig_lengthOfChainConfigs
244 
245 def getMergedEmptyStepName(alignmentGroup, stepNumber, multiplicity, signature):
246  currentStepName = 'Empty' + alignmentGroup +'Align'+str(stepNumber)+'_'+ str(multiplicity) + signature
247  return currentStepName
248 
249 
250 def getEmptySeqName(signature, step_number, alignGroup,order):
251  seqName = 'Empty'+ alignGroup +'Seq'+str(step_number)+ '_'+ str(order) + signature
252  return seqName
253 
254 
255 def isFullScanRoI(inputL1Nav):
256  fsRoIList = ['HLTNav_L1FSNOSEED','HLTNav_L1MET','HLTNav_L1J']
257  if inputL1Nav in fsRoIList:
258  return True
259  else:
260  return False
261 
262 
263 def getCurrentAG(chainStep):
264  filled_seq_ag = []
265  for iseq,seq in enumerate(chainStep.sequenceGens):
266  # In the case of dummy configs, they are all empty
267  if isEmptySequenceCfg(seq):
268  continue
269  else:
270  # get the alignment group of the leg that is running a non-empty sequence
271  # if we double-serial merge enough this will have to be recursive. Throw an error here for now
272  # if the length is greater than one. I don't think this will ever come up
273  if len(set(cp['alignmentGroup'] for cp in chainStep.stepDicts[iseq]['chainParts'])) > 1:
274  log.error("[getCurrentAG] The leg has more than one chainPart (%s). Either the alignmentGroup property is bad or this is an unimplemented situation.",chainStep.stepDicts[iseq]['chainParts'])
275  raise Exception("[getCurrentAG] Not sure what is happening here, but I don't know what to do.")
276  filled_seq_ag += [chainStep.stepDicts[iseq]['chainParts'][0]['alignmentGroup']]
277 
278  if len(filled_seq_ag) == 0:
279  log.error("[getCurrentAG] No non-empty sequences were found in %s", chainStep.sequenceGens)
280  log.error("[getCurrentAG] The chainstep is %s", chainStep)
281  raise Exception("[getCurrentAG] Cannot find the current alignment group for this chain")
282  elif len(set(filled_seq_ag)) > 1:
283  log.error("[getCurrentAG] Found more than one alignment group for this step %s", filled_seq_ag)
284  raise Exception("[getCurrentAG] Cannot find the current alignment group for this chain")
285  else:
286  return filled_seq_ag[0]
287 
288 def serial_zip(allSteps, chainName, chainDefList, legOrdering):
289 
290  #note: allSteps and chainDefList do not have the legs in the same order
291  #the legOrdering is a mapping between the two:
292  # chainDefList[legOrdering[0]] <=> allSteps[0]
293 
294  legs_per_part = [len(chainDefList[stepPlacement].steps[0].multiplicity) for stepPlacement in legOrdering]
295  n_parts = len(allSteps)
296  log.debug('[serial_zip] configuring chain with %d parts with multiplicities %s', n_parts, legs_per_part)
297  log.debug('[serial_zip] and leg ordering %s', legOrdering)
298  newsteps = []
299 
300  #per-part (horizontal) iteration by alignment ordering
301  #i.e. if we run muon then electron, allSteps[0] = muon steps, allSteps[1] = electron steps
302  #leg ordering tells us where it was ordered in the chain name, so e_mu in this case would
303  #have legOrdering = [1,0]
304  for chain_index, (chainSteps, stepPlacement) in enumerate(zip(allSteps, legOrdering)):
305 
306  for step_index, step in enumerate(chainSteps): #serial step iteration
307  if step_index == 0:
308  prev_ag_step_index = step_index
309  previousAG = getCurrentAG(step)
310  log.debug('[serial_zip] chain_index: %s step_index: %s, alignment group: %s', chain_index, step_index+1, previousAG)
311  # create list of correct length (chainSteps in parallel)
312  stepList = [None]*n_parts
313 
314  # put the step from the current sub-chain into the right place
315  stepList[stepPlacement] = step
316  log.debug('[serial_zip] Put step: %s', step.name)
317 
318  # all other chain parts' steps should contain an empty sequence
319  for chain_index2, (nLegs, stepPlacement2) in enumerate(zip(legs_per_part, legOrdering)): #more per-leg iteration
320  emptyStep = stepList[stepPlacement2]
321  if emptyStep is None:
322  if chain_index2 == chain_index:
323  log.error("chain_index2 = chain_index, but the stepList still has none!")
324  raise Exception("[serial_zip] duplicating existing leg, why has this happened??")
325 
326  #this WILL NOT work for jets!
327  step_mult = []
328  emptyChainDicts = []
329  if chain_index2 < chain_index:
330  emptyChainDicts = allSteps[chain_index2][-1].stepDicts
331  else:
332  emptyChainDicts = allSteps[chain_index2][0].stepDicts
333 
334  log.debug("[serial_zip] nLegs: %s, len(emptyChainDicts): %s, len(L1decisions): %s", nLegs, len(emptyChainDicts), len(chainDefList[stepPlacement2].L1decisions))
335  sigNames = []
336  for ileg,(emptyChainDict,_) in enumerate(zip(emptyChainDicts,chainDefList[stepPlacement2].L1decisions)):
337  is_fs_string = 'FS' if isFullScanRoI(chainDefList[stepPlacement2].L1decisions[ileg]) else ''
338  sigNames +=[emptyChainDict['chainParts'][0]['signature']+is_fs_string]
339 
340 
341  seqMultName = '_'.join([sigName for sigName in sigNames])
342  currentAG = ''
343 
344  #now we need to know what alignment group this step is in to properly name the empty sequence
345  if len(set(chainDefList[stepPlacement].alignmentGroups)) == 1:
346  currentAG = chainDefList[stepPlacement].alignmentGroups[0]
347  ag_step_index = step_index+1
348  else:
349  # this happens if one of the bits to serial merge is already serial merged.
350  currentAG = getCurrentAG(step)
351  if currentAG == previousAG:
352  ag_step_index = prev_ag_step_index + 1
353  prev_ag_step_index = ag_step_index
354  else:
355  ag_step_index = 1
356  previousAG = currentAG
357  prev_ag_step_index = 1
358 
359  seqStepName = getMergedEmptyStepName(currentAG, ag_step_index, nLegs, seqMultName)
360 
361  seqNames = [getEmptySeqName(emptyChainDicts[iSeq]['signature'], ag_step_index, currentAG,iSeq) for iSeq in range(nLegs)]
362 
363  log.verbose("[serial_zip] step name for this leg: %s", seqStepName)
364  log.verbose("[serial_zip] created empty sequence(s): %s", seqNames)
365  log.verbose("[serial_zip] L1decisions %s ", chainDefList[stepPlacement2].L1decisions)
366 
367  emptySequences = build_empty_sequences(emptyChainDicts, step_mult, 'serial_zip', chainDefList[stepPlacement2].L1decisions, seqNames, chainName)
368 
369  stepList[stepPlacement2] = ChainStep( seqStepName, SequenceGens = emptySequences,
370  chainDicts = emptyChainDicts)
371 
372  newsteps.append(stepList)
373  log.debug('After serial_zip')
374  for s in newsteps:
375  log.debug( ', '.join(map(str, [step.name for step in s]) ) )
376  return newsteps
377 
378 
379 def mergeSerial(chainDefList, chainDefListOrdering):
380  allSteps = []
381  legOrdering = []
382  nSteps = []
383  chainName = ''
384  l1Decisions = []
385  alignmentGroups = []
386  log.debug('[mergeSerial] Merge chainDefList:')
387  log.debug(chainDefList)
388  log.debug('[mergeSerial] wth ordering %s:',chainDefListOrdering)
389 
390  for ic,cOrder in enumerate(chainDefListOrdering):
391 
392  #put these in order of alignment
393  cConfig = chainDefList[chainDefListOrdering.index(ic)]
394  leg_order = chainDefListOrdering.index(ic) #but keep track of where it came from
395 
396  if chainName == '':
397  chainName = cConfig.name
398  elif chainName != cConfig.name:
399  log.error("[mergeSerial] Something is wrong with the combined chain name: cConfig.name = %s while chainName = %s", cConfig.name, chainName)
400  raise Exception("[mergeSerial] Cannot merge this chain, exiting.")
401 
402  #allSteps is ordered such that the first entry in the list is what we want to *run* first
403  allSteps.append(cConfig.steps)
404  legOrdering.append(leg_order)
405  nSteps.extend(cConfig.nSteps)
406  l1Decisions.extend(chainDefList[ic].L1decisions) # do not invert L1Decisions because they follow the order of the sequences inside teh steps
407  alignmentGroups.extend(cConfig.alignmentGroups)
408 
409  serialSteps = serial_zip(allSteps, chainName, chainDefList, legOrdering)
410  combChainSteps =[]
411  for chain_index in range(len(chainDefList)):
412  log.debug('[mergeSerial] Chain object to merge (i.e. chainDef) %s', chainDefList[chain_index])
413  for step_index, steps in enumerate(serialSteps):
414  mySteps = list(steps)
415  combStep = makeCombinedStep(mySteps, step_index+1, chainDefList)
416  combChainSteps.append(combStep)
417 
418  combinedChainDef = Chain(chainName, ChainSteps=combChainSteps, L1decisions=l1Decisions,
419  nSteps = nSteps, alignmentGroups = alignmentGroups)
420 
421  log.debug("[mergeSerial] Serial merged chain %s with number of steps/leg %s with these steps:", chainName, combinedChainDef.nSteps)
422  for step in combinedChainDef.steps:
423  log.debug(' %s', step)
424 
425  return combinedChainDef
426 
427 def checkStepContent(parallel_steps):
428  """
429  return True if any step contains a real Sequence
430  """
431  for step in parallel_steps:
432  if step is None or step.isEmpty:
433  continue
434  for seq in step.sequenceGens:
435  if not isEmptySequenceCfg(seq):
436  return True
437  return False
438 
439 def makeCombinedStep(parallel_steps, stepNumber, chainDefList, allSteps = None, currentChainSteps = None, leg_numbering = None, alignment_group = ""):
440  # default mutable values must be initialized to None
441  if allSteps is None: allSteps = []
442  if currentChainSteps is None: currentChainSteps = []
443  if leg_numbering is None: leg_numbering =[]
444 
445  stepName = 'merged' #we will renumber all steps after chains are aligned #Step' + str(stepNumber)
446 
447  log.debug("[makeCombinedStep] stepNumber %d, alignment_group %s, %d steps: [%s], %d chain list: [%s], alignment groups: [%s]", stepNumber, alignment_group, len(parallel_steps), ', '.join([step.name if step is not None else "EMPTY" for step in parallel_steps ]), len(chainDefList), ', '.join([chain.name for chain in chainDefList]), ', '.join([chain.alignmentGroups[0] for chain in chainDefList]))
448 
449 
450  stepDicts = []
451  comboHypoTools = []
452  comboHypo = None
453 
454  leg_counter = 0
455  currentStepName = ''
456  # if *all* the steps we're trying to merge are either empty sequences or empty steps
457  # we need to create a single empty step instead.
458  hasNonEmptyStep = checkStepContent(parallel_steps)
459  log.debug("hasNonEmptyStep %d", hasNonEmptyStep)
460 
461  if not hasNonEmptyStep:
462  # only empty steps here
463  if len(parallel_steps)>=len(chainDefList) and all(step is None for step in parallel_steps[len(chainDefList):]):
464  # We need to remove manually here the None steps exceeding the len of chainDefList. The right solution
465  # would be to make sure that these cases don't happen upstream, but I am not confident enough with this
466  # code to make such a large (and dangerous) change. But it would be nice to do that in future if possible..
467  parallel_steps=parallel_steps[:len(chainDefList)]
468  log.debug("[makeCombinedStep] removing empty steps exceeding chainDefList size. The new steps are now %s ", parallel_steps)
469 
470  for chain_index, step in enumerate(parallel_steps):
471  # every step is empty but some might have empty sequences and some might not
472  if step is None or step.isEmpty:
473  new_stepDicts = deepcopy(chainDefList[chain_index].steps[-1].stepDicts)
474  nLegs = len(chainDefList[chain_index].steps[-1].multiplicity)
475  currentStepName = getMergedEmptyStepName(chainDefList[chain_index].alignmentGroups[0], stepNumber, nLegs, new_stepDicts[0]['signature'])
476  log.debug('[makeCombinedStep] step has no sequences, making empty step %s', currentStepName)
477 
478  # we need a chain dict here, use the one corresponding to this leg of the chain
479  for new_stepDict in new_stepDicts:
480  oldLegName = new_stepDict['chainName']
481  if re.search('^leg[0-9]{3}_',oldLegName):
482  oldLegName = oldLegName[7:]
483  new_stepDict['chainName'] = legName(oldLegName,leg_counter)
484  log.debug("[makeCombinedStep] stepDict naming old: %s, new: %s", oldLegName, new_stepDict['chainName'])
485  stepDicts.append(new_stepDict)
486  leg_counter += 1
487 
488  else:
489  # Standard step with empty sequence(s)
490  currentStepName = step.name
491  #remove redundant instances of StepN_ and merged_ (happens when merging already merged chains)
492 
493  if currentStepName.startswith('merged_'):
494  currentStepName = currentStepName[7:]
495 
496  # update the chain dict list for the combined step with the chain dict from this step
497  log.debug('[makeCombinedStep] adding step dictionaries %s',step.stepDicts)
498 
499  for new_stepDict in deepcopy(step.stepDicts):
500  oldLegName = new_stepDict['chainName']
501  if re.search('^leg[0-9]{3}_',oldLegName):
502  oldLegName = oldLegName[7:]
503  if len(leg_numbering) > 0:
504  leg_counter = leg_numbering[chain_index]
505  new_stepDict['chainName'] = legName(oldLegName,leg_counter)
506  log.debug("[makeCombinedStep] stepDict naming old: %s, new: %s", oldLegName, new_stepDict['chainName'])
507  stepDicts.append(new_stepDict)
508  leg_counter += 1
509 
510  stepName += '_' + currentStepName
511 
512  theChainStep = ChainStep(stepName, chainDicts = stepDicts, isEmpty = True)
513  log.debug("[makeCombinedStep] Merged empty step: \n %s", theChainStep)
514  return theChainStep
515 
516  stepSeq = []
517  legsInStep = []
518  # count the number of legs inside this chain part/step (inner legs)
519  # this happens if the step is already the result of a merging, due to the alignemnt, and can have more than one leg
520  # use the alignmentGroups here, which is stored by grouping the legs per alignemnt group
521  # TODO: can be extracted from stepDict['chainParts'][0]['multiplicity']?
522  for num, chain in enumerate(chainDefList):
523  legsInStep.append(len(chain.alignmentGroups))
524  assert(len(legsInStep) == len(parallel_steps))
525 
526  for chain_index, step in enumerate(parallel_steps): #this is a horizontal merge!
527 
528  if step is None or (hasNonEmptyStep and step.isEmpty):
529  # this happens for merging chains with different numbers of steps, we need to "pad" out with empty sequences to propogate the decisions
530  # all other chain parts' steps should contain an empty sequence
531 
532  log.debug("[makeCombinedStep] step %s is Empty and has %d legs", step.name if step is not None else "None", legsInStep[chain_index])
533  if alignment_group == "":
534  alignment_group = chainDefList[0].alignmentGroups[0]
535 
536  # loop over the inner legs of this sub-chain and create one empty sequence per each inner leg
537  for innerLeg in range(legsInStep[chain_index]):
538  new_stepDict = deepcopy(chainDefList[chain_index].steps[-1].stepDicts[-1])
539  seqName = getEmptySeqName( new_stepDict['signature'], stepNumber, alignment_group, innerLeg)
540  log.debug("[makeCombinedStep] creating Empty sequence %s", seqName)
541  signature=new_stepDict['signature']
542  is_fs_string = 'FS' if isFullScanRoI(chainDefList[chain_index].L1decisions[0]) else ''
543  seqName=seqName+is_fs_string
544  signature=new_stepDict['signature']+is_fs_string
545  thisEmpty = createEmptyMenuSequenceCfg(None, seqName)
546  stepSeq.append(functools.partial(thisEmpty, name=seqName))
547  oldLegName = new_stepDict['chainName']
548  if re.search('^leg[0-9]{3}_',oldLegName):
549  oldLegName = oldLegName[7:]
550  new_stepDict['chainName'] = legName(oldLegName,leg_counter)
551  stepDicts.append(new_stepDict)
552  leg_counter += 1
553 
554  nLegs = legsInStep[chain_index]
555  currentStepName = getMergedEmptyStepName(alignment_group, stepNumber, nLegs, signature)
556 
557  log.debug("[makeCombinedStep] found empty step to be merged, step number: %d chain_index: %s, step name: %s, made new empty sequence name: %s", stepNumber, chain_index, currentStepName, seqName)
558 
559 
560  else:
561  # Standard step, append it to the combined step
562  log.debug("[makeCombinedStep] step %s, multiplicity = %s", step.name, str(step.multiplicity))
563  if len(step.sequenceGens):
564  log.debug("[makeCombinedStep] with sequences = %s", ' '.join(map(str, [seq.func.__name__ for seq in step.sequenceGens])))
565 
566  # this function only works if the input chains are single-object chains (one menu seuqnce)
567  if len(step.sequenceGens) > 1:
568  log.debug("[makeCombinedStep] combining in an already combined chain")
569 
570  if ( comboHypo is None or
571  (hasattr(step.comboHypoCfg, '__name__') and step.comboHypoCfg.__name__ != "ComboHypoCfg") ):
572  comboHypo = step.comboHypoCfg
573  currentStepName = step.name
574  #remove redundant instances of StepN_ and merged_ (happens when merging already merged chains)
575  if currentStepName.startswith('merged_'):
576  currentStepName = currentStepName[7:]
577  stepSeq.extend(step.sequenceGens)
578  comboHypoTools.extend(step.comboToolConfs)
579  # update the chain dict list for the combined step with the chain dict from this step
580  log.debug('[makeCombinedStep] adding step dictionaries %s',step.stepDicts)
581  log.debug('[makeCombinedStep] my leg_numbering is: %s, for chain_index %s',leg_numbering, chain_index)
582  for new_stepDict in deepcopy(step.stepDicts):
583  oldLegName = new_stepDict['chainName']
584  if re.search('^leg[0-9]{3}_',oldLegName):
585  oldLegName = oldLegName[7:]
586  if len(leg_numbering) > 0:
587  leg_counter = leg_numbering[chain_index]
588  new_stepDict['chainName'] = legName(oldLegName,leg_counter)
589  log.debug("[makeCombinedStep] stepDict naming old: %s, new: %s", oldLegName, new_stepDict['chainName'])
590  stepDicts.append(new_stepDict)
591  leg_counter += 1
592 
593 
594  # the step naming for combined chains needs to be revisted!!
595  stepName += '_' + currentStepName
596  log.debug('[makeCombinedStep] current step name %s, with %d sequences',stepName, len(stepSeq))
597  # for merged steps, we need to update the name to add the leg name
598 
599  comboHypoTools = list(set(comboHypoTools))
600  theChainStep = ChainStep(stepName, SequenceGens = stepSeq, chainDicts = stepDicts,
601  comboHypoCfg = comboHypo, comboToolConfs = comboHypoTools)
602  log.debug("[makeCombinedStep] Merged step index %d: \n %s", stepNumber, theChainStep)
603 
604 
605  return theChainStep
606 
607 # modified version of zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-, which takes into account the multiplicity of the steps
608 def zip_longest_parallel(AllSteps, multiplicity, fillvalue=None):
609 
610  iterators = [iter(it) for it in AllSteps]
611  inactives =set()
612  if len(iterators)==0:
613  return
614  while True:
615  values = []
616  for i, it in enumerate(iterators): #Here we loop over the different chain parts
617  try:
618  value = next(it)
619  except StopIteration:
620  if i not in inactives:
621  #We want to add the inactive iterator to the list of inactives iterators
622  inactives.add(i)
623  if len(inactives)>=len(iterators):
624  #We want to exit the while True if we reached the end of all iterators.
625  return
626  iterators[i] = repeat(fillvalue, int(multiplicity[i]))
627  value = fillvalue
628  values.append(value)
629  if int(multiplicity[i]) > 1 and value == fillvalue:
630  values.extend([fillvalue]*int(multiplicity[i]-1))
631 
632  yield tuple(values)
633 
634 
635 
636 def build_empty_sequences(emptyChainDicts, step_mult, caller, L1decisions, seqNames, chainName):
637  emptySequences = []
638  for ileg in range(len(L1decisions)):
639  is_fs_string = 'FS' if isFullScanRoI(L1decisions[ileg]) else ''
640  sname = seqNames[ileg]+is_fs_string
641  log.debug("[%s] adding %s empty sequenc with name %s", caller, is_fs_string, sname)
642  thisEmpty = createEmptyMenuSequenceCfg(None, sname)
643  emptySequences += [functools.partial(thisEmpty, name=sname)]
644 
645 
646  log.verbose("[%s] emptyChainDicts %s", caller, emptyChainDicts)
647  log.debug("[%s] %s has number of empty sequences %d and empty legs in stepDicts %d",
648  caller, chainName, len(emptySequences), len(emptyChainDicts))
649  if len(emptySequences) != len(emptyChainDicts):
650  log.error("[%s] %s has a different number of empty sequences/legs %d than stepDicts %d",
651  caller, chainName, len(emptySequences), len(emptyChainDicts))
652 
653  raise Exception(f"[{caller}] Cannot create this chain step, exiting.")
654 
655  for sd in emptyChainDicts:
656  if sd['signature'] == 'Jet' or sd['signature'] == 'Bjet':
657  step_mult += [1]
658  elif len(sd['chainParts']) != 1:
659  log.error("[%s] %s has chainParts has length != 1 within a leg! chain dictionary for this step: \n %s",
660  caller, chainName, sd)
661  raise Exception(f"[{caller}] Cannot create this chain step, exiting.")
662  else:
663  step_mult += [int(sd['chainParts'][0]['multiplicity'])]
664 
665  if len(emptySequences) != len(step_mult):
666  log.error("[%s] %s has a different number of empty sequences/legs %d than multiplicities %d",
667  caller, chainName, len(emptySequences), len(step_mult))
668  raise Exception(f"[{caller}] Cannot create this chain step, exiting.")
669 
670  log.verbose('[%s] step multiplicity %s',caller, step_mult)
671 
672 
673  return emptySequences
ChainMerging.getCurrentAG
def getCurrentAG(chainStep)
Definition: ChainMerging.py:263
max
constexpr double max()
Definition: ap_fixedTest.cxx:33
ChainMerging.checkStepContent
def checkStepContent(parallel_steps)
Definition: ChainMerging.py:427
min
constexpr double min()
Definition: ap_fixedTest.cxx:26
ChainMerging.mergeSerial
def mergeSerial(chainDefList, chainDefListOrdering)
Definition: ChainMerging.py:379
ChainMerging.isFullScanRoI
def isFullScanRoI(inputL1Nav)
Definition: ChainMerging.py:255
MenuComponents.isEmptySequenceCfg
def isEmptySequenceCfg(o)
Definition: MenuComponents.py:381
fillPileUpNoiseLumi.next
next
Definition: fillPileUpNoiseLumi.py:52
ChainMerging.build_empty_sequences
def build_empty_sequences(emptyChainDicts, step_mult, caller, L1decisions, seqNames, chainName)
Definition: ChainMerging.py:636
python.LArMinBiasAlgConfig.int
int
Definition: LArMinBiasAlgConfig.py:59
python.TrigCompositeUtils.legName
def legName(chainName, legCounter)
Definition: DecisionHandling/python/TrigCompositeUtils.py:12
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
ChainMerging.getMergedEmptyStepName
def getMergedEmptyStepName(alignmentGroup, stepNumber, multiplicity, signature)
Definition: ChainMerging.py:245
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:232
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
ChainMerging.check_leg_lengths
def check_leg_lengths(perSig_lengthOfChainConfigs)
Definition: ChainMerging.py:91
ChainMerging.makeCombinedStep
def makeCombinedStep(parallel_steps, stepNumber, chainDefList, allSteps=None, currentChainSteps=None, leg_numbering=None, alignment_group="")
Definition: ChainMerging.py:439
ChainMerging.mergeChainDefs
def mergeChainDefs(listOfChainDefs, chainDict, perSig_lengthOfChainConfigs=None)
Definition: ChainMerging.py:15
ChainMerging.getEmptySeqName
def getEmptySeqName(signature, step_number, alignGroup, order)
Definition: ChainMerging.py:250
str
Definition: BTagTrackIpAccessor.cxx:11
ChainMerging.serial_zip
def serial_zip(allSteps, chainName, chainDefList, legOrdering)
Definition: ChainMerging.py:288
ChainMerging.zip_longest_parallel
def zip_longest_parallel(AllSteps, multiplicity, fillvalue=None)
Definition: ChainMerging.py:608
Cut::all
@ all
Definition: SUSYToolsAlg.cxx:67
MenuComponents.createEmptyMenuSequenceCfg
def createEmptyMenuSequenceCfg(flags, name)
Definition: MenuComponents.py:367
ChainMerging.mergeParallel
def mergeParallel(chainDefList, offset, leg_numbering=None, perSig_lengthOfChainConfigs=None)
Definition: ChainMerging.py:119