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