ROOT7 TTree->RNTuple example (ntpl003) with vector fields

Dear experts,

Is the technique for building an RNTuple from a TTree as demonstrated in the ntpl003 example:
https://root.cern.ch/doc/v624/ntpl003__lhcbOpenData_8C.html

expected to work when the fields are not simple one-value-per-row-per-column types as contained in the LHCb open data file, but vectors of floats, ints etc? I find that when I try the same technique with vectors, I get a seg fault when I try to fill the RNtuple.

Of course, one could manually extract each vector from the TTree and then build the RNTuple structure field by field, but as the comment in the example notes, the method demonstrated in ntpl003 is more efficient since no copies are needed, and it avoids having to do all of this manual work. So if it is possible to do this with vector fields as well it would be nice to know how to do it.

I include my code below; it is basically the same as ntpl003 with the reading part stripped out, and set up to read a single vector branch to illustrate the problem. The TTree input file I’m using is here:
/afs/cern.ch/work/j/jcatmore/public/RNTuple/mc_301215.ZPrime2000_ee.2lep.root
(this is ATLAS open data)

Thanks for any help (including telling me that this isn’t yet possible and I need to do it manually :slight_smile: )

Best wishes,

James Catmore

#include <string.h>

#include <TBranch.h>
#include <TCanvas.h>
#include <TFile.h>
#include <TH1F.h>
#include <TLeaf.h>
#include <TTree.h>

#include <cassert>
#include <memory>
#include <vector>


// Import classes from experimental namespace for the time being
using RNTupleModel = ROOT::Experimental::RNTupleModel;
using RFieldBase = ROOT::Experimental::Detail::RFieldBase;
using RNTupleReader = ROOT::Experimental::RNTupleReader;
using RNTupleWriter = ROOT::Experimental::RNTupleWriter;

// Input and output files
constexpr char const* inputFileName = "mc_301215.ZPrime2000_ee.2lep.root";
//constexpr char const* inputFileName = "lhcb_B2HHH_MagnetUp.root";
constexpr char const* outputFileName = "RDF.root";

// Extract the TTree from the input files,
// build an RNTuple with the same structure,
// copy over, commit to output file
void Convert() {

   // Open input file
   std::unique_ptr<TFile> f(TFile::Open(inputFileName));
   assert(f && ! f->IsZombie());

   // Get a unique pointer to an empty RNTuple model
   auto model = RNTupleModel::Create();

   // Create RNTuple fields based on TTree branches
   auto tree = f->Get<TTree>("mini");
   for (auto b : TRangeDynCast<TBranch>(*tree->GetListOfBranches())) {

      // The dynamic cast to TBranch should never fail for GetListOfBranches()
      assert(b);

      // We assume every branch has a single leaf
      TLeaf *l = static_cast<TLeaf*>(b->GetListOfLeaves()->First());

      // Create an ntuple field with the same name and type as the tree branch
      // Pick out a single vector<float> to demonstrate the problem
      if (strcmp(l->GetName(), "lep_pt")!=0) continue;
      std::cout << "Convert leaf " << l->GetName() << " [" << l->GetTypeName() << "]";
      auto field = RFieldBase::Create(l->GetName(), l->GetTypeName()).Unwrap();
      std::cout << " --> " << "field " << field->GetName() << " [" << field->GetType() << "]" << std::endl;

      // Hand over ownership of the field to the ntuple model.  This will also create a memory location attached
      // to the model's default entry, that will be used to place the data 
      model->AddField(std::move(field));

      // Connect the model's default entry's memory location for the new field to the TTree branch
      void *fieldDataPtr = model->GetDefaultEntry()->GetValue(l->GetName()).GetRawPtr();
      tree->SetBranchAddress(b->GetName(), fieldDataPtr);

   } // end of for loop over TTree branches

   // The new ntuple takes ownership of the model
   auto ntuple = RNTupleWriter::Recreate(std::move(model), "mini", outputFileName);

   // Fill the n-tuple, row by row
   auto nEntries = tree->GetEntries();
   for (decltype(nEntries) i = 0; i < nEntries; ++i) {
      tree->GetEntry(i);
      ntuple->Fill();
      if (i && i % 1000 == 0)
         std::cout << "Wrote " << i << " entries" << std::endl;
   }

   return;

}

Please read tips for efficient and successful posting and posting code

ROOT Version: 6.24.00
Platform: Centos 7 via container
Compiler: gcc8


Hi @jcatmore,

For vectors, you have to do something slightly different (see here: https://github.com/jblomer/iotools/blob/master/gen_atlas.cxx#L102). Hope this example helps!

Finally, it is worth mentioning that we are currently working in a tool that allows automatic conversion from TTree to RNTuple. Such utility will migrate both, the schema and data, so that users would not have to write their own conversion code. :slight_smile:

Cheers,
J.

Hi @jalopezg ,

thanks - this worked perfectly!

James.

Hi @jcatmore,

I’m really happy to hear that! :slight_smile: Could you please mark the topic as solved?

Cheers,
J.

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