KAHIBARO
Discord Login Register

20.1. Trees with Multiple Branches

Complex event structures

In realistic analyses the information for a single event is rarely stored in a single number. A detector readout for one event can contain many related quantities, for example the energy in several calorimeter cells, the time of a trigger, the number of reconstructed tracks, and the parameters of each track. A TTree naturally represents this situation by grouping several branches that all share a common entry index. Entry $i$ in the tree corresponds to event $i$, and all branches at that entry refer to the same physical event.

A tree with multiple branches is therefore a way to store a structured event record. Each branch typically holds a single variable or a small, related group of variables. If you have, for example, a scalar run number, an integer with the number of tracks, and floating point values for total energy or missing transverse energy, you usually create separate branches for each of these. When you call Fill() on the tree, ROOT takes the current values of all branch variables and writes them as a consistent event snapshot.

You are free to mix data types across branches. A typical event structure might have integer branches for identifiers and counters, floating point branches for measured quantities, boolean branches for flags, and object branches for more complex types. From the tree point of view each branch is independent, but through the common entry index they form a coherent event structure.

Multiple branches become particularly important when you want to extend an existing dataset. You can add new branches without changing the existing ones, so you can compute new observables and store them alongside the original variables. When reading the tree you can choose which branches to access, which allows you to focus on the variables you need for a given analysis step and skip others.

The presence of many branches does not force you to process all of them on every pass. You can disable unused branches to avoid unnecessary I/O and speed up event loops, which is one of the main advantages of a tree compared with more rigid tabular formats. This selective reading is especially useful when the tree contains hundreds of branches but a particular analysis needs only a handful.

Conceptually, a multi-branch tree is similar to a table where each column is a branch and each row is an event. The difference is that branches can hold not only simple numbers but also arrays, vectors, and even complex C++ objects. This flexibility is what allows ROOT trees to represent detailed detector events with rich internal structure.

Arrays and vectors

Some event quantities cannot be described by a single scalar per event. A typical example is a variable number of reconstructed tracks, hits, or clusters. In such cases you need an event-wise collection. In a TTree this is usually implemented either with fixed-size C-style arrays or with std::vector objects stored in branches.

Fixed-size arrays are suitable when the maximum number of elements per event is known and small, and when you are willing to allocate the same space for every event. You typically store an integer branch to record how many elements are actually used in the current event, and a second branch that points to the array itself. In each call to Fill() you write the current value of the multiplicity together with the first n entries of the array. When reading, you check the stored multiplicity to know how many valid elements the array contains for a given entry.

This pattern gives you a structure where, for example, each event has a varying number of tracks but the array capacity is fixed by a compile-time constant. It is efficient in terms of memory layout and I/O, since ROOT can write the array values in contiguous blocks, but it forces you to choose an upper bound that must be large enough for all events. Events with fewer elements still reserve space up to that bound, which can lead to wasted storage for very sparse data.

In many modern analyses you instead use std::vector branches to represent variable-length per-event collections more naturally. With vectors you do not need to decide a fixed capacity in advance. For each event, you fill the vector with exactly as many elements as you have, and Fill() writes this dynamic content to the tree. When you read the branch back, the vector size for each entry reflects the actual multiplicity in that event.

Vectors integrate well with C++ loops and algorithms, and they are particularly convenient when the number of elements per event varies widely or is not known in advance. They also interact smoothly with higher level interfaces such as RDataFrame, where you can express transformations in terms of vector operations directly. ROOT has built-in support for common vector types, so they behave naturally when stored in branches, provided the appropriate headers and dictionaries are available at compile time or in the interpreter.

When you design a tree with arrays or vectors it is important to think about how you will access the data later. With arrays you usually rely on explicit loops over indices and you must be careful to respect the stored multiplicity for each entry. With vectors you can query the size at runtime and loop accordingly, which reduces the risk of out-of-range access. In both cases, each element in the collection is part of the same event, and you typically relate elements across branches by using the same index. For instance, element i in a pt vector branch and element i in an eta vector branch might belong to the same reconstructed track.

The choice between arrays and vectors involves a trade-off between rigid but simple fixed-size structures and flexible, dynamic collections. Arrays may be slightly more efficient for small, fixed-size data and can be easier to interface with older code, while vectors offer more convenience and natural handling of variable multiplicities. For most new analyses that work with variable numbers of objects per event, vectors are usually preferred.

Arrays in TTrees usually require a separate variable that stores the valid number of elements per event, and you must never read or process beyond that count. Vectors encode their own size per event, so you should always use the vector size for loop bounds when analysing variable-length event data.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!