Commit 51b75b25 authored by Nils Krumnack's avatar Nils Krumnack
Browse files

introduce PythonConfig and EL::PythonConfigBase

The goal here is to introduce a single python configurable in
AnalysisBase, instead of the current scheme in which there is a
separate configurable for AnaAlgorithm, AsgTool, etc. and essentially
duplicating a lot of code.

For now this duplicates even more code.  Once this is all tested and
validated, the old code will eitehr be completely removed or made to
inherit from the new classes.
parent 697e7375
......@@ -45,6 +45,15 @@ namespace asg
explicit AsgServiceConfig (const std::string& val_typeAndName);
/// \brief initializing constructor
/// \par Guarantee
/// strong
/// \par Failures
/// out of memory II
public:
explicit AsgServiceConfig (const AsgComponentConfig& val_config);
/// \brief Virtual destructor, to make PyROOT happy
///
/// Without it ROOT 6.22+ does not allow Python classes to inherit from this
......
......@@ -25,4 +25,11 @@ namespace asg
AsgServiceConfig (const std::string& val_typeAndName)
: AsgComponentConfig (val_typeAndName)
{}
AsgServiceConfig ::
AsgServiceConfig (const AsgComponentConfig& val_config)
: AsgComponentConfig (val_config)
{}
}
......@@ -42,6 +42,15 @@ namespace asg
explicit AsgToolConfig (const std::string& val_typeAndName);
/// \brief initializing constructor
/// \par Guarantee
/// strong
/// \par Failures
/// out of memory II
public:
explicit AsgToolConfig (const AsgComponentConfig& val_config);
/// \brief make a tool with the given configuration
///
/// Note that the exact creational patterns are not determined yet
......
......@@ -25,4 +25,11 @@ namespace asg
AsgToolConfig (const std::string& val_typeAndName)
: AsgComponentConfig (val_typeAndName)
{}
AsgToolConfig ::
AsgToolConfig (const AsgComponentConfig& val_config)
: AsgComponentConfig (val_config)
{}
}
......@@ -54,6 +54,24 @@ namespace EL
explicit AnaAlgorithmConfig (const std::string& val_typeAndName);
/// \brief initializing constructor
/// \par Guarantee
/// strong
/// \par Failures
/// out of memory II
public:
explicit AnaAlgorithmConfig (const AsgComponentConfig& val_config);
/// \brief Virtual destructor, to make PyROOT happy
///
/// Without it ROOT 6.22+ does not allow Python classes to inherit from this
/// type.
///
public:
virtual ~AnaAlgorithmConfig() = default;
/// \brief whether we use XAODs
/// \par Guarantee
/// no-fail
......
......@@ -12,6 +12,7 @@
#include "AnaAlgorithm/AnaAlgorithmWrapper.h"
#include "AnaAlgorithm/AnaReentrantAlgorithmConfig.h"
#include "AnaAlgorithm/AnaReentrantAlgorithmWrapper.h"
#include "AnaAlgorithm/PythonConfigBase.h"
#endif
#endif
......@@ -54,6 +54,15 @@ namespace EL
explicit AnaReentrantAlgorithmConfig (const std::string& val_typeAndName);
/// \brief initializing constructor
/// \par Guarantee
/// strong
/// \par Failures
/// out of memory II
public:
explicit AnaReentrantAlgorithmConfig (const AsgComponentConfig& val_config);
/// \brief Virtual destructor, to make PyROOT happy
///
/// Without it ROOT 6.22+ does not allow Python classes to inherit from this
......
/*
Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
*/
/// @author Nils Krumnack
#ifndef ANA_ALGORITHM__PYTHON_CONFIG_BASE_H
#define ANA_ALGORITHM__PYTHON_CONFIG_BASE_H
#ifndef ROOTCORE
#error only include this header in AnalysisBase
#endif
#include <AnaAlgorithm/Global.h>
#include <AsgTools/AsgComponentConfig.h>
namespace EL
{
struct AlgorithmWorkerData;
/// \brief the base class for the python configuration of any \ref
/// asg::AsgComponent
///
/// Most of the functionality is provided by \ref
/// asg::AsgComponentConfig, with this class mostly adding one extra
/// member that allows to the the component type. Another benefit
/// of this class is that this is its own type, meaning overload
/// resolution can tell that an object got set from python.
class PythonConfigBase : public asg::AsgComponentConfig
{
//
// public interface
//
/// \brief standard constructor
/// \par Guarantee
/// strong
/// \par Failures
/// out of memory I
public:
PythonConfigBase ();
/// \brief initializing constructor
/// \par Guarantee
/// strong
/// \par Failures
/// out of memory II
public:
explicit PythonConfigBase (const std::string& val_typeAndName);
/// \brief Virtual destructor, to make PyROOT happy
///
/// Without it ROOT 6.22+ does not allow Python classes to inherit from this
/// type.
///
public:
virtual ~PythonConfigBase () = default;
/// \brief the actual component type to create
/// \par Guarantee
/// no-fail / strong
/// \par Failures
/// out of memory II [2]
/// \{
public:
const std::string& componentType () const noexcept;
void setComponentType (const std::string& val_componentType);
/// \}
//
// private interface
//
/// \brief the value of \ref componentType
private:
std::string m_componentType;
};
}
#endif
......@@ -7,5 +7,6 @@
<class name="EL::AnaReentrantAlgorithmConfig" />
<class name="EL::AnaReentrantAlgorithmWrapper" />
<class name="EL::IAlgorithmWrapper" />
<class name="EL::PythonConfigBase" />
</lcgdict>
......@@ -49,6 +49,15 @@ namespace EL
AnaAlgorithmConfig ::
AnaAlgorithmConfig (const AsgComponentConfig& val_config)
: AsgComponentConfig (val_config)
{
RCU_NEW_INVARIANT (this);
}
bool AnaAlgorithmConfig ::
useXAODs () const noexcept
{
......
......@@ -48,6 +48,15 @@ namespace EL
AnaReentrantAlgorithmConfig ::
AnaReentrantAlgorithmConfig (const AsgComponentConfig& val_config)
: AsgComponentConfig (val_config)
{
RCU_NEW_INVARIANT (this);
}
::StatusCode AnaReentrantAlgorithmConfig ::
makeAlgorithm (std::unique_ptr<AnaReentrantAlgorithm>& algorithm,
const AlgorithmWorkerData& workerData) const
......
/*
Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
*/
/// @author Nils Krumnack
//
// includes
//
#include <AnaAlgorithm/PythonConfigBase.h>
//
// method implementations
//
namespace EL
{
PythonConfigBase ::
PythonConfigBase ()
{}
PythonConfigBase ::
PythonConfigBase (const std::string& val_typeAndName)
: AsgComponentConfig (val_typeAndName)
{}
const std::string& PythonConfigBase ::
componentType () const noexcept
{
return m_componentType;
}
void PythonConfigBase ::
setComponentType (const std::string& val_componentType)
{
m_componentType = val_componentType;
}
}
......@@ -10,6 +10,7 @@ except ImportError:
# Import(s):
import unittest
from AnaAlgorithm.AnaAlgorithmConfig import AnaAlgorithmConfig, indentBy
from AnaAlgorithm.PythonConfig import PythonConfig
class AlgSequence( object ):
"""Standalone algorithm sequence
......@@ -135,9 +136,10 @@ except ImportError:
# Check that the received object is of the right type:
if not isinstance( algOrSeq, AnaAlgorithmConfig ) and \
not isinstance( algOrSeq, PythonConfig ) and \
not isinstance( algOrSeq, AlgSequence ):
raise TypeError( 'The received object is not of type ' \
'AnaAlgorithmConfig or AlgSequence' )
'AnaAlgorithmConfig or PythonConfig or AlgSequence' )
pass
# Now check if an equivalent algorithm/sequence is already in the
......
# Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
def createAlgorithm( typeName, instanceName ):
"""Create an algorithm configurable
def createComponent( typeName, instanceName, componentType ):
"""Create a generic configurable
This function is used to create an algorithm "configurable" in a dual-use
way, either returning an actual Athena configurable, or an appropriately
configured EL::AnaAlgorithmConfig instance.
This function is used to create an component "configurable" in a
dual-use way, either returning an actual Athena configurable, or
an appropriately configured PythonConfig instance.
Keyword arguments:
typeName -- The C++ type name of the algorithm
instanceName -- The instance name of the algorithm to create
typeName -- The C++ type name of the component
instanceName -- The instance name of the component to create
componentType -- The type of component in AnalysisBase
"""
try:
......@@ -20,23 +22,53 @@ def createAlgorithm( typeName, instanceName ):
# '::' namespace delimeters with '__'.
pythonTypeName = typeName.replace( '::', '__' )
# Now look up the Athena configurable of this algorithm:
# Now look up the Athena configurable of this component:
from AthenaCommon import CfgMgr
algClass = getattr( CfgMgr, pythonTypeName )
componentClass = getattr( CfgMgr, pythonTypeName )
# Return the object:
return algClass( instanceName )
return componentClass( instanceName )
except ImportError:
# If that didn't work, then apparently we're in an EventLoop
# environment, so we need to use AnaAlgorithmConfig as the base class
# environment, so we need to use PythonConfig as the base class
# for the user's class.
from AnaAlgorithm.AnaAlgorithmConfig import AnaAlgorithmConfig
return AnaAlgorithmConfig( '%s/%s' % ( typeName, instanceName ) )
from AnaAlgorithm.PythonConfig import PythonConfig
component = PythonConfig( '%s/%s' % ( typeName, instanceName ) )
component.setComponentType( componentType )
return component
pass
def createAlgorithm( typeName, instanceName ):
"""Create an algorithm configurable
This function is used to create an algorithm "configurable" in a dual-use
way, either returning an actual Athena configurable, or an appropriately
configured EL::AnaAlgorithmConfig instance.
Keyword arguments:
typeName -- The C++ type name of the algorithm
instanceName -- The instance name of the algorithm to create
"""
return createComponent( typeName, instanceName, 'AnaAlgorithm' )
def createReentrantAlgorithm( typeName, instanceName ):
"""Create a reentrant algorithm configurable
This function is used to create an algorithm "configurable" in a dual-use
way, either returning an actual Athena configurable, or an appropriately
configured EL::AnaAlgorithmConfig instance.
Keyword arguments:
typeName -- The C++ type name of the algorithm
instanceName -- The instance name of the algorithm to create
"""
return createComponent( typeName, instanceName, 'AnaReentrantAlgorithm' )
def createPublicTool( typeName, toolName ):
"""Helper function for setting up a public tool for a dual-use algorithm
......@@ -75,10 +107,48 @@ def createPublicTool( typeName, toolName ):
except ImportError:
# If that didn't work, then apparently we're in an EventLoop
# environment, so let's use the EventLoop specific formalism.
from AnaAlgorithm.AnaAlgorithmConfig import AnaAlgorithmConfig
tool = AnaAlgorithmConfig( '%s/%s' % ( typeName, toolName ) )
tool.setIsPublicTool( True )
return tool
return createComponent( typeName, toolName, 'AsgTool' )
def createService( typeName, serviceName ):
"""Helper function for setting up a service for a dual-use algorithm
This function is meant to be used in the analysis algorithm sequence
configurations for setting up services on the analysis algorithms.
Services that could then be configured with a syntax shared between
Athena and EventLoop.
Keyword arguments:
typeName -- The C++ type name of the service
serviceName -- The name with which the service handle was configured on
the algorithm. Also the instance name of the service.
"""
try:
# Try to set up a public tool of this type for Athena. If this succeeds,
# we're obviously in an Athena environment.
# First off, construct a "python type name" for the class, replacing the
# '::' namespace delimeters with '__'.
pythonTypeName = typeName.replace( '::', '__' )
# Now look up the Athena configurable of this tool:
from AthenaCommon import CfgMgr
serviceClass = getattr( CfgMgr, pythonTypeName )
# Add an instance of the service to the ServiceMgr:
from AthenaCommon.AppMgr import ServiceMgr
if not hasattr( ServiceMgr, serviceName ):
ServiceMgr += serviceClass( serviceName )
pass
# Return the member on the ServiceMgr:
return getattr( ServiceMgr, serviceName )
except ImportError:
# If that didn't work, then apparently we're in an EventLoop
# environment, so let's use the EventLoop specific formalism.
return createComponent( typeName, serviceName, 'AsgService' )
def addPrivateTool( alg, toolName, typeName ):
......
# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
# Import(s):
import ROOT
import unittest
import copy
class PythonConfig( ROOT.EL.PythonConfigBase ):
"""Standalone Analysis Component Configuration
This class is used to describe the configuration of an analysis component
(a C++ class inheriting from asg::AsgComponentConfig) in Python. It behaves
similar to an Athena configurable, but is implemented in a much simpler
way.
An example of using it in configuring an EventLoop job could look like:
job = ROOT.EL.Job()
...
from AnaAlgorithm.PythonConfig import PythonConfig
alg = PythonConfig( "EL::UnitTestAlg2/TestAlg",
property = 1.23 )
alg.setComponentType( "AnaAlgorithm" )
alg.string_property = "Foo"
job.algsAdd( alg )
Note that the python code doesn't know what properties can actually be set
on any given C++ algorithm. Any mistake made in the Python configuration
(apart from syntax errors) is only discovered while initialising the
analysis job.
"""
# Class/static variable(s):
printHeaderWidth = 80
printHeaderPre = 3
def __init__( self, typeAndName, **kwargs ):
"""Constructor for an algorithm configuration object
Keyword arguments:
typeAndName -- The type/instance name of the algorithm
Note that you can pass (initial) properties to the constructor like:
alg = PythonConfig( "EL::UnitTestAlg2/TestAlg",
property = 1.23 )
"""
# Call the base class's constructor. Use the default constructor instead
# of the one receiving the type and name, to avoid ROOT-10872.
super( PythonConfig, self ).__init__()
self.setTypeAndName( typeAndName )
# Initialise the properties of the algorihm:
self._props = {}
# Set the properties on the object:
for key, value in kwargs.items():
self.setPropertyFromString( key, stringPropValue( value ) )
self._props[ key ] = copy.deepcopy( value )
pass
pass
def getName( self ):
"""Get the instance name of the algorithm
This is for compatibility with the getName() function of Athena
configurables.
"""
return self.name()
def getType( self ):
"""Get the type name of the algorithm
This is for compatibility with the getType() function of Athena
configurables.
"""
return self.type()
def __getattr__( self, name ):
"""Get a previously set property value from the configuration
This function allows us to retrieve the value of a property that was
already set for the algorithm, to possibly use it in some configuration
decisions in the Python code itself.
Keyword arguments:
name -- The name of the property
"""
# Fail if the property was not (yet) set:
if not name in self._props:
raise AttributeError( 'Property \'%s\' was not set on \'%s/%s\'' %
( name, self.type(), self.name() ) )
# Return the property value:
return self._props[ name ]
def __setattr__( self, key, value ):
"""Set an algorithm property on an existing configuration object
This function allows us to set/override properties on an algorithm
configuration object. Allowing for the following syntax:
alg = ...
alg.IntProperty = 66
alg.FloatProperty = 3.141592
alg.StringProperty = "Foo"
Keyword arguments:
key -- The key/name of the property
value -- The value to set for the property
"""
# Private variables should be set directly:
if key[ 0 ] == '_':
return super( PythonConfig, self ).__setattr__( key, value )
# Set the property, and remember its value:
super( PythonConfig,
self ).setPropertyFromString( key, stringPropValue( value ) )
self._props[ key ] = copy.deepcopy( value )
pass
def __eq__( self, other ):
"""Check for equality with another object
The implementation of this is very simple. We only check that the type
and the name of the algorithms would match.
"""
# First check that the other object is also an PythonConfig one:
if not isinstance( other, PythonConfig ):
return False
# Now check whether the type and the name of the algorithms agree:
return ( ( self.type() == other.type() ) and
( self.name() == other.name() ) )
def __ne__( self, other ):
"""Check for an inequality with another object
This is just defined to make the '!=' operator of Python behave
consistently with the '==' operator for such objects.
"""
return not self.__eq__( other )
def __str__( self ):
"""Print the algorithm configuration in a user friendly way