Last significant change: Printing candidates: 2004/04/26 Remainder: 2001/02/20
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:-
There is an overview tha draws together most of the classes introduced in this section.
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.
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:-
Some of our handles are smart pointers and from the explanation given above, it follows that they should normally be local objects.
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 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:-
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.
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:-
Candidates are created by an associated factory. The user passes in an AlgHandle and a CandContext and receives a CandHandle.
In order to create an Xxx object the following classes have to be developed.
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 = YyyListthere are a further set of Yyy classes except for YyyModule; the framework only knows about Yyy or lists of Yyy, not both.
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!
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.
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.
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.
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.
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.
To be supplied.
To be supplied.
To be supplied.
To be supplied.
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
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("");
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;
}
To be supplied.
To be supplied.
To be supplied.
To be supplied.