PyROOT
In the previous sections, we have introduced some basics of Python to you. Now, it's time to make the connection between ROOT and Python, and see why Python can be pretty convenient.
Python bindings
Many existing third-party applications and libraries have so-called "Python bindings".
Bindings
Binding generally refers to a mapping of one thing to another. In the context of software libraries, bindings are wrapper libraries that bridge two programming languages, so that a library written for one language can be used in another language.
Many software libraries are written in system programming languages such as C or C++. To use such libraries from another language, usually of higher-level, a binding to the library must be created in that language, possibly requiring recompiling the language's code, depending on the amount of modification needed. However, most languages offer a foreign function interface, such as Python's ctypes.
PyROOT provides Python bindings for ROOT:
- it enables cross-calls from ROOT/Cling into Python and vice versa,
- the intermingling of the two interpreters, and the
- transport of user-level objects from one interpreter to the other.
PyROOT enables access from ROOT to any application or library that itself has Python bindings, and it makes all ROOT functionality directly available from the python interpreter.
PyROOT, a Python extension module, provides the bindings for the ROOT class library in a generic way using the Cling dictionary. This way, it allows the use of any ROOT classes from the Python interpreter, and thus the “glue-ing” of ROOT libraries with any non-ROOT library or applications that provide Python bindings.
Access to ROOT from Python
There are several tools for scientific analysis that come with bindings that allow the use of these tools from the Python interpreter. PyROOT provides this for users who want to do analysis in Python with ROOT classes. The following example shows how to fill and display a ROOT histogram while working in Python. Of course, any actual analysis code may come from somewhere else through other bindings, e.g. from a C++ program.
When run, the next example will display a 1-dimensional histogram showing a Gaussian distribution.
from ROOT import gRandom,TCanvas,TH1F
c1 = TCanvas('c1','Example',200,10,700,500)
hpx = TH1F('hpx','px',100,-4,4)
for i in xrange(25000):
px = gRandom.Gaus()
hpx.Fill(px)
hpx.Draw()
c1.Update()
Make sure that PyROOT is enabled
To make use of the Python Bindings of ROOT, make sure that the build of the PyROOT module is enabeled when you compile ROOT. If you use ROOT through aliBuild, ROOT6 is built with python support out-of-the-box. For ROOT5 based builds, PyROOT support is disabeled by default in aliBuild. For a manual build of ROOT, Python bindings are enabled by default, but can be disabled via the CMake options.
To load the ROOT module in your Python code, make sure that libPyROOT.so
and the ROOT.py
module can be resolved by the system by entering your ROOT environment.
Access to ROOT classes
Before a ROOT class can be used from Python, its dictionary needs to be loaded into the current process. This happens automatically for all classes that are declared to the auto-loading mechanism through so-called rootmap files. Effectively, this means that all classes in the ROOT distributions are directly available for import.
For example:
from ROOT import TCanvas # available at startup
c = TCanvas()
from ROOT import TLorentzVector # triggers auto-load of libPhysics
l = TLorentzVector()
Although it is not recommended, a simple way of working with PyROOT is doing a global import:
from ROOT import *
c = TCanvas()
l = TLorentzVector()
Keeping the ROOT namespace (“import ROOT”), or only importing from ROOT those classes that you will actually use (see above), however, will always be cleaner and clearer:
import ROOT
c = ROOT.TCanvas()
l = ROOT.TLorentzVector()
Some simple examples
from ROOT import TF1, TCanvas
class Linear:
def __call__( self, x, par ):
return par[0] + x[0]*par[1]
# create a linear function with offset 5, and pitch 2
f = TF1('pyf2',Linear(),-1.,1.,2)
f.SetParameters(5.,2.)
# plot the function
c = TCanvas()
f.Draw()
Fitting a histogram
You have seen in the previous section that we can resolve all ROOT classes through python bindings.
Fitting a histogram
Take the code example from the previous section as a starting point, and write a simple piece of python code that
- defines a linear function
- fills a histogram with some linearly distributed data
- fits the linear function to the histogram
Click to check possible solutionClick to expand
Advanced examples: working with trees
Next to making histograms, working with trees is probably the most common part of any analysis. The TTree implementation uses pointers and dedicated buffers to reduce the memory usage and to speed up access. Consequently, mapping TTree functionality to Python is not straightforward.
But first ... create a tree
To run the Python code below, you need a small tree
Click here for a generic example which fills a suitable treeClick to expand
So, let's assume that you have cooked up a tree using the mini-code above, or you have a tree of your own. Below is a snippet of Python code that will read info from the branches of your tree and prints some extracted information to the screen. If you are interested, you can try to visualize the data in histograms.
from ROOT import TFile
# open the file
myfile = TFile('tree1.root')
# retrieve the ntuple of interest
mychain = myfile.Get('t1')
entries = mychain.GetEntriesFast()
for jentry in xrange(entries):
# get the next tree in the chain and verify
ientry = mychain.LoadTree(jentry)
if ientry < 0:
break
# copy next entry into memory and verify
nb = mychain.GetEntry(jentry)
if nb<=0:
continue
# use the values directly from the tree
nEvent = int(mychain.ev)
if nEvent<0:
continue
print(mychain.pz, '=', mychain.px*mychain.px, '+', mychain.py*mychain.py)
Writing a tree
Writing a ROOT TTree in a Python session is, like reading it, a little convoluted, if only because you will need a C++ class to make sure that data members can be mapped, unless you are working with built-in types. Here is an example for working with the latter only:
from ROOT import TFile, TTree
from array import array
h = TH1F('h1','test',100,-10.,10.)
f = TFile('test.root','recreate')
t = TTree('t1','tree with histos')
maxn = 10
n = array('i',[0])
d = array('f',maxn*[0.])
t.Branch('mynum',n,'mynum/I')
t.Branch('myval',d,'myval[mynum]/F')
for i in range(25):
n[0] = min(i,maxn)
for j in range(n[0]):
d[j] = i*0.1+j
t.Fill()
f.Write()
f.Close()
The use of arrays is needed, because the pointer to the address of the object that is used for filling must be given to the TTree::Branch() call, even though the formal argument is declared a ’void*'.
In the case of ROOT objects, similar pointer manipulation is unnecessary, because the full type information is available, and TTree::Branch() has been Pythonized to take care of the call details.
However, data members of such objects that are of built-in types, still require something extra since they are normally translated to Python primitive types on access and hence their address cannot be taken.
Creating your own classes
A user’s own classes can be accessed after loading the library that contains the dictionary.
Let's first make up a very small class, by putting in a file MyClass.C
the following code
class MyClass {
public:
MyClass(int value = 0) {
m_value = value;
}
void SetValue(int value) {
m_value = value;
}
int GetValue() {
return m_value;
}
private:
int m_value;
};
Create the shared library using ACLiC:
root -e 'gInterpreter->LoadMacro("MyClass.C+");' -q
Optimization or debug symbols ?
Anyone have any idea what the difference is between
root -e 'gInterpreter->LoadMacro("MyClass.C+");' -q
and
root -e 'gInterpreter->LoadMacro("MyClass.C+g");' -q
Drum rollClick to expand
We have now compiled our class. If we want to access it with Python, we can e.g. do
from ROOT import gSystem
# load library with MyClass dictionary
gSystem.Load('MyClass_C')
# get MyClass from ROOT
from ROOT import MyClass
# use MyClass
m = MyClass(42)
print(m.GetValue())
You can also load a macro directly, but then you will be restricted to use the default constructor of your class, which is otherwise fully functional. For example:
from ROOT import gROOT
# load MyClass definition macro (append '+' to compile)
gROOT.LoadMacro('MyClass.C')
# get MyClass from ROOT
from ROOT import MyClass
# use MyClass
m = MyClass()
m.SetValue(42)
print(m.GetValue())
So ... why would I use this?
Whether or not you to use PyROOT is fully up to you: most people find the syntax easier than that of C++, and of course on does not have to worry about memory management when you use Python.
With the advent of ROOT6, an attractive feature of PyROOT can be speed
- CLING will compile your macro's line-by-line, which can sometimes lead to sluggish execution
- PyROOT is interpreted
Python code is often ran as an interpreted language, it can be compiled, but you do don't need to compile it. Interpretation of macros is often much faster than compilation plus execution, unless the performance gain gained by compiling out-weighs the time the compiler spends on compiling the code.
Since macro's are often intended to do only simple things (or call optimized libraries to carry out the heavy work, such as fitting), interpretation often suffices.