Skip to content

Commit e1b0b9f

Browse files
committed
[ntuple] add GetAttributes methods to RNTupleAttrSetReader
1 parent c67d290 commit e1b0b9f

3 files changed

Lines changed: 349 additions & 5 deletions

File tree

tree/ntuple/inc/ROOT/RNTupleAttrReading.hxx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class RNTupleModel;
2525

2626
namespace Experimental {
2727

28+
class RNTupleAttrEntryIterable;
29+
2830
// clang-format off
2931
/**
3032
\class ROOT::Experimental::RNTupleAttrRange
@@ -111,6 +113,7 @@ for (auto idx : attrSet->GetAttributes(10)) {
111113
// clang-format on
112114
class RNTupleAttrSetReader final {
113115
friend class ROOT::RNTupleReader;
116+
friend class RNTupleAttrEntryIterable;
114117

115118
/// List containing pairs { entryRange, entryIndex }, used to quickly find out which entries in the Attribute
116119
/// RNTuple contain entries that overlap a given range. The list is sorted by range start, i.e.
@@ -121,8 +124,13 @@ class RNTupleAttrSetReader final {
121124
/// The reconstructed user model
122125
std::unique_ptr<ROOT::RNTupleModel> fUserModel;
123126

127+
static bool EntryRangesAreSorted(const decltype(fEntryRanges) &ranges);
128+
124129
explicit RNTupleAttrSetReader(std::unique_ptr<RNTupleReader> reader);
125130

131+
std::vector<ROOT::NTupleSize_t>
132+
GetAttributesRangeInternal(NTupleSize_t startEntry, NTupleSize_t endEntry, bool rangeIsContained);
133+
126134
public:
127135
RNTupleAttrSetReader(const RNTupleAttrSetReader &) = delete;
128136
RNTupleAttrSetReader &operator=(const RNTupleAttrSetReader &) = delete;
@@ -148,6 +156,92 @@ public:
148156

149157
/// Returns the number of all attribute entries in this attribute set.
150158
std::size_t GetNEntries() const { return fEntryRanges.size(); }
159+
160+
/// Returns all the attributes in this Set. The returned attributes are sorted by entry range start.
161+
RNTupleAttrEntryIterable GetAttributes();
162+
/// Returns all the attributes whose range contains index `entryIndex`.
163+
RNTupleAttrEntryIterable GetAttributes(NTupleSize_t entryIndex);
164+
/// Returns all the attributes whose range fully contains `[startEntry, endEntry)`
165+
RNTupleAttrEntryIterable GetAttributesContainingRange(NTupleSize_t startEntry, NTupleSize_t endEntry);
166+
/// Returns all the attributes whose range is fully contained in `[startEntry, endEntry)`
167+
RNTupleAttrEntryIterable GetAttributesInRange(NTupleSize_t startEntry, NTupleSize_t endEntry);
168+
};
169+
170+
// clang-format off
171+
/**
172+
\class ROOT::Experimental::RNTupleAttrEntryIterable
173+
\ingroup NTuple
174+
\brief Iterable class used to loop over attribute entries.
175+
176+
This class allows to perform range-for iteration on some set of attributes, typically returned by the
177+
RNTupleAttrSetReader::GetAttributes family of methods.
178+
179+
See the documentation of RNTupleAttrSetReader for example usage.
180+
*/
181+
// clang-format on
182+
class RNTupleAttrEntryIterable final {
183+
public:
184+
struct RFilter {
185+
RNTupleAttrRange fRange;
186+
bool fIsContained;
187+
};
188+
189+
private:
190+
RNTupleAttrSetReader &fReader;
191+
std::optional<RFilter> fFilter;
192+
193+
public:
194+
class RIterator final {
195+
private:
196+
using Iter_t = decltype(std::declval<RNTupleAttrSetReader>().fEntryRanges.begin());
197+
Iter_t fCur, fEnd;
198+
std::optional<RFilter> fFilter;
199+
200+
Iter_t Next() const;
201+
bool FullyContained(RNTupleAttrRange range) const;
202+
203+
public:
204+
using iterator_category = std::forward_iterator_tag;
205+
using iterator = RIterator;
206+
using value_type = NTupleSize_t;
207+
using difference_type = std::ptrdiff_t;
208+
using pointer = const value_type *;
209+
using reference = const value_type &;
210+
211+
RIterator(Iter_t iter, Iter_t end, std::optional<RFilter> filter) : fCur(iter), fEnd(end), fFilter(filter)
212+
{
213+
if (fFilter) {
214+
if (fFilter->fRange.GetLength() == 0)
215+
fCur = end;
216+
else
217+
fCur = Next();
218+
}
219+
}
220+
iterator operator++()
221+
{
222+
++fCur;
223+
fCur = Next();
224+
return *this;
225+
}
226+
iterator operator++(int)
227+
{
228+
iterator it = *this;
229+
++fCur;
230+
fCur = Next();
231+
return it;
232+
}
233+
reference operator*() { return fCur->second; }
234+
bool operator!=(const iterator &rh) const { return !operator==(rh); }
235+
bool operator==(const iterator &rh) const { return fCur == rh.fCur; }
236+
};
237+
238+
explicit RNTupleAttrEntryIterable(RNTupleAttrSetReader &reader, std::optional<RFilter> filter = {})
239+
: fReader(reader), fFilter(filter)
240+
{
241+
}
242+
243+
RIterator begin() { return RIterator{fReader.fEntryRanges.begin(), fReader.fEntryRanges.end(), fFilter}; }
244+
RIterator end() { return RIterator{fReader.fEntryRanges.end(), fReader.fEntryRanges.end(), fFilter}; }
151245
};
152246

153247
} // namespace Experimental

