next up previous contents
Next: Conventions Up: The MINOS Off-line Software Previous: Message Service   Contents

Subsections


Modules, Algorithms and Candidates

Last significant change: Printing candidates: 2004/04/26 Remainder: 2001/02/20

Introduction

This chapter will tell you how event reconstruction works in our framework and how you write reconstruction code.

At its heart of the reconstruction is simple: input data-like objects are processed to create new output data-like objects which can subsequently be used as input for further processing. In terms of this framework, the processing is performed by Algorithm objects and the outputs are Candidate objects. The framework structures a job into a set of modules each of which is responsible for passing input to a Algorithm and returning the results to the framework. The framework's primary responsibilities are to:-

Your primary responsibility, as an Algorithm developer, is simply to write the single member function that performs reconstruction. Typically individual member functions are procedural so, although set in an OO framework, the task is not so very different from the equivalent job in a Fortran framework.

In this chapter we will focus on reconstruction. However the machinery applies equally well to those phases of analysis that involve the creation of further objects. The remainder of this chapter is divided into the following sections:-

Concepts
The concepts that underpin Modules, Handles, CandContexts, MOM, Algorithms and Candidates.

Naming Convention
The naming convention used for Modules, Handles, Algorithms and Candidates.

A Simple Example: DigitListModule
A walk through a simple reconstruction process.

Working with Modules
How to design a Module.

Working with Candidates
How to design, create and use Candidates.

Working with Algorithms
How to design, create and use Algorithms.

Concepts

There is an overview tha draws together most of the classes introduced in this section.

Module

A Module is the basic building block of a job. Eventually there will be modules for I/O, calibration, reconstruction, analysis etc. As part of job configuration, the job user tells the framework what Module objects it must create and use.

All modules inherit from JobCModule which is essentially an abstract interface, leaving all the real work to its subclasses. Modules are informed of state changes such as job begin or file end. They have member functions that are called to set them to work. For example, reconstruction modules are called via their Reco method to perform reconstruction on the current event.


Handle

A handle gives you controlled access to an object. If you are familiar with Fortran memory managers such as BOS or ZEBRA, then a handle is a bit like a link. Just as with links you can have multiple handles to the same object. In our framework we use handles for Algorithm and Candidate objects. The primary reasons why we use them are:-

Avoiding Memory Leaks
If you directly create any object then it is your responsibility to destroy it when no longer needed, or find another object that is prepared to take on that responsibility! That's why local (stack-based) objects, i.e. objects declared locally within a function are so good; as soon as you leave the function they get destroyed. However this option won't work for event reconstruction! It is not possible to determine, at creation time, how long an individual reconstruction object will live; for later reconstruction may need them even if the creator no longer does. The solution is to use what are called smart pointers. The idea is that any number of smart pointers can point to an object but they must be the only means of access. Once all smart pointers to an object are deleted the object itself can safely be deleted, as it has become inaccessible. What prevents smart pointers from being a source of memory leaks themselves is that they are lightweight and ideally suited to be local objects. However they can also be passed back out of function by value so that its rather like a baton in a relay race; so long as it is passed between functions the object remains alive, but drop the last baton and the object dies.

Some of our handles are smart pointers and from the explanation given above, it follows that they should normally be local objects.

Object Protection
Reconstruction objects are initially modifiable but subsequently they become frozen as subsequent reconstruction becomes dependent on them. In our framework the way we prevent objects from being modified is as follows:-

  1. First we move all its get and set methods into a companion handle which is made a friend. Now, even if you have an object, you cannot change it, you must have its handle to do that.

  2. The handle can determine if a modifying access is allowed and if not either forbid it or create a clone copy of the object which the user can safely modify.

CandContext

A CandContext object is a lightweight object, typically created on the stack that identifies which part of the current event is being used in the role of input. CandContexts are created by Modules and passed to Algorithms so that they know what to operate on.

MOM

MOM is the Minos Object Mapper (O.K. so the name is a bit contrived). Its function is to provide access to all the major components, or fragments, of an event. All I/O works at the fragment level so MOM is really an IO stream manager. Currently MOM is still in prototype design and actually owns all its fragments. A single object reconstruction consists of the following stages:-

  1. Ask MOM for an input fragment.

  2. Create a CandContext that points to this fragment.

  3. Create a new reconstruction object specifying this CandContext and an Algorithm.

  4. Hand the resulting object back to MOM.

Algorithm

