Writing TTree w/custom container class - variable length arrays

Dear all,
I am getting back to ROOT after a long sweet time off (several years). I am trying to organize a neat little custom tree to hold the data from some detectors. I attach the class(es) definition and the program that compiles it and fills 10 events. The detector has several (may vary) “Axes” that hold arrays of channel readouts (floats) and channel numbers to implement mapping later on. The number of channels in different hardware is different (known, I can read it from the hardware) and it would be great to have those arrays holding channels data, to be of variable size / depth.

I have class Event and the nested class Axis, and I can add axes via method AddAxis according to whatever number I read.
One way to implement variable arrays in a class I found is described here:
http://www.fredosaurus.com/notes-cpp/newdelete/50dynamalloc.html

But it does not work (you will see my pathetic attempts commented out in the header file).
Anyway… how would I do it? I know it is more C++ than anything ROOT but this must be common use case. Please help!

Thanks, and warmest regards,
Eugene.

detclass.h

//------------------------  detclass.h ---------------------------
// to build shared library call:
// root
// .L detclass.C+

#include <iostream>
#include <TClonesArray.h>


class Axis: public TObject {
public:

    Int_t   axisid;
    Int_t   nchannels;
    //Int_t*   chnum = NULL;
    Int_t   chnum  [300];
    Float_t chdata [300];

    Axis(): TObject() {
        clear();
    }

    Axis(Int_t axisid_, Int_t nchannels_): TObject() {
        clear();
        axisid      = axisid_;
        nchannels   = nchannels_;
    }

    Axis(const Axis& axis): TObject(axis) {

        axisid    = axis.axisid;
        nchannels = axis.nchannels;
        //chnum = new Int_t [nchannels];

        // channels information - values
        for (int i=0; i<axis.nchannels; i++) {

            chnum   [i] = axis.chnum    [i]; // could not implement variable array...
            chdata  [i] = axis.chdata   [i];
        }// end loop axis channels

    }
    
    void clear() {

        axisid    = -99;
        nchannels = -99;

        //delete [] chnum;
        //chnum = NULL;
    }

    //ClassDef(Axis,2)

};// end Axis class

   

class Event: public TObject {
public:
   
    Int_t   evt;
    Int_t   nhps;
    Int_t   naxis;
    ULong_t timestamp;

    TClonesArray* axis;
        Event(): TObject() {
        axis = new TClonesArray("Axis");
        clear();
    }
    ~Event() {
        delete axis;    
        axis        = 0;
    }
    void clear() {
        evt         = 0;
        nhps        = 0;
        naxis       = 0;
        timestamp   = 0;
        axis -> Clear();
    }
    void AddAxis(const Axis* axis_) {
        naxis = axis->GetLast()+1;
        new ((*axis)[naxis]) Axis(*axis_);
        naxis = axis->GetLast()+1; // update naxis
    }
    
    //ClassDef(Event,2) // Initial version
};// end Event class

detclass.C (1.6 KB) detclass.h (1.8 KB)


X Please read tips for efficient and successful posting and posting code_

ROOT Version: 6.20/04
Platform: Debian 10
Compiler: g++ (Debian 8.3.0-6) 8.3.0

Search for “variable length” in: ROOT User’s Guide
Search for “variable size” in: TTree

Thank you for your reply.
I did read those items before posting ( and this is how I had arrived to what I have now ).
My case is most coincident with the “12.17 Example 4: A Tree with an Event Class” that has Track class nested in it, and used TObjectArray of those.
I still fail to see how I can define the size of an array contained within the class, by an integer supplied during the initialization of that particular instance of that class.

I do know how to do a simple tree with:
t2.Branch(“lmec”,gstep.lmec,“lmec[nmec]/I”);

This is not what I have been asking. This is not my case.
In class Axis, I have the number of channels, nchannels, amd some data, say their order numbers,
Int_t chnum [300];
and the Float_t chdata [300];
and it stays size 300. What I do want is I want it sized by the
Int_t nchannels;
when I initialize the instance of the Axis, and I want it only be nchannels deep, and NOT 300 deep.

