Skip to content

Commit 26dc6f0

Browse files
committed
[df] Allow snapshotting from TTree to RNTuple
1 parent 8be732b commit 26dc6f0

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

tree/dataframe/inc/ROOT/RDF/RInterface.hxx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,11 +1371,6 @@ public:
13711371
};
13721372

13731373
if (options.fOutputFormat == ESnapshotOutputFormat::kRNTuple) {
1374-
if (RDFInternal::GetDataSourceLabel(*this) == "TTreeDS") {
1375-
throw std::runtime_error("Snapshotting from TTree to RNTuple is not yet supported. The current recommended "
1376-
"way to convert TTrees to RNTuple is through the RNTupleImporter.");
1377-
}
1378-
13791374
// The data source of the RNTuple resulting from the Snapshot action does not exist yet here, so we create one
13801375
// without a data source for now, and set it once the actual data source can be created (i.e., after
13811376
// writing the RNTuple).

tree/dataframe/test/NTupleStruct.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
struct Electron {
1111
float pt;
1212

13+
friend bool operator==(const Electron &left, const Electron &right) { return left.pt == right.pt; }
1314
friend bool operator<(const Electron &left, const Electron &right) { return left.pt < right.pt; }
1415
};
1516

tree/dataframe/test/TwoFloats.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@ class TwoFloats {
1414
}
1515
ClassDef(TwoFloats, 2)
1616
};
17-

tree/dataframe/test/dataframe_snapshot_ntuple.cxx

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ class FileRAII {
3535
std::string GetPath() const { return fPath; }
3636
};
3737

