Skip to content

Commit bc3a50b

Browse files
committed
[df] Allow snapshotting from TTree to RNTuple
1 parent 4c90e94 commit bc3a50b

File tree

4 files changed

+99
-17
lines changed

4 files changed

+99
-17
lines changed

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,11 +1379,6 @@ public:
13791379
};
13801380

13811381
if (options.fOutputFormat == ESnapshotOutputFormat::kRNTuple) {
1382-
if (RDFInternal::GetDataSourceLabel(*this) == "TTreeDS") {
1383-
throw std::runtime_error("Snapshotting from TTree to RNTuple is not yet supported. The current recommended "
1384-
"way to convert TTrees to RNTuple is through the RNTupleImporter.");
1385-
}
1386-
13871382
// The data source of the RNTuple resulting from the Snapshot action does not exist yet here, so we create one
13881383
// without a data source for now, and set it once the actual data source can be created (i.e., after
13891384
// 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: 98 additions & 11 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"};
@@ -448,19 +460,39 @@ void WriteTestTree(const std::string &tname, const std::string &fname)
448460
{
449461
TFile file(fname.c_str(), "RECREATE");
450462
TTree t(tname.c_str(), tname.c_str());
451-
float pt;
463+
464+
float pt = 42.f;
465+
std::vector<float> photons{1.f, 2.f, 3.f};
466+
Electron electron{137.f};
467+
Jet jets;
468+
jets.electrons.emplace_back(Electron{122.f});
469+
jets.electrons.emplace_back(Electron{125.f});
470+
jets.electrons.emplace_back(Electron{129.f});
471+
472+
Int_t nmuons = 1;
473+
float muon_pt[3] = {10.f, 20.f, 30.f};
474+
475+
struct {
476+
Int_t x = 1;
477+
Int_t y = 2;
478+
} point;
479+
452480
t.Branch("pt", &pt);
481+
t.Branch("photons", &photons);
482+
t.Branch("electron", &electron);
483+
t.Branch("jets", &jets);
484+
t.Branch("nmuons", &nmuons);
485+
t.Branch("muon_pt", muon_pt, "muon_pt[nmuons]");
486+
t.Branch("point", &point, "x/I:y/I");
453487

454-
pt = 42.0;
455488
t.Fill();
456-
457489
t.Write();
458490
}
459491

460-
TEST(RDFSnapshotRNTuple, DisallowFromTTree)
492+
TEST(RDFSnapshotRNTuple, FromTTree)
461493
{
462494
const auto treename = "tree";
463-
FileRAII fileGuard{"RDFSnapshotRNTuple_disallow_from_ttree.root"};
495+
FileRAII fileGuard{"RDFSnapshotRNTuple_from_ttree.root"};
464496

465497
WriteTestTree(treename, fileGuard.GetPath());
466498

@@ -469,13 +501,68 @@ TEST(RDFSnapshotRNTuple, DisallowFromTTree)
469501
RSnapshotOptions opts;
470502
opts.fOutputFormat = ROOT::RDF::ESnapshotOutputFormat::kRNTuple;
471503

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

481568
#ifdef R__USE_IMT

0 commit comments

Comments
 (0)