Walkthrough: Using a C++ TTreeReader to analyze a Tree

(10 minutes)

Why TTreeReader?

If you look through the ROOT TTree examples, you’ll see that using TTreeReader is the preferred method for reading n-tuples and TTrees.

I’ve prepared an example to get you started. In your UNIX window, copy AnalyzeReader.C from my home directory:1

> cp ~seligman/root-class/AnalyzeReader.C

Take a look:

> less AnalyzeReader.C

Here’s an abbreviated version of that file, without some of the comments: 2

Listing 42: Example C++ TTreeReader macro
void AnalyzeReader() {

  //******** Definition section *********

  //******** Initialization section *********

  // Open the file containing the tree.
  TFile* myFile = TFile::Open("experiment.root");

  // Create a TTreeReader for the TTree "tree1" in 'myFile'.
  TTreeReader myReader("tree1", myFile);

  // Tell the TTreeReader which branches (or columns) we'll read.
  TTreeReaderValue<Float_t> ebeam(myReader, "ebeam");

  // Loop over all the entries of the TTree.
  while (myReader.Next()) {
    
    //******** Loop section *********

  }

  //******** Wrap-up section *********
}

Compare this with the Python code in Listing 39.

While you look through the code, it helps to understand the typical steps needed for an analysis task:

  • Definition – define the variables we’re going to use.

  • Initialization - open files, create histograms, etc.

  • Loop - for each event in the n-tuple or Tree, perform some tasks: calculate values, apply cuts, fill histograms, etc.

  • Wrap-up - display results, save histograms, etc.

For this macro, the Definition and Initialization parts aren’t really distinct, the way they are in a TSelector macro.

A closer look (optional)

Let’s take a more abstract view of the key statements in Listing 42:

TFile* file = TFile::Open(NameOfFile);

This opens a ROOT file (in read-only mode by default).

  • file = A variable that contains the pointer to a ROOT file.

  • NameOfFile = A C-style string constant containing the name of the ROOT file.

TTreeReader myReader(NtupleName, file);
  • myReader = The name of the TTreeReader variable. This will be used to fetch the contents of an n-tuple from the file.

  • NtupleName = A C-style text string with the name of the n-tuple within the file.

TTreeReaderValue<TYPE> branchVariable(myReader, BranchName);
  • TYPE = This is the C++ type of the variable stored in the branch (or column) in the n-tuple; e.g., int, float, double, etc.

  • branchVariable = A variable that acts like a pointer to the value of the given branch.

  • BranchName = A C-style text string that contains the name of the branch in the n-tuple.

In a typical analysis task, you’ll have one TTreeReaderValue<T> definition for every branch you want to access.

How do you know the names and types of the branches? What I usually do is load the file in interactive ROOT and use the TTree::Print() method:3

> root experiment.root
[] tree1->Print()

When I type those commands, what I see is this:

******************************************************************************
*Tree    :tree1     : Reconstruction ntuple                                  *
*Entries :   100000 : Total =         2810761 bytes  File  Size =    2171135 *
*        :          : Tree compression factor =   1.30                       *
******************************************************************************
*Br    0 :event     : event/I                                                *
*Entries :   100000 : Total  Size=     401519 bytes  File Size  =     134514 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   2.85     *
*............................................................................*
*Br    1 :ebeam     : ebeam/F                                                *
*Entries :   100000 : Total  Size=     401519 bytes  File Size  =     260330 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   1.47     *
*............................................................................*
*Br    2 :px        : px/F                                                   *
*Entries :   100000 : Total  Size=     401465 bytes  File Size  =     359238 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   1.07     *
*............................................................................*
*Br    3 :py        : py/F                                                   *
*Entries :   100000 : Total  Size=     401465 bytes  File Size  =     359138 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   1.07     *
*............................................................................*
*Br    4 :pz        : pz/F                                                   *
*Entries :   100000 : Total  Size=     401465 bytes  File Size  =     292046 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   1.31     *
*............................................................................*
*Br    5 :zv        : zv/F                                                   *
*Entries :   100000 : Total  Size=     401465 bytes  File Size  =     349087 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   1.10     *
*............................................................................*
*Br    6 :chi2      : chi2/F                                                 *
*Entries :   100000 : Total  Size=     401501 bytes  File Size  =     321049 *
*Baskets :       12 : Basket Size=      32000 bytes  Compression=   1.20     *
*............................................................................*

Most of what you see is not relevant to this tutorial; for example, you’re not going to care about “Baskets” unless you want to optimize ROOT I/O for gigabyte-size files. What’s important here are the names of the branches; e.g.;

*Br    6 :chi2      : chi2/F

tells you that the sixth branch’s name is chi2. More important, it also tells you the type of the variable: chi2/F means that type of chi2 is float. So if you wanted to access the value of chi2, you’d include a statement like:

TTreeReaderValue<Float_t> chi2(myReader, "chi2");

You can find the correspondence between the C++ types and those one-letter abbreviations on the TTree documentation page.


1

If you’re not at Nevis, you can download it.

2

If you’re working through all the sections in this tutorial, Your first reaction on looking at this code is that it doesn’t look much like Analyze.C, the code that we saw in Listing 41.

It’s actually the same code, but it’s arranged differently. In that walkthrough, I have you use MakeSelector to create an “code skeleton” for you. The TTreeReader and TTReaderValue declarations were “hidden” in Analyze.h, so you could focus on the event loop.

Listing 42 is simpler, but I chose not to introduce a newcomer C++-based analysis with this code because:

  • It uses a template (as in TTreeReaderValue<double>). For students who are relatively new to C++, this can be confusing.

  • TTreeReader and TTReaderValue are not normally used in Python/pyroot. I want the C++ code and the Python code (in Listing 39) to resemble each other.

3

Don’t forget that I use the different prompts to indicate whether you’re running in UNIX or ROOT. Don’t actually type the > or []!