root-project / root

The official repository for ROOT: analyzing, storing and visualizing big data, scientifically
https://root.cern
Other
2.54k stars 1.23k forks source link

[ROOT_5159] Improve TTree documentation about SetMakeClass() #14583

Open vepadulano opened 5 months ago

vepadulano commented 5 months ago

Explain what you would like to see improved and how.

From https://its.cern.ch/jira/browse/ROOT-5159

It recently took me many many hours to figure out that my code could not read out a branch because TTree::SetMakeClass() was not set. As far as I can tell this is neither explained in the ROOT manual, nor the TTree documentation page. I found a old RootTalk message (http://root.cern.ch/root/roottalk/roottalk03/1853.html) regarding it, but only post mortem because one has to search for SetMakeClass, and at least this one had no clue that SetMakeClass was the problem to begin with.

Some explanation and warning about this feature in the TTree documentation would be very much appreciated.

Sue Ann Koay sakoay@cern.ch

ROOT version

Any

Installation method

Any

Operating system

Any

Additional context

No response

atolosadelgado commented 5 months ago

Hi @pcanal @vepadulano

I was not able to reproduce the original error. The following piece of code does the following:

If I understand correctly the OP and a later comment by @pcanal in the ROOT forum, the call tree->SetMakeClass(1); would be needed for the second case, however it works without it.

#include "TFile.h"
#include "TTree.h"
#include "TBranch.h"

#include <iostream>

class myclass
{
public:
    int x = {0};
    int y = {0};
};

void printTree_splitclass(const char *path) {
  std::cout << __FUNCTION__ << '\t';
  printf("%s:\n", path);
  std::unique_ptr<TFile> file(TFile::Open(path));
  auto *tree = file->Get<TTree>("tree");

  int x,y;
  tree->SetBranchAddress( "mybranch.x", &x );
  tree->SetBranchAddress( "mybranch.y", &y );
  //tree->SetMakeClass(1);
  for( int i =0; i< tree->GetEntries(); ++i)
  {
    tree->GetEntry(i);
    std::cout << '\t' << x << std::endl;
  }
}

void printTree_fullclass(const char *path) {
  std::cout << __FUNCTION__ << '\t';
  printf("%s:\n", path);
  std::unique_ptr<TFile> file(TFile::Open(path));
  auto *tree = file->Get<TTree>("tree");
  myclass * object4branch = new myclass();
  tree->SetBranchAddress( "mybranch", &object4branch );
  //tree->SetMakeClass(1);
  for( int i =0; i< tree->GetEntries(); ++i)
  {
    tree->GetEntry(i);
    std::cout <<  '\t' << object4branch->x << std::endl;
  }
}

void ROOT_1853() {
  {
    std::unique_ptr<TFile> file(TFile::Open("b.root", "RECREATE"));
    TTree *tree = new TTree("tree", "title");
    myclass object4branch;
    tree->Branch("mybranch", &object4branch);
    for( int i = 0; i< 10; ++i) 
    {
        object4branch.x =  i;
        object4branch.y = -i;
        tree->Fill();
    }
    tree->Write();
  }

  printTree_fullclass("b.root");
  printTree_splitclass("b.root");
}

Now I am not sure if the call of SetMakeClass is automatically done, and therefore this function became internal, or if it is still worth to be documented.

ferdymercury commented 3 months ago

I managed a reproducer:

First run this tutorial. https://root.cern/doc/master/tree4_8C.html

Then run:

root -l 'openTree.C(0)' -b -q
Processing openTree.C(0)...
7.31217e-322ºC

root -l 'openTree.C(1)' -b -q
Processing openTree.C(1)...
20.2826ºC

File openTree.C:

#include <iostream>
#include "TFile.h"
#include "TTree.h"

void openTree(bool setMakeClass = false) {
   auto f = TFile::Open("tree4.root");
   TTree *t4 = (TTree*)f->Get("t4");
   t4->SetMakeClass(setMakeClass);
   Double32_t temp;
   TBranch *br = t4->GetBranch("fTemperature");
   br->SetAddress(&temp);
   br->GetEntry(0);
   std::cout << temp << "ºC" << std::endl;
}
atolosadelgado commented 3 months ago

I managed a reproducer:

First run this tutorial. https://root.cern/doc/master/tree4_8C.html

Then run:

root -l 'openTree.C(0)' -b -q
Processing openTree.C(0)...
7.31217e-322ºC

root -l 'openTree.C(1)' -b -q
Processing openTree.C(1)...
20.2826ºC

File openTree.C:

#include <iostream>
#include "TFile.h"
#include "TTree.h"

void openTree(bool setMakeClass = false) {
   auto f = TFile::Open("tree4.root");
   TTree *t4 = (TTree*)f->Get("t4");
   t4->SetMakeClass(setMakeClass);
   Double32_t temp;
   TBranch *br = t4->GetBranch("fTemperature");
   br->SetAddress(&temp);
   br->GetEntry(0);
   std::cout << temp << "ºC" << std::endl;
}

Hi,

I suspect about an issue in the tree4.C file that is leading to this subsequent error: when writing the branches, each branch name should end with a dot to avoid degeneracy (objects of the same class are used for 2 different branches).

Please see this tutorial and particularly this comment by @pcanal during the review: https://github.com/root-project/root/pull/13205#discussion_r1265655686

Can you try generating the ROOT files with that modification and checking if the error persist?

@vepadulano would it be worth to add P. Canal comment about the dot at the end of the branch name into the TTree web documentation?

Best, Alvaro

ferdymercury commented 3 months ago

I suspect about an issue in the tree4.C file that is leading to this subsequent error: when writing the branches, each branch name should end with a dot to avoid degeneracy (objects of the same class are used for 2 different branches).

You mean setting the branch name as "event_split." and "event_not_split." ? But are there any subelements with the same name there?

pcanal commented 3 months ago
root -l 'openTree.C(0)' -b -q
Processing openTree.C(0)...
7.31217e-322ºC

root -l 'openTree.C(1)' -b -q
Processing openTree.C(1)...
20.2826ºC

This is the expected result. TBranch::SetAddress is a lower level interface and has less ability to check for incorrect setup.

Without SetMakeClass, if the branch is within an object (i.e. the case here), the input of SetAddress is expected to be the start of the object (and thus the offset of the data member is added to the provided address). The explicit purpose of SetMakeClass is to disable this addition of the offset.

Note that TTree::SetBranchAddress will detect this case and automatically call SetMakeClass for the branch:

t4->SetBranchAddress("fTemperature", &temp);