tree/ntuple/src/RNTupleAttrReading.cxx

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,145 @@ std::unique_ptr<ROOT::REntry> ROOT::Experimental::RNTupleAttrSetReader::CreateEn
9898
return fUserModel->CreateEntry();
9999
}
100100

101+
// Entry ranges should be sorted with respect to GetStart by construction.
102+
bool ROOT::Experimental::RNTupleAttrSetReader::EntryRangesAreSorted(const decltype(fEntryRanges) &ranges)
103+
{
104+
ROOT::NTupleSize_t prevStart = 0;
105+
for (const auto &[range, _] : ranges) {
106+
if (range.GetStart() < prevStart)
107+
return false;
108+
prevStart = range.GetStart();
109+
}
110+
return true;
111+
};
112+
113+
std::vector<ROOT::NTupleSize_t>
114+
ROOT::Experimental::RNTupleAttrSetReader::GetAttributesRangeInternal(NTupleSize_t startEntry, NTupleSize_t endEntry,
115+
bool rangeIsContained)
116+
{
117+
std::vector<ROOT::NTupleSize_t> result;
118+
119+
if (endEntry < startEntry) {
120+
R__LOG_WARNING(ROOT::Internal::NTupleLog())
121+
<< "end < start when getting attributes from Attribute Set '" << GetDescriptor().GetName()
122+
<< "' (range given: [" << startEntry << ", " << endEntry << "].";
123+
return result;
124+
}
125+
126+
assert(EntryRangesAreSorted(fEntryRanges));
127+
128+
const auto FullyContained = [rangeIsContained](auto startInner, auto endInner, auto startOuter, auto endOuter) {
129+
if (rangeIsContained) {
130+
std::swap(startOuter, startInner);
131+
std::swap(endOuter, endInner);
132+
}
133+
return startOuter <= startInner && endInner <= endOuter;
134+
};
135+
136+
// TODO: consider using binary search, since fEntryRanges is sorted
137+
// (maybe it should be done only if the size of the list is bigger than a threshold).
138+
for (const auto &[range, index] : fEntryRanges) {
139+
const auto &firstLast = range.GetFirstLast();
140+
if (!firstLast)
141+
continue;
142+
143+
const auto &[first, last] = *firstLast;
144+
if (first >= endEntry)
145+
break; // We can break here because fEntryRanges is sorted.
146+
147+
if (FullyContained(startEntry, endEntry, first, last + 1)) {
148+
result.push_back(index);
149+
}
150+
}
151+
152+
return result;
153+
}
154+
155+
ROOT::Experimental::RNTupleAttrEntryIterable
156+
ROOT::Experimental::RNTupleAttrSetReader::GetAttributesContainingRange(NTupleSize_t startEntry, NTupleSize_t endEntry)
157+
{
158+
RNTupleAttrRange range;
159+
if (endEntry <= startEntry) {
160+
R__LOG_WARNING(ROOT::Internal::NTupleLog())
161+
<< "empty range given when getting attributes from Attribute Set '" << GetDescriptor().GetName()
162+
<< "' (range given: [" << startEntry << ", " << endEntry << ")).";
163+
// Make sure we find 0 entries
164+
range = RNTupleAttrRange::FromStartLength(startEntry, 0);
165+
} else {
166+
range = RNTupleAttrRange::FromStartEnd(startEntry, endEntry);
167+
}
168+
RNTupleAttrEntryIterable::RFilter filter{range, false};
169+
return RNTupleAttrEntryIterable{*this, filter};
170+
}
171+
172+
ROOT::Experimental::RNTupleAttrEntryIterable
173+
ROOT::Experimental::RNTupleAttrSetReader::GetAttributesInRange(NTupleSize_t startEntry, NTupleSize_t endEntry)
174+
{
175+
RNTupleAttrRange range;
176+
if (endEntry <= startEntry) {
177+
R__LOG_WARNING(ROOT::Internal::NTupleLog())
178+
<< "empty range given when getting attributes from Attribute Set '" << GetDescriptor().GetName()
179+
<< "' (range given: [" << startEntry << ", " << endEntry << ")).";
180+
// Make sure we find 0 entries
181+
range = RNTupleAttrRange::FromStartLength(startEntry, 0);
182+
} else {
183+
range = RNTupleAttrRange::FromStartEnd(startEntry, endEntry);
184+
}
185+
RNTupleAttrEntryIterable::RFilter filter{range, true};
186+
return RNTupleAttrEntryIterable{*this, filter};
187+
}
188+
189+
ROOT::Experimental::RNTupleAttrEntryIterable
190+
ROOT::Experimental::RNTupleAttrSetReader::GetAttributes(NTupleSize_t entryIndex)
191+
{
192+
RNTupleAttrEntryIterable::RFilter filter{RNTupleAttrRange::FromStartEnd(entryIndex, entryIndex + 1), false};
193+
return RNTupleAttrEntryIterable{*this, filter};
194+
}
195+
196+
ROOT::Experimental::RNTupleAttrEntryIterable ROOT::Experimental::RNTupleAttrSetReader::GetAttributes()
197+
{
198+
return RNTupleAttrEntryIterable{*this};
199+
}
200+
201+
//
202+
// RNTupleAttrEntryIterable
203+
//
204+
bool ROOT::Experimental::RNTupleAttrEntryIterable::RIterator::FullyContained(RNTupleAttrRange range) const
205+
{
206+
assert(fFilter);
207+
if (fFilter->fIsContained) {
208+
return fFilter->fRange.GetStart() <= range.GetStart() && range.GetEnd() <= fFilter->fRange.GetEnd();
209+
} else {
210+
return range.GetStart() <= fFilter->fRange.GetStart() && fFilter->fRange.GetEnd() <= range.GetEnd();
211+
}
212+
}
213+
214+
ROOT::Experimental::RNTupleAttrEntryIterable::RIterator::Iter_t
215+
ROOT::Experimental::RNTupleAttrEntryIterable::RIterator::Next() const
216+
{
217+
// TODO: consider using binary search, since fEntryRanges is sorted
218+
// (maybe it should be done only if the size of the list is bigger than a threshold).
219+
for (auto it = fCur; it != fEnd; ++it) {
220+
const auto &[range, index] = *it;
221+
// If we have no filter, every entry is valid.
222+
if (!fFilter)
223+
return it;
224+
225+
const auto &firstLast = range.GetFirstLast();
226+
// If this is nullopt it means this is a zero-length entry: we always skip those except
227+
// for the "catch-all" GetAttributes() (which is when fFilter is also nullopt).
228+
if (!firstLast)
229+
continue;
230+
231+
const auto &[first, last] = *firstLast;
232+
if (first >= fFilter->fRange.GetEnd()) {
233+
// Since fEntryRanges is sorted we know we are at the end of the iteration
234+
// TODO: tweak fEnd to directly pass the last entry?
235+
return fEnd;
236+
}
237+
238+
if (FullyContained(RNTupleAttrRange::FromStartEnd(first, last + 1)))
239+
return it;
240+
}
241+
return fEnd;
242+
}

0 commit comments

Comments
 (0)