ATLAS Offline Software
Loading...
Searching...
No Matches
CFElements.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2from AthenaConfiguration.ComponentFactory import CompFactory
3import collections
4
5AthSequencer = CompFactory.AthSequencer # cache lookup
6
7def parAND(name, subs=[], invert=False):
8 """parallel AND sequencer"""
9 return AthSequencer( name,
10 ModeOR = False,
11 Sequential = False,
12 StopOverride = True,
13 Invert = invert,
14 Members = subs.copy() )
15
16def parOR(name, subs=[], invert=False):
17 """parallel OR sequencer
18 This is the default sequencer and lets the DataFlow govern the execution entirely.
19 """
20 return AthSequencer( name,
21 ModeOR = True,
22 Sequential = False,
23 StopOverride = True,
24 Invert = invert,
25 Members = subs.copy() )
26
27def seqAND(name, subs=[], invert=False):
28 """sequential AND sequencer"""
29 return AthSequencer( name,
30 ModeOR = False,
31 Sequential = True,
32 StopOverride = False,
33 Invert = invert,
34 Members = subs.copy() )
35
36def seqOR(name, subs=[], invert=False):
37 """sequential OR sequencer
38 Used when a barrier needs to be set by all subs reached irrespective of the decision
39 """
40 return AthSequencer( name,
41 ModeOR = True,
42 Sequential = True,
43 StopOverride = True,
44 Invert = invert,
45 Members = subs.copy() )
46
47
48def getSequenceChildren(comp):
49 """Return sequence children (empty if comp is not a sequence)"""
50 try:
51 return comp.Members
52 except AttributeError:
53 return []
54
55
56def checkSequenceConsistency( seq ):
57 """ Enforce rules for sequence graph - identical items can not be added to itself (even indirectly) """
58
59 def __noSubSequenceOfName( s, n, seen = set() ):
60 seen = seen.copy()
61 seen.add (s)
62 for c in getSequenceChildren( s ):
63 if c in seen:
64 raise RuntimeError(f"Sequence {c.getName()} contains itself")
65 if isSequence( c ):
66 if c.getName() == n:
67 raise RuntimeError(f"Sequence {n} contains sub-sequence of the same name")
68 __noSubSequenceOfName( c, c.getName(), seen ) # check each sequence for repetition as well
69 __noSubSequenceOfName( c, n, seen )
70
71 __noSubSequenceOfName( seq, seq.getName() )
72
73
74def isSequence( obj ):
75 return type(obj) is AthSequencer # faster than isinstance and we do not care about inheritance
76
77
78def findSubSequence( start, nameToLookFor ):
79 """ Traverse sequences tree to find a sequence of a given name. The first one is returned. """
80 # Implemented as an iterative Depth-First search, which is faster than recursion in Python.
81
82 stack = [start]
83 while stack:
84 current = stack.pop()
85
86 if current.getName() == nameToLookFor:
87 return current
88
89 # Collect child sequences (plain loop is faster than generator expression)
90 for c in getSequenceChildren(current):
91 if isSequence(c):
92 stack.append(c)
93
94 return None
95
96
97def findOwningSequence( start, nameToLookFor ):
98 """ find sequence that owns the sequence nameTooLookFor"""
99 for c in getSequenceChildren(start):
100 if c.getName() == nameToLookFor:
101 return start
102 if isSequence( c ):
103 found = findOwningSequence( c, nameToLookFor )
104 if found:
105 return found
106 return None
107
108
109def findAlgorithmByPredicate( startSequence, predicate, depth = 1000000 ):
110 """ Traverse sequences tree to find the first algorithm satisfying given predicate. The first encountered is returned.
111
112 Depth of the search can be controlled by the depth parameter.
113 Typical use is to limit search to the startSequence with depth parameter set to 1
114 """
115 for c in getSequenceChildren(startSequence):
116 if not isSequence(c):
117 if predicate(c):
118 return c
119 else:
120 if depth > 1:
121 found = findAlgorithmByPredicate( c, predicate, depth-1 )
122 if found:
123 return found
124
125 return None
126
127
128def findAlgorithm( startSequence, nameToLookFor, depth = 1000000 ):
129 """ Traverse sequences tree to find the algorithm of given name. The first encountered is returned.
130
131 The name() method is used to obtain the algorithm name, that one has to match to the request.
132 """
133 return findAlgorithmByPredicate( startSequence, lambda alg: alg.getName() == nameToLookFor, depth )
134
135
136def findAllAlgorithms(sequence, nameToLookFor=None):
137 """
138 Returns flat listof of all algorithm instances in this, and in sub-sequences
139 """
140 algorithms = []
141 for child in getSequenceChildren(sequence):
142 if isSequence(child):
143 algorithms += findAllAlgorithms(child, nameToLookFor)
144 else:
145 if nameToLookFor is None or child.getName() == nameToLookFor:
146 algorithms.append(child)
147 return algorithms
148
149
150def findAllAlgorithmsByName(sequence, namesToLookFor=None):
151 """
152 Finds all algorithms in sequence and groups them by name
153
154 Resulting dict has a following structure
155 {"Alg1Name":[(Alg1Instance, parentSequenceA, indexInSequenceA),(Alg1Instance, parentSequenceB, indexInSequenceB)],
156 "Alg2Name":(Alg2Instance, parentSequence, indexInThisSequence),
157 ....}
158 """
159 algorithms = collections.defaultdict(list)
160 for idx, child in enumerate(getSequenceChildren(sequence)):
161 if child.getName() == sequence.getName():
162 raise RuntimeError(f"Recursively-nested sequence: {child.getName()} contains itself")
163 if isSequence(child):
164 childAlgs = findAllAlgorithmsByName(child, namesToLookFor)
165 for algName in childAlgs:
166 algorithms[algName] += childAlgs[algName]
167 else:
168 if namesToLookFor is None or child.getName() in namesToLookFor:
169 algorithms[child.getName()].append( (child, sequence, idx) )
170 return algorithms
171
172
173def flatAlgorithmSequences( start ):
174 """ Converts tree like structure of sequences into dictionary
175 keyed by top/start sequence name containing lists of of algorithms & sequences."""
176
177 def __inner( seq, collector ):
178 for c in getSequenceChildren(seq):
179 collector[seq.getName()].append( c )
180 if isSequence( c ):
181 __inner( c, collector )
182
183 from collections import defaultdict,OrderedDict
184 c = defaultdict(list)
185 __inner(start, c)
186 return OrderedDict(c)
187
188
189def iterSequences( start ):
190 """Iterator of sequences and their algorithms from (and including) the `start`
191 sequence object. Do start from a sequence name use findSubSequence."""
192 def __inner( seq ):
193 for c in getSequenceChildren(seq):
194 yield c
195 if isSequence(c):
196 yield from __inner(c)
197 yield start
198 yield from __inner(start)
199
200
201
202# self test
203import unittest
204class TestCF( unittest.TestCase ):
205 def setUp( self ):
206 import AthenaPython.PyAthena as PyAthena
207
208 top = parOR("top")
209 top.Members += [parOR("nest1")]
210 nest2 = seqAND("nest2")
211 top.Members += [nest2]
212 top.Members += [PyAthena.Alg("SomeAlg0")]
213 nest2.Members += [parOR("deep_nest1")]
214 nest2.Members += [parOR("deep_nest2")]
215
216 nest2.Members += [PyAthena.Alg("SomeAlg1")]
217 nest2.Members += [PyAthena.Alg("SomeAlg2")]
218 nest2.Members += [PyAthena.Alg("SomeAlg3")]
219 self.top = top
220
221 def test_findTop( self ):
222 f = findSubSequence( self.top, "top")
223 self.assertIsNotNone( f, "Can not find sequence at start" )
224 self.assertEqual( f.getName(), "top", "Wrong sequence" )
225 # a one level deep search
226 nest2 = findSubSequence( self.top, "nest2" )
227 self.assertIsNotNone( nest2, "Can not find sub sequence" )
228 self.assertEqual( nest2.getName(), "nest2", "Sub sequence incorrect" )
229
230 def test_findDeep( self ):
231 # deeper search
232 d = findSubSequence( self.top, "deep_nest2")
233 self.assertIsNotNone( d, "Deep searching for sub seqeunce fails" )
234 self.assertEqual( d.getName(), "deep_nest2", "Wrong sub sequence in deep search" )
235
236 def test_findMissing( self ):
237 # algorithm is not a sequence
238 d = findSubSequence( self.top, "SomeAlg1")
239 self.assertIsNone( d, "Algorithm confused as a sequence" )
240
241 # no on demand creation
242 inexistent = findSubSequence( self.top, "not_there" )
243 self.assertIsNone( inexistent, "ERROR, found sub sequence that does not relay exist" )
244
245 # owner finding
246 inexistent = findOwningSequence(self.top, "not_there")
247 self.assertIsNone( inexistent, "ERROR, found owner of inexistent sequence " )
248
249 def test_findRespectingScope( self ):
250 owner = findOwningSequence( self.top, "deep_nest1")
251 self.assertEqual( owner.getName(), "nest2", "Wrong owner %s" % owner.getName() )
252
253 owner = findOwningSequence( self.top, "deep_nest2")
254 self.assertEqual( owner.getName(), "nest2", "Wrong owner %s" % owner.getName() )
255
256 owner = findOwningSequence( self.top, "SomeAlg1")
257 self.assertEqual( owner.getName(), "nest2", "Wrong owner %s" % owner.getName() )
258
259 owner = findOwningSequence( self.top, "SomeAlg0")
260 self.assertEqual( owner.getName() , "top", "Wrong owner %s" % owner.getName() )
261
262 def test_iterSequences( self ):
263 # Traverse from top
264 result = [seq.getName() for seq in iterSequences( self.top )]
265 self.assertEqual( result, ['top', 'nest1', 'nest2', 'deep_nest1', 'deep_nest2',
266 'SomeAlg1', 'SomeAlg2', 'SomeAlg3', 'SomeAlg0'] )
267
268 # Traverse from nested sequence
269 nest2 = findSubSequence( self.top, "nest2" )
270 result = [seq.getName() for seq in iterSequences( nest2 )]
271 self.assertEqual( result, ['nest2', 'deep_nest1', 'deep_nest2',
272 'SomeAlg1', 'SomeAlg2', 'SomeAlg3'] )
273
274 # Traverse empty sequence
275 deep_nest2 = findSubSequence( self.top, "deep_nest2" )
276 result = [seq.getName() for seq in iterSequences( deep_nest2 )]
277 self.assertEqual( result, ['deep_nest2'] )
278
279 # Traverse from algorithm
280 alg1 = findAlgorithm( self.top, "SomeAlg1" )
281 result = [seq.getName() for seq in iterSequences( alg1 )]
282 self.assertEqual( result, ['SomeAlg1'] )
283
284 def test_findAlgorithms( self ):
285 a1 = findAlgorithm( self.top, "SomeAlg0" )
286 self.assertIsNotNone( a1, "Can't find algorithm present in sequence" )
287
288 a1 = findAlgorithm( self.top, "SomeAlg1" )
289 self.assertIsNotNone( a1, "Can't find nested algorithm " )
290
291 nest2 = findSubSequence( self.top, "nest2" )
292
293 a1 = findAlgorithm( nest2, "SomeAlg0" )
294 self.assertIsNone( a1, "Finding algorithm that is in the upper sequence" )
295
296 a1 = findAlgorithm( nest2, "NonexistentAlg" )
297 self.assertIsNone( a1, "Finding algorithm that is does not exist" )
298
299 a1 = findAlgorithm( self.top, "SomeAlg0", 1)
300 self.assertIsNotNone( a1, "Could not find algorithm within the required nesting depth == 1" )
301
302 a1 = findAlgorithm( self.top, "SomeAlg1", 1)
303 self.assertIsNone( a1, "Could find algorithm even if it is deep in sequences structure" )
304
305 a1 = findAlgorithm( self.top, "SomeAlg1", 2)
306 self.assertIsNotNone( a1, "Could not find algorithm within the required nesting depth == 2" )
307
308 a1 = findAlgorithm( self.top, "SomeAlg3", 2)
309 self.assertIsNotNone( a1 is None, "Could find algorithm even if it is deep in sequences structure" )
310
311
312class TestNest( unittest.TestCase ):
313 def test( self ):
314 global isComponentAccumulatorCfg
315 isComponentAccumulatorCfg = lambda : True # noqa: E731 (lambda for mockup)
316
317 top = parOR("top")
318 nest1 = parOR("nest1")
319 nest2 = seqAND("nest2")
320 top.Members += [nest1, nest2]
321
322 deep_nest1 = seqAND("deep_nest1")
323 nest1.Members += [deep_nest1]
324
325 nest2.Members += [nest1] # that one is ok
326 checkSequenceConsistency( top )
327 deep_nest1.Members += [nest1] # introducing an issue
328 self.assertRaises( RuntimeError, checkSequenceConsistency, top )
329
parAND(name, subs=[], invert=False)
Definition CFElements.py:7