Retrieving TVector3 from a Tree

ROOT Version: 6.28/04
Platform: linux x86 64
Compiler: gcc

Hi, as I understand it, the following is how I can create a new root file which has a tree that contains a TVector3 in it.

void write(){

  TFile * f = new TFile("out.root","RECREATE");
  TTree * t = new TTree("my_tree", "my_tree_title");
  TVector3 myVec(1,2,3);

  t->Branch("my_branch", &myVec);

  t->Fill();

  f->Write();
  f->Close();

}

And to retrieve the tree, we can do something like

void read(){

  TFile *inFile = TFile::Open("out.root");
  TTree *inTree = (TTree*) inFile->Get("my_tree");
  TVector3*inVec_ptr=nullptr;

  inTree->SetBranchAddress("my_branch",&inVec_ptr);
  inTree->GetEntry(0);

  std::cout<< inVec_ptr->X() << " " << inVec_ptr->Y() << " " << inVec_ptr->Z() << std::endl;

}

However, I am not sure why inVec_ptr above has to be a pointer though (sorry I am pretty new to C++ and ROOT). As I understand it, to retrieve a double, say, one would write

double my_variable;
inTree->SetBranchAddress("my_variable_name", &my_variable);

which in this case my_variable is not a pointer.

Is it because TVector3 is a larger “thing” than something like a double?

I am also not sure why the pointer inVec_ptr has to be initialized to a nullptr.

Any help would be greatly appreciated!

Hello,

Welcome to the ROOT community!

Thanks for the interesting question. The modern and preferred way to treat columnar datasets, i.e. TTrees, is through RDataFrame. It allows you to treat datasets, without bothering with the details, in a high level but yet very performant way.

Below, you find an example code that uses RDataFrame starting from the rootfile you can create with your write function above:

void read(){
  auto df = ROOT::RDataFrame("my_tree", "out.root");
  auto myPrintFcn = [](const TVector3& v){std::cout<< v.X() << " " << v.Y() << " " << v.Z() << std::endl;};
  df.Foreach(myPrintFcn, {"my_branch"});
  // outputs 1 2 3 
}

I hope this is helpful!

Cheers,
Danilo

1 Like

Hi again,

Just for completeness: with RDataFrame, it’s also possible to create datasets. Below I try to propose a RDataFrame based version of your write function:

void write()
{
   const auto nEvts = 1;
   auto myIdx = 0;
   auto df = ROOT::RDataFrame(nEvts);
   df.Define("my_branch",
             [&myIdx](){myIdx++;return TVector3(myIdx, myIdx + 1, myIdx + 2);})
      .Snapshot("my_tree", "out.root");
}

Of course, the data is not very meaningful: hopefully the example is useful.

Cheers,
Danilo

1 Like

Hi Danilo,

Many thanks for the pointer! I will give RDataFrame’s doc a read and try it out.

But I am still confused as to why in the old approach we need to use the address of a null pointer of TVector3:

TVector*myVec_ptr=nullptr;

instead of an address of just a TVecror3 when we
SetBranchAddress(...)

Is this just how C++ does things when it comes to the “non-primitive” types?

Sincerely,
Jason

Hi @unmovingcastle,

it’s designed like the because ROOT needs to support general types in TTrees and avoid unnecessary copies.

Assume you have a TVector myVec when reading, instead of a point. Then, when getting the TTree entries, ROOT would have to copy-assign to this myVec, from the TVector inside the TTree, right? That would cause some overhead, and only support types that are copy-assignable.

So instead ROOT expects you do do this:

  TVector3*inVec_ptr=nullptr;

  inTree->SetBranchAddress("my_branch",&inVec_ptr);
  inTree->GetEntry(0);

So what’s going on? You create some pointer to a TVector3, and then you pass it to ROOT by pointer (e.g. ROOT gets a pointer to a pointer to a TVector). This allows ROOT to modify the pointer when you call GetEntry() to not be a nullptr anymore, but to point to the actual object in the TTree for the corresponding entry.

For “primitive” types like floating point, integer and bool types it’s different yes. You have to pass a pointer to the variable directly and then ROOT will update the variable when loading the entries. That’s because the situation is different there: there types are so small that the values can be quickly copied, while using also this updating of pointers would have the larger overhead.

I hope this taught you something about C++ and ROOT :slight_smile:

Cheers,
Jonas

1 Like

Hi Jonas,

Thank you very much for the detailed explanation!

All the best
Jason

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.