From 4b282c1d346f58345caef7dfb6be83de40759fcd Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Thu, 15 Jan 2026 10:56:19 +0100 Subject: [PATCH] Add tests for ROOT::RVec ... with all possible index column types, copied from std::vector. Closes #19 --- types/README.md | 1 + types/RVec/README.md | 7 +++ types/RVec/fundamental/README.md | 15 ++++++ types/RVec/fundamental/read.C | 68 +++++++++++++++++++++++++++ types/RVec/fundamental/write.C | 67 ++++++++++++++++++++++++++ types/RVec/nested/README.md | 14 ++++++ types/RVec/nested/read.C | 81 ++++++++++++++++++++++++++++++++ types/RVec/nested/write.C | 76 ++++++++++++++++++++++++++++++ 8 files changed, 329 insertions(+) create mode 100644 types/RVec/README.md create mode 100644 types/RVec/fundamental/README.md create mode 100644 types/RVec/fundamental/read.C create mode 100644 types/RVec/fundamental/write.C create mode 100644 types/RVec/nested/README.md create mode 100644 types/RVec/nested/read.C create mode 100644 types/RVec/nested/write.C diff --git a/types/README.md b/types/README.md index 21e4239..c131b58 100644 --- a/types/README.md +++ b/types/README.md @@ -4,6 +4,7 @@ * [`multiset`](multiset): `std::multiset` with all `[Split]Index{32,64}` column types * [`optional`](optional): `std::optional` with different element types * [`pair`](pair): `std::pair` with different element types + * [`RVec`](RVec): `ROOT::RVec` with all `[Split]Index{32,64}` column types * [`set`](set): `std::set` with all `[Split]Index{32,64}` column types * [`string`](string): `std::string` with all `[Split]Index{32,64}` column types * [`tuple`](tuple): `std::tuple` with different element types diff --git a/types/RVec/README.md b/types/RVec/README.md new file mode 100644 index 0000000..52b0f30 --- /dev/null +++ b/types/RVec/README.md @@ -0,0 +1,7 @@ +# `ROOT::RVec` + + * [`fundamental`](fundamental): `ROOT::RVec` + * [`nested`](nested): `ROOT::RVec>` + +__Missing:__ +Field with the shorter alias `ROOT::RVec`; ROOT always stores the fully qualified type name `ROOT::VecOps::RVec`. diff --git a/types/RVec/fundamental/README.md b/types/RVec/fundamental/README.md new file mode 100644 index 0000000..39187d6 --- /dev/null +++ b/types/RVec/fundamental/README.md @@ -0,0 +1,15 @@ +# `ROOT::RVec` + +## Fields + + * `[Split]Index{32,64}` + +with the corresponding column type for offset column of the collection parent field. +All child fields use the default column encoding `Int32`. + +## Entries + +1. Single-element vectors, with ascending values +2. Empty vectors +3. Increasing number of elements in the vector: + one element in the first field, two elements in the second field, etc. diff --git a/types/RVec/fundamental/read.C b/types/RVec/fundamental/read.C new file mode 100644 index 0000000..f1d4400 --- /dev/null +++ b/types/RVec/fundamental/read.C @@ -0,0 +1,68 @@ +#include +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include +#include +#include +#include +#include + +using Vector = ROOT::RVec; + +static void PrintVectorValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + Vector &value = *entry.GetPtr(name); + os << " \"" << name << "\": ["; + bool first = true; + for (auto element : value) { + if (first) { + first = false; + } else { + os << ","; + } + os << "\n " << element; + } + if (!value.empty()) { + os << "\n "; + } + os << "]"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.RVec.fundamental.root", + std::string_view output = "types.RVec.fundamental.json") { + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintVectorValue(entry, "Index32", os); + PrintVectorValue(entry, "Index64", os); + PrintVectorValue(entry, "SplitIndex32", os); + PrintVectorValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/RVec/fundamental/write.C b/types/RVec/fundamental/write.C new file mode 100644 index 0000000..a7ae7bd --- /dev/null +++ b/types/RVec/fundamental/write.C @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include +#include +#include + +using Vector = ROOT::RVec; + +static std::shared_ptr MakeVectorField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.RVec.fundamental.root") { + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeVectorField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeVectorField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeVectorField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeVectorField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element vectors, with ascending values + *Index32 = {1}; + *Index64 = {2}; + *SplitIndex32 = {3}; + *SplitIndex64 = {4}; + writer->Fill(); + + // Second entry: empty vectors + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the vector + *Index32 = {1}; + *Index64 = {2, 3}; + *SplitIndex32 = {4, 5, 6}; + *SplitIndex64 = {7, 8, 9, 10}; + writer->Fill(); +} diff --git a/types/RVec/nested/README.md b/types/RVec/nested/README.md new file mode 100644 index 0000000..54ed245 --- /dev/null +++ b/types/RVec/nested/README.md @@ -0,0 +1,14 @@ +# `ROOT::RVec>` + +## Fields + + * `[Split]Index{32,64}` + +with the corresponding column type for offset columns of the two collection parent fields. +All child fields use the default column encoding `Int32`. + +## Entries + +1. Single-element vectors, with ascending values +2. Empty vectors +3. Increasing number of elements in the outer vector, with arbitrary lengths of the inner vectors diff --git a/types/RVec/nested/read.C b/types/RVec/nested/read.C new file mode 100644 index 0000000..7a0977a --- /dev/null +++ b/types/RVec/nested/read.C @@ -0,0 +1,81 @@ +#include +#include +#include + +using ROOT::Experimental::REntry; +using ROOT::Experimental::RNTupleReader; + +#include +#include +#include +#include +#include + +using Vector = ROOT::RVec>; + +static void PrintNestedVectorValue(const REntry &entry, std::string_view name, + std::ostream &os, bool last = false) { + Vector &value = *entry.GetPtr(name); + os << " \"" << name << "\": ["; + bool outerFirst = true; + for (auto inner : value) { + if (outerFirst) { + outerFirst = false; + } else { + os << ","; + } + os << "\n ["; + bool innerFirst = true; + for (auto element : inner) { + if (innerFirst) { + innerFirst = false; + } else { + os << ","; + } + os << "\n " << element; + } + if (!inner.empty()) { + os << "\n "; + } + os << "]"; + } + if (!value.empty()) { + os << "\n "; + } + os << "]"; + if (!last) { + os << ","; + } + os << "\n"; +} + +void read(std::string_view input = "types.RVec.nested.root", + std::string_view output = "types.RVec.nested.json") { + std::ofstream os(std::string{output}); + os << "[\n"; + + auto reader = RNTupleReader::Open("ntpl", input); + auto &entry = reader->GetModel().GetDefaultEntry(); + bool first = true; + for (auto index : *reader) { + reader->LoadEntry(index); + + if (first) { + first = false; + } else { + os << ",\n"; + } + os << " {\n"; + + PrintNestedVectorValue(entry, "Index32", os); + PrintNestedVectorValue(entry, "Index64", os); + PrintNestedVectorValue(entry, "SplitIndex32", os); + PrintNestedVectorValue(entry, "SplitIndex64", os, /*last=*/true); + + os << " }"; + // Newline is intentionally missing, may need to print a comma before the + // next entry. + } + os << "\n"; + os << "]\n"; +} diff --git a/types/RVec/nested/write.C b/types/RVec/nested/write.C new file mode 100644 index 0000000..6cbbc9e --- /dev/null +++ b/types/RVec/nested/write.C @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include + +using ROOT::Experimental::EColumnType; +using ROOT::Experimental::RField; +using ROOT::Experimental::RNTupleModel; +using ROOT::Experimental::RNTupleWriteOptions; +using ROOT::Experimental::RNTupleWriter; + +#include +#include +#include + +using Inner = ROOT::RVec; +using Vector = ROOT::RVec; + +static std::shared_ptr MakeVectorField(RNTupleModel &model, + std::string_view name, + EColumnType indexType) { + auto field = std::make_unique>(name); + field->SetColumnRepresentatives({{indexType}}); + field->GetSubFields()[0]->SetColumnRepresentatives({{indexType}}); + model.AddField(std::move(field)); + return model.GetDefaultEntry().GetPtr(name); +} + +void write(std::string_view filename = "types.RVec.nested.root") { + auto model = RNTupleModel::Create(); + + // Non-split index encoding + auto Index32 = MakeVectorField(*model, "Index32", EColumnType::kIndex32); + auto Index64 = MakeVectorField(*model, "Index64", EColumnType::kIndex64); + + // Split index encoding + auto SplitIndex32 = + MakeVectorField(*model, "SplitIndex32", EColumnType::kSplitIndex32); + auto SplitIndex64 = + MakeVectorField(*model, "SplitIndex64", EColumnType::kSplitIndex64); + + RNTupleWriteOptions options; + options.SetCompression(0); + auto writer = + RNTupleWriter::Recreate(std::move(model), "ntpl", filename, options); + + // First entry: single-element vectors, with ascending values + *Index32 = {{1}}; + *Index64 = {{2}}; + *SplitIndex32 = {{3}}; + *SplitIndex64 = {{4}}; + writer->Fill(); + + // Second entry: empty vectors + *Index32 = {}; + *Index64 = {}; + *SplitIndex32 = {}; + *SplitIndex64 = {}; + writer->Fill(); + + // Third entry: increasing number of elements in the outer vector + *Index32 = {{1}}; + *Index64 = {{}, {2, 3}}; + *SplitIndex32 = {{4}, {}, {5, 6}}; + *SplitIndex64 = {{}, {7, 8, 9}, {}, {10}}; + writer->Fill(); + + // Work around bug in 6.34 for destroying RVec's, later fixed by commit + // https://github.com/root-project/root/commit/996cac359d for 6.36. + Index32->clear(); + Index64->clear(); + SplitIndex32->clear(); + SplitIndex64->clear(); +}