38+
template <typename T>
39+
void expect_vec_eq(const ROOT::RVec<T> &v1, const ROOT::RVec<T> &v2)
40+
{
41+
ASSERT_EQ(v1.size(), v2.size()) << "Vectors 'v1' and 'v2' are of unequal length";
42+
for (std::size_t i = 0ull; i < v1.size(); ++i) {
43+
EXPECT_EQ(v1[i], v2[i]) << "Vectors 'v1' and 'v2' differ at index " << i;
44+
}
45+
}
46+
3847
TEST(RDFSnapshotRNTuple, FromScratch)
3948
{
4049
FileRAII fileGuard{"RDFSnapshotRNTuple_from_scratch.root"};
@@ -444,16 +453,36 @@ void WriteTestTree(const std::string &tname, const std::string &fname)
444453
{
445454
TFile file(fname.c_str(), "RECREATE");
446455
TTree t(tname.c_str(), tname.c_str());
447-
float pt;
456+
457+
float pt = 42.f;
458+
std::vector<float> photons{1.f, 2.f, 3.f};
459+
Electron electron{137.f};
460+
Jet jets;
461+
jets.electrons.emplace_back(Electron{122.f});
462+
jets.electrons.emplace_back(Electron{125.f});
463+
jets.electrons.emplace_back(Electron{129.f});
464+
465+
Int_t nmuons = 1;
466+
float muon_pt[3] = {10.f, 20.f, 30.f};
467+
468+
struct {
469+
Int_t x = 1;
470+
Int_t y = 2;
471+
} point;
472+
448473
t.Branch("pt", &pt);
474+
t.Branch("photons", &photons);
475+
t.Branch("electron", &electron);
476+
t.Branch("jets", &jets);
477+
t.Branch("nmuons", &nmuons);
478+
t.Branch("muon_pt", muon_pt, "muon_pt[nmuons]");
479+
t.Branch("point", &point, "x/I:y/I");
449480

450-
pt = 42.0;
451481
t.Fill();
452-
453482
t.Write();
454483
}
455484

456-
TEST(RDFSnapshotRNTuple, DisallowFromTTree)
485+
TEST(RDFSnapshotRNTuple, FromTTree)
457486
{
458487
const auto treename = "tree";
459488
FileRAII fileGuard{"RDFSnapshotRNTuple_disallow_from_ttree.root"};
@@ -465,13 +494,68 @@ TEST(RDFSnapshotRNTuple, DisallowFromTTree)
465494
RSnapshotOptions opts;
466495
opts.fOutputFormat = ROOT::RDF::ESnapshotOutputFormat::kRNTuple;
467496

468-
try {
469-
auto sdf = df.Define("x", [] { return 10; }).Snapshot("ntuple", fileGuard.GetPath(), {"pt", "x"}, opts);
470-
FAIL() << "snapshotting from RNTuple to TTree is not (yet) possible";
471-
} catch (const std::runtime_error &err) {
472-
EXPECT_STREQ(err.what(), "Snapshotting from TTree to RNTuple is not yet supported. The current recommended way "
473-
"to convert TTrees to RNTuple is through the RNTupleImporter.");
497+
{
498+
// FIXME(fdegeus): snapshotting leaflist branches as-is (i.e. without explicitly providing their leafs) is not
499+
// supported, because we have no way of reconstructing the memory layout of the branch itself from only the
500+
// TTree's on-disk information without JITting. For RNTuple, we would be able to do this using anonymous record
501+
// fields, however. Once this is implemented, this test should be changed to check the result of snapshotting
502+
// "point" fully.
503+
auto sdf = df.Define("x", [] { return 10; })
504+
.Snapshot("ntuple", fileGuard.GetPath(),
505+
{"x", "pt", "photons", "electron", "jets", "muon_pt", "point.x", "point.y"}, opts);
506+
507+
auto x = sdf->Take<int>("x");
508+
auto pt = sdf->Take<float>("pt");
509+
auto photons = sdf->Take<ROOT::RVec<float>>("photons");
510+
auto electron = sdf->Take<Electron>("electron");
511+
auto jet_electrons = sdf->Take<ROOT::RVec<Electron>>("jets.electrons");
512+
auto nMuons = sdf->Take<int>("nmuons");
513+
auto muonPt = sdf->Take<ROOT::RVec<float>>("muon_pt");
514+
auto pointX = sdf->Take<int>("point_x");
515+
auto pointY = sdf->Take<int>("point_y");
516+
517+
ASSERT_EQ(1UL, x->size());
518+
ASSERT_EQ(1UL, pt->size());
519+
ASSERT_EQ(1UL, photons->size());
520+
ASSERT_EQ(1UL, electron->size());
521+
ASSERT_EQ(1UL, jet_electrons->size());
522+
ASSERT_EQ(1UL, nMuons->size());
523+
ASSERT_EQ(1UL, muonPt->size());
524+
ASSERT_EQ(1UL, pointX->size());
525+
ASSERT_EQ(1UL, pointY->size());
526+
527+
EXPECT_EQ(10, x->front());
528+
EXPECT_EQ(42.f, pt->front());
529+
expect_vec_eq<float>({1.f, 2.f, 3.f}, photons->front());
530+
EXPECT_EQ(Electron{137.f}, electron->front());
531+
expect_vec_eq({Electron{122.f}, Electron{125.f}, Electron{129.f}}, jet_electrons->front());
532+
EXPECT_EQ(1, nMuons->front());
533+
expect_vec_eq({10.f}, muonPt->front());
534+
EXPECT_EQ(1, pointX->front());
535+
EXPECT_EQ(2, pointY->front());
474536
}
537+
538+
auto reader = RNTupleReader::Open("ntuple", fileGuard.GetPath());
539+
540+
auto x = reader->GetView<int>("x");
541+
auto pt = reader->GetView<float>("pt");
542+
auto photons = reader->GetView<ROOT::RVec<float>>("photons");
543+
auto electron = reader->GetView<Electron>("electron");
544+
auto jet_electrons = reader->GetView<ROOT::RVec<Electron>>("jets.electrons");
545+
auto nMuons = reader->GetView<int>("nmuons");
546+
auto muonPt = reader->GetView<ROOT::RVec<float>>("muon_pt");
547+
auto pointX = reader->GetView<int>("point_x");
548+
auto pointY = reader->GetView<int>("point_y");
549+
550+
EXPECT_EQ(10, x(0));
551+
EXPECT_EQ(42.f, pt(0));
552+
expect_vec_eq<float>({1.f, 2.f, 3.f}, photons(0));
553+
EXPECT_EQ(Electron{137.f}, electron(0));
554+
expect_vec_eq({Electron{122.f}, Electron{125.f}, Electron{129.f}}, jet_electrons(0));
555+
EXPECT_EQ(1, nMuons(0));
556+
expect_vec_eq({10.f}, muonPt(0));
557+
EXPECT_EQ(1, pointX(0));
558+
EXPECT_EQ(2, pointY(0));
475559
}
476560

477561
#ifdef R__USE_IMT

0 commit comments

Comments
 (0)