Editing Module Code
Contents:
The aim of this and the following section is to introduce you
to some of the most common C++ code methods in BaBar modules.
With just a few basic syntax snippets, you will be well on your
way to being able to edit a BaBar module for your analysis - or even
to write your own module.
BaBar code is written in C++, and modules are implemented
as a C++ class. So editing module code means working with C++.
If you have not yet learned C++, now is the time to do it.
You do not need to become an expert programmer, but you will
should try to become familiar with how C++ variables,
functions and classes work.
Here are some useful references for beginners:
There are also many many C++ books available from libraries and bookstores. Your colleagues probably have some lying around.
A module named MyModule consists of two files: the header file
MyModule.hh, and the implementation file MyModule.cc. The
header file contains the declarations of all the module's
member objects and member functions. That is, it
provides the compiler with a list of module's member functions and objects.
The implementation file contains the definitions of the
member functions. That is, it tells the compiler what the
module's functions do.
The following are the skeleton .hh and .cc files for a module.
This is the absolute minimum code that you need in a module.
Right now, this module does precisely nothing. To make a useful
module, you need to add code into the skeleton framework.
Skeleton header file: MyModule.hh
#ifndef MYMODULE_HH
#define MYMODULE_HH
#include "Framework/AppModule.hh"
#include "AbsEvent/AbsEvent.hh"
class MyModule : public AppModule {
public:
// Constructors
MyModule( const char* const theName, const char* const theDescription );
// Destructor
virtual ~MyModule( );
// Operations
virtual AppResult beginJob( AbsEvent* anEvent );
virtual AppResult event( AbsEvent* anEvent );
virtual AppResult endJob ( AbsEvent* anEvent );
protected:
private:
};
#endif
Skeleton implementation file: MyModule.cc
#include "BaBar/BaBar.hh"
#include "MyPackage/MyModule.hh"
// Constructors
MyModule::MyModule( const char* const theName,
const char* const theDescription )
: AppModule( theName, theDescription )
{
}
// Destructor
MyModule::~MyModule( )
{
}
// Operations
AppResult MyModule::beginJob( AbsEvent* anEvent )
{
return AppResult::OK;
}
AppResult MyModule::endJob( AbsEvent* anEvent )
{
return AppResult::OK;
}
AppResult MyModule::event( AbsEvent* anEvent )
{
return AppResult::OK;
}
There are 5 main locations where you will add code:
In the .hh file:
- #include's section. Here you must #include the
header files for all objects declared in the header file.
- member declaration section. The region below
"public:", "private;" or "protected:".
In this section you must declare any member objects,
such as histograms and ntuples.
In the .cc file:
- #include's section. Here you must #include the
header files for all objects used in any of the member functions.
- beginJob() This is where you initialize the member objects
defined in the .hh file (such as histograms and ntuples).
- event() As emphasized throughout the Workbook, the
event() function is where the analysis code goes.
It is
from here that you access Event Store lists of particle candidates
and other event information. It is also here that histograms
and ntuples get filled.
- endJob() This is a "clean up" section, used to delete
pointers, and print "Module finished" messages.
The BaBar environment package for histograms and ntuples
is the HepTuple package. The histogram class is
HepHistogram,
and the ntuple class is
HepTuple.
To create and use either a histogram or an ntuple from within the
BaBar framework, you also need a HepTupleManager.
The analysis module QExample class (from the sample
analysis job) books a histogram of the number of tracks per event.
If an analysis module is to use classes from the HepTuple histogramming
package, its header file must #include the Histogram header file.
In QExample.hh, you have:
#include "HepTuple/Histogram.h"
An analysis module that will book a histogram needs to
have a (preferably private) histogram data member.
HepHistogram* _numTracksHisto;
The next step is to create a histogram manager. First, the
analysis module's .cc file needs to #include the defining header files:
#include "AbsEnv/AbsEnv.hh"
#include "GenEnv/GenEnv.hh"
#include "HepTuple/TupleManager.h"
From within the beginJob function a histogram manager
needs to be declared via:
HepTupleManager* manager = gblEnv->getGen()->ntupleManager();
and then used to book a histogram, which initializes the
histogram data member of the class.
_numTrkHisto = manager->histogram("Tracks per Event", 20, 0., 20. );
The histogram is declared with four arguments:
- the title ("Tracks per Event"),
- the number of bins (20),
- the lower limit of the histogram (0.), and
- the upper limit of the histogram (20.).
This completes the declaration and the definition of the histogram.
The histogram can be filled for each event (from within the
event function of the analysis module)
with a call to the accumulate member function
of the histogram object.
_numTrkHisto->accumulate( trkList->length() );
Ntuples
Managing ntuples is very similar to managing histograms.
Once again, the first step is to #include the required
header in the module's .hh file,
#include "HepTuple/Tuple.h" in the header file.
and declare a private ntuple pointer:
HepTuple* _ntuple;
In the .cc file, #include the header files needed for
the HepTupleManager:
#include "HepTuple/TupleManager.h"
#include "AbsEnv/AbsEnv.hh"
#include "GenEnv/GenEnv.hh"
In beginJob(), create a HepTuple manager:
HepTupleManager* manager = gblEnv->getGen()->ntupleManager();
In beginJob(), initialize your ntuple:
_ntuple = manager->ntuple("MyTuple");
This ntuple is declared with only one argument:
the name of the ntuple.
This completes the declaration and the definition of the ntuple.
The ntuple can be filled for each event (from within the
event function of the analysis module) with a call to the column
function:
_ntuple->column("Benergy", Benergy, -99.0);
The column function has three arguments:
- The name of the variable ("Benergy").
- The value of the variable. (Benergy)
- A default value (-99.0)
The variable can be an integer, a double, a float, or a
boolean. There are also several other supported types.
In this example, Benergy is a double, the B meson energy
that was (presumably) calculated or determined earlier
in the event() function.
If in some event the column function is not called
for some reason (but has been called in previous events),
the ntuple will be filled with the default value for
that event. So it is a good idea to set it to a non-physical
value, so that this case easy to identify. Here, the default
is set to -99.0, an very unphysical energy.
The name of the variable is the name that will be used
in the ntuple to refer to the variable. You should always
give your variables names that make sense - like Benergy
for B meson energy, pleptonCM for lepton momentum
in the center-of-mass frame, and so on.
The last but very important step is to dump all of the
event's data into the ntuple. Add the line:
_ntuple->dumpData();
somewhere after your last call to the ntuple's column function,
but before the end of the module's event function. Don't forget
this step, or else your ntuple will not be written!
As described in the Event
Information section of the Workbook, one of the most common
data structures is a list of particle candidates. The list
could be a standard list from the Event Store, or a run-time
list created at run-time. Either way, one of the most common
tasks that a module needs to perform is to access and use a particle
candidate list.
BaBar's C++ class for lists is called HepAList.
You can have a HepAList of lots of types of pointers,
but the most important type of HepAList is the HepAList
of BtaCandidate*s (pointers to BtaCandidates):
HepAList<BtaCandidate>*
To begin, #include the required header files in the
module's .cc file:
#include "Beta/BtaCandidate.h"
#include "CLHEP/Alist/AList.h"
#include "ProxyDict/Ifd.hh"
Like all event information, the Event Store's HepALists
of BtaCandidates must be accessed from the module's event
function. The syntax is:
QExample::event(AbsEvent* anEvent) {
...
HepAList<BtaCandidate> *trkList =
Ifd<HepAList< BtaCandidate > >::get(anEvent, "ChargedTracks");
The Ifd<T>::get function requires two arguments:
- The event function's AbsEvent pointer (anEvent).
- The name of the list ("ChargedTracks")
The name of the list must be one of the available
lists in the Event Store. (In other words, this is not a name
that you assign - it is one that you use to access the list.)
To see the particle candidate lists available, see the
Table of
BtaCandidate lists from the Workbook's
Event Information section.
The most common way to use a HepAList of BtaCandidates
is to loop over it. That is, you examine each BtaCandidate
on the HepAlist, one at a time.
To loop over a HepAList, you need another C++ object,
called a HepAListIterator.
The first step is to #include the required header files
in the module's .cc file:
#include "CLHEP/Alist/AIterator.h"
The syntax to create the HepAListIterator is:
HepAListIterator<BtaCandidate> iterTrk(*trkList);
Here, iterTrk is the name you assign to your iterator,
and trkList is the (pointer to) the HepAList of BtaCandidates.
(By the way, if trkList were just a HepAList, rather than a
pointer to a HepAList, then you would not need the dereferencing
asterix * above.)
To loop over your HepAList<BtaCandidate>*, you need
a "placeholder" BtaCandidate*. This BtaCandidate* will take
on the identity of each BtaCandidate* in the list, one at
a time.
BtaCandidate* trk(0);
Finally, you are ready to loop over the HepAList:
int nTrkHard(0);
while ( trk = iterTrk()) {
double etrk = trk->energy();
if (etrk > 1.0) nTrkHard++;
}
Within the while loop, trk is the current value of the
BtaCandidate*. You can treat it like any other BtaCandidate,
which means that you can access its energy, momentum,
charge, and other particle information. In the above code
snippet, you get the energy of each track, and count the
number of tracks with energy greater than 1.0 GeV.
For more information about how to use BtaCandidates, see the Workbook's
BtaCandidates page.
There are 2 steps to add a module into the Framework:
- Add the module in the package's AppUserBuild file.
- Append the module to the analysis path in one of the
package's controlling tcl files.
Add the module in AppUserBuild
Every analysis package comes with a file called AppUserBuild.cc.
The AppUserBuild object gives the Framework its list of modules.
So to put your module on this list, you need to add it in the
AppUserBuild file.
Unfortunately, the BetaMiniUser package (which is the
one we are using) has its own peculiar AppUserBuild method,
and this method is different from the method used for
all other BaBar packages. So first I will show you the syntax
used in most packages, and then I will show you how to
add your module in BetaMiniUser.
For most packages
In most packages, there are two lines to add in AppUserBuild.cc.
First, you need to #include the module's header file:
#include "ThePackage/MyModule.hh"
where "ThePackage" is whatever package you put your module in.
Next, you need to add the module in the AppUserBuild constructor:
add( new MyModule( "MyModule", "Skeleton module" ) );
The add function takes two arguments:
- The module name ("MyModule")
- A description of the module.
The description of the module is the description you will
see when you use the path list or module list
commands in a framework session.
For BetaMiniUser only
In BetaMiniUser, AppUserBuild's job has been divided
among four AppUserBuild files:
- AppUserBuild.cc
- AppUserBuildBase.cc
- AppUserBuildBase.hh
- AppUserBuildRoo.cc
The file that you need to modify to add your
module to the Framework is AppUserBuildBase.cc.
First, #include your module's header file:
#include "BetaMiniUser/MyModule.hh"
Then, in the AppUserBuildBase constructor, add the module:
void AppUserBuildBase(AppUserBuild* theBuild, AppFramework* theFramework)
{
...
theBuild->add(new MyModule("MyModule", "Skeleton module"));
Again, the add function takes two arguments:
- The module name ("MyModule")
- A description of the module.
Now you have created your new module and added it to the
Framework's list of modules. But you must also add it to the
path, or else the Framework will ignore it.
To add your module to the analysis path, you need to
modify one of the package's tcl files.
In the tcl file, you must append your module
to the main analysis path, or to one of the smaller paths or
sequences that make up the main analysis path.
The basic syntax to add (append) your module to a path is:
path append path_name MyModule
Similarly, for a sequence, the syntax is:
sequence append sequence_name MyModule
Exactly where to put your new module depends on the package.
The best way to proceed is to examine how another module
in the package is appended, and follow the same procedure for
your module.
For example, the BetaMiniUser package comes with the
module MyMiniAnalysis. To find the tcl file where
MyMiniAnalysis is appended, use grep to search the
BetaMiniUser package:
BetaMiniUser > grep MyMiniAnalysis * | grep append
MyMiniAnalysis.tcl:path append Everything MyMiniAnalysis
You see that MyMiniAnalysis is appended to a path
called "Everything" in the tcl file MyMiniAnalysis.tcl.
So now you know how to edit MyMiniAnalysis.tcl to
append your module as well:
path append Everything MyMiniAnalysis
path append Everything MyModule
This procedure ensures that if MyMiniAnalysis is on the
analysis path, then MyModule will be there, too.
A word of warning, however: for a given package there
is usually more than one possible analysis path.
(Different tcl configurations lead to different paths.)
So if you run one of the analysis jobs that does NOT
use MyMiniAnalysis, then MyModule will not be there, either.
Now it is time to put together everything you've learned.
The following examples will give you some practice in modifying
and writing code in modules.
Example 1: Annotated Quick Tour Analysis Code
As a first step in becoming familiar with some of the analysis code,
you can look at the annotated header
and implementation files for the
Quicktour's QExample analysis module. This module is used to generate
the number-of-tracks-per-event histogram. The comments inserted for
these purposes are in blue.
Quick links:
The easiest way to edit analysis code is to modify
an existing module. As a second example, you will
modify the QExample module and add a track momentum histogram
in addition to its number-of-tracks histogram.
Open your QExample file in a text editor, such as emacs.
(Don't worry about making mistakes as you edit the code.
If you get stuck and can't find or fix your error,
you can always re-copy the original code from
$BFROOT/www/doc/workbook/examples/ex0/.)
When you modify an existing module, the required header
files are often already #included. Normally, the first step
in adding a histogram would be to #include "HepTuple/Histogram.h".
However, QExample.hh already has that #include statement.
So you can move onto the next step: declaring the histogram.
Declare the new HepHistogram as a private data
member in the header file:
HepHistogram* _pHisto;
The reason that histograms and ntuples are added as
class members is because you want the histograms
to retain all the information they store over multiple
events, so the histogram needs to be of greater scope
than the event( ) function. Making them private ensures
that other modules cannot interfere with them.
Now QExample now has a new HepHistogram pointer, but it
has not yet been initialized. Member objects, like the histogram
and the histogram manager, should be initialized in the module's
beginJob function.
In our case, a histogram manager has already been initialized
for QExample's _numTracksHisto. You can use the same
histogram manager for _pHisto:
_pHisto = manager->histogram("Momentum", 25, 0., 1. );
The first argument (in quotes) is the title of the
histogram, the second argument (an integer) is the number
of bins, the third and fourth arguments (doubles) are the
low and high values of the x-axis.
Finally, you are ready to fill the momentum histogram.
In your number-of-tracks histogram, all you needed was the length of
the tracks list, which is a property of the tracks list as a whole. But
momentum is a property of a single track. So you need to iterate (loop)
over the BtaCandidates in trkList, and store the momentum
for each one:
// Loop over track candidates to plot momentum
HepAListIterator<BtaCandidate> iterTrk(*trkList);
BtaCandidate* trk(0);
while ( trk = iterTrk()) {
_pHisto->accumulate( trk->p() );
}
The original QExample did not use a HepAListIterator, so the
HepAListIterator header still needs to be included:
#include "CLHEP/Alist/AIterator.h"
CLHEP, Class Library for High Energy Physics, is a package that contains
general utility classes. If you are looking for the home of a class or
function named HEPsomething, it's probably there.
That's all there is to it! Working examples of the
modified QExample module can be found in:
$BFROOT/www/doc/workbook/examples/ex2/
or viewed here, where the
added code is in blue.
However, the C++ code must be re-compiled and re-linked (gmake all)
before the changes will be incorporated into the executable. You
will do this in the next section of the workbook:
Compile and Link.
This third example will take you step-by-step through the process
of creating a new module from the basic module skeleton.
This example is independent of the rest of the Workbook,
and it is optional. If you choose to try it,
you have two options: you can just read through the example, or
you can copy the skeleton code and follow along. Either way, this
will be a useful reference if someday you need to create your
own module.
If you want to follow along, then copy the skeleton code for a
module to your analysis package:
Package> cp -r $BFROOT/www/doc/workbook/examples/ex3/skeleton/* .
(For the Quicktour and the QExample module, the analysis
package is BetaMiniUser, but this example is intended to
be general so that it will work in any analysis package.)
Now you are ready to begin:
Example 3: Creating a new module
At this point you have learned the basics of what you need to know
to modify BaBar code, and maybe even write your own module. You
have seen how to manage the two main data-storage objects,
HepHistograms and HepTuples, and how to access the main data-storage
object, the HepAList of BtaCandidates*.
In the next section, you will learn how to compile and link
your code, so that your modified or new module will be incorporated
into a new BetaMiniApp.
Related Documents:
Page maintained by Adam Edwards
Last modified: January 2008
|