ATLAS Offline Software
HLTCFConfig.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2 
3 """
4  ------ Documentation on HLT Tree creation -----
5 
6 ++ Filter creation strategy
7 
8 ++ Connections between InputMaker/HypoAlg/Filter
9 
10 ++ Seeds
11 
12 ++ Combined chain strategy
13 
14 - The combined chains use duplicates of the single-object-HypoAlg, called HypoAlgName_for_stepName.
15  These duplicates are connected to a dedicated ComboHypoAlg (added by the framework), able to count object multiplicity
16  -- This is needed for two reasons:
17  -- the HypoAlg is designed to have only one input TC (that is already for the single object)
18  -- otherwise the HypoAlg would be equipped with differnt HypoTools with the same name (see for example e3_e8)
19  -- If the combined chain is symmetric (with multiplicity >1), the Hypo is duplicated only once,
20  equipped with a HypoTool configured as single object and followed by one ComboHypoAlg
21 
22 
23 """
24 
25 from TriggerMenuMT.HLT.Config.ControlFlow.HLTCFDot import stepCF_DataFlow_to_dot, stepCF_ControlFlow_to_dot, all_DataFlow_to_dot
26 from TriggerMenuMT.HLT.Config.ControlFlow.HLTCFComponents import RoRSequenceFilterNode, PassFilterNode, CFGroup
27 from TriggerMenuMT.HLT.Config.ControlFlow.MenuComponentsNaming import CFNaming
28 from AthenaCommon.CFElements import parOR, isSequence
29 from AthenaCommon.AlgSequence import dumpSequence
30 from AthenaCommon.Logging import logging
31 
32 from AthenaConfiguration.ComponentFactory import CompFactory
33 
34 from DecisionHandling.DecisionHandlingConfig import TriggerSummaryAlg
35 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
36 
37 from builtins import map, range, str, zip
38 from collections import OrderedDict, defaultdict
39 import re
40 
41 log = logging.getLogger( __name__ )
42 
43 
44 def makeSummary(flags, name, flatDecisions):
45  """ Returns a TriggerSummaryAlg connected to given decisions"""
46 
47  summary = TriggerSummaryAlg( flags, CFNaming.stepSummaryName(name) )
48  summary.InputDecision = "HLTSeedingSummary"
49  summary.FinalDecisions = list(OrderedDict.fromkeys(flatDecisions))
50  return summary
51 
52 
53 def createStepFilterNode(name, seq_list, dump=False):
54  """ Elementary HLT filter step: OR node containing all Filters of the sequences. The node gates execution of next reco step """
55 
56  log.debug("Create filter step %s with %d filters", name, len(seq_list))
57  stepCF = parOR(name + CFNaming.FILTER_POSTFIX)
58  filter_list=[]
59  for seq in seq_list:
60  filterAlg = seq.filter.Alg
61  log.debug("createStepFilterNode: Add %s to filter node %s", filterAlg.getName(), name)
62  if filterAlg not in filter_list:
63  filter_list.append(filterAlg)
64 
65  stepCF = parOR(name + CFNaming.FILTER_POSTFIX, subs=filter_list)
66  if dump:
67  dumpSequence (stepCF, indent=0)
68  return stepCF
69 
70 
71 
72 
75 
76 def matrixDisplay( allCFSeq ):
77 
78  def __getHyposOfStep( step ):
79  if len(step.sequences):
80  step.getChainNames()
81  return []
82 
83  # fill dictionary to cumulate chains on same sequences, in steps (dict with composite keys)
84  mx = defaultdict(list)
85 
86  for stepNumber,cfseq_list in enumerate(allCFSeq, 1):
87  for cfseq in cfseq_list:
88  chains = __getHyposOfStep(cfseq.step)
89  for seq in cfseq.step.sequences:
90  if seq.name == "Empty":
91  mx[stepNumber, "Empty"].extend(chains)
92  else:
93  mx[stepNumber, seq.sequence.Alg.getName()].extend(chains)
94 
95  # sort dictionary by fist key=step
96  sorted_mx = OrderedDict(sorted( list(mx.items()), key= lambda k: k[0]))
97 
98  log.debug( "" )
99  log.debug( "="*90 )
100  log.debug( "Cumulative Summary of steps")
101  log.debug( "="*90 )
102  for (step, seq), chains in list(sorted_mx.items()):
103  log.debug( "(step, sequence) ==> (%d, %s) is in chains: ", step, seq)
104  for chain in chains:
105  log.debug( " %s",chain)
106 
107  log.debug( "="*90 )
108 
109 
110 def sequenceScanner( HLTNode ):
111  """ Checks the alignement of sequences and steps in the tree"""
112  # +-- AthSequencer/HLTAllSteps
113  # +-- AthSequencer/Step1_filter
114  # +-- AthSequencer/Step1_reco
115 
116  _seqMapInStep = defaultdict(set)
117  _status = True
118  def _mapSequencesInSteps(seq, stepIndex, childInView):
119  """ Recursively finds the steps in which sequences are used"""
120  if not isSequence(seq):
121  return stepIndex
122  match=re.search('^Step([0-9]+)_filter',seq.name)
123  if match:
124  stepIndex = match.group(1)
125  log.debug("sequenceScanner: This is another step: %s %s", seq.name, stepIndex)
126  inViewSequence = ""
127  inView = False
128  for c in seq.Members:
129  if isSequence(c):
130  # Detect whether this is the view sequence pointed to
131  # by the EV creator alg, or if it is in such a sequence
132  inView = c.getName()==inViewSequence or childInView
133  stepIndex = _mapSequencesInSteps(c, stepIndex, childInView=inView)
134  _seqMapInStep[c.name].add((stepIndex,inView))
135  log.verbose("sequenceScanner: Child %s of sequence %s is in view? %s --> '%s'", c.name, seq.name, inView, inViewSequence)
136  else:
137  if isinstance(c, CompFactory.EventViewCreatorAlgorithm):
138  inViewSequence = c.ViewNodeName
139  log.verbose("sequenceScanner: EventViewCreatorAlg %s is child of sequence %s with ViewNodeName %s", c.name, seq.name, c.ViewNodeName)
140  log.debug("sequenceScanner: Sequence %s is in view? %s --> '%s'", seq.name, inView, inViewSequence)
141  return stepIndex
142 
143  # do the job:
144  final_step=_mapSequencesInSteps(HLTNode, 0, childInView=False)
145 
146  for alg, steps in _seqMapInStep.items():
147  if 'PassSequence' in alg or 'HLTCaloClusterMakerFSRecoSequence' in alg \
148  or 'HLTCaloCellMakerFSRecoSequence' in alg: # do not count PassSequences, which is used many times. Harcoding HLTCaloClusterMakerFSRecoSequence and HLTCaloCellMakerFSRecoSequence when using FullScanTopoCluster building for photon triggers with RoI='' (also for Jets and MET) following discussion in ATR-24722. To be fixed
149  continue
150  # Sequences in views can be in multiple steps
151  nonViewSteps = sum([0 if isInViews else 1 for (stepIndex,isInViews) in steps])
152  if nonViewSteps > 1:
153  steplist = [stepIndex for stepIndex,inViewSequence in steps]
154  log.error("sequenceScanner: Sequence %s is expected outside of a view in more than one step: %s", alg, steplist)
155  match=re.search('Step([0-9]+)',alg)
156  if match:
157  candidateStep=match.group(1)
158  log.error("sequenceScanner: ---> candidate good step is %s", candidateStep)
159  _status=False
160  raise RuntimeError(f"Duplicated event-scope sequence {alg} in steps {steplist}")
161 
162  log.debug("sequenceScanner: scanned %s steps with status %d", final_step, _status)
163  return _status
164 
165 
166 def decisionTreeFromChains(flags, HLTNode, chains, allDicts):
167  """ Creates the decision tree, given the starting node and the chains containing the sequences """
168  log.info("[decisionTreeFromChains] Run decisionTreeFromChains on %s", HLTNode.getName())
169  HLTNodeName = HLTNode.getName()
170  acc = ComponentAccumulator()
171 
172  if len(chains) == 0:
173  log.info("[decisionTreeFromChains] Configuring empty decisionTree")
174  acc.addSequence(HLTNode)
175  return ([], [], acc)
176 
177  ( finalDecisions, CFseq_list) = createDataFlow(flags, chains)
178  addChainsToDataFlow(flags, CFseq_list, allDicts)
179  # now connect all algorithms and creates the CAs
180  cfAcc = createControlFlow(flags, HLTNode, CFseq_list)
181  acc.merge(cfAcc)
182 
183  # create dot graphs
184  log.debug("finalDecisions: %s", finalDecisions)
185  if flags.Trigger.generateMenuDiagnostics:
186  all_DataFlow_to_dot(HLTNodeName, CFseq_list)
187 
188  # matrix display
189  # uncomment for serious debugging
190  # matrixDisplay( CFseq_list )
191 
192  return (finalDecisions,CFseq_list, acc)
193 
194 def createDataFlow(flags, chains):
195  """ Creates the filters and connect them to the menu sequences"""
196 
197  # find tot nsteps
198  chainWithMaxSteps = max(chains, key = lambda chain: len(chain.steps))
199  NSTEPS = len(chainWithMaxSteps.steps)
200  log.info("[createDataFlow] creating DF for %d chains and total %d steps", len(chains), NSTEPS)
201 
202  # initialize arrays for monitor
203  finalDecisions = [ [] for n in range(NSTEPS) ]
204  CFseqList = [ [] for n in range(NSTEPS) ]
205  CFSeqByFilterName = [ {} for n in range(NSTEPS) ] # CFSeqeunces keyed by filter name (speedup)
206 
207  # loop over chains
208  for chain in chains:
209  log.debug("\n Configuring chain %s with %d steps: \n - %s ", chain.name,len(chain.steps),'\n - '.join(map(str, [{step.name:step.multiplicity} for step in chain.steps])))
210 
211  lastCFgroup = None
212  lastDecisions = []
213  for nstep, chainStep in enumerate( chain.steps ):
214  if not flags.Trigger.fastMenuGeneration:
215  #create all sequences CA in all steps to allow data flow connections
216  chainStep.createSequences()
217  log.debug("\n************* Start connecting step %d %s for chain %s", nstep+1, chainStep.name, chain.name)
218  if nstep == 0:
219  filterInput = chain.L1decisions
220  else:
221  filterInput = lastDecisions
222  if len(filterInput) == 0 :
223  log.error("[createDataFlow] Filter for step %s has %d inputs! At least one is expected", chainStep.name, len(filterInput))
224  raise Exception("[createDataFlow] Cannot proceed, exiting.")
225 
226  log.debug("Set Filter input: %s while setting the chain: %s", filterInput, chain.name)
227 
228  # make one filter per step:
229  sequenceFilter = None
230  filterName = CFNaming.filterName(chainStep.name)
231  if chainStep.isEmpty:
232  filterOutput = filterInput
233  else:
234  filterOutput = [CFNaming.filterOutName(filterName, inputName) for inputName in filterInput ]
235 
236  # TODO: Check sequence consistency if skipping, to avoid issues like https://its.cern.ch/jira/browse/ATR-28617
237  foundCFgroup = CFSeqByFilterName[nstep].get(filterName, None)
238  log.debug("%s CF sequences with filter name %s", "Not found" if foundCFgroup is None else "Found", filterName)
239  if foundCFgroup is None:
240  sequenceFilter = buildFilter(filterName, filterInput, chainStep.isEmpty)
241  if flags.Trigger.fastMenuGeneration:
242  #create the sequences CA of this step in fast mode
243  chainStep.createSequences()
244  # add the step to a new group
245  CFgroup = CFGroup( ChainStep = chainStep, FilterAlg = sequenceFilter)
246  CFgroup.connect(filterOutput)
247  CFSeqByFilterName[nstep][sequenceFilter.Alg.getName()] = CFgroup
248  CFseqList[nstep].append(CFgroup)
249  lastCFgroup = CFgroup
250  else:
251  lastCFgroup = foundCFgroup
252  sequenceFilter = lastCFgroup.sequenceCA.filterNode
253  if len(list(set(sequenceFilter.getInputList()).intersection(filterInput))) != len(list(set(filterInput))):
254  [ sequenceFilter.addInput(inputName) for inputName in filterInput ]
255  [ sequenceFilter.addOutput(outputName) for outputName in filterOutput ]
256  lastCFgroup.connect(filterOutput)
257 
258  lastDecisions = lastCFgroup.sequenceCA.decisions
259 
260  # add chains to the filter:
261  chainLegs = chainStep.getChainLegs()
262  if len(chainLegs) != len(filterInput):
263  log.error("[createDataFlow] lengths of chainlegs = %s differ from inputs = %s", str(chainLegs), str(filterInput))
264  raise Exception("[createDataFlow] Cannot proceed, exiting.")
265  for finput, leg in zip(filterInput, chainLegs):
266  log.debug("Adding chain %s to input %s of %s", leg, finput, sequenceFilter.Alg.name)
267  sequenceFilter.addChain(leg, finput)
268 
269  log.debug("Now Filter has chains: %s", sequenceFilter.getChains())
270  log.debug("Now Filter has chains/input: %s", sequenceFilter.getChainsPerInput())
271  # store legs and mult in the CFGroup
272  lastCFgroup.addStepLeg(chainStep, chain.name)
273 
274  if len(chain.steps) == nstep+1:
275  log.debug("Adding finalDecisions for chain %s at step %d:", chain.name, nstep+1)
276  for dec in lastDecisions:
277  finalDecisions[nstep].append(dec)
278  log.debug(dec)
279 
280  #end of loop over steps
281  log.debug("\n Built CD for chain %s with %d steps: \n - %s ", chain.name,len(chain.steps),'\n - '.join(map(str, [{step.name:step.multiplicity} for step in chain.steps])))
282  #end of loop over chains
283 
284  log.debug("End of createDataFlow for %d chains and total %d steps", len(chains), NSTEPS)
285  return (finalDecisions, CFseqList)
286 
287 
288 def createControlFlow(flags, HLTNode, CFseqList):
289  """ Creates Control Flow Tree starting from the CFSequences"""
290  HLTNodeName = HLTNode.getName()
291  log.debug("[createControlFlow] on node %s with %d CFsequences",HLTNodeName, len(CFseqList))
292 
293  acc = ComponentAccumulator()
294  acc.addSequence(HLTNode)
295 
296  for nstep, sequences in enumerate(CFseqList):
297  stepSequenceName = CFNaming.stepName(nstep)
298  log.debug("\n******** Create CF Tree %s with %d AthSequencers", stepSequenceName, len(sequences))
299 
300  # create filter node
301  log.debug("[createControlFlow] Create filter step %s with %d filters", stepSequenceName, len(CFseqList[nstep]))
302  stepCFFilter = parOR(stepSequenceName + CFNaming.FILTER_POSTFIX)
303  acc.addSequence(stepCFFilter, parentName=HLTNodeName)
304 
305  filter_list = []
306  # add the filter to the node
307  for cgroup in sequences:
308  filterAlg = cgroup.sequenceCA.filterNode.Alg
309  if filterAlg.getName() not in filter_list:
310  log.debug("[createControlFlow] Add %s to filter node %s", filterAlg.getName(), stepSequenceName)
311  filter_list.append(filterAlg.getName())
312  stepCFFilter.Members += [filterAlg]
313 
314  # create reco step node
315  log.debug("[createControlFlow] Create reco step %s with %d sequences", stepSequenceName, len(CFseqList))
316  stepCFReco = parOR(stepSequenceName + CFNaming.RECO_POSTFIX)
317  acc.addSequence(stepCFReco, parentName = HLTNodeName)
318 
319  # add the sequences to the reco node
320  addedEmtpy = False
321  for cgroup in sequences:
322  cseq=cgroup.sequenceCA
323  if cseq.step.isEmpty and addedEmtpy:
324  cseq.ca.wasMerged()
325  continue
326  if cseq.step.isEmpty: # adding Empty only once to avoid merging multiple times the PassSequence
327  addedEmtpy= True
328  log.debug(" *** Create CF Tree for CFSequence %s", cseq.step.name)
329  acc.merge(cseq.ca, sequenceName=stepCFReco.getName())
330 
331  # add the monitor summary
332  stepDecisions = []
333  for CFseq in CFseqList[nstep]:
334  stepDecisions.extend(CFseq.sequenceCA.decisions)
335 
336  summary = makeSummary( flags, stepSequenceName, stepDecisions )
337  acc.addEventAlgo([summary],sequenceName = HLTNode.getName())
338 
339  if flags.Trigger.generateMenuDiagnostics:
340  log.debug("Now Draw Menu Diagnostic dot graphs...")
341  stepCF_DataFlow_to_dot( stepCFReco.getName(), CFseqList[nstep] )
342  stepCF_ControlFlow_to_dot( stepCFReco )
343 
344  log.debug("************* End of step %d, %s", nstep+1, stepSequenceName)
345 
346  return acc
347 
348 def addChainsToDataFlow(flags, CFseq_list, allDicts):
349  for groupsInStep in CFseq_list:
350  for cfgroup in groupsInStep:
351  chains = cfgroup.chains
352  CFS = cfgroup.sequenceCA
353  # add chains to the ComboHypo:
354  if CFS.step.combo is not None:
355  for chain in chains:
356  CFS.step.combo.addChain( [d for d in allDicts if d['chainName'] == chain ][0])
357  log.debug("Added chains to ComboHypo: %s",CFS.step.combo.getChains())
358 
359  # add HypoTools to this step (cumulating all same steps)
360  cfgroup.createHypoTools(flags)
361 
362 
363 
364 def buildFilter(filter_name, filter_input, empty):
365  """
366  Build the FILTER
367  one filter per previous sequence at the start of the sequence: always create a new one
368  if the previous hypo has more than one output, try to get all of them
369  one filter per previous sequence: 1 input/previous seq, 1 output/next seq
370  """
371  if empty:
372  log.debug("Calling PassFilterNode %s", filter_name)
373  sfilter = PassFilterNode(name = filter_name)
374  for i in filter_input:
375  sfilter.addInput(i)
376  sfilter.addOutput(i)
377  else:
378  sfilter = RoRSequenceFilterNode(name = filter_name)
379  for i in filter_input:
380  sfilter.addInput(i)
381  sfilter.addOutput(CFNaming.filterOutName(filter_name, i))
382 
383  log.debug("Added inputs to filter: %s", sfilter.getInputList())
384  log.debug("Added outputs to filter: %s", sfilter.getOutputList())
385  log.debug("Filter Done: %s", sfilter.Alg.name)
386 
387 
388  return (sfilter)
389 
390 
391 
HLTCFConfig.matrixDisplay
def matrixDisplay(allCFSeq)
CORE of Decision Handling.
Definition: HLTCFConfig.py:76
HLTCFConfig.createDataFlow
def createDataFlow(flags, chains)
Definition: HLTCFConfig.py:194
max
#define max(a, b)
Definition: cfImp.cxx:41
python.JetAnalysisCommon.ComponentAccumulator
ComponentAccumulator
Definition: JetAnalysisCommon.py:302
HLTCFConfig.createControlFlow
def createControlFlow(flags, HLTNode, CFseqList)
Definition: HLTCFConfig.py:288
HLTCFConfig.createStepFilterNode
def createStepFilterNode(name, seq_list, dump=False)
Definition: HLTCFConfig.py:53
HLTCFConfig.decisionTreeFromChains
def decisionTreeFromChains(flags, HLTNode, chains, allDicts)
Definition: HLTCFConfig.py:166
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
intersection
std::vector< std::string > intersection(std::vector< std::string > &v1, std::vector< std::string > &v2)
Definition: compareFlatTrees.cxx:25
HLTCFConfig.addChainsToDataFlow
def addChainsToDataFlow(flags, CFseq_list, allDicts)
Definition: HLTCFConfig.py:348
python.DecisionHandlingConfig.TriggerSummaryAlg
def TriggerSummaryAlg(flags, name)
Definition: DecisionHandlingConfig.py:27
convertTimingResiduals.sum
sum
Definition: convertTimingResiduals.py:55
HLTCFConfig.sequenceScanner
def sequenceScanner(HLTNode)
Definition: HLTCFConfig.py:110
python.JetAnalysisCommon.parOR
parOR
Definition: JetAnalysisCommon.py:271
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:195
add
bool add(const std::string &hname, TKey *tobj)
Definition: fastadd.cxx:55
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
DerivationFramework::TriggerMatchingUtils::sorted
std::vector< typename T::value_type > sorted(T begin, T end)
Helper function to create a sorted vector from an unsorted one.
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
HLTCFDot.all_DataFlow_to_dot
def all_DataFlow_to_dot(name, step_list)
Definition: HLTCFDot.py:87
HLTCFDot.stepCF_ControlFlow_to_dot
def stepCF_ControlFlow_to_dot(stepCF)
Definition: HLTCFDot.py:23
HLTCFDot.stepCF_DataFlow_to_dot
def stepCF_DataFlow_to_dot(name, cfseq_list)
Definition: HLTCFDot.py:146
HLTCFConfig.makeSummary
def makeSummary(flags, name, flatDecisions)
Functions to create the CF tree from CF configuration objects.
Definition: HLTCFConfig.py:44
get
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition: hcg.cxx:127
str
Definition: BTagTrackIpAccessor.cxx:11
python.CFElements.isSequence
def isSequence(obj)
Definition: CFElements.py:96
HLTCFConfig.buildFilter
def buildFilter(filter_name, filter_input, empty)
Definition: HLTCFConfig.py:364