GAUDI User Guide

Chapter 5
Writing algorithms

5.1  Overview

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.

5.2   Algorithm base class

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.

Listing 5 The definition of the Algorithm base class.

1: class Algorithm : virtual public IAlgorithm,
2: virtual public IProperty {
3: public:
4: // Constructor and destructor
5: Algorithm( const std::string& name, ISvcLocator *svcloc );
6: virtual ~Algorithm();
7:
8: StatusCode sysInitialize();
9: virtual StatusCode initialize();
10: virtual StatusCode execute();
11: StatusCode sysFinalize();
12: virtual StatusCode finalize();
13: const std::string&  name() const;
14:
15: IMessageSvc*   messageService();
16: IDataProviderSvc*   eventDataService();
17: IConversionSvc*     eventDataCnvService();
18: IDataProviderSvc*   detDataService();
19: IConversionSvc*     detDataCnvService();
20: IHistogramSvc*      histogramDataService();
21: INtupleSvc*         ntupleService();
22: ISvcLocator*    serviceLocator();
23: void setOutputLevel( int level );
24:
25: StatusCode createSubAlgorithm( const std::string& type,
26: const std::string& name, Algorithm*& pSubAlg );
27:
28: virtual StatusCode setProperty(const Property& p);
29: virtual StatusCode getProperty(Property* p) const;
30: StatusCode setProperties();
31:
32: StatusCode declareProperty(const std::string& name, int& reference);
33: StatusCode declareProperty(const std::string& name, float& reference);
34: StatusCode declareProperty(const std::string& name, double& reference);
35: StatusCode declareProperty(const std::string& name, bool& reference); 
36: StatusCode declareProperty(const std::string& name,                  
37:                             std::string& reference);
38: StatusCode declareProperty(const std::string& name,                                               std::vector& reference);
39: StatusCode declareProperty(const std::string& name,                                               std::vector& reference);
40: StatusCode declareProperty(const std::string& name,                                               std::vector& reference);
41: StatusCode declareProperty(const std::string& name,                                               std::vector& reference);
42: StatusCode declareProperty(const std::string& name,                                               std::vector& reference);
43:
44: private:
45: // Data members not shown
46:
47: Algorithm(const Algorithm& a);   // NO COPY ALLOWED
48: Algorithm& operator=(const Algorithm& rhs); // NO ASSIGNMENT ALLOWED
49: };
Constructor and Destructor
The base class has a single constructor which takes two arguments: The first is the name that will identify the algorithm object being instantiated and the second is a pointer to one of the interfaces implemented by the application manager: ISvcLocator. This interface may be used to request special services that an algorithm may wish to use, but which are not available via the standard accessor methods (below).
The IAlgorithm interface
Principally this consists of the three methods that must be over-ridden by a derived algorithm if it is to do anything useful: initialize(), execute() and finalize(). These are discussed in more detail in section 5.3 . Other methods of the interface are: sysInitialize() and sysFinalize(), which are used internally by the framework, and the accessor: name() which returns the algorithm's identifying name. The latter three methods are not virtual and may not be overridden.
Service accessor methods
Lines 15 to 22 declare accessor methods which return pointers to key service interfaces. These methods are available for use only after the Algorithm base class has been initialised, i.e. they may not be used from within a concrete algorithm constructor, but may be used from within the initialize() method (see 5.3.3 ). The services and interface types to which they point are self explanatory (see also chapter 2 ). Line 23 declares a facility to modify the message output level from within the code (the message service is described in detail in Chapter 11 ).
Creation of sub algorithms
The method on line 25 is intended to be used by a derived class to create sub algorithms, as discussed in section 5.4 .
Declaration and setting of properties
As mentioned above, one of the responsibilities of the base class is the management of properties. The methods in lines 28 to 30 are used by the framework to set properties as defined in the job options file. The declareProperty methods (lines 32 to 42 ) are intended to be used by a derived class to declare its properties. This is discussed in more detail in section 5.3.2 . and in Chapter 11 .

5.3   Derived algorithm classes

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.

5.3.1   Creation (and 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).

5.3.2   Declaring properties

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.

Listing 6 Declaring member variables as properties.

1: //------- In the header file --------------------------------------//
2: class TriggerDecision : public Algorithm {
3:
4: private:
5:   bool m_passAllMode;
6:   int m_muonCandidateCut;
7:   std::vector m_ECALEnergyCuts;
8: }
9: //------- In the implementation file -------------------------------//
10: static const AlgFactory s_factory;
11: const IAlgFactory& TriggerDecisionFactory = s_factory;
12:
13: TriggerDecision::TriggerDecision(std::string name, ISvcLocator *pSL) :
14:   Algorithm(name, pSL), m_passAllMode(false), m_muonCandidateCut(0) {
15:   m_ECALenergyCuts.push_back(0.0);
16:   m_ECALenergyCuts.push_back(0.6);
17:
18:   declareProperty("PassAllMode", m_passAllMode);
19:   declareProperty("MuonCandidateCut", m_muonCandidateCut);
20:   declareProperty("ECALEnergyCuts", m_ECALEnergyCuts);
21: }
22:
23: StatusCode TriggerDecision::inialize() {
24: }

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.

5.3.3   Implementing IAlgorithm

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.

Initialisation
In a standard job the application manager will initialise all top level algorithms exactly once before reading any event data. It does this by invoking the sysInitialize() method of each top-level algorithm in turn. Figure 5 shows an example trace of the initialization phase. Sub-algorithms are discussed in section 5.4 .

Figure 5 Algorithm initialization.

 

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.

Execution

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.

Finalisation
The finalize() method is called at the end of the job. It can be used to analyse statistics, fit histograms, or whatever you like. Similarly to initialization, the framework invokes a sysFinalize method which in turn invokes the finalize() method of the algorithm and of any sub-algorithms.

 

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.

5.4   Nesting algorithms

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.

 

 
Algorithm* m_pSubAlgorithm;   // Pointer to the sub algorithm 
                              // Must be a member variable of the parent class 
std::string type;             // Type of sub algorithm 
std::string name;             // Name to be given to subAlgorithm 
StatusCode sc;                // Status code returned by the call 
sc = createSubAlgorithm(type, name, Algorithm*& m_pSubAlgorithm);       

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);

5.5  Requesting additional services

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.