3.3. Functions Inside ROOT Macros
Table of Contents
Creating reusable analysis functions
Inside a ROOT macro you can define ordinary C++ functions. This lets you separate small analysis tasks into reusable pieces instead of repeating the same code many times.
A typical ROOT macro file contains at least one function that ROOT will call directly, for example void myMacro(). Inside the same file you can place additional helper functions above or below the main macro function. These helper functions can perform well defined tasks, such as filling a histogram, computing a quantity from some inputs, or processing one event.
For interpreted ROOT macros, you can write:
// File: MyAnalysis.C
double ComputeEnergy(double px, double py, double pz, double mass)
{
double p2 = px*px + py*py + pz*pz;
return std::sqrt(p2 + mass*mass);
}
void MyAnalysis()
{
double e = ComputeEnergy(10.0, 0.0, 0.0, 0.13957);
std::cout << "Energy = " << e << std::endl;
}
When you run MyAnalysis() from ROOT, ROOT first loads the macro file, then you can call both MyAnalysis() and ComputeEnergy() interactively. The helper function ComputeEnergy is reusable. You can call it from other functions inside the same macro file, or from the command line after loading the macro.
You are not limited to functions that return simple numbers. You can write functions that create and return ROOT objects such as histograms or graphs. This is a common pattern to encapsulate a piece of analysis.
TH1F* MakePtHistogram(const char* name, const char* title,
int nbins, double xmin, double xmax)
{
TH1F* h = new TH1F(name, title, nbins, xmin, xmax);
h->GetXaxis()->SetTitle("p_{T} [GeV]");
h->GetYaxis()->SetTitle("Events");
return h;
}
void MyPtAnalysis()
{
TH1F* hPt = MakePtHistogram("hPt", "Transverse momentum", 100, 0.0, 100.0);
// Fill and draw hPt here
}Here the macro defines a function that creates a histogram with a standard style. Any other function in the macro file can reuse this. This is the core idea of reusable analysis functions.
You can also create functions that operate on existing ROOT objects and do not return anything. In C++ these are void functions. For instance, you might define a function that applies consistent style settings to any histogram it receives.
void StyleHistogram(TH1* h, int color)
{
if (!h) return;
h->SetLineColor(color);
h->SetLineWidth(2);
h->SetStats(false);
}Then in your macro entry point you can call:
void PlotSpectra()
{
TH1F* h1 = new TH1F("h1", "Spectrum 1", 100, 0, 10);
TH1F* h2 = new TH1F("h2", "Spectrum 2", 100, 0, 10);
// Fill histograms here
StyleHistogram(h1, kRed);
StyleHistogram(h2, kBlue);
h1->Draw();
h2->Draw("SAME");
}
By using helper functions like MakePtHistogram or StyleHistogram, you avoid copying the same styling and setup code many times. If you change the style later, you edit only one function and the whole macro benefits.
Reusable analysis functions in macros should do one clear job, have a small and well defined interface, and avoid hard coded values that you will want to change later.
When you start to have many helper functions that are shared between different macros, it is usually better to move them to a separate file that you load from ROOT, or even into a compiled library. For beginners, however, placing a few reusable functions inside a single .C macro file is a good first step toward more structured analysis code.
If you plan to compile your macro with ACLiC, you must remember that C++ syntax rules are enforced strictly. All function declarations and definitions must be consistent, and you must include any needed headers, such as <cmath> for std::sqrt, at the top of the macro file. Interpreted mode is more forgiving, but compiling early helps you catch mistakes inside your reusable functions.
Passing parameters
Reusable functions are only useful if they are flexible. In C++ you make a function flexible by passing information to it through parameters. Parameters appear in the function definition inside parentheses. Each parameter has a type and a name, for example int nbins or double mass.
Consider again the example of a function that computes an energy. Here the parameters px, py, pz, and mass are inputs that the caller controls:
double ComputeEnergy(double px, double py, double pz, double mass)
{
double p2 = px*px + py*py + pz*pz;
return std::sqrt(p2 + mass*mass);
}In the main macro function you pass arguments to this function by writing:
void MyAnalysis()
{
double px = 1.0;
double py = 2.0;
double pz = 3.0;
double pionMass = 0.13957;
double e = ComputeEnergy(px, py, pz, pionMass);
}
Here px is passed to the first parameter, py to the second, and so on, matching type and order. These values are called arguments at the call site.
You can pass numbers, bool values, std::string or C-style strings, and ROOT objects such as TH1 or TTree. For ROOT analysis you often pass pointers to objects, so the function can operate on them. In the earlier styling example, the parameter is of type TH1*. At the call site you pass a pointer to a histogram object:
StyleHistogram(h1, kRed);The function receives the same pointer and can modify the histogram directly. Any changes are visible to the caller because both use the same object.
Sometimes you want a function to produce more than one result. For numeric results you may decide to return a single value and ignore the rest, or you can pass parameters by reference so that the function can fill them. For example:
void ComputeMeanAndRMS(TH1* h, double& mean, double& rms)
{
if (!h) { mean = 0.0; rms = 0.0; return; }
mean = h->GetMean();
rms = h->GetRMS();
}
void AnalyzeHistogram()
{
TH1F* h = new TH1F("h", "Example", 100, 0, 1);
// Fill h here
double mean = 0.0;
double rms = 0.0;
ComputeMeanAndRMS(h, mean, rms);
std::cout << "Mean = " << mean << ", RMS = " << rms << std::endl;
}
Here mean and rms are passed by reference using &. Inside ComputeMeanAndRMS the function writes into these variables and the caller sees the updated values.
When passing parameters to functions, match the types and order exactly, avoid unnecessary global variables, and prefer passing objects and values explicitly so that the function is easy to reuse and test.
In many analysis functions, you also pass configuration parameters such as number of bins, axis ranges, or selection cuts. This makes it possible to reuse the same function for different datasets and studies.
TH1F* MakeSpectrum(const char* name, const char* title,
int nbins, double xmin, double xmax,
const char* xTitle)
{
TH1F* h = new TH1F(name, title, nbins, xmin, xmax);
h->GetXaxis()->SetTitle(xTitle);
h->GetYaxis()->SetTitle("Events");
return h;
}You can now create spectra with different binnings or axis labels simply by passing different arguments:
TH1F* hEnergy = MakeSpectrum("hEnergy", "Energy spectrum", 200, 0.0, 200.0, "Energy [MeV]");
TH1F* hTime = MakeSpectrum("hTime", "Time spectrum", 100, 0.0, 100.0, "Time [ns]");If you find that you always use the same values for some parameters, you can define overloaded functions or use default arguments in the function declaration. For compiled macros you can write:
TH1F* MakeSpectrum(const char* name, const char* title,
int nbins = 100, double xmin = 0.0, double xmax = 1.0)
{
TH1F* h = new TH1F(name, title, nbins, xmin, xmax);
return h;
}
Now you can call MakeSpectrum("h", "Default spectrum") and the default binning will be used. ROOT’s interpreter also supports default arguments, but it is safer to keep the function declarations simple until you are comfortable with compiled macros.
Passing parameters is also important for the entry function of your macro. ROOT allows you to define a macro function that takes arguments, then call it with different settings:
void RunSelection(double ptMin = 0.5, double ptMax = 10.0)
{
std::cout << "Running selection with pT in [" << ptMin << ", " << ptMax << "]" << std::endl;
// Use ptMin and ptMax in your selection cuts
}From the ROOT prompt you can run:
root[0] .L RunSelection.C
root[1] RunSelection() // uses default values
root[2] RunSelection(1.0, 20.0) // override defaultsThis lets you reuse the same macro code for many variations of an analysis by changing only the parameters, not the body of the macro.
Views: 12
KAHIBARO