Interpreted ROOT dictionaries
I find that the ROOT web site documentation for creating a dictionary is a bit opaque. That’s why I had to write my little test suite to understand how it worked. It’s also why I’m sharing it with you.
Part of the reason why I have difficulty with that page is that the method it discusses first, ACLiC, assumes that you’re working with ROOT macros interactively. As you know from Exercise 11, I prefer to use ROOT by compiling stand-alone programs. Also, ACLiC does not fit well with using Python/pyroot.
So let’s consider two other ways to create a ROOT dictionary:
Using an interpreter, which means that the dictionary is created at the time the program executes. That’s discussed on this web page.
Compiled, which means that the dictionary is compiled into a binary load library, and is linked when the C++ program is compiled. That discussed on the next web page.
When it comes to reading ROOT files using a dictionary, either method works with Python. I’ll show examples later.
The example custom class
To start, you’ll want to copy my dictionary-testing directory to your own area:1
cp -arv ~seligman/root-class/root-dict $PWD
cd root-dict
# Suggestion: Delete the existing dictionary files
# to see the dictionary-generation process in action.
rm -f Auto* *.pcm *.so
Start with the file
STL_TrackList.h.
This shows a simplistic example of how data might be structured for
particle tracks in a detector: a struct
that describes a single
track with multiple steps/scatters, and a class that contains a list
of multiple tracks and provides access to that list.
Creating the dictionary
The file
STLntupleCreate.cxx
creates a TTree with several branches.2 The first couple of branches,
run
and event
, are of type int
and don’t require any
special handling beyond what is shown in Listing 58. The next two branches, a vector<int>
and a
vector<double>
, also don’t require anything special.
The remaining three branches all involve more complex structures that
need a dictionary for ROOT I/O. For the description of vMap_t
and
map2D_t
, see the file
STLntupleTypes.h.3
The actual dictionary generation is in STLntuple.icc. I put the dictionary-generation code in a separate file as a simplistic way to assure that any C++ program I wrote for this test would use the same commands.
The “magic” happens with these lines:
gInterpreter->GenerateDictionary("map<int,double>", "map");
gInterpreter->GenerateDictionary("map<tuple<int,int>, double>", "map;tuple");
gInterpreter->GenerateDictionary("TrackList", "STL_TrackList.h;map;vector");
The ROOT global variable gInterpreter
points to an instance of a
global command-line
interpreter. You
can think of this as the program that would interpret ROOT commands
line-by-line as you typed them into an interactive ROOT session (even
though none of these programs are interactive).
As the comments in the file indicate, we’re actually generating three dictionaries, one for each branch in the TTree with a special C++ object.
What does TInterpreter::GenerateDictionary
actually do? It uses
ROOT’s internal C++ interpreter,
cling, to interpret the C++ code in
the first argument, using the header files given in the second
argument. It breaks those structures down into the primitive
leaves that ROOT
already knows how to write.
This operation is using an interpreter instead of a compiler, so it takes some time. Fortunately, ROOT’s dictionary generator is pretty smart: If a dictionary for a given object already exists in the current directory and has not changed, it won’t go through the process again.
You can test this if you like. Use the compilation statement at the
top of STLntupleCreate.cxx
:
g++ `root-config --cflags --libs` STLntupleCreate.cxx -o STLntupleCreate
Execute the program with:
./STLntupleCreate
There will be a delay as ROOT creates the dictionaries for the
first time.4 If you just hit the up-arrow to execute the command again, or
compile-and-execute the example in the next section, the program will
execute almost immediately. If you update any of the header files in
the GenerateDictionary
method; e.g.,:
touch STL_TrackList.h
then if you re-run the programs there’ll be another long pause as the dictionary is generated again.
If you type ls
, you’ll see that new files with names of the form
AutoDict_*.cxx
have been created in your directory. As you’ve
probably guessed, these contain the C++ source code for the
dictionaries. TInterpreter::GenerateDictionary
created those files,
then used TCling
to interpret them and create the libraries
AutoDict_*.so
and auxiliary files AutoDict_*.pcm
and
AutoDict_*.d
. Those libaries are then loaded when ROOT does any I/O
of these custom data collections.
Reading with the dictionary
Reading a TTree
with a dictionary is roughly the same process as
creating the TTree
: Load the dictionary, then use the standard
ROOT methods for input.
Let’s start with a quick skeleton of reading a variable from an n-tuple.
// Define the input file.
auto input = new TFile("experiment.root");
// Define the which TTree to read.
TTreeReader myReader("tree1", input);
// Define which variable(s) we'll read. This behaves like a pointer;
// in the code we'll use "*ebeam".
TTreeReaderValue<double> ebeam(myReader, "ebeam");
// For each row in the TTree:
while (myReader.Next()) {
// ... do something with *ebeam ...
}
In
STLntupleRead.cxx
we see a more extended example of Listing 60, using the classes defined in
STL_TrackList.h
and the dictionary-generation code in
STLntuple.icc. If
you’re running these examples in sequence, note that that if you
previously generated the dictionary when running STLntupleCreate
,
STLntupleRead
will execute quickly, since it won’t regenerate the
dictionary if STL_TrackList.h
is not changed.
If you went through The RDataframe Path, you may be interested in my
test program
STLntupleRDF.cxx. Since
“upgrading” the STLntupleRead
code seemed too simple to me, I
upgraded my self-appointed exercise to include an additional feature:
using RDataFrame
in multi-threaded mode to accumulate histograms.
If you’re a Python programmer, you’ll want to focus on
STLntupleRead.py.
This has the identical functionality of STLntupleRead
. This
demonstrates that it only takes a minor “pythonization” of the C++ ROOT
code to use dictionaries in Python.
- 1
If you’re not at Nevis, you can download the files from here.
- 2
If you look at the comments and the overall organization of the
STL*.cxx
files in these examples, you may correctly deduce that they also illustrate the solution to Exercise 11, the stand-alone C++ program. If you gave up on that exercise and decided to skim the appendices instead, congratulations! You have the solution!- 3
I’ll be the first to admit that these type names aren’t very good. In real code, I’d probably use names that defined the kind of data represented by the collection; e.g.,
WireGrid_type
. Remember, all of this represents a quick study on my part.- 4
If there is no delay, or you get some kind of load library error, you probably copied over the AutoDict files from my directory. You may want to delete them so you can see for yourself how the process works:
rm -f AutoDict*