ATLAS Offline Software
AnaAlgorithmConfig.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
2 
3 # Import(s):
4 import ROOT
5 import unittest
6 import copy
7 
8 class AnaAlgorithmConfig( ROOT.EL.AnaAlgorithmConfig ):
9  """Standalone Analysis Algorithm Configuration
10 
11  This class is used to describe the configuration of an analysis algorithm
12  (a C++ class inheriting from EL::AnaAlgorithm) in Python. It behaves
13  similar to an Athena configurable, but is implemented in a much simpler
14  way.
15 
16  An example of using it in configuring an EventLoop job could look like:
17 
18  job = ROOT.EL.Job()
19  ...
20  from AnaAlgorithm.AnaAlgorithmConfig import AnaAlgorithmConfig
21  alg = AnaAlgorithmConfig( "EL::UnitTestAlg2/TestAlg",
22  property = 1.23 )
23  alg.string_property = "Foo"
24  job.algsAdd( alg )
25 
26  Note that the python code doesn't know what properties can actually be set
27  on any given C++ algorithm. Any mistake made in the Python configuration
28  (apart from syntax errors) is only discovered while initialising the
29  analysis job.
30  """
31 
32  # Class/static variable(s):
33  printHeaderWidth = 80
34  printHeaderPre = 3
35 
36  def __init__( self, typeAndName, **kwargs ):
37  """Constructor for an algorithm configuration object
38 
39  Keyword arguments:
40  typeAndName -- The type/instance name of the algorithm
41 
42  Note that you can pass (initial) properties to the constructor like:
43 
44  alg = AnaAlgorithmConfig( "EL::UnitTestAlg2/TestAlg",
45  property = 1.23 )
46  """
47 
48  # Call the base class's constructor. Use the default constructor instead
49  # of the one receiving the type and name, to avoid ROOT-10872.
50  super( AnaAlgorithmConfig, self ).__init__()
51  self.setTypeAndName( typeAndName )
52 
53  # Initialise the properties of the algorihm:
54  self._props = {}
55 
56  # Set the properties on the object:
57  for key, value in kwargs.items():
58  self.setPropertyFromString( key, stringPropValue( value ) )
59  self._props[ key ] = copy.deepcopy( value )
60  pass
61 
62  pass
63 
64  def getName( self ):
65  """Get the instance name of the algorithm
66 
67  This is for compatibility with the getName() function of Athena
68  configurables.
69  """
70 
71  return self.name()
72 
73  def getType( self ):
74  """Get the type name of the algorithm
75 
76  This is for compatibility with the getType() function of Athena
77  configurables.
78  """
79 
80  return self.type()
81 
82  def __getattr__( self, name ):
83  """Get a previously set property value from the configuration
84 
85  This function allows us to retrieve the value of a property that was
86  already set for the algorithm, to possibly use it in some configuration
87  decisions in the Python code itself.
88 
89  Keyword arguments:
90  name -- The name of the property
91  """
92 
93  # Fail if the property was not (yet) set:
94  if not name in self._props:
95  raise AttributeError( 'Property \'%s\' was not set on \'%s/%s\'' %
96  ( name, self.type(), self.name() ) )
97 
98  # Return the property value:
99  return self._props[ name ]
100 
101  def __setattr__( self, key, value ):
102  """Set an algorithm property on an existing configuration object
103 
104  This function allows us to set/override properties on an algorithm
105  configuration object. Allowing for the following syntax:
106 
107  alg = ...
108  alg.IntProperty = 66
109  alg.FloatProperty = 3.141592
110  alg.StringProperty = "Foo"
111 
112  Keyword arguments:
113  key -- The key/name of the property
114  value -- The value to set for the property
115  """
116 
117  # Private variables should be set directly:
118  if key[ 0 ] == '_':
119  return super( AnaAlgorithmConfig, self ).__setattr__( key, value )
120 
121  # Set the property, and remember its value:
122  super( AnaAlgorithmConfig,
123  self ).setPropertyFromString( key, stringPropValue( value ) )
124  self._props[ key ] = copy.deepcopy( value )
125  pass
126 
127  def __eq__( self, other ):
128  """Check for equality with another object
129 
130  The implementation of this is very simple. We only check that the type
131  and the name of the algorithms would match.
132  """
133 
134  # First check that the other object is also an AnaAlgorithmConfig one:
135  if not isinstance( other, AnaAlgorithmConfig ):
136  return False
137 
138  # Now check whether the type and the name of the algorithms agree:
139  return ( ( self.type() == other.type() ) and
140  ( self.name() == other.name() ) )
141 
142  def __ne__( self, other ):
143  """Check for an inequality with another object
144 
145  This is just defined to make the '!=' operator of Python behave
146  consistently with the '==' operator for such objects.
147  """
148  return not self.__eq__( other )
149 
150  def __str__( self ):
151  """Print the algorithm configuration in a user friendly way
152 
153  This is just to help with debugging configurations, allowing
154  the user to get a nice printout of their job configuration.
155  """
156 
157  name = ''
158  if self.isPublicTool():
159  name = 'Public Tool %s/%s' % ( self.type(), self.name() )
160  else:
161  name = 'Algorithm %s/%s' % ( self.type(), self.name() )
162  pass
163  result = AnaAlgorithmConfig._printHeader( name )
164  result += '\n'
165  for key, value in sorted( self._props.items() ):
166  if isinstance( value, str ):
167  printedValue = "'%s'" % value
168  else:
169  printedValue = value
170  pass
171  result += "|- %s: %s\n" % ( key, indentBy( printedValue, "| " ) )
172  pass
173  result += AnaAlgorithmConfig._printFooter( name )
174  return result
175 
176  def addPrivateTool( self, name, type ):
177  """Create a private tool for the algorithm
178 
179  This function is used in 'standalone' mode to declare a private tool
180  for the algorithm, or a private tool for an already declared private
181  tool.
182 
183  Can be used like:
184  config.addPrivateTool( 'tool1', 'ToolType1' )
185  config.addPrivateTool( 'tool1.tool2', 'ToolType2' )
186 
187  Keyword arguments:
188  name -- The full name of the private tool
189  type -- The C++ type of the private tool
190  """
191 
192  # And now set up the Python object that will take care of setting
193  # properties on this tool.
194 
195  # Tokenize the tool's name. In case it is a subtool of a tool, or
196  # something possibly even deeper.
197  toolNames = name.split( '.' )
198 
199  # Look up the component that we need to set up the private tool on.
200  component = self
201  for tname in toolNames[ 0 : -1 ]:
202  component = getattr( component, tname )
203  pass
204 
205  # Check that the component doesn't have such a (tool) property yet.
206  if hasattr( component, toolNames[ -1 ] ):
207  raise RuntimeError( "Tool with name '%s' already exists" % name )
208  pass
209 
210  # Now set up a smart object as a property on that component.
211  component._props[ toolNames[ -1 ] ] = PrivateToolConfig( self, name,
212  type )
213 
214  # Finally, tell the C++ code what to do.
215  self.createPrivateTool( name, type ).ignore()
216 
217  pass
218 
219  def addPrivateToolInArray( self, name, type ):
220  """Create a private tool in an array for the algorithm
221 
222  This function is used in 'standalone' mode to declare a
223  private tool in a tool array for the algorithm, or a private
224  tool in a tool array for an already declared private tool.
225 
226  Can be used like:
227  tool = config.addPrivateToolInArray( 'tool1', 'ToolType1' )
228  tool = config.addPrivateToolInArray( 'tool1.tool2', 'ToolType2' )
229 
230  Keyword arguments:
231  name -- The full name of the private tool
232  type -- The C++ type of the private tool
233  """
234 
235  # And now set up the Python object that will take care of setting
236  # properties on this tool.
237 
238  # Tokenize the tool's name. In case it is a subtool of a tool, or
239  # something possibly even deeper.
240  toolNames = name.split( '.' )
241 
242  # Look up the component that we need to set up the private tool on.
243  component = self
244  for tname in toolNames[ 0 : -1 ]:
245  component = getattr( component, tname )
246  pass
247 
248  # Finally, tell the C++ code what to do.
249  actualName = self.createPrivateToolInArray( name, type )
250 
251  # Tokenize the actual tool's name. In case it is a subtool of
252  # a tool, or something possibly even deeper.
253  actualToolNames = actualName.split( '.' )
254 
255  # Now set up a smart object as a property on that component.
256  config = PrivateToolConfig( self, actualName, type )
257  component._props[ actualToolNames[ -1 ] ] = config
258  return config
259 
260  @staticmethod
261  def _printHeader( title ):
262  """Produce a nice header when printing the configuration
263 
264  This function is used for printing the header of both algorithms
265  and tools.
266 
267  Keyword arguments:
268  indentString -- String used as indentation
269  title -- The title of the algorithm/tool
270  """
271 
272  preLength = AnaAlgorithmConfig.printHeaderPre
273  postLength = AnaAlgorithmConfig.printHeaderWidth - 3 - preLength - \
274  len( title )
275  return '/%s %s %s' % ( preLength * '*', title, postLength * '*' )
276 
277  @staticmethod
278  def _printFooter( title ):
279  """Produce a nice footer when printing the configuration
280 
281  This function is used for printing the footer of both algorithms
282  and tools.
283 
284  Keyword arguments:
285  indentString -- String used as indentation
286  title -- The title of the algorithm/tool
287  """
288 
289  preLength = AnaAlgorithmConfig.printHeaderPre
290  postLength = AnaAlgorithmConfig.printHeaderWidth - 12 - preLength - \
291  len( title )
292  return '\\%s (End of %s) %s' % ( preLength * '-', title,
293  postLength * '-' )
294 
295  pass
296 
297 
299  """Standalone Private Tool Configuration
300 
301  This class is used to mimic the behaviour of Athena tool configurable
302  classes. To be able to set the properties of private tools used by
303  dual-use algorithms in a way that's valid for both Athena and EventLoop.
304  """
305 
306  def __init__( self, algorithm, prefix, type ):
307  """Constructor for an private tool configuration object
308  """
309 
310  self._algorithm = algorithm
311  self._prefix = prefix
312  self._type = type
313  self._props = {}
314 
315  pass
316 
317  def __getattr__( self, name ):
318  """Get a previously set property value from the configuration
319 
320  This function allows us to retrieve the value of a tool property that
321  was already set for an algorithm's private tool, to possibly use it in
322  some configuration decisions in the Python code itself.
323 
324  Keyword arguments:
325  name -- The name of the property
326  """
327 
328  # Fail if the property was not (yet) set:
329  if not name in self._props:
330  raise AttributeError( 'Property "%s" was not set on "%s/%s.%s"' %
331  ( name, self._algorithm.type(),
332  self._algorithm.name(), self._prefix ) )
333 
334  # Return the property value:
335  return self._props[ name ]
336 
337  def __setattr__( self, key, value ):
338  """Set a tool property on an existing configuration object
339 
340  This function allows us to set/override properties on a private tool
341  of an algorithm configuration object. Allowing for the following syntax:
342 
343  alg = ...
344  alg.Tool.IntProperty = 66
345  alg.Tool.FloatProperty = 3.141592
346  alg.Tool.StringProperty = "Foo"
347 
348  Keyword arguments:
349  key -- The key/name of the property
350  value -- The value to set for the property
351  """
352 
353  # Private variables should be set directly:
354  if key[ 0 ] == '_':
355  return super( PrivateToolConfig, self ).__setattr__( key, value )
356 
357  # Construct the full name, used in the C++ code:
358  fullName = self._prefix + "." + key
359 
360  # Set the property, and remember its value:
361  self._algorithm.setPropertyFromString( fullName,
362  stringPropValue( value ) )
363  self._props[ key ] = copy.deepcopy( value )
364  pass
365 
366  def __str__( self ):
367  """Print the private tool configuration in a user friendly way
368 
369  This is just to help with debugging configurations, allowing
370  the user to get a nice printout of their job configuration.
371  """
372 
373  name = 'Private Tool %s/%s' % ( self._type, self._prefix )
374  result = ' \n'
375  result += AnaAlgorithmConfig._printHeader( name )
376  result += '\n'
377  for key, value in sorted( self._props.items() ):
378  if isinstance( value, str ):
379  printedValue = "'%s'" % value
380  else:
381  printedValue = value
382  pass
383  result += "|- %s: %s\n" % ( key, indentBy( printedValue, "| " ) )
384  pass
385  result += AnaAlgorithmConfig._printFooter( name )
386  return result
387 
388  pass
389 
390 
391 def stringPropValue( value ):
392  """Helper function producing a string property value"""
393 
394  stringValue = str( value )
395  if isinstance( value, bool ):
396  stringValue = str( int( value ) )
397  pass
398  return stringValue
399 
400 
401 def indentBy( propValue, indent ):
402  """Helper function used in the configuration printout"""
403 
404  stringValue = str( propValue )
405  result = ""
406  for stringLine in stringValue.split( '\n' ):
407  if len( result ):
408  result += "\n" + indent
409  pass
410  result += stringLine
411  pass
412  return result
413 
414 
415 #
416 # Declare some unit tests for the code
417 #
418 
419 
420 class TestAlgTypeAndName( unittest.TestCase ):
421 
422 
424  def test_singletypename( self ):
425  config1 = AnaAlgorithmConfig( "TypeName" )
426  self.assertEqual( config1.type(), "TypeName" )
427  self.assertEqual( config1.name(), "TypeName" )
428  config2 = AnaAlgorithmConfig( "NS::SomeType" )
429  self.assertEqual( config2.type(), "NS::SomeType" )
430  self.assertEqual( config2.name(), "NS::SomeType" )
431  pass
432 
433 
435  def test_typeandname( self ):
436  config1 = AnaAlgorithmConfig( "TypeName/InstanceName" )
437  self.assertEqual( config1.type(), "TypeName" )
438  self.assertEqual( config1.name(), "InstanceName" )
439  config2 = AnaAlgorithmConfig( "NS::SomeType/Instance" )
440  self.assertEqual( config2.type(), "NS::SomeType" )
441  self.assertEqual( config2.name(), "Instance" )
442  pass
443 
444 
445 class TestAlgProperties( unittest.TestCase ):
446 
447 
448  def setUp( self ):
449  self.config = AnaAlgorithmConfig( "Type/Name" )
450  pass
451 
452 
453  def test_propaccess( self ):
454  self.config.Prop1 = "Value1"
455  self.config.Prop2 = [ "Value2" ]
456  self.assertEqual( self.config.Prop1, "Value1" )
457  self.assertEqual( self.config.Prop2, [ "Value2" ] )
458  self.assertNotEqual( self.config.Prop1, "Foo" )
459  self.assertNotEqual( self.config.Prop2, "Value2" )
460  pass
461 
462 
463  def test_nonexistentprop( self ):
464  with self.assertRaises( AttributeError ):
465  value = self.config.Prop3
466  pass
467  pass
468 
469 
470 class TestAlgPrivateTool( unittest.TestCase ):
471 
472 
473  def setUp( self ):
474  self.config = AnaAlgorithmConfig( "AlgType/AlgName" )
475  pass
476 
477 
478  def test_privatetool( self ):
479  self.config.addPrivateTool( "Tool1", "ToolType1" )
480  self.config.Tool1.Prop1 = "Value1"
481  self.config.Tool1.Prop2 = [ 1, 2, 3 ]
482  self.assertEqual( self.config.Tool1.Prop1, "Value1" )
483  self.assertEqual( self.config.Tool1.Prop2, [ 1, 2, 3 ] )
484  pass
485 
486 
488  tool = self.config.addPrivateToolInArray( "Tool1", "ToolType1" )
489  tool.Prop1 = "Value1"
490  tool.Prop2 = [ 1, 2, 3 ]
491  self.assertEqual( tool.Prop1, "Value1" )
492  self.assertEqual( tool.Prop2, [ 1, 2, 3 ] )
493  pass
494 
495 
497  self.config.addPrivateTool( "Tool1", "ToolType1" )
498  self.config.addPrivateTool( "Tool1.Tool2", "ToolType2" )
499  self.config.Tool1.Tool2.Prop3 = "Foo"
500  self.config.Tool1.Tool2.Prop4 = [ "Bar" ]
501  self.assertEqual( self.config.Tool1.Tool2.Prop3, "Foo" )
502  self.assertEqual( self.config.Tool1.Tool2.Prop4, [ "Bar" ] )
503  pass
504 
505 
506  def test_nonexistentprop( self ):
507  self.config.addPrivateTool( "Tool1", "ToolType1" )
508  with self.assertRaises( AttributeError ):
509  value = self.config.Tool1.BadProp
510  pass
511  self.config.addPrivateTool( "Tool1.Tool2", "ToolType2" )
512  with self.assertRaises( AttributeError ):
513  value = self.config.Tool1.Tool2.BadProp
514  pass
515  pass
516 
517 
518  def test_nonexistenttool( self ):
519  with self.assertRaises( AttributeError ):
520  self.config.addPrivateTool( "BadTool.Tool4", "BadToolType" )
521  pass
522  pass
python.AnaAlgorithmConfig.PrivateToolConfig.__init__
def __init__(self, algorithm, prefix, type)
Definition: AnaAlgorithmConfig.py:306
python.AnaAlgorithmConfig.TestAlgPrivateTool
Test case for using private tools.
Definition: AnaAlgorithmConfig.py:470
python.AnaAlgorithmConfig.TestAlgPrivateTool.test_nonexistenttool
def test_nonexistenttool(self)
Test that private tools can't be set up on not-yet-declared tools.
Definition: AnaAlgorithmConfig.py:518
python.AnaAlgorithmConfig.TestAlgProperties
Test case for the algorithm property handling.
Definition: AnaAlgorithmConfig.py:445
python.AnaAlgorithmConfig.PrivateToolConfig
Definition: AnaAlgorithmConfig.py:298
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.DualUseConfig.addPrivateTool
def addPrivateTool(alg, toolName, typeName)
Definition: DualUseConfig.py:180
python.AnaAlgorithmConfig.PrivateToolConfig._props
_props
Definition: AnaAlgorithmConfig.py:313
python.AnaAlgorithmConfig.TestAlgProperties.test_nonexistentprop
def test_nonexistentprop(self)
Test that an unset property can't be accessed.
Definition: AnaAlgorithmConfig.py:463
python.AnaAlgorithmConfig.AnaAlgorithmConfig.__str__
def __str__(self)
Definition: AnaAlgorithmConfig.py:150
python.AnaAlgorithmConfig.AnaAlgorithmConfig.addPrivateToolInArray
def addPrivateToolInArray(self, name, type)
Definition: AnaAlgorithmConfig.py:219
python.AnaAlgorithmConfig.PrivateToolConfig.__setattr__
def __setattr__(self, key, value)
Definition: AnaAlgorithmConfig.py:337
python.AnaAlgorithmConfig.PrivateToolConfig._algorithm
_algorithm
Definition: AnaAlgorithmConfig.py:310
python.AnaAlgorithmConfig.AnaAlgorithmConfig.addPrivateTool
def addPrivateTool(self, name, type)
Definition: AnaAlgorithmConfig.py:176
python.AnaAlgorithmConfig.AnaAlgorithmConfig.getType
def getType(self)
Definition: AnaAlgorithmConfig.py:73
python.AnaAlgorithmConfig.AnaAlgorithmConfig.__setattr__
def __setattr__(self, key, value)
Definition: AnaAlgorithmConfig.py:101
python.AnaAlgorithmConfig.TestAlgProperties.test_propaccess
def test_propaccess(self)
Test that properties that got set, can be read back.
Definition: AnaAlgorithmConfig.py:453
python.AnaAlgorithmConfig.AnaAlgorithmConfig.__init__
def __init__(self, typeAndName, **kwargs)
Definition: AnaAlgorithmConfig.py:36
python.AnaAlgorithmConfig.AnaAlgorithmConfig.__getattr__
def __getattr__(self, name)
Definition: AnaAlgorithmConfig.py:82
python.DualUseConfig.addPrivateToolInArray
def addPrivateToolInArray(alg, toolName, typeName)
Definition: DualUseConfig.py:229
python.AnaAlgorithmConfig.TestAlgPrivateTool.setUp
def setUp(self)
Set up the main algorithm object to test.
Definition: AnaAlgorithmConfig.py:473
python.AnaAlgorithmConfig.AnaAlgorithmConfig.__eq__
def __eq__(self, other)
Definition: AnaAlgorithmConfig.py:127
DiTauMassTools::ignore
void ignore(T &&)
Definition: PhysicsAnalysis/TauID/DiTauMassTools/DiTauMassTools/HelperFunctions.h:54
python.AnaAlgorithmConfig.TestAlgPrivateTool.test_nonexistentprop
def test_nonexistentprop(self)
Test that unset properties on the tools can't be used.
Definition: AnaAlgorithmConfig.py:506
python.AnaAlgorithmConfig.TestAlgPrivateTool.test_privatetool
def test_privatetool(self)
Test setting up and using one private tool.
Definition: AnaAlgorithmConfig.py:478
python.AnaAlgorithmConfig.AnaAlgorithmConfig._printFooter
def _printFooter(title)
Definition: AnaAlgorithmConfig.py:278
python.AnaAlgorithmConfig.TestAlgTypeAndName.test_typeandname
def test_typeandname(self)
Test that specifying the type and name separately in the same string works as expected.
Definition: AnaAlgorithmConfig.py:435
python.AnaAlgorithmConfig.indentBy
def indentBy(propValue, indent)
Definition: AnaAlgorithmConfig.py:401
python.AnaAlgorithmConfig.PrivateToolConfig._prefix
_prefix
Definition: AnaAlgorithmConfig.py:311
python.AnaAlgorithmConfig.TestAlgTypeAndName.test_singletypename
def test_singletypename(self)
Test that the type and name are set correctly when using a single argument.
Definition: AnaAlgorithmConfig.py:424
python.AnaAlgorithmConfig.TestAlgPrivateTool.test_privatetoolarray
def test_privatetoolarray(self)
Test setting up and using one private tool.
Definition: AnaAlgorithmConfig.py:487
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.
python.AnaAlgorithmConfig.PrivateToolConfig.__str__
def __str__(self)
Definition: AnaAlgorithmConfig.py:366
python.AnaAlgorithmConfig.AnaAlgorithmConfig._props
_props
Definition: AnaAlgorithmConfig.py:54
name
std::string name
Definition: Control/AthContainers/Root/debug.cxx:195
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.AnaAlgorithmConfig.PrivateToolConfig.__getattr__
def __getattr__(self, name)
Definition: AnaAlgorithmConfig.py:317
python.AnaAlgorithmConfig.AnaAlgorithmConfig._printHeader
def _printHeader(title)
Definition: AnaAlgorithmConfig.py:261
python.AnaAlgorithmConfig.AnaAlgorithmConfig
Definition: AnaAlgorithmConfig.py:8
python.AnaAlgorithmConfig.TestAlgTypeAndName
Test case for the algorithm type/name handling.
Definition: AnaAlgorithmConfig.py:420
python.AnaAlgorithmConfig.TestAlgPrivateTool.config
config
Definition: AnaAlgorithmConfig.py:474
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78
python.AnaAlgorithmConfig.AnaAlgorithmConfig.__ne__
def __ne__(self, other)
Definition: AnaAlgorithmConfig.py:142
pickleTool.object
object
Definition: pickleTool.py:30
str
Definition: BTagTrackIpAccessor.cxx:11
python.AnaAlgorithmConfig.TestAlgProperties.config
config
Definition: AnaAlgorithmConfig.py:449
python.AnaAlgorithmConfig.stringPropValue
def stringPropValue(value)
Definition: AnaAlgorithmConfig.py:391
python.AnaAlgorithmConfig.PrivateToolConfig._type
_type
Definition: AnaAlgorithmConfig.py:312
python.AnaAlgorithmConfig.TestAlgPrivateTool.test_privatetoolofprivatetool
def test_privatetoolofprivatetool(self)
Test setting up and using a private tool of a private tool.
Definition: AnaAlgorithmConfig.py:496
python.AnaAlgorithmConfig.TestAlgProperties.setUp
def setUp(self)
Common setup for the tests.
Definition: AnaAlgorithmConfig.py:448
python.AnaAlgorithmConfig.AnaAlgorithmConfig.getName
def getName(self)
Definition: AnaAlgorithmConfig.py:64