From b57330a1526a92d382159e315c8249856f0ec8e3 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Tue, 25 Jan 2022 19:27:58 +0100 Subject: [PATCH] groot/{rcmd,riofs,rtree,rvers}: add rtree.Reader support for TNtuple{,D} Signed-off-by: Sebastien Binet --- groot/gen.rboot.go | 2 +- groot/rcmd/dump_test.go | 50 ++++++++++++++++++++++++++ groot/rdict/cxx_root_streamers_gen.go | 28 +++++++++++++++ groot/riofs/gendata/gen-tntuple.go | 46 ++++++++++++++++++++++++ groot/riofs/gendata/gen-tntupled.go | 46 ++++++++++++++++++++++++ groot/riofs/riofs.go | 2 ++ groot/rtree/reader.go | 4 +++ groot/rtree/tree.go | 47 ++++++++++++++++++++++++ groot/rvers/versions_gen.go | 1 + groot/testdata/tntuple.root | Bin 0 -> 5811 bytes groot/testdata/tntupled.root | Bin 0 -> 5851 bytes 11 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 groot/riofs/gendata/gen-tntuple.go create mode 100644 groot/riofs/gendata/gen-tntupled.go create mode 100644 groot/testdata/tntuple.root create mode 100644 groot/testdata/tntupled.root diff --git a/groot/gen.rboot.go b/groot/gen.rboot.go index 7f5c4033d..e08a3483c 100644 --- a/groot/gen.rboot.go +++ b/groot/gen.rboot.go @@ -96,7 +96,7 @@ var ( "TLeafF", "TLeafD", "TLeafF16", "TLeafD32", "TLeafC", - "TNtuple", + "TNtuple", "TNtupleD", "TTree", } ) diff --git a/groot/rcmd/dump_test.go b/groot/rcmd/dump_test.go index c70b9c43c..e7cfa28cd 100644 --- a/groot/rcmd/dump_test.go +++ b/groot/rcmd/dump_test.go @@ -52,6 +52,56 @@ func TestDump(t *testing.T) { [000][branch1.floatleaf]: 15.5 [000][branch2.intleaf]: 20 [000][branch2.floatleaf]: 781.2 +`, + }, + { + name: "../testdata/tntuple.root", + want: `key[000]: ntup;1 "my ntuple title" (TNtuple) +[000][x]: 0 +[000][y]: 0.5 +[001][x]: 1 +[001][y]: 1.5 +[002][x]: 2 +[002][y]: 2.5 +[003][x]: 3 +[003][y]: 3.5 +[004][x]: 4 +[004][y]: 4.5 +[005][x]: 5 +[005][y]: 5.5 +[006][x]: 6 +[006][y]: 6.5 +[007][x]: 7 +[007][y]: 7.5 +[008][x]: 8 +[008][y]: 8.5 +[009][x]: 9 +[009][y]: 9.5 +`, + }, + { + name: "../testdata/tntupled.root", + want: `key[000]: ntup;1 "my ntuple title" (TNtupleD) +[000][x]: 0 +[000][y]: 0.5 +[001][x]: 1 +[001][y]: 1.5 +[002][x]: 2 +[002][y]: 2.5 +[003][x]: 3 +[003][y]: 3.5 +[004][x]: 4 +[004][y]: 4.5 +[005][x]: 5 +[005][y]: 5.5 +[006][x]: 6 +[006][y]: 6.5 +[007][x]: 7 +[007][y]: 7.5 +[008][x]: 8 +[008][y]: 8.5 +[009][x]: 9 +[009][y]: 9.5 `, }, { diff --git a/groot/rdict/cxx_root_streamers_gen.go b/groot/rdict/cxx_root_streamers_gen.go index 9e60e349b..3f44a43e4 100644 --- a/groot/rdict/cxx_root_streamers_gen.go +++ b/groot/rdict/cxx_root_streamers_gen.go @@ -5268,6 +5268,34 @@ func init() { Factor: 0.000000, }.New()}, })) + StreamerInfos.Add(NewCxxStreamerInfo("TNtupleD", 1, 0x8de8d873, []rbytes.StreamerElement{ + NewStreamerBase(Element{ + Name: *rbase.NewNamed("TTree", "Tree descriptor (the main ROOT I/O class)"), + Type: rmeta.Base, + Size: 0, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 1919213695, 0, 0, 0}, + Offset: 0, + EName: "BASE", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New(), 20), + &StreamerBasicType{StreamerElement: Element{ + Name: *rbase.NewNamed("fNvar", "Number of columns"), + Type: rmeta.Int, + Size: 4, + ArrLen: 0, + ArrDim: 0, + MaxIdx: [5]int32{0, 0, 0, 0, 0}, + Offset: 0, + EName: "int", + XMin: 0.000000, + XMax: 0.000000, + Factor: 0.000000, + }.New()}, + })) StreamerInfos.Add(NewCxxStreamerInfo("TTree", 20, 0x7264e07f, []rbytes.StreamerElement{ NewStreamerBase(Element{ Name: *rbase.NewNamed("TNamed", "The basis for a named object (name, title)"), diff --git a/groot/riofs/gendata/gen-tntuple.go b/groot/riofs/gendata/gen-tntuple.go new file mode 100644 index 000000000..f85a6c6e3 --- /dev/null +++ b/groot/riofs/gendata/gen-tntuple.go @@ -0,0 +1,46 @@ +// Copyright ©2022 The go-hep Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore +// +build ignore + +package main + +import ( + "flag" + "log" + + "go-hep.org/x/hep/groot/internal/rtests" +) + +var ( + root = flag.String("f", "test-tntuple.root", "output ROOT file") +) + +func main() { + flag.Parse() + + out, err := rtests.RunCxxROOT("gentntuple", []byte(script), *root) + if err != nil { + log.Fatalf("could not run ROOT macro:\noutput:\n%v\nerror: %+v", string(out), err) + } +} + +const script = ` +void gentntuple(const char* fname) { + int bufsize = 32000; + int evtmax = 10; + + auto f = TFile::Open(fname, "RECREATE"); + auto t = new TNtuple("ntup", "my ntuple title", "x:y"); + + for (int i = 0; i != evtmax; i++) { + t->Fill(i, i+0.5); + } + f->Write(); + f->Close(); + + exit(0); +} +` diff --git a/groot/riofs/gendata/gen-tntupled.go b/groot/riofs/gendata/gen-tntupled.go new file mode 100644 index 000000000..dbab7e23d --- /dev/null +++ b/groot/riofs/gendata/gen-tntupled.go @@ -0,0 +1,46 @@ +// Copyright ©2022 The go-hep Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore +// +build ignore + +package main + +import ( + "flag" + "log" + + "go-hep.org/x/hep/groot/internal/rtests" +) + +var ( + root = flag.String("f", "test-tntupled.root", "output ROOT file") +) + +func main() { + flag.Parse() + + out, err := rtests.RunCxxROOT("gentntuple", []byte(script), *root) + if err != nil { + log.Fatalf("could not run ROOT macro:\noutput:\n%v\nerror: %+v", string(out), err) + } +} + +const script = ` +void gentntuple(const char* fname) { + int bufsize = 32000; + int evtmax = 10; + + auto f = TFile::Open(fname, "RECREATE"); + auto t = new TNtupleD("ntup", "my ntuple title", "x:y"); + + for (int i = 0; i != evtmax; i++) { + t->Fill(i, i+0.5); + } + f->Write(); + f->Close(); + + exit(0); +} +` diff --git a/groot/riofs/riofs.go b/groot/riofs/riofs.go index cf90a7cc3..9d94b9a32 100644 --- a/groot/riofs/riofs.go +++ b/groot/riofs/riofs.go @@ -26,6 +26,8 @@ import ( //go:generate go run ./gendata/gen-tlv.go -f ../testdata/tlv-split00.root -split=0 //go:generate go run ./gendata/gen-tlv.go -f ../testdata/tlv-split01.root -split=1 //go:generate go run ./gendata/gen-tlv.go -f ../testdata/tlv-split99.root -split=99 +//go:generate go run ./gendata/gen-tntuple.go -f ../testdata/tntuple.root +//go:generate go run ./gendata/gen-tntupled.go -f ../testdata/tntupled.root // Directory describes a ROOT directory structure in memory. type Directory interface { diff --git a/groot/rtree/reader.go b/groot/rtree/reader.go index b940b718d..7f5625e1f 100644 --- a/groot/rtree/reader.go +++ b/groot/rtree/reader.go @@ -253,6 +253,10 @@ func newReader(t Tree, rvars []ReadVar, n int, beg, end int64) reader { switch t := t.(type) { case *ttree: return newRTree(t, rvars, n, beg, end) + case *tntuple: + return newRTree(&t.ttree, rvars, n, beg, end) + case *tntupleD: + return newRTree(&t.ttree, rvars, n, beg, end) case *chain: return newRChain(t, rvars, n, beg, end) case *join: diff --git a/groot/rtree/tree.go b/groot/rtree/tree.go index 71b12e17e..1783de409 100644 --- a/groot/rtree/tree.go +++ b/groot/rtree/tree.go @@ -646,6 +646,10 @@ type tntuple struct { nvars int } +func (*tntuple) RVersion() int16 { + return rvers.Ntuple +} + func (*tntuple) Class() string { return "TNtuple" } @@ -668,6 +672,37 @@ func (nt *tntuple) UnmarshalROOT(r *rbytes.RBuffer) error { return r.Err() } +type tntupleD struct { + ttree + nvars int +} + +func (*tntupleD) RVersion() int16 { + return rvers.NtupleD +} + +func (*tntupleD) Class() string { + return "TNtupleD" +} + +func (nt *tntupleD) UnmarshalROOT(r *rbytes.RBuffer) error { + if r.Err() != nil { + return r.Err() + } + + beg := r.Pos() + /*vers*/ _, pos, bcnt := r.ReadVersion(nt.Class()) + + if err := nt.ttree.UnmarshalROOT(r); err != nil { + return err + } + + nt.nvars = int(r.ReadI32()) + + r.CheckByteCount(pos, bcnt, beg, nt.Class()) + return r.Err() +} + type tioFeatures uint8 func (*tioFeatures) Class() string { return "TIOFeatures" } @@ -739,6 +774,13 @@ func init() { } rtypes.Factory.Add("TNtuple", f) } + { + f := func() reflect.Value { + o := &tntupleD{} + return reflect.ValueOf(o) + } + rtypes.Factory.Add("TNtupleD", f) + } } var ( @@ -753,6 +795,11 @@ var ( _ Tree = (*tntuple)(nil) _ rbytes.Unmarshaler = (*tntuple)(nil) + _ root.Object = (*tntupleD)(nil) + _ root.Named = (*tntupleD)(nil) + _ Tree = (*tntupleD)(nil) + _ rbytes.Unmarshaler = (*tntupleD)(nil) + _ root.Object = (*tioFeatures)(nil) _ rbytes.Marshaler = (*tioFeatures)(nil) _ rbytes.Unmarshaler = (*tioFeatures)(nil) diff --git a/groot/rvers/versions_gen.go b/groot/rvers/versions_gen.go index 8be41ee4b..e7533295d 100644 --- a/groot/rvers/versions_gen.go +++ b/groot/rvers/versions_gen.go @@ -104,5 +104,6 @@ const ( LeafD32 = 1 // ROOT version for TLeafD32 LeafC = 1 // ROOT version for TLeafC Ntuple = 2 // ROOT version for TNtuple + NtupleD = 1 // ROOT version for TNtupleD Tree = 20 // ROOT version for TTree ) diff --git a/groot/testdata/tntuple.root b/groot/testdata/tntuple.root new file mode 100644 index 0000000000000000000000000000000000000000..dd972566739e690170c44ee8a68c8f23d18b33eb GIT binary patch literal 5811 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 Gz`p=cC358e literal 0 HcmV?d00001 diff --git a/groot/testdata/tntupled.root b/groot/testdata/tntupled.root new file mode 100644 index 0000000000000000000000000000000000000000..4987dc17d501d97f972eb5e834e90acfefd2fce8 GIT binary patch literal 5851 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