All Algorithm objects inherit from AlgBase. The role of an Algorithm is to initialise or modify a Candidate i.e. a reconstruction object. Your job is to create a subclass and write the overloaded member function RunAlg which performs this operation.

Conceptually Algorithms are stateless objects although in reality what this means is that during its lifetime changes in its state do not influence the way its RunAlg works. In fact Algorithms have several types of state:-

Algorithms are accessed via AlgHandles. This is both to avoid a memory leaks (see section 3.2.2 on handles) and to allow an Algorithm and an AlgConfig to be packaged up together into a single operational unit. The user asks AlgFactory for a specific Algorithm and is handed back an AlgHandle.

Candidate

All reconstruction objects are Candidates and inherit from CandBase.

The name is meant to convey the concept that such objects could in principle be tentative and that alternatives are possible. For example there may be several ways to assign hits to a Track and a Shower object if they overlap. The Candidate package has support machinery to allow multiple reconstructions from the same data, using different configured Algorithms and includes the hooks for equality and overlap testing. Of course this facility will not be used at every stage of reconstruction, but stands ready for use where appropriate. Other built-in support includes:-

All Candidates have a list of daughter Candidates, although strictly speaking the list is of CandHandles to the daughters. The term ``daughter'' is somewhat misleading as it implies dependence. Sometimes this implication is correct, for example a CandDigitList owns a list of CandDigits; if the CandDigitList is destroyed, so are its CandDigits. However later we will want objects, say CandPhysicsEvent that are built from pre-existing CandTrack and CandShower objects but will not own them. Its is better to think of Candidates as being of two types:-

Simple
The daughter list is empty. The Candidate's state is to be found in its other data members.

Compound
The daughter list is not empty. The Candidate's state is to be found only in its daughters. Of course the daughter list might be empty on occasions, but that is still the only state.

Candidates are created by an associated factory. The user passes in an AlgHandle and a CandContext and receives a CandHandle.

Naming Convention

In order to create an Xxx object the following classes have to be developed.

CandXxx
The reconstruction object.
CandXxxHandle
Its handle.
AlgXxx
The Algorithm that creates it.
AlgXxxConfig
The configuration for AlgXxx.
XxxModule
The Module that gets called to create and which puts AlgXxx to work to create the Xxx object.

Often, Xxx is a actually a list of other Candidates, for example CandDigitList is simple a list of CandDigits. In such cases i.e where:-

Xxx = YyyList
there are a further set of Yyy classes except for YyyModule; the framework only knows about Yyy or lists of Yyy, not both.

A Simple Example: DigitListModule

Introduction

We shall see all of the above concepts brought into play by studying a simple reconstruction process: the creation of a list of CandDigits from the corresponding RawDigits.

DigitListModule code

At the framework level, what is being created is a CandDigitList and so, by the naming convention above, the Module that is called is DigitListModule.

Examining the source for this we see that most member functions are dummy. for example:-

 void DigitListModule::BeginJob() 
{
  MSG("JobC", Msg::kInfo) << "DigitListModule::BeginJobn";
}

and

JobC::Filter_t DigitListModule::AnaNonPhysics(const MomNavigator *mom) 
{
  MSG("JobC", Msg::kInfo) << "DigitListModule::AnaNonPhysicsn";
  return JobC::kNoDecision;
}

all the real work is done in:-

JobC::Filter_t DigitListModule::Reco(MomNavigator *mom);

It is passed mom and has to return a JobC::Filter_t. Eventually this return will be used as a way of filtering further processing. For now it is dummy. Stripping out all the comments and printout, the code is remarkably short:-

   RawRecord *rr = (RawRecord *) mom->GetFragment("RawRecord");
   AlgHandle adlh = AlgFactory::GetAlgDigitList();
   CandContext cx(this, mom);
   cx.SetDataIn(rr);
   CandDigitListHandle cdlh = CandDigitList::MakeCandidate(adlh, cx);
   CandDigitListHandle *onheap = new CandDigitListHandle(cdlh);
   mom->AdoptFragment(onheap);
   return JobC::kNoDecision;

We will now pick this apart, statement by statement.

   RawRecord *rr = (RawRecord *) mom->GetFragment("RawRecord");

First mom is asked to find the fragment called "RawRecord". It returns a TObject pointer so has to be cast up to a RawRecord pointer.

   AlgHandle adlh = AlgFactory::GetAlgDigitList();

Next a stack based AlgHandle is filled by asking the AlgFactory for a handle to a AlgDigitList. Note the very typical passing by value when using handles, they are so lightweight that is easier and safer not to use pointers.

