KAHIBARO
Discord Login Register

19.4. Event Analysis

Applying cuts

In ROOT, an event is usually represented by one entry in a TTree. When you analyze Geant4 results, you will often want to keep only events that satisfy certain physical or detector conditions. These conditions are called cuts. Conceptually, a cut is a logical expression that is either true or false for each event.

The simplest way to apply cuts in ROOT is through the TTree::Draw or TTree::GetEntry loop, using a selection string. Suppose your Geant4 application created a TTree called "Events" in a file "output.root" and that the tree contains branches such as "eventID", "edep", "x", "y", "z", "particle", and "time". In an interactive ROOT session you can apply cuts directly in a draw command:

cpp
Events->Draw("edep", "edep > 0");

This fills a histogram with energy deposition only for events with strictly positive "edep". The second argument is the cut, written as a C++ boolean expression using branch names. You can combine several conditions:

cpp
Events->Draw("edep", "edep > 0 && x > -5 && x < 5 && z > 0");

This cut keeps only events with nonzero energy deposit and with positions inside a specific region of the detector. Logical operators are written as in C++, such as "&&" for AND, "||" for OR, and "!" for NOT. Comparisons use ">", "<", ">=", "<=", and "==". You can also use mathematical functions, for example "sqrt(xx + yy) < 10" to select a cylinder of radius 10 around the beam axis.

Cuts are not limited to numeric variables. You can write conditions that depend on particle names or identifiers. If you stored particle type as a string branch "particle", you might write "particle == \"gamma\"" in a cut. If instead you stored integer PDG codes, you would use numeric comparisons such as "pdg == 11" for electrons.

ROOT lets you define named cuts to reuse them. In C++:

cpp
TCut centralRegion = "sqrt(x*x + y*y) < 10";
TCut energyCut     = "edep > 0.1 && edep < 1.0";
Events->Draw("edep", centralRegion && energyCut);

This is especially convenient when you analyze data from many files or run the same selection repeatedly. You can also combine cuts with weights, for example to form a weighted histogram:

cpp
Events->Draw("edep >> hWeighted(100,0,2)", "edep > 0 && weight", "goff");

where "weight" is a branch that contains an event weight. The "goff" option disables graphics output and is useful in batch mode.

In more complex analyses you often write a dedicated C++ or Python script that loops over tree entries, evaluates cuts explicitly, and fills histograms or ntuples. In this case the logic looks like:

cpp
for (Long64_t i = 0; i < Events->GetEntries(); ++i) {
  Events->GetEntry(i);
  if (edep <= 0.1) continue;
  if (fabs(z) > 50.0) continue;
  // Fill histograms
}

This approach gives you full control over selection flow and can be easier to maintain for long analyses.

Important rule: Always define and document cuts in physics terms first, then translate them into ROOT expressions. This reduces the risk of silently applying the wrong selection and misinterpreting your Geant4 simulation results.

When you design cuts, you should keep in mind that every cut removes events and therefore increases the statistical uncertainty of your remaining sample. It is useful to check how many events survive each stage of selection. A common pattern is to count entries before and after a cut with TTree::GetEntries:

cpp
Long64_t nAll   = Events->GetEntries();
Long64_t nPass  = Events->GetEntries("edep > 0.1");
double   eff    = double(nPass) / nAll;

You can then quote the efficiency of each cut and understand how strongly you are filtering your sample.

There is an important distinction between event level cuts and object level cuts. An event may contain several detector hits or several tracks. If your tree structure stores hits in a separate TTree or as arrays within one entry, you should decide whether a cut applies to the event as a whole or just to a single hit. For example, you might want to keep all events that contain at least one hit with "edep > 0.5 MeV", but you still want to keep information about other hits in the same event. In that case, your selection code should test all hits before discarding the event.

In ROOT, cuts can also be used for projections. For instance, you can project a 2D correlation only for a subset of events:

cpp
Events->Draw("y:x", "edep > 0.2 && particle == 22");

which creates a scatter plot of y versus x for gamma interactions with energy deposition above 0.2. Similarly, you can build conditional spectra such as "energy spectrum in a given detector module" by including the detector ID in the cut expression.

