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:
R__LOAD_LIBRARY(ROOTNTuple)
#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 {
private:
double fX = 0;
double fY = 0;
double fZ = 0;
public:
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;
CalculateMagnitude();
}
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;
options.SetCompression(ROOT::RCompressionSetting::EDefaults::kUseGeneralPurpose);
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();
updater->BeginUpdate();
updater->AddField(std::move(newField));
updater->CommitUpdate();
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));
ntuple->Fill();
}
std::cout<<"done filling tree"<<std::endl;
}
void ntpl_test() {
std::cout<<"Generating ntuple"<<std::endl;
Generate();
std::cout<<"Done generating ntuple"<<std::endl;
auto ntuple = RNTupleReader::Open("Vector3", kNTupleFileName);
// Display the schema of the ntuple
ntuple->PrintInfo();
// Display information about the storage layout of the data
ntuple->PrintInfo(ENTupleInfo::kStorageDetails);
// Display the first entry
ntuple->Show(0);
// 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
ntuple->EnableMetrics();
// Plot the y components of vector3
TCanvas *c1 = new TCanvas("c1","RNTuple Demo", 10, 10, 600, 800);
c1->Divide(1,2);
c1->cd(1);
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()) {
h1.Fill(viewX(i));
}
}
h1.DrawCopy();
c1->cd(2);
TH1F h2("y", "y component of Vector3", 100, -5, 20);
auto viewY = ntuple->GetView<double>("v3.fY");
for (auto i : ntuple->GetEntryRange()) {
h2.Fill(viewY(i));
}
h2.DrawCopy();
// Display the I/O operation statistics performed by the RNTuple reader
ntuple->PrintInfo(ENTupleInfo::kMetrics);
// 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() +
ntuple->GetMetrics().GetCounter("RNTupleReader.RPageSourceFile.szReadOverhead")->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;
}