Although you don't really need to know, there is nothing very mysterious going on here, if you look in AlgFactory. you will see that GetAlgDigitList is a static method (i.e. is called without an object):-

  AlgHandle AlgFactory::GetAlgDigitList()
{
   if (!fsAlgInstanceDigitList)
     fsAlgInstanceDigitList = new AlgDigitList();
   if (!fsAlgConfigDigitList)
     fsAlgConfigDigitList = new AlgConfigDigitList();
   return AlgHandle(*fsAlgInstanceDigitList, *fsAlgConfigDigitList);
}

which is simply creating static an AlgDigitList and an AlgConfigDigitList if needed and then creating an AlgHandle from them.

Returning to DigitListModule::Reco,

   CandContext cx(this, mom);
   cx.SetDataIn(rr);

The first statements creates a stack based CandContext which is passed this, a pointer to the DigitListModule, so that the reconstruction history can record who the creating module and the mom were. The second statement programs the CandContext to point to the RawRecord.

   CandDigitListHandle cdlh = CandDigitList::MakeCandidate(adlh, cx);

This is where the real action takes place! MakeCandidate is a static method of CandDigitList and it creates a CandDigitList object using the Algorithm handle and the CandContext. It returns a CandDigitListHandle by value. During execution of this statement the AlgDigitList is passed the newly created CandDigitList and the CandContext and uses the input data it has selected to fill out the CandDigitList.

   CandDigitListHandle *onheap = new CandDigitListHandle(cdlh);

This bit of fancy footwork is needed because we are about to give the new CandDigitList to mom who adopts objects by address. We cannot pass the address of cdlh, as this is on the stack and will shortly disappear. Instead we make a copy of the stack CandDigitListHandle on the heap so that it can be passed to mom.

   mom->AdoptFragment(onheap);

Now the CandDigitListHandle and associated CandDigitList are passed to mom. The word Adopt is important, its part of our Coding Convention (see NAM-7), and means that mom now owns the CandDigitListHandle and has to worry about its ultimate destruction.

   return JobC::kNoDecision;

Finally the function signals back to the caller. Currently this is dummy hence the kNoDecision value.

Its worth taking one last look at the code. Although it indirectly createda heap based CandDigitList and possibly a AlgDigitList, the code itself only created stack objects for the most part. The one exception was that last CandDigitList which it made mom's problem!

CandDigitList code

Now its time to take a closer look at the all important MakeCandidate:-

   CandDigitListHandle cdlh = CandDigitList::MakeCandidate(adlh, cx);

Looking at CandDigitList member function MakeCandidate:-

 CandDigitListHandle CandDigitList::MakeCandidate
                              ( AlgHandle &ah,
                                CandContext &cx)
{
  CandDigitListHandle cdlh;
  new CandDigitList(ah, cdlh, cx);
  return cdlh;
}

It creates a stack based CandDigitListHandle and then creates a heap based CandDigitList which is passed the AlgHandle, the local CandDigitListHandle and the CandContext. Passing the CandDigitListHandle to the CandDigitList is the way it gets connected. Finally the CandDigitListHandle gets returned by value.

Now we have reached the constructor for CandDigitList which contains:-

 CandDigitList::CandDigitList(AlgHandle &ah, CandHandle &ch,
                                                      CandContext &cx) :
  CandBase(ah)
{
 // Run Algorithm to construct Candidate
   {                                        // Start of scope.
     CandDigitListHandle cdlh(this);        // cdlh will go out of scope
     ch = cdlh;                             // after setting ch.
   }                                        // End of scope.
   ah.RunAlg(ch, cx);
}

The first few statements ensure that the CandBase class is initialised with the AlgHandle and that ch, the CandHandle that has been passed in by reference, gets connected to the new CandDigitList. The crucial line is:-

   ah.RunAlg(ch, cx);

for this is where the AlgDigitList::RunAlg gets called.

AlgDigitList code