To achieve that , I have been trying to initialize the class with a pointer, then associate pointer with the array that I will actually size appropriately for each object intsance.

Note that if a class inherits from TObject it must have a ClassDef (and a dictionary).

class Axis: public TObject {
public:

    Int_t   axisid;
    Int_t   nchannels;
    Int_t*   chnum = nullptr;     ///< [nchannels]
    Float_t* chdata = nullptr;   ///< [nchannels]

    Axis(): TObject() {
        clear();
    }

    Axis(Int_t axisid_, Int_t nchannels_): TObject() {
        clear();
        axisid      = axisid_;
        nchannels   = nchannels_;
        chnum = new Int_t[nchannels];
        chdata = new Float_t[nchannels];
    }

    Axis(const Axis& axis): TObject(axis) {

....
    }
    
    void clear() {

        axisid    = -99;
        nchannels = -99;

        delete [] chnum;
        chnum = nullptr;
        chdata [] chnum;
        chdata = nullptr;
    }


    ClassDef(Axis,2)

};// end Axis class

The comment for chnum and chdata are essential to indicate to ROOT the size of the arrays.

1 Like

Gotcha! Thank you very much. I was second close to it too :wink: but you beat me to it. Appreciate your help!

There is still I think problem in paradise :slightly_smiling_face:
It >> runs << but does not fill in the goodies, I think.
and that InconsistentHash does not promise anything good either…

‘HashInconsistentHash’ is an infrastructure function (part of ClassDef).

Does clicking @size shows that there is elements in the axis array?

Does the ‘nchannels’ histogram looks fine?

Thank you for following up with me and this hiccup… No, in fact I do not see meaningful stuff in the axis. In theory, the detclass.C does not vary the size of array event to event - it is what it is, 32. So the histograms should have been there… but they are not.

Humm … okay … so you may also have a problem in the way you enter the Axis object into the TClonesArray. Can you show that code? and the result of t->Scan("evt.axis.@size","","",5,0);

Really appreciate your help…!
Attached are the header and the generator. Thanks!.. just hard to get back in shape for me :wink:

root
vdetclass.C (1.7 KB) vdetclass.h (1.8 KB) .L vdetclass.C+

It breaks down when I am trying to do another axis,
evt -> AddAxis(axis);

Fixed at least the AddAxis.

    void AddAxis(const Axis* axis_) {
        cerr << "inside AddAxis 1\n";
        naxis = axis->GetLast()+1;
        cerr << "inside AddAxis 2\n";
        //new ((*axis)[naxis]) Axis(*axis_);
        Axis* currentAxis = (Axis*) axis->At(naxis);
        cerr << "inside AddAxis 3\n";
        naxis = axis->GetLast()+1; // update naxis
    }

hummm … this version of AddAxis does not actually put the data in the array.
Try

void AddAxis(const Axis* axis_) {
    new ((*axis)[naxis++]) Axis(*axis_);
}

