RDataFrame Fill to fill a TTree

I’m trying to use RDataFrame’s Fill action to fill a ttree instead of snapshot so I can save defined branches as c-style arrays. So the custom class looks something like:

class Filltree{
    TTree* output_tree;
    int npassingjets;
    float passingjetpt[64];

    public:
    Filltree();
    Filltree(const Filltree &t2) {output_tree = t2.output_tree;}
    void Fill(int, ROOT::RVec<float>);
    void Merge(const std::vector<Filltree *> &);
    TTree *GetTree() {return output_tree;}

};

and

Filltree::Filltree(){
    output_tree = new TTree("output_tree","output_tree");
    output_tree->Branch("nPassingJets",&npassingjets,"nPassingJets/i");
    output_tree->Branch("passingJetPt",&passingjetpt,"passingJetPt[nPassingJets]/f");
}

void Filltree::Fill(int nPassingJets, ROOT::RVec<float> passingJetPt){
    npassingjets = nPassingJets;
    for (int i=0; i<nPassingJets; i++){
        passingjetpt[i] = passingJetPt[i];
    }
    output_tree->Fill();
}

void Filltree::Merge(const std::vector<Filltree *> &others)
   {
      TList l;
      for (auto *o : others)
         l.Add(o->GetTree());
      output_tree->Merge(&l);
   }

where nPassingJets and passingJetPt are defined by Define statements earlier, but I’m getting the error

/usr/include/root/ROOT/RDF/RVariedAction.hxx:119:75: error: cannot pass object of non-trivial type 'ROOT::VecOps::RVec<float>' through variadic method; call will abort at runtime [-Wnon-pod-varargs]

Is what I’m attempting here not possible? I tried taking out the RVec branch and just filled a tree with ints but even then when I saved the tree to a file all the results were nonsense. If I can’t do this, is there any way to achieve what I’m attempting, and save defined branches as arrays?


ROOT Version: Not Provided
Platform: Not Provided
Compiler: Not Provided


Dear @Patrick_Flanagan ,

In principle what you are trying to do should work, let’s try to understand why it doesn’t. First, can I ask you to share a full reproducer of the problem, at least with the code that calls RDataFrame::Fill ?

Then, looking at your code above, my first suggestion is converting that TTree * data mamber to a std::unique_ptr or at least std::shared_ptr, currently you are creating the TTree with new but never deleteing it. Also, the Signature of the Fill method should take the vector by const ref as in const ROOT::RVecF &, to avoid an extra copy.

Cheers,
Vincenzo

Can you clarify converting the TTree * to a std::unique_ptr, I tried that change and now it’s giving me the error:

error: call to deleted constructor of 'std::unique_ptr<TTree>'
    std::unique_ptr<TTree> GetTree() {return output_tree;}

Why do we want it deleted and how do I do this properly?

The full code is:

#ifndef FRAME_CLASS
#define FRAME_CLASS
#include "TTree.h"
#include <ROOT/RDataFrame.hxx>
#include "TFile.h"
class Filltree{
    TTree * output_tree;
    int numpassingjets;
    float passingjetpt[64];

    public:
    Filltree();
    Filltree(const Filltree &t2) {output_tree = t2.output_tree;}
    void Fill(int, const ROOT::RVec<float>);
    void Merge(const std::vector<Filltree *> &);
    TTree* GetTree() {return output_tree;}

};

#endif

and

#include "Frameanalyze.h"
Filltree::Filltree(){
    output_tree = new TTree("output_tree","output_tree");
    output_tree->Branch("numPassingJets",&numpassingjets,"numPassingJets/i");
    output_tree->Branch("passingJetPt",&passingjetpt,"passingJetPt[numPassingJets]/f");
}

void Filltree::Fill(int numPassingJets, const ROOT::RVec<float> passingJetPt){
    numpassingjets = numPassingJets;
    for (int i=0; i<numPassingJets; i++){
        passingjetpt[i] = passingJetPt[i];
    }
    output_tree->Fill();
}

void Filltree::Merge(const std::vector<Filltree *> &others)
   {
      TList l;
      for (auto *o : others)
         l.Add(o->GetTree());
      output_tree->Merge(&l);
   }