For that we need to look at AlgDigitList. and in particular:-

 void AlgDigitList::RunAlg(AlgConfig &ac, CandHandle &ch,CandContext &cx)
{

// Iterate over DigitDataBlock or CandDigitList daughters.
   if (cx.GetDataIn()->IsA()->InheritsFrom("RawRecord")) {

// Iterate over input RawDigit's to fill input CandDigitList.

     AlgHandle ah = AlgFactory::GetAlgDigit();
     CandContext cxx(this, cx.GetMom());

// Iterate over RawRecord's RawDataBlockList to find DigitDataBlock.
     RawRecord *rr = (RawRecord *) (cx.GetDataIn());
     TIter rdbit = rr->RawDataBlockIter();
     TObject *tob;
     while ((tob = rdbit())) {
       if (tob->IsA()->InheritsFrom("DigitDataBlock")) {
         DigitDataBlock *rdb = (DigitDataBlock *) tob;
         TIter rdit = rdb->GetDatumIter();
         RawDigit *rd;
         while ((rd = (RawDigit *) rdit())) { 
           cxx.SetDataIn(rd);
           CandHandle cdh = CandDigit::MakeCandidate(ah, cxx);
           ch.AddDaughterLink(cdh);
         }
       }
     }
   }
   else if (cx.GetDataIn()->IsA()->InheritsFrom("TCollection")) {
     TCollection *cdl = (TCollection *) cx.GetDataIn();
     TIter cdit(cdl);
     CandDigitHandle *cdh;
     while ((cdh = (CandDigitHandle *) cdit())) { 
       ch.AddDaughterLink(*cdh);
     }
   }
}

The outer if statement:-

   if (cx.GetDataIn()->IsA()->InheritsFrom("RawRecord")) {

   ...


   else if (cx.GetDataIn()->IsA()->InheritsFrom("TCollection")) {

   ...

   }

demonstrates how a single Algorithm can be made to work on different sorts of input data. The CandContext is sent its GetDataIn message and responds by returning a TObject pointer to it. Then using standard ROOT services the TObject is asked for its TClass which in turn is asked if it InheritsFrom from "RawRecord" or "TCollection" Asking if it InheritsFrom allows the code to work even if the data are subclasses of the required classes. Now, in our case we are interested in the "RawRecord" branch as we know that back in DigitListModule::Reco the CandContext was programmed to point to a RawRecord. Taking this branch to bits:-

     AlgHandle ah = AlgFactory::GetAlgDigit();

This code should look familiar, its a replay of what happened in DigitListModule::Reco only now we want an AlgHandle for a AlgDigit rather than an AlgDigitList.

     CandContext cxx(this, cx.GetMom());

This too is familiar. Here a local CandContext object is created that will be used to select an individual RawDigit for AlgDigit to operate on.

Ignoring the details of the inner loops, the remainder of the code is as follows:-

// Iterate over RawRecord's RawDataBlockList to find DigitDataBlock.
     RawRecord *rr = (RawRecord *) (cx.GetDataIn());
     TIter rdbit = rr->RawDataBlockIter();
     TObject *tob;
     while ((tob = rdbit())) {
      ...
     }
   }

First the RawRecord is retrieved using the supplied CandContext. (i.e. then CandContext associated with the CandDigitList). The RawRecord is asked to provide an iterator over all its RawDataBlocks and this iterator is loop on until exhausted.