:(( Just crashes on that very line.

How do you run the code?
Could you let me know what

valgrind --suppressions=$ROOTSYS/etc/valgrind-root.supp root.exe -b -l -q vdetclass.C+

print out?

The execution of the vdetclass.C will fail like that as the shared library hasn’t been loaded.
To produce the shared library, I link the C-file, from root command prompt
.L vdetclass.C+
or if that has already been done, load the shared library,
.L vdetclass_C.so
then execute detwrite() function with default arguments.
If one wants to execute it with .x then we need to rename that function into vdetclass() which I have just now done. Now your usage of valgrind starts making sense. Even so, I have never used that tool, so I had it installed. The “suppressions” could not be found,
==808== FATAL: can’t open suppressions file “/usr/opt/root/etc/valgrind-root.supp”
but we’ll nevermind for the time being.

egalyaev@EUGMSI:~/work/rootcontclass_example$ valgrind root -b -l -q vdetclass.C+
==886== Memcheck, a memory error detector
==886== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==886== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==886== Command: root -b -l -q vdetclass.C+
==886==
==886== error calling PR_SET_PTRACER, vgdb might block

Processing vdetclass.C+...
Info in <TUnixSystem::ACLiC>: creating shared library /home/egalyaev/work/rootcontclass_example/./vdetclass_C.so
detwrite: output file vdetclass.root
detwrite: start loop
Event: 0
        Axis: 0
                Channel[5]: 1
                Channel[6]: 1.0625
                Channel[7]: 1.125
                Channel[8]: 1.1875
                Channel[9]: 1.25
                Channel[10]: 1.3125
                Channel[11]: 1.375
                Channel[12]: 1.4375
                Channel[13]: 1.5
                Channel[14]: 1.5625
                Channel[15]: 1.625
                Channel[16]: 1.6875
                Channel[17]: 1.75
                Channel[18]: 1.8125
                Channel[19]: 1.875
                Channel[20]: 1.9375
                Channel[21]: 2
                Channel[22]: 2.0625
                Channel[23]: 2.125
                Channel[24]: 2.1875
                Channel[25]: 2.25
                Channel[26]: 2.3125
                Channel[27]: 2.375
                Channel[28]: 2.4375
                Channel[29]: 2.5
                Channel[30]: 2.5625
                Channel[31]: 2.625
                Channel[32]: 2.6875
                Channel[33]: 2.75
                Channel[34]: 2.8125
                Channel[35]: 2.875
                Channel[36]: 2.9375
blah1!
inside AddAxis 1: naxis=0
inside AddAxis 2: naxis=1

 *** Break *** segmentation violation


Root > ==886==
==886== HEAP SUMMARY:
==886==     in use at exit: 0 bytes in 0 blocks
==886==   total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==886==
==886== All heap blocks were freed -- no leaks are possible
==886==
==886== For counts of detected and suppressed errors, rerun with: -v
==886== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

I am incredibly frustrated as this simple exercise should be a piece of cake, a walk in a park.

Maybe you’ll get better report if you try: vdetclass.C++g

1 Like

You subtly but fatally changed my recommendation. The .exe is essentially (there is an executable named ‘root’ and another (called by the first one) named ‘root.exe’. To get any actual information use:

valgrind root.exe -b -l -q vdetclass.C+

==808== FATAL: can’t open suppressions file “/usr/opt/root/etc/valgrind-root.supp”

humm … odd … without that file we will flooded with false negative. So I am not sure where that file is for your installation but using it would make things simpler.

1 Like

I >>> almost << have it working…
At least it iterates and forms tree structure… alas, empty Axes items :frowning: Maybe you can help me to beat this into shape… please.
vdetclass.h (2.7 KB)

//------------------------  detclass.h ---------------------------
// to build shared library call:
// root
// .L detclass.C+
#ifndef vdetclass_h
#define vdetclass_h


#include <iostream>
#include <TObject.h>
#include <TClonesArray.h>

class Axis: public TObject {
public:

    Int_t       axisid;
    Int_t       nchannels;
    Int_t*      chnum  = nullptr;   ///< [nchannels]
    Float_t*    chdata = nullptr;   ///< [nchannels]

    Axis(): TObject() {
        clear();
    }

    Axis(Int_t axisid_, Int_t nchannels_): TObject() {
        clear();
        axisid      = axisid_;
        nchannels   = nchannels_;
        chnum  = new Int_t  [nchannels];
        chdata = new Float_t[nchannels];  
    }

    Axis(const Axis& axis): TObject(axis) {
        
        axisid    = axis.axisid;
        nchannels = axis.nchannels;

        for (Int_t i=0; i<axis.nchannels; i++) {
            chnum   [i] = axis.chnum  [i];
            chdata  [i] = axis.chdata [i];
        }// end loop axis channels

    }
    
    void clear() {

        axisid    = -99;
        nchannels = -99;

        delete [] chnum;
        chnum = nullptr;
        delete [] chdata;
        chdata = nullptr;
    }


    ClassDef(Axis,1)

};// end Axis class


class MyEvent: public TObject {
public:
   
    Int_t   evt;
    Int_t   nhps;
    Int_t   naxis;
    ULong_t timestamp;

    TClonesArray *fAxes;

    MyEvent(){
        fAxes = new TClonesArray("Axis", 10);
        evt = nhps = naxis = timestamp = 0;
    }

    ~MyEvent() {
        clear();
        delete fAxes;    
        fAxes = 0;
    }

    void clear() {
        evt         = 0;
        nhps        = 0;
        naxis       = 0;
        timestamp   = 0;
        fAxes -> Clear();
    }


    void AddAxis(const Axis* axis_) {

        cerr << "inside AddAxis 1: naxis=" << naxis << "\n";
        TClonesArray &axes = *fAxes;
        axis_ = new ((axes)[naxis++]) Axis();
        cerr << "inside AddAxis 2: naxis=" << naxis << "\n";
    }
/*
    Axis *AddAxis() {
        
        cerr << "inside AddAxis 1: naxis=" << naxis << "\n";

        TClonesArray &axes = *fAxes;
        Axis *axis = new(axes[naxis++]) Axis();
        return axis;



        //new ((*axis)[naxis]) Axis(*axis_);
        //naxis = axis->GetLast()+1;
        //naxis++;
        //cerr << "inside AddAxis 2: naxis=" << naxis << "\n";

        //new ((*axis)[naxis++]) Axis(*axis_);
        //cerr << "inside AddAxis 3: naxis=" << naxis << "\n";    
        
        //Axis* currentAxis = (Axis*) axis->At(naxis);
        //cerr << "inside AddAxis 3\n";
        //naxis = axis->GetLast()+1; // update naxis
        //cerr << "inside AddAxis 4: naxis=" << naxis << "\n";
    }
*/   
    ClassDef(MyEvent,1) // Initial version

};// end Event class


#endif

vdetclass.C (1.8 KB)

//------------------------ detclass.C ----------------------------

#include "vdetclass.h"

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

using std::cout;     
using std::endl;

ClassImp(Axis)
ClassImp(MyEvent)

void vdetclass(const char* ofname="vdetclass.root", Int_t num=10)
{
    Int_t setnaxes = 3;
    Int_t setnaxchannels = 32;
    
    
    TFile* outfile = new TFile(ofname,"recreate");
    cout << "detwrite: output file " << outfile->GetName() << endl;  

    TTree* tree = new TTree("t","dettree");

    MyEvent* event = new MyEvent();

    tree->Branch("event", &event, 8000,2);
    cout << "detwrite: start loop" << endl;
    
    for (int ievt=0; ievt<num; ++ievt){
        
        cout<< "Event: " << ievt << "\n";
        event->clear();
        
        Int_t nax = 0; // haven't added any yet

        for(int iax = 0; iax < setnaxes; iax++){
            
            Axis* axis = new Axis(iax, setnaxchannels);

            for(int ich = 0; ich < setnaxchannels; ich++){
                axis-> chnum [ich] = ich+5;
                axis-> chdata[ich] = 1.+ich*0.0625;
                cout << "\t\tChannel[" << axis-> chnum[ich] << "]: " << axis-> chdata[ich] << "\n";
            }
            
            cerr << "blah1!\n";
            event->AddAxis(axis);
            //delete axis;
            nax++;            
            
        }// end axis loop

        event->evt   = ievt;
        event->nhps  = 1;
        event->naxis = nax; // should be three
        event->timestamp = 100000+ievt*125;

        tree->Fill();

    }// end events loop

    //delete event;
    cout<< "detwrite: Filled events: " << tree->GetEntries() << endl;
   
    outfile = tree->GetCurrentFile();
    if (outfile) {
        outfile->Write();
        outfile->Close();
    }

}// end jetwrite

vdetclass.root (8.9 KB)