As mentioned previously the framework makes use of the inheritance mechanism for specialising the Algorithm component. In other words, a concrete algorithm class must inherit from ("be derived from" in C++ parlance, "extend" in Java) the Algorithm base class.
In this chapter we first look at the base class itself. We then discuss what is involved in creating concrete algorithms: specifically how to declare properties, what to put into the methods of the IAlgorithm interface and the use of private objects. Finally we look at how to nest algorithms.
Since a concrete algorithm object is-an Algorithm object it may use all of the public methods of the Algorithm base class. The base class has no protected methods or data members, and no public data members, so in fact, these are the only methods that are available. Most of these methods are in fact provided solely to make the implementation of derived algorithms easier. The base class has two main responsibilities: the initialisation of certain internal pointers and the management of the properties of derived algorithm classes.
A part of the Algorithm base class definition is shown in
Listing 5
. Include directives, forward declarations and private member variables have all been suppressed. It declares a constructor and destructor; the three key methods of the
IAlgorithm interface; several accessors to services that a concrete algorithm will almost certainly require; a method to create a sub algorithm, the two methods of the
IProperty
interface; and a whole series of methods for declaring properties.
In order for an algorithm object to do anything useful it must be specialised, i.e. it must extend (inherit from, be derived from) the
Algorithm
base class. In general it will be necessary to implement the methods of the
IAlgorithm
interface, and declare the algorithm's properties to the property management machinery of the
Algorithm
base class. Additionally there is one non-obvious technical matter to cover, namely algorithm factories.
As mentioned before, a concrete algorithm class must specify a single constructor with the same parameter signature as the constructor of the base class.
In addition to this a concrete algorithm factory must be provided. This is a technical matter which permits the application manager to create new algorithm objects without having to include all of the concrete algorithm header files. From the point of view of an algorithm developer it implies adding two lines into the implementation file, of the form:
static const AlgFactory<ConcreteAlgorithm> s_factory; const IAlgFactory& ConcreteAlgorithmFactory = s_factory; |
where "ConcreteAlgorithm" should be replaced by the name of the derived algorithm class (see for example lines 10 and 11 in Listing 6 below).
In general a concrete algorithm class will have several data members which are used in the execution of the algorithm proper. These data members should of course be initialised in the constructor, but if this was the only mechanism available to set their value it would be necessary to recompile the code every time you wanted to run with different settings. In order to avoid this, the framework provides a mechanism for setting the values of member variables at run time.
The mechanism comes in two parts: the declaration of properties and the setting of their values. As an example consider the class TriggerDecision in Listing 6 which has a number of variables whose value we would like to set at run time.
The default values for the variables are set within the constructor (within an initialiser list) as per normal. To declare them as properties it suffices to call the declareProperty() method. This method is overloaded to take an std::string as the first parameter and a variety of different types for the second parameter. The first parameter is the name by which this member variable shall be referred to, and the second parameter is a reference to the member variable itself.
In the example we associate the name "PassAllMode" to the member variable
m_passAllMode
, and the name "MuonCandidateCut" to m_muonCandidateCut. The first is of type boolean and the second an integer. If the job options service (described in
Chapter 11
) finds an option in the job options file belonging to this algorithm and whose name matches one of the names associated with a member variable, then that member variable will be set to the value specified in the job options file.
In order to implement
IAlgorithm
(in a useful way) you must override at least one, and in general all three, of its key methods. For a top level algorithm, i.e. one controlled directly by the application manager, the methods are invoked as is described in section
4.6
. This dictates what it is useful to put into each of the methods.
|
The framework takes care of setting up internal references to standard services. The algorithm properties are set before the initialize() method is called by calling the method Algorithm::setProperties(). This makes a call to one of the methods of the job options service passing a reference to itself as a parameter. The job options service then makes repeated calls to the setProperty() method of the algorithm which actually assigns values to the member variables.
The initialize() method can be used to do such things as creating histograms, or creating sub-algorithms if required (see section 5.4 ).
If an algorithm fails to initialise it should return StatusCode::FAILURE. This will cause the job to terminate.
The guts of the algorithm class is in the execute() method. For top level algorithms this will be called once per event for each algorithm object in the order in which they were declared to the application manager. For sub-algorithms (see section 5.4 ) the control flow may be as you like: you may call the execute() method once, many times or not at all.
Just because an algorithm derives from the
Algorithm
base class does not mean that it is limited to using or overriding only the methods defined by the base class. In general, your code will be much better structured (i.e. understandable, maintainable, etc.) if you do not, for example, implement the execute() method as a single block of 100 lines, but instead define your own utility methods and classes to better structure the code.
If an algorithm fails in some manner, e.g. a fit fails to converge, or its data is nonsense it should return from the execute() method with StatusCode::FAILURE. This will cause the application manager to skip the rest of the processing for that event. If the application manager detects that an algorithm is failing all the time, (or more often than some defined rate) it may decide to abort the job. In this case it will not read further events but instead go to the finalisation step.
The following is a list of things to check when implementing an algorithm. You may choose to not do one or more of these, but if so you should understand the consequences.
Algorithm
base class.The application manager is responsible for initialising, executing once per event, and finalising the set of top level algorithms, i.e. the set of algorithms specified in the job options file. However such a simple linear structure is very limiting. You may wish to execute some algorithms only for specific types of event, or you may wish to "loop" over an algorithm's execute method. Within the LHCb application framework the way to have such control is via the nesting of algorithms. A nested (or sub-) algorithm is one which is created by, and thus belongs to and is controlled by, another algorithm (its parent) as opposed to the application manager. In this section we discuss a number of points which are specific to sub-algorithms.
In the first place, the parent algorithm will need a member variable of type Algorithm* (see the code fragment below) in which to store a pointer to the sub-algorithm.
The sub-algorithm itself is created by invoking the createSubAlgorithm() method of the
Algorithm
base class. The parameters passed are the type of the algorithm, its name and a reference to the pointer which will be set to point to the newly created sub-algorithm. Note that the name passed into the createSubAlgorithm() method is the same name that should be used within the job options file for specifying algorithm properties.
The algorithm type (i.e. class name) string is used by the application manager to decide which factory should create the algorithm object.
The execution of the sub-algorithm is entirely the responsibility of the parent algorithm whereas the initialize() and finalize() methods are invoked automatically by the framework as shown in Figure 5 . Similarly the properties of a sub-algorithm are also automatically set by the framework.
Note that the createSubAlgorithm() method returns a pointer to an
Algorithm
object not an IAlgorithm interface. This means that you have access to the methods of both the IAlgorithm and IProperty interfaces, and consequently as well as being able to call execute() etc. you can also explicitly call the setProperty(Property&) method of the sub-algorithm, as is done in the following code fragment. For this reason with nested algorithms you are not restricted to calling setProperty() only at initialisation. You may also change the properties of a sub-algorithm during the main event loop.
Algorithm *m_pSubAlgorithm; sc = createSubAlgorithm(type, name, Algorithm*& m_pSubAlgorithm); IntegerProperty p("Counter", 1024); m_pSubAlgorithm->setProperty(p); |
The Algorithm base class provides a number of accessor methods for the standard framework services. It is possible for an algorithm to make a request to the application manager if it requires something more specialised. This is done via the ISvcLocator interface, as shown, for example, in section 11.4.1 for the Particle Properties service.