Storing nested structure (event->track->step) into tree

This is a follow-up post of Proper way to store an event into a Tree

Thanks to @bellenot I managed to store an “event” (containing only a single ID integer field) into a tree.

I am facing issues when storing a more complex structure. I want my events to store a container of “tracks” which in turn store a container of “steps”. I have managed to store my tracks into the event via an std::vector (I think the proper way to do this is via TClonesArray but don’t know how to do this, advice is welcome).

However I cannot store the steps into my tracks, they do not get saved into the ttree.

A minimal example to reproduce this is available at GitHub - lobis/root_event_tree_cmake at a2018d8b9b2d09dd6355259ff85331321f0e275f

Code:


// DataModel.h
class Step {
public:
    Int_t fStepID;

    Step() {}
    ~Step() {}
};

class Track {
public:
    Int_t fTrackID;
    std::vector<Step> fSteps;

    Track() {}
    ~Track() {}
};

class Event : public TObject {

public:
    Int_t fEventID;

    Event() {}
    ~Event() {}

    std::vector<Track> fTracks;

    ClassDefInline(Event, 1);
};

// main.cpp

#include <TFile.h>
#include <TInterpreter.h>
#include <TTree.h>

#include <iostream>

#include "DataModel.h"

using namespace std;

int main() {
    TString filename = "tree.root";
    TString treeName = "tree";

    gInterpreter->Declare("#include \"DataModel.h\"");

    auto tree = new TTree(treeName, "My Tree");
    auto event = new Event();

    tree->Branch("fEvents", &event);

    for (int i = 0; i < 100; i++) {
        event->fEventID = i;

        for (int j = 0; j < 10; j++) {
            Track track;
            track.fTrackID = j;

            for (int k = 0; k < 20; k++) {
                Step step;
                step.fStepID = k;
                track.fSteps.push_back(step);
            }

            event->fTracks.push_back(track);
        }
        tree->Fill();
    }


    auto file = new TFile(filename, "RECREATE");
    tree->Write();
    file->Close();
    delete file;

    cout << "READING TREE..." << endl;

    file = new TFile(filename);
    file->ls();

    file->GetObject(treeName, tree);

    tree->Print();
    tree->Show(0);
}

And a sample of the output:

/tmp/tmp.5hQmftc1lu/cmake-build-debug/root_event_tree_cmake
Warning in <TClassTable::Add>: class Event already in TClassTable
READING TREE...
TFile**		tree.root	
 TFile*		tree.root	
  KEY: TTree	tree;1	My Tree
******************************************************************************
*Tree    :tree      : My Tree                                                *
*Entries :      100 : Total =         4463365 bytes  File  Size =      46925 *
*        :          : Tree compression factor =  99.26                       *
******************************************************************************
*Branch  :fEvents                                                            *
*Entries :      100 : BranchElement (see below)                              *
*............................................................................*
*Br    0 :fUniqueID : UInt_t                                                 *
*Entries :      100 : Total  Size=        981 bytes  File Size  =         99 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   4.81     *
*............................................................................*
*Br    1 :fBits     : UInt_t                                                 *
*Entries :      100 : Total  Size=       1369 bytes  File Size  =        286 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   3.08     *
*............................................................................*
*Br    2 :fEventID  : Int_t                                                  *
*Entries :      100 : Total  Size=        976 bytes  File Size  =        247 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   1.92     *
*............................................................................*
*Br    3 :fTracks   : Int_t fTracks_                                         *
*Entries :      100 : Total  Size=       4588 bytes  File Size  =        440 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   2.00     *
*............................................................................*
*Br    4 :fTracks.fTrackID : Int_t fTrackID[fTracks_]                        *
*Entries :      100 : Total  Size=     203650 bytes  File Size  =       2824 *
*Baskets :        7 : Basket Size=      32000 bytes  Compression=  71.90     *
*............................................................................*
*Br    5 :fTracks.fSteps : vector<Step> fSteps[fTracks_]                     *
*Entries :      100 : Total  Size=    4253508 bytes  File Size  =      41009 *
*Baskets :       87 : Basket Size=      32000 bytes  Compression= 103.67     *
*............................................................................*
======> EVENT:0
 fEvents         = (Event*)0x55e0d6e56fc0
 fUniqueID       = 0
 fBits           = 50331648
 fEventID        = 0
 fTracks         = (vector<Track>*)0x55e0d6e56ff0
 fTracks.fTrackID = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
 fTracks.fSteps  = (vector<Step>*)0x55e0d6e8a778, (vector<Step>*)0x55e0d6e8a798, (vector<Step>*)0x55e0d6e8a7b8, (vector<Step>*)0x55e0d6e8a7d8, (vector<Step>*)0x55e0d6e8a7f8, (vector<Step>*)0x55e0d6e8a818, (vector<Step>*)0x55e0d6e8a838, (vector<Step>*)0x55e0d6e8a858, (vector<Step>*)0x55e0d6e8a878, (vector<Step>*)0x55e0d6e8a898

