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:

Listing 59: The key C++ lines for generating a dictionary for STL_TrackList.h
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.

Listing 60: An example of how to read a variable from a TTree in C++. Compare this with Listing 42.
  // 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*