ATLAS Offline Software
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.
4 try:
5  from AthenaConfiguration.ComponentFactory import CompFactory, isComponentAccumulatorCfg
7  AlgSequence = CompFactory.AthSequencer
8  else:
9  from AthenaCommon.AlgSequence import AthSequencer as AlgSequence
10 except 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 
19  class AlgSequence( object ):
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 
325  def test_sequenceBasics( self ):
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 
398  def test_sequenceBasics( self ):
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
python.AlgSequence.AlgSequence.__iter__
def __iter__(self)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:131
python.AlgSequence.AlgSequence
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:19
python.AlgSequence.AlgSequence.__str__
def __str__(self)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:206
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs.setUp
def setUp(self)
Function setting up the (elaborate) sequence to be tested.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:376
python.AlgSequence.AlgSequenceIterator
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:234
python.AlgSequence.AlgSequence.addSelfToJob
def addSelfToJob(self, job)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:61
python.AlgSequence.AlgSequenceIterator._iterator
_iterator
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:252
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs.test_sequenceBasics
def test_sequenceBasics(self)
Test basic features of the algorithm sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:398
python.AlgSequence.AlgSequenceIterator.__next__
def __next__(self)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:261
python.AlgSequence.AlgSequence._name
_name
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:34
python.AlgSequence.TestAlgSequenceWithAlgs.test_indexLookup
def test_indexLookup(self)
Test index lookup operations on the algorithm sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:331
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs.test_indexLookup
def test_indexLookup(self)
Test index lookup operations on the algorithm sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:403
python.AlgSequence.TestAlgSequenceWithAlgs.test_sequenceBasics
def test_sequenceBasics(self)
Test basic features of the algorithm sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:325
python.AlgSequence.TestAlgSequenceWithAlgs.test_insertAlg
def test_insertAlg(self)
Test the insertion of one algorithm.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:355
python.AlgSequence.AlgSequence.__ne__
def __ne__(self, other)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:195
python.AlgSequence.AlgSequenceIterator.__init__
def __init__(self, sequence)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:242
PixelModuleFeMask_create_db.remove
string remove
Definition: PixelModuleFeMask_create_db.py:83
python.AlgSequence.AlgSequence.__eq__
def __eq__(self, other)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:177
python.AsgServiceConfig.indentBy
def indentBy(propValue, indent)
Definition: AsgServiceConfig.py:364
python.AlgSequence.AlgSequence.insert
def insert(self, index, algOrSeq)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:48
python.AlgSequence.AlgSequenceIterator._index
_index
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:251
python.AlgSequence.TestAlgSequenceWithAlgs.setUp
def setUp(self)
Function setting up the sequence to be tested.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:318
python.AlgSequence.AlgSequence.__init__
def __init__(self, name="AlgSequence")
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:26
python.AlgSequence.TestAlgSequenceWithAlgs
Test case for a sequence with only algorithms in it.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:315
python.AlgSequence.AlgSequenceIterator._sequence
_sequence
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:250
python.AlgSequence.AlgSequence.__getitem__
def __getitem__(self, index)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:72
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs
Test case for a sequence with algorithms and sub-sequences.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:373
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs.test_nameLookup
def test_nameLookup(self)
Test 'by name' lookup operations on the algorithm sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:412
python.AlgSequence.AlgSequence.__iadd__
def __iadd__(self, algOrSeq, index=None)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:142
python.AlgSequence.TestAlgSequenceWithAlgs.test_deleteAlg
def test_deleteAlg(self)
Test the deletion of an algorithm.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:364
name
std::string name
Definition: Control/AthContainers/Root/debug.cxx:240
python.AlgSequence.TestAlgSequenceWithAlgs.seq
seq
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:319
python.AlgSequence.AlgSequenceIterator.__iter__
def __iter__(self)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:256
python.JetAnalysisCommon.isComponentAccumulatorCfg
isComponentAccumulatorCfg
Definition: JetAnalysisCommon.py:263
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs.test_iteration
def test_iteration(self)
Test the ability to iterate over the algorithms in the sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:425
python.AlgSequence.AlgSequence.__len__
def __len__(self)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:223
python.AlgSequence.TestAlgSequenceWithAlgs.test_iteration
def test_iteration(self)
Test the ability to iterate over the algorithms in the sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:349
python.AlgSequence.TestAlgSequenceWithAlgs.test_nameLookup
def test_nameLookup(self)
Test 'by name' lookup operations on the algorithm sequence.
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:340
python.AlgSequence.AlgSequence.name
def name(self)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:39
python.AlgSequence.TestAlgSequenceWithAlgsAndSeqs.seq
seq
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:377
pickleTool.object
object
Definition: pickleTool.py:30
str
Definition: BTagTrackIpAccessor.cxx:11
python.AlgSequence.AlgSequence.__getattr__
def __getattr__(self, name)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:86
python.AlgSequence.AlgSequence.__delattr__
def __delattr__(self, name)
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:107
python.AlgSequence.AlgSequence._algsAndSequences
_algsAndSequences
Definition: PhysicsAnalysis/D3PDTools/AnaAlgorithm/python/AlgSequence.py:35