Process finished with exit code 0

Also it want to point out that when opening the tree.root file, I get the following warnings:

root [0]
Attaching file tree.root as _file0...
Warning in <TClass::Init>: no dictionary for class Event is available
Warning in <TClass::Init>: no dictionary for class Track is available
(TFile *) 0x5557791d6290

I find it interesting that there is no warning for a dictionary of Step.

I think you should generate a dictionary for your classes

You forgot ClassDefInline(Track, 1); and ClassDefInline(Step, 1);, after adding them I see:

C:\Users\bellenot\rootdev\Forum\lobis>main
Warning in <TClassTable::Add>: class ROOT::Detail::TTypedIter<class TEnumConstant> already in TClassTable
Warning in <TClassTable::Add>: class Step already in TClassTable
Warning in <TClassTable::Add>: class Track already in TClassTable
Warning in <TClassTable::Add>: class Event already in TClassTable
READING TREE...
TFile**         tree.root
 TFile*         tree.root
  KEY: TTree    tree;1  My Tree
******************************************************************************
*Tree    :tree      : My Tree                                                *
*Entries :      100 : Total =         4462965 bytes  File  Size =      46595 *
*        :          : Tree compression factor =  99.98                       *
******************************************************************************
*Branch  :fEvents                                                            *
*Entries :      100 : BranchElement (see below)                              *
*............................................................................*
*Br    0 :fUniqueID : UInt_t                                                 *
*Entries :      100 : Total  Size=        981 bytes  File Size  =         99 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   4.81     *
*............................................................................*
*Br    1 :fBits     : UInt_t                                                 *
*Entries :      100 : Total  Size=       1369 bytes  File Size  =        286 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   3.08     *
*............................................................................*
*Br    2 :fEventID  : Int_t                                                  *
*Entries :      100 : Total  Size=        976 bytes  File Size  =        247 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   1.92     *
*............................................................................*
*Br    3 :fTracks   : Int_t fTracks_                                         *
*Entries :      100 : Total  Size=       4588 bytes  File Size  =        440 *
*Baskets :        1 : Basket Size=      32000 bytes  Compression=   2.00     *
*............................................................................*
*Br    4 :fTracks.fTrackID : Int_t fTrackID[fTracks_]                        *
*Entries :      100 : Total  Size=     203650 bytes  File Size  =       2824 *
*Baskets :        7 : Basket Size=      32000 bytes  Compression=  71.90     *
*............................................................................*
*Br    5 :fTracks.fSteps : vector<Step> fSteps[fTracks_]                     *
*Entries :      100 : Total  Size=    4253108 bytes  File Size  =      40680 *
*Baskets :       87 : Basket Size=      32000 bytes  Compression= 104.50     *
*............................................................................*
======> EVENT:0
 fEvents         = (Event*)0x10ed8da0
 fUniqueID       = 0
 fBits           = 50331648
 fEventID        = 0
 fTracks         = (vector<Track>*)0x10ed8dc0
 fTracks.fTrackID = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
 fTracks.fSteps  = (vector<Step>*)0x10730570, (vector<Step>*)0x10730584, (vector<Step>*)0x10730598, (vector<Step>*)0x107305ac, (vector<Step>*)0x107305c0, (vector<Step>*)0x107305d4, (vector<Step>*)0x107305e8, (vector<Step>*)0x107305fc, (vector<Step>*)0x10730610, (vector<Step>*)0x10730624

