Hi all,
Consider the following code:
// test.cpp
void test(){
int var1 = 1;
int var2 = 2;
int var3 = 3;
// there is a lot of variables to compute
auto file = std::make_unique<TFile>("results.root", "RECREATE");
auto tree = std::make_unique<TTree>("events", "events");
tree->Branch("var1", &var1);
tree->Branch("var2", &var2);
tree->Fill();
file->Write();
}
In this code I accidentally forgot to bind var3.
tree->Branch("var3", &var3);
I would like an advice on how can I catch such mistakes early on, ideally during the compilcation, rather than to discover I missed one after all of my X hour jobs are finished. Of course, I could add a dedicated testing, but I would like to achieve that with a minimum extra boilerplate code.
ChatGPT proposed me a nice alternative of writing the Event class:
struct Event {
int var1;
int var2;
int var3;
};
void test(){
auto event = Event(1, 2, 3);
auto file = std::make_unique<TFile>("results.root", "RECREATE");
auto tree = std::make_unique<TTree>("events", "events");
tree->Branch("Event", &event);
tree->Fill();
file->Write();
}
In such a way, the branches are created automatically and there is no possibility to forget anything, which I like.
However, this makes reading files in python quite annoying.
import ROOT
f = ROOT.TFile.Open("results.root")
t = f.Get("events")
for event in t:
# this I could do with the naked variables
print(event.var1)
# this I have to do if there is an intermediate branch
# and I get the "no dictionary for class Event" warning in addition
print(event.GetLeaf("var1").GetValue())
My question is:
Is there a middle ground?
Ideally I would like to define the TTree structure with my custom class w/o this intermediate Branch class.
As the root develops faster than the tutorials, maybe I am missing something that already exists ![]()
EDIT:
For my simple example I could also do something like this:
void test(){
int var1 = 1;
int var2 = 2;
int var3 = 3;
std::map<const char*, int*> vars = {
{"var1", &var1},
{"var2", &var2},
{"var3", &var3}
};
auto file = std::make_unique<TFile>("results.root", "RECREATE");
auto tree = std::make_unique<TTree>("events", "events");
for (const auto& [name, ptr] : vars) {
tree->Branch(name, ptr);
}
tree->Fill();
file->Write();
}
However, in my application the variables that I write have different types, so the actual problem I would like to improve looks more like this:
void test(){
bool is_good_event = true;
int n_tracks = 5;
int n_hits = 10;
std::string filename = "original.data";
float true_energy = 42.f;
std::vector<float> track_momenta = {1.f, 2.f, 3.f, 4.f, 5.f};
std::vector<float> hit_energies = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0};
std::vector<bool> is_good_hit = {true, true, true, false, true, true, true, false, false, true};
// and so on
auto file = std::make_unique<TFile>("results.root", "RECREATE");
auto tree = std::make_unique<TTree>("events", "events");
tree->Branch("is_good_event", &is_good_event);
tree->Branch("n_tracks", &n_tracks);
tree->Branch("n_hits", &n_hits);
tree->Branch("filename", &filename);
tree->Branch("true_energy", &true_energy);
tree->Branch("track_momenta", &track_momenta);
tree->Branch("hit_energies", &hit_energies);
tree->Branch("is_good_hit", &is_good_hit);
tree->Fill();
file->Write();
}