Skip to content

Commit 70e54ce

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

File tree

4 files changed

+98
-16
lines changed

4 files changed

+98
-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: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ 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+
if constexpr (std::is_floating_point_v<T>)
44+
EXPECT_FLOAT_EQ(v1[i], v2[i]) << "Vectors 'v1' and 'v2' differ at index " << i;
45+
else
46+
EXPECT_EQ(v1[i], v2[i]) << "Vectors 'v1' and 'v2' differ at index " << i;
47+
}
48+
}
49+
3850
TEST(RDFSnapshotRNTuple, FromScratch)
3951
{
4052
FileRAII fileGuard{"RDFSnapshotRNTuple_from_scratch.root"};
@@ -444,16 +456,36 @@ void WriteTestTree(const std::string &tname, const std::string &fname)
444456
{
445457
TFile file(fname.c_str(), "RECREATE");
446458
TTree t(tname.c_str(), tname.c_str());
447-
float pt;
459+
460+
float pt = 42.f;
461+
std::vector<float> photons{1.f, 2.f, 3.f};
462+
Electron electron{137.f};
463+
Jet jets;
464+
jets.electrons.emplace_back(Electron{122.f});
465+
jets.electrons.emplace_back(Electron{125.f});
466+
jets.electrons.emplace_back(Electron{129.f});
467+
468+
Int_t nmuons = 1;
469+
float muon_pt[3] = {10.f, 20.f, 30.f};
470+
471+
struct {
472+
Int_t x = 1;
473+
Int_t y = 2;
474+
} point;
475+
448476
t.Branch("pt", &pt);
477+
t.Branch("photons", &photons);
478+
t.Branch("electron", &electron);
479+
t.Branch("jets", &jets);
480+
t.Branch("nmuons", &nmuons);
481+
t.Branch("muon_pt", muon_pt, "muon_pt[nmuons]");
482+
t.Branch("point", &point, "x/I:y/I");
449483

450-
pt = 42.0;
451484
t.Fill();
452-
453485
t.Write();
454486
}
455487

456-
TEST(RDFSnapshotRNTuple, DisallowFromTTree)
488+
TEST(RDFSnapshotRNTuple, FromTTree)
457489
{
458490
const auto treename = "tree";
459491
FileRAII fileGuard{"RDFSnapshotRNTuple_disallow_from_ttree.root"};
@@ -465,13 +497,68 @@ TEST(RDFSnapshotRNTuple, DisallowFromTTree)
465497
RSnapshotOptions opts;
466498
opts.fOutputFormat = ROOT::RDF::ESnapshotOutputFormat::kRNTuple;
467499

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

477564
#ifdef R__USE_IMT

0 commit comments

Comments
 (0)