Taking the next loop:-

     while ((tob = rdbit())) {
     if (tob->IsA()->InheritsFrom("DigitDataBlock")) {
         DigitDataBlock *rdb = (DigitDataBlock *) tob;
         TIter rdit = rdb->GetDatumIter();
         RawDigit *rd;
         while ((rd = (RawDigit *) rdit())) { 
          ...
         }
     }

If a particular RawDataBlock happens to be, or inherits from, a DigitDataBlock then this block is processed which involves iterating over its individual RawDigits.

The innermost loop starts:-

         while ((rd = (RawDigit *) rdit())) { 
           cxx.SetDataIn(rd);
           CandHandle cdh = CandDigit::MakeCandidate(ah, cxx);

For each RawDigit the local CandDigit is programmed to point to it and the CandDigit factory method MakeCandidate is used to make a CandDigit from the CandContext and the AlgDigit. The factory returns a CandHandle to the CandDigit. Again this should look familiar from DigitListModule::Reco.

        ch.AddDaughterLink(cdh);

In DigitListModule::Reco the CandHandle was given to mom, but in this case the CandDigit is handed, via its handle, to the CandDigitList's daughter list. Note that it is doing this not by sending messages directly to the CandDigitList, but to its handle.

AlgDigit code

All that remains to be analysed is:-

           CandHandle cdh = CandDigit::MakeCandidate(ah, cxx);

Much of this is a replay of CandDigitList::MakeCandidate. A CandDigitHandle and CandDigit get created and connected and during construction of CandDigit, AlgDigit member function RunAlg get called to fill it.

This method is for the most part very straight forward:-

 void AlgDigit::RunAlg(AlgConfig &ac, CandHandle &ch, CandContext &cx)
{
   CandDigitHandle &cdh = (CandDigitHandle &) ch;
   RawDigit *digit = (RawDigit *) cx.GetDataIn();

// Use CandDigitHandle methods to set CandDigit member variables.
   cdh.SetChannelId(digit->GetChannel());
   cdh.SetCharge((Float_t) digit->GetADC()); // Set to 100. * p.e. count
   cdh.SetTime((Float_t) digit->GetTime());  // Set to 1. ns time units?

// For a PlexHandle we need a context
   MomNavigator *mom = (MomNavigator *) cx.GetMom();

   if (mom) {
     RawRecord *rawrec = (RawRecord *) mom->GetFragment("RawRecord");
     VldContext vldc = rawrec->GetRawHeader()->GetVldContext();

// Get a PlexHandle for PlexStripEndId to RawChannelId conversions
     PlexHandle ph(&vldc);

     PlexSEIdAltL altlist = ph.ChannelToStripEnds(digit->GetChannel());
     cdh.SetPlexSEIdAltL(altlist);
   }
}

This first part simply consists of transfering data from the RawDigit to the CandDigit. Again notice how modification goes via the CandDigitHandle.

The second part involves the creation of a complete list of possible strip ends as determined by the multiplexing for this digit. Here we are getting into a level of detail that is not appropriate for this chapter; the only point to note is that again CandDigit is being modified via its handle.

Summary

To summarise, we have seen how:-

Although this may all look a little daunting, design of Candidate classes will mostly be left to the core group and XxxModules can almost be created starting with an existing one and making a couple of global replaces, which just leaves AlgXxx and in particular AlgXxx:RunAlg, and that last really is the job of the algorithm developer!

George, I need an example of a AlgXxx::RunAlg modifying an existing CandXxx i.e. being call outside the constructor.

Working with Modules

Modules are informed of states changes such as the begin and end of the job, file and run by calls to the appropraie member function, for example:-

void JobCModule::BeginRun();

Modules also have member functions Get and Put for I/O and Ana and Reco for analysis and reconstruction processing e.g.:-

To be continued.

Working with Candidates

To be supplied.

Designing Candidates

To be supplied.

Creating Candidates

To be supplied.

Using Candidates

To be supplied.

Printing Candidates

One can prevail upon the Candidates to print themselves. Given a CandHandle one can use either of the following methods to induce a nested, formatted output.

   virtual void Print(Option_t *option="nid3") const;
   virtual std::ostream& FormatToOStream(std::ostream& os,
                                         Option_t *option="nid3") const;

In all cases the class name and number of daughters are printed. The option string controls various other components

"n"
The object's name is printed.
"t"
The object's title is printed.
"i"
The object's UIDs are printed.
"dn"
The object's daughters are printed to the n-th level in a nested format.
Use "d0" to suppress recursing over the daughters.
The lack of a n is taken to be a unlimited depth.
"vn"
Verbosity level. Use "v0" to suppress any class specific data.

The class specific data portion is indented with the string "| " to help delineate the structure. This can be reset using the static method:

   CandBase::SetDataIndent("");

Teaching Candidates to print themselves

Each Candidate class that adds new data members above and beyond those in the class it derives from must implement the method:
  virtual std::ostream& FormatToOStream(std::ostream& os, Option_t *option="") const;

Here is a basic template for writing the method. Substitute the correct names for CandDerived and CandDerivedFrom. The loop over the daughter list is handled after the all the class data has been printed (and is done automagically); this doesn't handle other user additions of TObjArrays - the user should decide whether it is important to print those as well.

std::ostream& CandDerived::FormatToOStream(std::ostream& os, Option_t *option) const
{
  CandDerivedFrom::FormatToOStream(os,option);  // let base classes print their part first

  TString opt(option);
  if (!opt.Contains("v0")) { // v0 means suppress the data values
    const TString& indent = GetIndentString();

    os << indent << GetDataIndent()  // use this whenever starting a new line
                                     // for correct nesting
       << "DataVariableName " << fDataVariable  // this is (only) the data in CandDerived
       << " MoreVarNames " << fMoreVars         // CandDerivedFrom's is already printed
       << endl;
  }
  return os;
}

Working with Algorithms

To be supplied.

Designing Algorithms

To be supplied.

Creating Algorithms

To be supplied.

Using Algorithms

To be supplied.


next up previous contents
Next: Conventions Up: The MINOS Off-line Software Previous: Message Service   Contents
MINOS Software 2017-10-29