ATLAS Offline Software
Loading...
Searching...
No Matches
PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py
Go to the documentation of this file.
1# Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
2
3# Check if we have the Athena package available. If yes, just use that code.
4try:
5 from AthenaConfiguration.ComponentFactory import CompFactory, isComponentAccumulatorCfg
6 if isComponentAccumulatorCfg():
7 AlgSequence = CompFactory.AthSequencer
8 else:
9 from AthenaCommon.AlgSequence import AthSequencer as AlgSequence
10except ImportError:
11
12 # If not, then we implement a standalone version of it...
13
14 # Import(s):
15 import unittest
16 from AnaAlgorithm.AnaAlgorithmConfig import AnaAlgorithmConfig, indentBy
17 from AnaAlgorithm.PythonConfig import PythonConfig
18
20 """Standalone algorithm sequence
21
22 This is a light-weight emulation of Athena's AthSequencer class,
23 implementing a simple algorithm sequence for EventLoop jobs.
24 """
25
26 def __init__( self, name = "AlgSequence" ):
27 """Algorithm sequence constructor
28
29 Keyword arguments:
30 name -- The name of the algorithm sequence (for debugging)
31 """
32
33 # Set up the member variables:
34 self._name = name
36
37 return
38
39 def name( self ):
40 """Return the name of this sequence
41
42 Mainly for debugging purposes, and for when we are embedding
43 one sequence into another one.
44 """
45
46 return self._name
47
48 def insert( self, index, algOrSeq ):
49 """Insert one algorithm/sequence into this sequence
50
51 This allows us to extend existing sequences with a greater
52 flexibility.
53
54 Keyword arguments:
55 index -- The index to insert the algorithm/sequence under
56 algOrSeq -- The object to insert
57 """
58
59 return self.__iadd__( algOrSeq, index = index )
60
61 def addSelfToJob( self, job ):
62 """add a copy of this config to the EventLoop job object
63
64 Keyword arguments:
65 job -- The job object to add ourself to
66 """
67 for alg in self:
68 alg.addSelfToJob (job)
69 pass
70 pass
71
72 def __getitem__( self, index ):
73 """Return one algorithm/sequence from the sequence by index
74
75 This is to allow getting the n'th element of an algorithm sequence
76 (which itself may either be an algorithm or a sequence),
77 including the n'th element from the back of it if needed.
78
79 Keyword arguments:
80 index -- The index of the algorithm/sequence to get from the
81 sequence
82 """
83
84 return self._algsAndSequences[ index ]
85
86 def __getattr__( self, name ):
87 """Access one algorithm/sequence in this sequence, by name
88
89 This is to allow modifying the properties of algorithms in a
90 sequence that was set up centrally.
91
92 Keyword arguments:
93 name -- The name of the algorithm/sequence to look up in the
94 sequence
95 """
96
97 # Look up the algorithm by name:
98 for algOrSeq in self._algsAndSequences:
99 if algOrSeq.name() == name:
100 return algOrSeq
101 pass
102
103 # If no algorithm with this name was found, that's a problem:
104 raise AttributeError( 'Algorithm/sequence with name "%s" was not ' \
105 'found' % name )
106
107 def __delattr__( self, name ):
108 """Remove one algorithm/sequence from this sequence, by name
109
110 This is to allow removing algorithms (or even sequences) from this
111 sequence in case that would be needed.
112
113 Keyword arguments:
114 name -- The name of the algorithm/sequence to delete from the
115 sequence
116 """
117
118 # Look up the algorithm by name:
119 for algOrSeq in self._algsAndSequences:
120 if algOrSeq.name() == name:
121 # If we found it, remove it:
122 self._algsAndSequences.remove( algOrSeq )
123 return
124 pass
125
126 # If no algorithm/sequence with this name was found, that's a
127 # problem:
128 raise AttributeError( 'Algorithm/sequence with name "%s" was not ' \
129 'found' % name )
130
131 def __iter__( self ):
132 """Create an iterator over all the algorithms of this sequence
133
134 This is to allow for a Python-like iteration over all algorithms
135 that are part of the sequence. This includes iterating over the
136 algorithms that may be in sub-sequences of this sequence.
137 """
138
139 # Create the iterator to process the internal list of algorithms:
141
142 def __iadd__( self, algOrSeq, index = None ):
143 """Add one algorithm/sequence to the sequence
144
145 This function is used to add one algorithm (or algorithm sequence)
146 to the sequence object, using the '+=' operator.
147
148 Keyword arguments:
149 algOrSeq -- The algorithm/sequence to add to the sequence
150 """
151
152 # Check that the received object is of the right type:
153 if not isinstance( algOrSeq, AnaAlgorithmConfig ) and \
154 not isinstance( algOrSeq, PythonConfig ) and \
155 not isinstance( algOrSeq, AlgSequence ):
156 raise TypeError( 'The received object is not of type ' \
157 'AnaAlgorithmConfig or PythonConfig or AlgSequence' )
158 pass
159
160 # Now check if an equivalent algorithm/sequence is already in the
161 # list. As that's also an error.
162 if algOrSeq in self:
163 raise RuntimeError( 'Algorithm/sequence %s is already in ' \
164 'this sequence' % algOrSeq.name() )
165 pass
166
167 # Add the algorithm/sequence to the internal list:
168 if not index:
169 self._algsAndSequences.append( algOrSeq )
170 else:
171 self._algsAndSequences.insert( index, algOrSeq )
172 pass
173
174 # Return the modified object:
175 return self
176
177 def __eq__( self, other ):
178 """Check for equality with another object
179
180 The implementation of this is very simple. We only check that
181 the name of the sequences would match.
182
183 Keyword arguments:
184 other -- The object to compare this one against
185 """
186
187 # First check that the other object is also an AlgSequence one:
188 if not isinstance( other, AlgSequence ):
189 return False
190
191 # Base the equality purely on the name of the sequence. This may
192 # need to be made smarter at one point.
193 return ( self.name() == other.name() )
194
195 def __ne__( self, other ):
196 """Check for an inequality with another object
197
198 This is just defined to make the '!=' operator of Python behave
199 consistently with the '==' operator for such objects.
200
201 Keyword arguments:
202 other -- The object to compare this one against
203 """
204 return not self.__eq__( other )
205
206 def __str__( self ):
207 """Print the algorithm sequence in a user-friendly way
208
209 This function takes care of printing the full configuration of every
210 algorithm in the sequence.
211 """
212
213 result = AnaAlgorithmConfig._printHeader( 'AlgSequence/%s' %
214 self.name() )
215 result += '\n'
216 for algOrSeq in self._algsAndSequences:
217 result += '| %s\n' % indentBy( str( algOrSeq ), '| ' )
218 pass
219 result += AnaAlgorithmConfig._printFooter( 'AlgSequence/%s' %
220 self.name() )
221 return result
222
223 def __len__( self ):
224 """Return the size/length of the algorithm sequence
225
226 Just returning the number of algorithms and sequences that are in
227 this sequence. So this is not a recursive count.
228 """
229
230 return len( self._algsAndSequences )
231
232 pass
233
235 """Iterator over a standalone algorithm sequence
236
237 This custom class is needed to implement a "recursive iteration", which
238 would loop over all algorithms in a sequence that itself may have
239 sub-sequences inside of it.
240 """
241
242 def __init__( self, sequence ):
243 """Constructor for the algorithm sequence iterator
244
245 Keyword arguments:
246 sequence -- The sequence to iterate over (recursively)
247 """
248
249 # Set up the internal variables:
250 self._sequence = sequence
251 self._index = 0
252 self._iterator = None
253
254 return
255
256 def __iter__( self ):
257 """Function making this iterator iterable itself
258 """
259 return self
260
261 def __next__( self ):
262 """Function implementing the recursive iteration over an AlgSequence
263
264 This is where most of the logic is. The iterator loops over the
265 elements of the AlgSequence that was given to it, one by one. When
266 it finds an element in the AlgSequence that itself is also an
267 AlgSequence, then it creates a helper iterator object that would
268 process that sub-sequence, and continue the iteration using that
269 helper.
270
271 The end result is that the iteration should loop over every
272 algorithm in the sequence and its sub-sequences.
273 """
274
275 # First off, check whether we reached the end of the sequence.
276 if self._index >= len( self._sequence ):
277 raise StopIteration()
278
279 # If not, check whether we are currently iterating over a
280 # sub-sequence.
281 if self._iterator:
282 try:
283 return self._iterator.__next__()
284 except StopIteration:
285 # If the sub-sequence is exhaused, then switch to the
286 # next element in our sequence, and call this function
287 # recursively.
288 self._index += 1
289 self._iterator = None
290 return self.__next__()
291 pass
292
293 # If we are not iterating over a sub-sequence at the moment, let's
294 # just take the next element of our sequence.
295 element = self._sequence[ self._index ]
296
297 # If this element is a sequence itself, then switch to "sub-sequence
298 # iterating mode", and call this function recursively in that mode.
299 if isinstance( element, AlgSequence ):
300 self._iterator = AlgSequenceIterator( element )
301 return self.__next__()
302
303 # Apparently it's an algorithm we found. So update the internal
304 # index, and simply return the algorithm.
305 self._index += 1
306 return element
307
308 pass
309
310 #
311 # Unit tests for the code
312 #
313
314
315 class TestAlgSequenceWithAlgs( unittest.TestCase ):
316
317
318 def setUp( self ):
319 self.seq = AlgSequence( 'TestSequence' )
320 self.seq += AnaAlgorithmConfig( 'AlgType1/Algorithm1' )
321 self.seq += AnaAlgorithmConfig( 'AlgType2/Algorithm2' )
322 return
323
324
326 self.assertEqual( self.seq.name(), 'TestSequence' )
327 self.assertEqual( len( self.seq ), 2 )
328 return
329
330
331 def test_indexLookup( self ):
332 self.assertEqual( self.seq[ 0 ].name(), 'Algorithm1' )
333 self.assertEqual( self.seq[ 1 ].name(), 'Algorithm2' )
334 with self.assertRaises( IndexError ):
335 self.seq[ 2 ]
336 pass
337 return
338
339
340 def test_nameLookup( self ):
341 self.assertEqual( self.seq.Algorithm1.type(), 'AlgType1' )
342 self.assertEqual( self.seq.Algorithm2.type(), 'AlgType2' )
343 with self.assertRaises( AttributeError ):
344 self.seq.Algorithm3.type()
345 pass
346 return
347
348
349 def test_iteration( self ):
350 algNames = [ alg.name() for alg in self.seq ]
351 self.assertEqual( algNames, [ 'Algorithm1', 'Algorithm2' ] )
352 return
353
354
355 def test_insertAlg( self ):
356 self.seq.insert( 1, AnaAlgorithmConfig( 'AlgType3/Algorithm3' ) )
357 self.assertEqual( len( self.seq ), 3 )
358 self.assertEqual( self.seq[ 0 ].name(), 'Algorithm1' )
359 self.assertEqual( self.seq[ 1 ].name(), 'Algorithm3' )
360 self.assertEqual( self.seq[ 2 ].name(), 'Algorithm2' )
361 return
362
363
364 def test_deleteAlg( self ):
365 del self.seq.Algorithm1
366 self.assertEqual( len( self.seq ), 1 )
367 self.assertEqual( self.seq[ 0 ].name(), 'Algorithm2' )
368 return
369
370 pass
371
372
373 class TestAlgSequenceWithAlgsAndSeqs( unittest.TestCase ):
374
375
376 def setUp( self ):
377 self.seq = AlgSequence( 'MainSequence' )
378 self.seq += AnaAlgorithmConfig( 'PreSelectionAlg/PreSelection' )
379 self.seq += AlgSequence( 'MuonSequence' )
380 self.seq.MuonSequence += \
381 AnaAlgorithmConfig( 'MuonAnalysisAlg/MuonAnalysis' )
382 self.seq.MuonSequence += \
383 AnaAlgorithmConfig( 'MuonHistoAlg/MuonHistogramming' )
384 self.seq += AlgSequence( 'JetSequence' )
385 self.seq.JetSequence += AlgSequence( 'JetPreSequence' )
386 self.seq.JetSequence.JetPreSequence += \
387 AnaAlgorithmConfig( 'JetPreSelectorAlg/JetPreSelector' )
388 self.seq.JetSequence.JetPreSequence += \
389 AnaAlgorithmConfig( 'JetDecoratorAlg/JetDecorator' )
390 self.seq.JetSequence += \
391 AnaAlgorithmConfig( 'JetAnalysisAlg/JetAnalysis' )
392 self.seq.JetSequence += \
393 AnaAlgorithmConfig( 'JetHistoAlg/JetHistogramming' )
394 self.seq += AnaAlgorithmConfig( 'PostProcessingAlg/PostProcessing' )
395 return
396
397
399 self.assertEqual( len( self.seq ), 4 )
400 return
401
402
403 def test_indexLookup( self ):
404 self.assertEqual( self.seq[ 0 ].name(), 'PreSelection' )
405 self.assertEqual( self.seq[ 2 ].name(), 'JetSequence' )
406 with self.assertRaises( IndexError ):
407 self.seq[ 4 ]
408 pass
409 return
410
411
412 def test_nameLookup( self ):
413 self.assertEqual( self.seq.MuonSequence.MuonAnalysis.type(),
414 'MuonAnalysisAlg' )
415 self.assertEqual( self.seq.JetSequence.JetHistogramming.type(),
416 'JetHistoAlg' )
417 self.assertEqual( self.seq.JetSequence.JetPreSequence.JetDecorator.type(),
418 'JetDecoratorAlg' )
419 with self.assertRaises( AttributeError ):
420 self.seq.MuonSequence.InvalidAlg.type()
421 pass
422 return
423
424
425 def test_iteration( self ):
426 algNames = [ alg.name() for alg in self.seq ]
427 self.assertEqual( algNames, [ 'PreSelection', 'MuonAnalysis',
428 'MuonHistogramming', 'JetPreSelector',
429 'JetDecorator', 'JetAnalysis',
430 'JetHistogramming',
431 'PostProcessing' ] )
432 return
433
434 pass
435
436 pass
setUp(self)
Function setting up the (elaborate) sequence to be tested.
test_indexLookup(self)
Test index lookup operations on the algorithm sequence.
test_iteration(self)
Test the ability to iterate over the algorithms in the sequence.
test_nameLookup(self)
Test 'by name' lookup operations on the algorithm sequence.
test_iteration(self)
Test the ability to iterate over the algorithms in the sequence.
test_indexLookup(self)
Test index lookup operations on the algorithm sequence.
test_nameLookup(self)
Test 'by name' lookup operations on the algorithm sequence.