Branch with Object Pointer

I am trying to fill a TTree, but the data naturally leads me to want to use a “nested” format, where the Branches have heterogeneous structures. To illustrate, I am trying this code:

ROOT.gROOT.ProcessLine(\
    "struct pairinfo{\
       Int_t n_pair;\
       Float_t arrival_time,charge;\
     };")
#For each electron-ion pair in each cluster there is a pairinfo.
from ROOT import pairinfo

NPAIRSMAX = 300
ROOT.gROOT.ProcessLine(\
    "struct clusterinfo{\
        Int_t n_cluster;\
        Float_t drift_time, diffusion;\
        Int_t pairs;\
        pairinfo pair["+str(NPAIRSMAX)+"];\
    };")
#For each cluster in each track there is a clusterinfo.
from ROOT import clusterinfo

However, when I then make a cluster = clusterinfo() object, how do I make a Branch know about the pairinfo bits?? If it was a regular built-in, I would use syntax like this:

But for the composite pairinfo part, all I could find was the normal ROOT suggestion

where p_object is a pointer to the thing in question (a pairinfo object in my case). Unfortunately there is no ROOT.PointerTo() function in PyROOT, so I’m rather stuck as to what to put in there. Also the & means “address of”, so I’m supposed to put the address of the pointer to the object?

Thanks for any help or advice in this matter.

Jean-François

Jean-François,

you could use “AddressOf(cluster)” if you want, but you could also simply pass “cluster” in the same place for
"&p_object". The difference is that a data member of builtin type always gets copied to the equivalent python object, so it can be passed anymore as a member of the class. A full object does not have that problem, since it is always carried as a pointer.

Only important bit is to make sure that the lifetime of outtree and the cluster object are the same. The easiest way to achieve that is by doing “outtree.cluster = cluster” after the call to “outtree.Branch()”.

Cheers,
Wim

Thanks for the answer, but I’m still rather confused. Below is the actual code I wish to use (with irrelevant bits removed). Perhaps this will make it more clear what I am trying to do.

outfile = ROOT.TFile("testout.root","recreate")
outtree = ROOT.TTree("proto2",outfile.GetName())

pairinfo_def = "struct pairinfo{\
       Int_t n_pair;\
       Float_t arrival_time,charge;\
     };\
"
ROOT.gROOT.ProcessLine(pairinfo_def)
#For each electron-ion pair in each cluster there is a pairinfo.
from ROOT import pairinfo

NPAIRSMAX = 300
ROOT.gROOT.ProcessLine(\
    "struct clusterinfo{\
        Int_t n_cluster;\
        Float_t drift_time, diffusion;\
        Int_t pairs;\
        pairinfo pair["+str(NPAIRSMAX)+"];\
    };")
#For each cluster in each track there is a clusterinfo.
from ROOT import clusterinfo

NSAMPLES = 1024
ROOT.gROOT.ProcessLine(\
    "struct signalinfo{\
        Int_t n_wire;\
        Float_t signal["+str(NSAMPLES)+"];\
        Int_t timeidx["+str(NSAMPLES)+"];\
    };")
#I have one signalinfo per signal saved per track.
from ROOT import signalinfo

NCLUSTERSMAX = 300
NSIGNALS = 1
ROOT.gROOT.ProcessLine(\
    "struct trackinfo{\
        Int_t n_track;\
        Float_t x0,y0,z0,x1,y1,z1;\
        Int_t n_clusters;\
        clusterinfo cluster["+str(NCLUSTERSMAX)+"];\
        signalinfo signals["+str(NSIGNALS)+"];\
    };")
#I have one trackinfo per track, with many clusterinfos inside.
#There are also signalinfos inside the trackinfo.
from ROOT import trackinfo

track = ROOT.trackinfo()
for i in range(NCLUSTERSMAX):
    track.cluster[i] = ROOT.clusterinfo()
for cluster in track.cluster:
    cluster.pair = [ROOT.pairinfo() for i in range(NPAIRSMAX)]
track.signal = [ROOT.signalinfo() for i in range(NSIGNALS)]