And also:

C:\Users\bellenot\rootdev\Forum\lobis>root -l
root [0] auto f = TFile::Open("tree.root");
Warning in <TClass::Init>: no dictionary for class Event is available
Warning in <TClass::Init>: no dictionary for class Track is available
root [1]

But:

C:\Users\bellenot\rootdev\Forum\lobis>root -l
root [0] gInterpreter->Declare("#include \"DataModel.h\"");
root [1] auto f = TFile::Open("tree.root");
root [2]

Thanks for the quick reply.

I have done as you suggested but it doesn’t seem to make a difference.

I still see no further information on “fSteps”, where I would expect something similar to what I get in “fTracks”, instead I just see a leave.

image

I would expect to see something like:

======> EVENT:0
 fEvents         = (Event*)0x10ed8da0
 fUniqueID       = 0
 fBits           = 50331648
 fEventID        = 0
 fTracks         = (vector<Track>*)0x10ed8dc0
 fTracks.fTrackID = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
 fTracks.fSteps  = (vector<Step>*)0x10730570, (vector<Step>*)0x10730584, (vector<Step>*)0x10730598, (vector<Step>*)0x107305ac, (vector<Step>*)0x107305c0, (vector<Step>*)0x107305d4, (vector<Step>*)0x107305e8, (vector<Step>*)0x107305fc, (vector<Step>*)0x10730610, (vector<Step>*)0x10730624
...
 fTracks.fSteps.fStepID = 0, 1, 2, 3, ...

You mean:
image
It’s what I get with gInterpreter->Declare("#include \"DataModel.h\"");

Okay, my problem is that I was using tree->StartViewer() instead of the TBrowser. I feel like it should also work with the tree viewer.

However my goal is not to use dictionaries and that the root file is able to be read (with uproot for instance) without loading anything. This works for the Event and Track but not for the Steps since its inside Track instead of inside Event. Since it works for “one recursion level” I feel it should also work for two.

Please let me know if what I am asking for is doable.

Thanks a lot!

OK, so I don’t know. Maybe @pcanal knows better

Currently dictionary are required for STL collections (and thedictionary for STL collection of simple type are already provided). So you will need to generate the dictionary for std::vector<Step>

2 Likes

So I found this solution that works for me (details in GitHub - lobis/root_event_tree_cmake at 0da27de218cbb2b086247790b275016cfc5a53c3)

Instead of storing Step in a stl container in Tracks just store a a single container class for steps, called Steps. Something like:

#include <TObject.h>

class Steps {
public:
    std::vector<Int_t> fStepID;
    std::vector<TString> fProcessName;

    Steps() {}
    ~Steps() {}

    inline void InsertStep(Int_t i, const TString &processName) {
        fStepID.push_back(i);
        fProcessName.emplace_back(processName);
    }

    ClassDefInline(Steps, 1);// is this really necessary?
};

class Track {
public:
    Int_t fTrackID;
    Steps fSteps;

    Track() {
        fSteps = Steps();
    }
    ~Track() {}

    ClassDefInline(Track, 1);// is this really necessary?
};

class Event {

public:
    Int_t fEventID;

    Event() {}
    ~Event() {}

    std::vector<Track> fTracks;

    ClassDefInline(Event, 1);
};

With this you can successfully browse the data in the tree viewer.

image

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