@@ -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+
3850TEST (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