KAHIBARO
Discord Login Register

13.3. Filling a TTree

Event loops

Once you have created a TTree and its branches, the next step is to fill it with data. In a typical analysis or simulation this happens inside an event loop. Each iteration of the loop corresponds to one event, one measurement, or one row of your dataset. The role of the event loop is to compute or read all the quantities you want to store for that event, assign them to the variables connected to the branches, and then tell the TTree that a new entry is ready.

In ROOT, TTrees are usually filled in C++ macros or compiled programs. Before the loop, you create the TTree, define branches, and connect each branch to a C++ variable. Inside the loop, you update those variables and then call the tree filling routine. The loop itself is just standard C++ control flow, for example a for loop over event indices or a while loop that reads until the end of a file.

A simple but complete pattern for an event loop over a fixed number of simulated events looks like this:

cpp
const Int_t nEvents = 10000;
TFile *outfile = new TFile("example.root", "RECREATE");
TTree *tree = new TTree("tree", "Example TTree");
// Variables to be stored
Float_t energy;
Int_t   nHits;
// Branch definitions
tree->Branch("energy", &energy, "energy/F");
tree->Branch("nHits",  &nHits,  "nHits/I");
// Event loop
for (Int_t i = 0; i < nEvents; ++i) {
   // Compute or read event quantities
   energy = 0.5 * i;        // placeholder example
   nHits  = i % 10;         // placeholder example
   // Store this event in the tree
   tree->Fill();
}
// Write tree to file
tree->Write();
outfile->Close();

The essential structure is always the same. First, prepare branches and their associated variables. Second, run an event loop that fills these variables for each event. Third, after the loop, write the TTree to a file and close it.

Often, event loops are driven by external data. For instance, you may read lines from a text file, parse values, and assign them to branch variables. An event loop then continues until there is no more input. In such a case, the loop condition comes from an input stream rather than a simple event counter. The key idea stays the same: for each logical event, prepare values and then call the TTree filling function.

When filling TTrees from existing ROOT data, such as reading from another TTree to create a skimmed or derived tree, the event loop will iterate over entries of the input tree, use GetEntry to read them, compute any derived quantities, and then call Fill on the output tree. This pattern is central to many ROOT analyses and is the bridge between the concepts of events and entries in TTrees.

Important: Each iteration of the event loop must set all branch variables to consistent values before calling the tree filling routine. Uninitialized or leftover values will be written to the tree and can silently corrupt your dataset.

Calling `Fill()`

The TTree::Fill method is the core operation that actually writes one new entry into the tree. Once you have created branches and connected them to C++ variables, a single call to tree->Fill() reads the current contents of all those variables and copies them into a new entry.

The typical sequence inside one event iteration is:

  1. Assign values to all variables associated with branches.
  2. Call tree->Fill() exactly once to store that entry.

For example, with scalar branches:

cpp
Float_t energy;
Int_t   nHits;
tree->Branch("energy", &energy, "energy/F");
tree->Branch("nHits",  &nHits,  "nHits/I");
for (Int_t i = 0; i < nEvents; ++i) {
   energy = ComputeEnergy(i);
   nHits  = CountHits(i);
   tree->Fill();  // writes a new entry with these values
}

For each call to Fill, the TTree takes the current values of energy and nHits and appends them as a new row. The entry index increases automatically. You do not manually manage indices when filling.

If you use branches that refer to more complex objects, such as std::vector, the logic is similar. You fill the vector inside the loop, then call Fill. For example:

cpp
std::vector<Float_t> hitEnergies;
tree->Branch("hitEnergies", &hitEnergies);
for (Int_t i = 0; i < nEvents; ++i) {
   hitEnergies.clear();
   // Fill vector for this event
   Int_t nHits = GenerateNumberOfHits(i);
   for (Int_t j = 0; j < nHits; ++j) {
      hitEnergies.push_back(GenerateHitEnergy(i, j));
   }
   tree->Fill();
}

Again, Fill stores the contents of hitEnergies exactly as they are at the moment of the call. On the next event, you change the contents of the same vector object and call Fill again. The TTree keeps internal copies of the data for each entry, so later changes to the variable do not modify previous entries.

Sometimes, you may want to compute a quantity only for some events and leave it undefined for others. For simple numeric types it is common to assign a special value, for example a negative number that is outside any physical range, before calling Fill. The TTree has no concept of missing values by itself, it simply stores whatever is in the variable. Your analysis code must interpret any special sentinel values.

TTree::Fill returns an integer. A positive return value means that the entry was successfully filled. In basic usage you usually do not check this value, but in more advanced or long running jobs you might check the return code to detect write failures.

It is important to call Fill once for every event that you want in your tree. If you forget to call Fill inside the loop, or call it only under certain conditions, the number of entries in the tree will not match the number of iterations of your loop. This is sometimes intentional, for example when you only store events that pass a selection, but you should always be aware that each Fill corresponds exactly to one stored entry.

Rule: One call to TTree::Fill() creates one new entry using the current values of all branch variables. Always set the variables first, then call Fill(), and do not modify the variables again for that event after Fill() has been called.

After the event loop ends and all desired entries have been filled, you should write the TTree to a file. The Fill calls only build the tree in memory. A final call to Write on the file or on the tree, followed by closing the file, ensures that your filled TTree is safely stored for later analysis.

Views: 11

Comments

Please login to add a comment.

Don't have an account? Register now!