RNTuple::Fill leads to out_of_range?

ROOT Version: 6.30.02
Platform: Ubuntu 22.04

I want to test RNTuple to see how that would work for us, and to that end I modified one of the tutorials (ntpl005_introspection.C) to use an empty model that is updated via a model updater instead of creating fields directly. This is because in the application we would not know what fields to create beforehand.

When I try to run the script if would fail at the end of the Generate function when things go out of scope:

terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 0) >= this->size() (which is 0)

If I comment out the line ntuple->Fill(); this does not happen. This is most likely because I am doing something wrong here, but I’m not certain what I’m doing wrong.

This is what the script looks like:


#include <ROOT/RNTuple.hxx>
#include <ROOT/RNTupleModel.hxx>
#include <ROOT/RNTupleOptions.hxx>

#include <Compression.h>
#include <TCanvas.h>
#include <TH1.h>
#include <TRandom.h>
#include <TSystem.h>

#include <cassert>

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

constexpr char const* kNTupleFileName = "ntpl_test.root";

// Store entries of type Vector3 in the ntuple
class Vector3 {
   double fX = 0;
   double fY = 0;
   double fZ = 0;

   Vector3() { std::cout<<"Created Vector3: "<<this<<std::endl; }
   ~Vector3() { std::cout<<"Deleting Vector3: "<<this<<std::endl; }
   double x() const { return fX; }
   double y() const { return fY; }
   double z() const { return fZ; }

   void SetXYZ(double x, double y, double z) {
      fX = x;
      fY = y;
      fZ = z;
   virtual double CalculateMagnitude() {
      return TMath::Sqrt(fX*fX+fY*fY+fZ*fZ);
   void Print() { std::cout<<fX<<", "<<fY<<", "<<fZ<<std::endl; }

void Generate()
   // Explicitly enforce a certain compression algorithm
   RNTupleWriteOptions options;

   auto model = RNTupleModel::Create();
   //auto fldVector3 = model->MakeField<Vector3>("v3");
   auto ntuple = RNTupleWriter::Recreate(std::move(model), "Vector3", kNTupleFileName, options);
   std::cout<<"Created ntuple writer"<<std::endl;
   auto updater = ntuple->CreateModelUpdater();
   auto newField = ROOT::Experimental::Detail::RFieldBase::Create("Vector3", "Vector3").Unwrap();

   TRandom r;
   std::cout<<"type: "<<ntuple->GetModel()->GetField("Vector3")->GetType()<<std::endl;
   auto entry = ntuple->GetModel()->CreateEntry();
   std::cout<<"filling tree"<<std::endl;
   auto rawVector3 = static_cast<TVector3*>(entry->GetRawPtr("Vector3"));
   std::cout<<"Got raw ptr "<<rawVector3<<std::endl;
   std::cout<<"x "<<rawVector3->x()<<", y "<<rawVector3->y()<<", z "<<rawVector3->z()<<std::endl;
   for (unsigned int i = 0; i < 500000; ++i) {
      rawVector3->SetXYZ(r.Gaus(0,1), r.Landau(0,1), r.Gaus(100,10));
   std::cout<<"done filling tree"<<std::endl;

void ntpl_test() {
   std::cout<<"Generating ntuple"<<std::endl;
   std::cout<<"Done generating ntuple"<<std::endl;

   auto ntuple = RNTupleReader::Open("Vector3", kNTupleFileName);

   // Display the schema of the ntuple

   // Display information about the storage layout of the data

   // Display the first entry

   // Collect I/O runtime counters when processing the data set.
   // Maintaining the counters comes with a small performance overhead, so it has to be explicitly enabled

   // Plot the y components of vector3
   TCanvas *c1 = new TCanvas("c1","RNTuple Demo", 10, 10, 600, 800);
   TH1F h1("x", "x component of Vector3", 100, -3, 3);
      /// We enclose viewX in a scope in order to indicate to the RNTuple when we are not
      /// anymore interested in v3.fX
      auto viewX = ntuple->GetView<double>("v3.fX");
      for (auto i : ntuple->GetEntryRange()) {

   TH1F h2("y", "y component of Vector3", 100, -5, 20);
   auto viewY = ntuple->GetView<double>("v3.fY");
   for (auto i : ntuple->GetEntryRange()) {

   // Display the I/O operation statistics performed by the RNTuple reader

   // We read 2 out of the 3 Vector3 members and thus should have requested approximately 2/3 of the file
   FileStat_t fileStat;
   auto retval = gSystem->GetPathInfo(kNTupleFileName, fileStat);
   assert(retval == 0);
   float fileSize = static_cast<float>(fileStat.fSize);
   float nbytesRead = ntuple->GetMetrics().GetCounter("RNTupleReader.RPageSourceFile.szReadPayload")->GetValueAsInt() +

   std::cout << "File size:      " << fileSize / 1024. / 1024. << " MiB" << std::endl;
   std::cout << "Read from file: " << nbytesRead / 1024. / 1024. << " MiB" << std::endl;
   std::cout << "Ratio:          " << nbytesRead / fileSize << std::endl;

Hi @vaubee,

Thank you for trying out RNTuple, and raising this question! I tried your reproducer and indeed I observe the same error with ROOT 6.30.02. However, it appears to work fine with the latest master build (I did have to change the field names to Vector3.fX and Vector3.fY in your reproducer). Could you try this as well?

In the meanwhile, we will look for the cause of the initial error and which change seems to have fixed it, to try and make sure we’re not missing any underlying issue here.


1 Like

Okay, I’ve compiled the latest version of the master branch (6.31.01) and it doesn’t crash anymore. For some reason all entries have x, y, and z set to zero, but that is a different issue.
So I can confirm that this was fixed in 6.31.01.

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