Skip to content

Commit 72ee413

Browse files
committed
[Framework] Add opt-in skipping of empty grouped slices
Filter empty associations before constructing typed slices while preserving the original grouping policy and windows. Keep IncludeEmpty as the default and document consumer and cache preconditions. Add Arrow-backed integration coverage for Pair and Triple policies, iterator copies, cache refresh, filtered grouping, bindings and window metadata.
1 parent 1dc8fd9 commit 72ee413

3 files changed

Lines changed: 520 additions & 0 deletions

File tree

‎Framework/Core/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ add_executable(o2-test-framework-core
239239
test/test_FrameworkDataFlowToO2Control.cxx
240240
test/test_Graphviz.cxx
241241
test/test_GroupSlicer.cxx
242+
test/test_GroupedCombinations.cxx
242243
test/test_HistogramRegistry.cxx
243244
test/test_HTTPParser.cxx
244245
test/test_IndexBuilder.cxx

‎Framework/Core/include/Framework/GroupedCombinations.h‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@
2121
namespace o2::framework
2222
{
2323

24+
/// Select which tuples of the original grouping policy are yielded.
25+
/// IncludeEmpty preserves the existing stream, including empty associated slices.
26+
/// SkipEmpty omits tuples with any empty association. Use it only when omitting
27+
/// those tuples has no observable effect on the consumer, including counters,
28+
/// normalization, window bookkeeping, random-number streams and diagnostics.
29+
/// Window flags and neighbour counts retain their original-policy meaning: they
30+
/// are not recomputed for surviving tuples, and a window may yield no tuple.
31+
/// The grouping policy must terminate and advance without observable side effects;
32+
/// an entirely empty association can end iteration without visiting every step.
33+
enum class GroupedSlicePolicy { IncludeEmpty,
34+
SkipEmpty };
35+
36+
template <typename T>
37+
struct SupportsEmptyAssociatedGate : std::false_type {};
38+
39+
template <aod::is_aod_hash L, aod::is_aod_hash D, aod::is_origin_hash O, typename... C>
40+
struct SupportsEmptyAssociatedGate<soa::Table<L, D, O, C...>>
41+
: std::bool_constant<O::hash != "JOIN"_h && O::hash != "CONC"_h> {};
42+
2443
// Create an instance of a tuple interleaved from given tuples
2544
template <typename... T1s, typename... T2s, std::size_t... Is>
2645
auto interleaveTuplesImpl(std::tuple<T1s...>& t1, std::tuple<T2s...>& t2, std::index_sequence<Is...>)
@@ -67,6 +86,12 @@ struct GroupedCombinationsGenerator {
6786
: GroupingPolicy(groupingPolicy), mIndexColumns{getMatchingIndexNode<G, As>()...}, cache{cache_}
6887
{
6988
}
89+
GroupedIterator(const GroupingPolicy& groupingPolicy, SliceCache* cache_, GroupedSlicePolicy slicePolicy)
90+
requires((SupportsEmptyAssociatedGate<As>::value && ...))
91+
: GroupedIterator(groupingPolicy, cache_)
92+
{
93+
mSlicePolicy = slicePolicy;
94+
}
7095
template <typename... T2s>
7196
GroupedIterator(const GroupingPolicy& groupingPolicy, const G& grouping, const std::tuple<T2s...>& associated, SliceCache* cache_)
7297
: GroupingPolicy(groupingPolicy),
@@ -172,8 +197,42 @@ struct GroupedCombinationsGenerator {
172197
return std::get<I>(*mAssociated).sliceByCached(mIndexColumns[I], ind, *cache);
173198
}
174199

200+
template <std::size_t I>
201+
bool associatedHasRows() const
202+
{
203+
using A = std::tuple_element_t<I, std::tuple<As...>>;
204+
auto const& associated = std::get<I>(*mAssociated);
205+
if (associated.size() == 0) {
206+
return false;
207+
}
208+
auto const& node = mIndexColumns[I];
209+
auto localCache = cache->ptr->getCacheFor({"", soa::originReplacement(cache->ptr->newOrigin)(soa::getMatcherFromTypeForKey<A>(node.name)), node.name});
210+
uint64_t index = *std::get<0>(std::get<I>(this->mCurrent).getIndices());
211+
return localCache.getSliceFor(index).second != 0;
212+
}
213+
214+
template <std::size_t... I>
215+
bool allAssociatedHaveRows(std::index_sequence<I...>) const
216+
{
217+
return (associatedHasRows<I>() && ...);
218+
}
219+
175220
void setCurrentGroupedCombination()
176221
{
222+
if constexpr ((SupportsEmptyAssociatedGate<As>::value && ...)) {
223+
if (mSlicePolicy == GroupedSlicePolicy::SkipEmpty && !this->mIsEnd) {
224+
// A globally empty association makes every original pair ineligible.
225+
if (std::apply([](auto const&... tables) { return ((tables.size() == 0) || ...); }, *mAssociated)) {
226+
GroupingPolicy::moveToEnd();
227+
return;
228+
}
229+
// Advance the unchanged policy. Never remove grouping rows or refill
230+
// a window with a more distant partner after rejecting an empty pair.
231+
while (!this->mIsEnd && !allAssociatedHaveRows(std::index_sequence_for<As...>{})) {
232+
this->addOne();
233+
}
234+
}
235+
}
177236
if (!this->mIsEnd) {
178237
auto& currentGrouping = GroupingPolicy::mCurrent;
179238
mSlices.emplace(getAssociatedTables());
@@ -187,6 +246,7 @@ struct GroupedCombinationsGenerator {
187246
std::optional<std::tuple<As...>> mSlices;
188247
std::optional<GroupedIteratorType> mCurrentGrouped;
189248
SliceCache* cache = nullptr;
249+
GroupedSlicePolicy mSlicePolicy = GroupedSlicePolicy::IncludeEmpty;
190250
};
191251

192252
using iterator = GroupedIterator;
@@ -218,6 +278,24 @@ struct GroupedCombinationsGenerator {
218278
{
219279
setTables(grouping, associated);
220280
}
281+
/// Opt in to the tuple filter described by GroupedSlicePolicy.
282+
/// The type constraint accepts raw associated tables and excludes Join, Concat
283+
/// and filtered associations. It does not validate sorting or cache contents.
284+
/// As with sliceByCached, each matching association index must be sorted and
285+
/// have an initialized sorted SliceCache entry for the current table/dataframe.
286+
/// Grouping iterator indices must use the same coordinates as the cache keys.
287+
/// The cache and bound tables must remain valid and unchanged during iteration.
288+
/// Invalid/missing cache entries are not validated by this filter, and skipped
289+
/// typed-slice construction need not reproduce diagnostics for invalid inputs.
290+
template <typename... T2s>
291+
GroupedCombinationsGenerator(const BP& binningPolicy, int catNeighbours, const T1& outsider, const G& grouping, const std::tuple<T2s...>& associated, SliceCache* cache, GroupedSlicePolicy slicePolicy)
292+
requires((SupportsEmptyAssociatedGate<As>::value && ...))
293+
: mBegin(GroupingPolicy(binningPolicy, catNeighbours, outsider), cache, slicePolicy),
294+
mEnd(GroupingPolicy(binningPolicy, catNeighbours, outsider), cache, slicePolicy)
295+
{
296+
setTables(grouping, associated);
297+
}
298+
221299
GroupedCombinationsGenerator(GroupedCombinationsGenerator const&) = default;
222300
GroupedCombinationsGenerator& operator=(GroupedCombinationsGenerator const&) = default;
223301
~GroupedCombinationsGenerator() = default;

0 commit comments

Comments
 (0)