Key statement: Cuts define which subset of simulated events you interpret as "measured data". Changing cuts can strongly affect derived quantities such as efficiency, resolution, and background level, so treat them as part of the physics definition of your analysis.

Selecting particles

Geant4 simulations typically contain many particle types and interaction products, but for a given analysis only some of them are relevant. In ROOT, selecting particles means building cuts that keep only tracks, hits, or events associated with certain particle properties.

In your Geant4 application you usually decide how to encode particle identity in the output. Common choices are particle name as a string, PDG code as an integer, or separate flags indicating primaries and secondaries. For example, you might write a hit ntuple with branches called "pdg", "isPrimary", "parentID", and "trackID". During event analysis in ROOT you then use these branches in selection expressions.

If you store PDG codes, each particle type is identified by a unique integer. For common particles:

ParticlePDG code
gamma22
electron $e^-$11
positron $e^+$-11
proton2212
neutron2112
muon $\\mu^-$13
muon $\\mu^+$-13

You can select only electrons with:

cpp
Hits->Draw("edep", "pdg == 11");

or only gammas with:

cpp
Hits->Draw("edep", "pdg == 22");

To group particles, you can combine conditions, for example to select all charged leptons:

cpp
Hits->Draw("edep", "pdg == 11 || pdg == -11 || pdg == 13 || pdg == -13");

If you stored particle names as strings, selection uses string comparison:

cpp
Hits->Draw("edep", "particle == \"gamma\"");

Be careful with spelling and capitalization. Particle names should be consistent with what your Geant4 code writes, for example "gamma", "e-", "proton", and not arbitrary labels.

Frequently you need to distinguish primary particles from secondaries. If you wrote a branch "parentID", where primary particles have "parentID == 0", you can select primaries with:

cpp
Tracks->Draw("energy", "parentID == 0");

and secondaries with:

cpp
Tracks->Draw("energy", "parentID != 0");

Alternatively you might store a boolean "isPrimary". Similarly, you can follow the ancestry of secondaries by recording parent and track IDs during simulation, then applying cuts such as "parentID == someTrackID" in ROOT.

For event level analyses, you might need to select events according to the presence of a given particle. Suppose you have an event TTree and a separate hits TTree. One approach is to loop over hits in C++, group them by eventID, and decide whether each event contains at least one particle of interest. You can then write out a new TTree that contains only those events, or define a boolean event flag branch such as "hasGamma". The ROOT logic then becomes straightforward:

cpp
Events->Draw("edep", "hasGamma");

Another common pattern is to analyze only particles in a certain energy or angular range. In that case you combine particle selection with kinematic cuts. For example, to select forward protons:

cpp
Tracks->Draw("energy", "pdg == 2212 && theta < 20*TMath::DegToRad()");

where "theta" is the polar angle in radians. You can also restrict to particles passing through specific detector elements using detector IDs.

Important rule: Always record enough information in your Geant4 ntuples to distinguish particle type and origin, for example PDG code and parent ID. Without this you will not be able to perform precise particle selection during ROOT event analysis.

When you build physics observables such as energy spectra, angular distributions, or time of flight histograms, particle selection is part of the definition of the observable. An "electron energy spectrum" must explicitly require "pdg == 11". A "prompt gamma time distribution" must include both particle identity and a timing cut, such as "pdg == 22 && time < 10 ns". In practice you will often write compact selection expressions like:

cpp
Hits->Draw("time", "pdg == 22 && edep > 0.05 && detectorID == 3");

This selects gamma hits above a minimum energy threshold in one detector element.

If you have multiple detector regions with different materials, you might want to compare how different particles behave in each region. You can do so by filling separate histograms with different particle selections, or by using 2D plots with particle identity on one axis. Another useful technique is to overlay histograms for different particle types in one canvas, for instance electrons vs gammas, to study backgrounds and signal composition.

Finally, particle selection is tightly connected to background rejection. In realistic simulations, undesired particles may enter your sensitive volume. By selecting only the particle types and energies that correspond to your measurement, you can reduce simulated background contributions and obtain distributions that are easier to interpret and compare with experimental data.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!