void Frameanalyze(string filename){
    ROOT::EnableImplicitMT();
    RDataFrame d1("Events", filename.c_str())
    ROOT::RDF::RNode Node(d1); 
    Node = Node.Define("numPassingJets","(int)Jet_pt_nom[Jet_pt_nom>30&&Jet_jetId>=6&&abs(Jet_eta)<4.7].size()").Define("passingJetPt","Jet_pt_nom[Jet_pt_nom>30&&Jet_jetId>=6&&abs(Jet_eta)<4.7]");
    Filltree treeholder;
    auto output = Node.Fill(treeholder,{"numPassingJets","passingJetPt"});
    auto outputptr = output.GetPtr();
    TTree *output_tree = outputptr->GetTree();
    TFile *output_file = new TFile("someoutputdirectory/someoutputfile.root","RECREATE");
    output_tree->Write();
}

Which gives:

/builddir/build/BUILD/root-6.28.08/x86_64-redhat-linux-gnu/include/ROOT/RDF/RAction.hxx:105:53: error: cannot pass object of non-trivial type 'ROOT::VecOps::RVec<float>' through variadic method; call will abort at runtime [-Wnon-pod-varargs]
      fHelper.Exec(slot, fValues[slot][S]->template Get<ColTypes>(entry)...);
                                                    ^

You may want to try:

void Filltree::Fill(int nPassingJets, const ROOT::RVec<float> &passingJetPt)

That gave the same error.

I got it to work using ForeachSlot. There may be a better way to do this but the following works:

#ifndef FRAME_CLASS
#define FRAME_CLASS
#include "TTree.h"
#include <ROOT/RDataFrame.hxx>
#include "TFile.h"
class Filltree{
    TTree * output_tree;
    int numpassingjets;
    float passingjetpt[64];

    public:
    Filltree();
    Filltree(const Filltree &t2) {output_tree = t2.output_tree;}
    void Fill(int, const ROOT::RVec<float> &);
    void Merge(const std::vector<Filltree> &);
    TTree* GetTree() {return output_tree;}

};

#endif

and

#include "Frameanalyze.h"
Filltree::Filltree(){
    output_tree = new TTree("output_tree","output_tree");
    output_tree->Branch("numPassingJets",&numpassingjets,"numPassingJets/i");
    output_tree->Branch("passingJetPt",&passingjetpt,"passingJetPt[numPassingJets]/f");
}

void Filltree::Fill(int numPassingJets, const ROOT::RVec<float> &passingJetPt){
    numpassingjets = numPassingJets;
    for (int i=0; i<numPassingJets; i++){
        passingjetpt[i] = passingJetPt[i];
    }
    output_tree->Fill();
}

void Filltree::Merge(const std::vector<Filltree> &others)
   {
      TList l;
      for (auto o : others)
         l.Add(o.GetTree());
      output_tree->Merge(&l);
   }
void Frameanalyze(string filename){
    ROOT::EnableImplicitMT();
    const unsigned int nSlots = ROOT::GetThreadPoolSize();
    std::vector<Filltree> filltrees(nSlots);
    ROOT::RDataFrame d1("Events", filename.c_str());
    ROOT::RDF::RNode Node(d1); 
    Node = Node.Define("numPassingJets","(int)Jet_pt_nom[Jet_pt_nom>30&&Jet_jetId>=6&&abs(Jet_eta)<4.7].size()").Define("passingJetPt","Jet_pt_nom[Jet_pt_nom>30&&Jet_jetId>=6&&abs(Jet_eta)<4.7]");
    Filltree treeholder;
    Node.ForeachSlot([&filltrees](unsigned int slot, int numPassingJets, const ROOT::RVec<float> &passingJetPt){
        filltrees[slot].Fill(numPassingJets,passingJetPt);
    },{"numPassingJets","passingJetPt"});
    treeholder.Merge(filltrees);
    TTree *output_tree = treeholder.GetTree();
    TFile *output_file = new TFile("someoutputfile.root","RECREATE");
    output_file->cd();
    output_tree->CloneTree()->Write();
    output_file->Write();
}