outtree.Branch('n_track',ROOT.AddressOf(track,'n_track'),'n_track/i')
outtree.Branch('x0',ROOT.AddressOf(track,'x0'),'x0/F')
outtree.Branch('y0',ROOT.AddressOf(track,'y0'),'y0/F')
outtree.Branch('z0',ROOT.AddressOf(track,'z0'),'z0/F')
outtree.Branch('x1',ROOT.AddressOf(track,'x1'),'x1/F')
outtree.Branch('y1',ROOT.AddressOf(track,'y1'),'y1/F')
outtree.Branch('z1',ROOT.AddressOf(track,'z1'),'z1/F')
outtree.Branch('n_clusters',ROOT.AddressOf(track,'n_clusters'),'n_clusters/i')
outtree.Branch('cluster','clusterinfo',track.cluster)
outtree.Branch('signal','signalinfo',track.signal)

while True:
    fill_each_variable()
    outtree.Fill()
outfile.Write()
outfile.Close()
end_time = time.time()

Of course this one does not work, it fails on

with error message: ‘clusterinfo’ object does not support item assignment

My plan was to have one trackinfo object only, including all the data members clusterinfo, signalinfo, and pairsinfo. After filling all of these, I would call outtree.Fill(), then change all the values and fill() again. At the end I would write to the file and close it.

Is it possible, or is my proposed data structure too complex? Would it be easier to “flatten” the tree? Instead of giving each entry an array of clusterinfo objects, I could just give the entry a whole bunch of individually addressed clusterinfos. I could probably do all the outtree.Branch() calls in a loop. That feels like giving up though.

Jean-François

Jean-François,

both interpreted code (instead of compiled dictionaries) and builtin arrays of objects (instead of e.g. std::vector) are things that have loose ends, mostly b/c the proper information is missing to build the bindings out of.

Also, since the builtin array of objects will have default-constructed objects in there, I’m not sure why you reinitialize them with default constructed objects on the python side, rather than using their members directly as you describe you want to do, since that is the easiest when working with TTrees?

Another option, if the point is only to collect information in a object/container that is otherwise flattened in the TTree (i.e. that object isn’t used in any C++ code), you can use python arrays (see module array and it’s use in e.g. http://wlav.web.cern.ch/wlav/pyroot/tpytree.html, section A.2.2) and build a python object out of those bits.

Cheers,
Wim

In the end I wish to produce a ROOT TTree to share with other workers who are more enthusiastic about C++, so I can’t just save everything as a python object.

I followed your suggestion (and the page you linked) about using arrays. Everything works fine except the branches of the tree which are supposed to hold strings (or TStrings in my case).

Here is roughly what I am doing:

class TTreeInfo():
    def __init__(self):
        self.gasfile = ROOT.TString('')
...
ttreeobj = TTreeInfo()
outtree.Branch('gasfile',ttreeobj.gasfile)
...
    ttreeobj.gasfile.Append('apythonstring')
    outtree.Fill()
...

I create a flattened python object of type TTreeInfo with members that are assigned to branches of the ultimate TTree. I initialize all of its members as fixed-size array.array()s or at ROOT.TString(’’), to which I later append the actual strings to be entered. I am very careful about not changing the type of the members on the TTreeInfo object.

The code runs fine and saves the tree. Later I can open the tree and draw bits using its numerical branches, but the string branches cause a severe crash (crashing ROOT and the python interpreter).

Any advice on getting the stringy bits done properly? Should I be initializing the string with a suitably large capacity with ROOT.TString(200), then only replacing values in place?

Jean-François,

you can then still safely use python array’s as the point of them is to be a contiguous piece of memory, completely like C arrays are. However, the best way to share with a C++ developer, is to have your class sit in a header file that can then be used in C++ projects. If you use this class header to generate a compiled dictionary, life will be easier than when using an interpreted version of the class. Sure, ideally compiled and interpreted are the same thing, but in practice there are corner cases that turn around and bite you.

As for TString problems, a ROOT I/O expert should pipe in, but I found http://root.cern.ch/root/HowtoWriteTree.html stating that split mode should be disabled for TString (by setting the fifth parameter of the call to Branch(), i.e. splitlevel, to 0).

Cheers,
Wim