cvs -d /afs/desy.de/user/b/blist/.cvs co jbltools/sfh
Consider this example:
SFH1F *h1 = new SFH1F ("JPsiElasticTT", "#muODS: m_{J/#Psi}: elastic production, (#mu-#mu)", 40, 2., 4., *this, jpsiMass, jpsiElastic&&jpsiTrackTrack&&jpsiNoCosmic&&jpsi2mu);
Without going into the details yet it is certainly plausible that this should be a histogram with name "JPsiElasticTT" and title "
ODS: 
: elastic production, 
", with 40 bins between 2 and 4 GeV, which shows the mass of 
 meson candidates, after cuts have been applied that demand:
The classes contained in the package SFH provide the means to write this type of code.
At the core of our toolkit are, obviously, the self-filling histogram classes, which are called SFH1F, SFH2F, etc, similarly to the corresponding root classes TH1F, TH2F. etc. from which they are derived.
SFH1F::Fill(), which in contrast to the method of Int_t TH1F::Fill(Axis_t x) does not take an argument.
When we loop over the entries of a root Tree, the Fill() method has to be called once per entry. If we have a list of all self-filling histograms, which is provided by class HList, this is easily done in a single call, which in turn calls all Fill() methods of the histograms.
jpsiMass, jpsiElastic etc, which are declared as follows (at the moment we assume there is only one, and exactly one, J/psi candidate per Tree entry): FloatFun& jpsiMass = *new JpsiMassFun; BaseCut& jpsiElastic = *new JpsiElasticCut; BaseCut& jpsiTrackTrack = *new JpsiTTCut; BaseCut& jpsiNoCosmic = *new JpsiNoCosmicCut; BaseCut& jpsi2mu = *new Jpsi2MuCut;
So, jpsiMass is an object of type JpsiMassFun.
Assuming we have a ntuple declared in an include file NTuple.h, automatically generated by root, and this ntuple has a member jpsimass, we could have this code:
#include "Ntuple.h" #include "jbltools/sfh/FloatFun.h" external Ntuple* ntuple; // global pointer to ntuple class JpsiMassFun : public FloatFun { public: virtual float operator() () const { return nt->jpsimass; } };
So, JpsiMassFun is a class that implements operator(), which, when called, returns the jpsi mass stored in the ntuple row that is currently in memory.
This class is so light-weight that it can be completely implemented within a header file, without need for a separate implementation file.
Consequently, the base class FloatFun is an abstract base class which does not much more than declare that any FloatFun subclass must implement operator():
class FloatFun { public: virtual float operator() () const = 0; protected: virtual ~FloatFun() {}; };
(Why the destructor is protected is an issue that will be covered later.)
class BaseCut { public: virtual bool operator() () const = 0; protected: virtual ~BaseCut() {}; };
So, to implement one cut we could write:
#include "Ntuple.h" #include "jbltools/sfh/BaseCut.h" external Ntuple* ntuple; // global pointer to ntuple class Jpsi2MuCut : public BaseCut { public: virtual bool operator() () const { return nt->trackIsMuon[0] && nt->trackIsMuon[1]; } };
class AndOfTwoCuts: public BaseCut { public: AndOfTwoCuts (const BaseCut& lhs_, const BaseCut& rhs_) : lhs (&lhs_), rhs (&rhs_) {}; virtual bool operator() () const {return (*lhs)() && (*rhs)();} protected: const BaseCut *lhs; const BaseCut *rhs; }; inline AndOfTwoCuts& operator&& (const BaseCut& lhs_, const BaseCut& rhs_) { return *new AndOfTwoCuts (lhs_, rhs_); }
(The real code, residing in BaseCut.h, is slightly more complicated, but not much.)
We first define a class AndOfTwoCuts that holds pointers to two other BaseCut objects, and returns the AND of their results. Additionally we overload operator&&(), so that it generates a new AndOfTwoCuts object, which we can pass (by reference!) on to a SFH1F. Voilą!
The purpose of an AnalysisLoop object is to book, fill, and write out a number of histograms. It might look like this:
#include "jbltools/sfh/EventLoop.h" #include "jbltools/sfh/Binning.h" #include "jbltools/sfh/SFH1F.h" #include "PTMissFun.h" class AnalysisLoop: public EventLoop { public: // Constructor books histograms AnalysisLoop (Ntuple* ntuple) { Binning ptmissbinning (50, 0., 100.); FloatFun& ptmiss = *new PTMissFun (ntuple); // self-filling histogram for missing pt h = new SFH1F ("ptmiss", "Missing pt", ptmissbinning, this, ptmiss); } // Destructor does nothing virtual ~AnalysisLoop () {} // output method writes histos to postscript and file virtual void output (const char* rootfile = "", const char* psfile = "") { // create canvas TCanvas *canvas = new TCanvas("canvas", "ptmiss", 600, 800); // Write plot to psfile TPostScript ps (psfile, 111); canvas->Clear(); h->Draw ("E0"); // Draws the histo canvas->Update(); ps.Close(); // Write histogram to file TFile file (rootfile, "RECREATE"); h->Write(); // Writes out the histo file.Write(); file.Close(); } protected: SFH1F *h; // The self-filling histogram };
The corresponding main program opens an ntuple and loops over its entries, calling the (inherited) loop() method of the AnalysisLoop object for each row:
#define Ntuple_cxx #include "Ntuple.h" // The ntuple #include "AnalysisLoop.h" // The AnalysisLoop int main(int argc, char** argv) { Ntuple nt; // open ntuple TApplication theApp("main", &argc, argv); // for graphics AnalysisLoop theAnalysisLoop (&nt); // book histos // Loop over ntuple entries for (int i = 0; i < int(nt.fChain->GetEntriesFast()); ++i) { if (nt.LoadTree (i) < 0) break; nt.fChain->GetEntry(i); theAnalysisLoop.loop(); // fill the histos } theAnalysisLoop.output("out.root","out.ps"); // store the histos return 0; }
Here we use the headerfile Ntuple.h, generated by TTree::MakeClass, that describes the ntuple structure.
AnalysisLoop is derived from EventLoop, and thereby from SFROList (list of self-filling registered objects). SFROList defines a Fill() method that fills all self-filling objects by calling their respective Fill() method. EventLoop::loop() calls this Fill() method, and therefore fills all self-filling objects that have registered themselves with the loop, in our example the ptmiss-histogram.
 
1.3.2