diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 9030cf5fe7eec..888e30f317570 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -541,8 +541,23 @@ struct Index : o2::soa::IndexColumn> { template using is_dynamic_t = framework::is_specialization; +namespace persistent_type_helper +{ +// This checks both for the existence of the ::persistent member in the class T as well as the value returned stored in it. +// Hack: a pointer to any field of type int inside persistent. Both true_type and false_type do not have any int field, but anyways we pass nullptr. +// The compiler picks the version with exact number of arguments when only it can, i.e., when T::persistent is defined. +template +typename T::persistent test(int T::persistent::*); + +template +std::false_type test(...); +} // namespace persistent_type_helper + template -using is_persistent_t = typename std::decay_t::persistent::type; +using is_persistent_t = decltype(persistent_type_helper::test(nullptr)); + +template +using is_persistent_v = typename is_persistent_t::value; template using is_external_index_t = typename std::conditional, std::true_type, std::false_type>::type; @@ -1112,10 +1127,12 @@ class Table auto getId() const { using decayed = std::decay_t; - if constexpr (framework::has_type_v) { + if constexpr (framework::has_type_v) { // index to another table constexpr auto idx = framework::has_type_at_v(bindings_pack_t{}); return framework::pack_element_t::getId(); - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { // self index + return this->globalIndex(); + } else if constexpr (is_index_t::value && decayed::mLabel == "Index") { // soa::Index<> return this->globalIndex(); } else { return static_cast(-1); @@ -1505,14 +1522,14 @@ namespace row_helpers template std::array getArrowColumns(arrow::Table* table, framework::pack) { - static_assert(std::conjunction_v, "BinningPolicy: only persistent columns accepted (not dynamic and not index ones"); + static_assert(std::conjunction_v, "Arrow columns: only persistent columns accepted (not dynamic and not index ones"); return std::array{o2::soa::getIndexFromLabel(table, Cs::columnLabel())...}; } template std::array, sizeof...(Cs)> getChunks(arrow::Table* table, framework::pack, uint64_t ci) { - static_assert(std::conjunction_v, "BinningPolicy: only persistent columns accepted (not dynamic and not index ones"); + static_assert(std::conjunction_v, "Arrow chunks: only persistent columns accepted (not dynamic and not index ones"); return std::array, sizeof...(Cs)>{o2::soa::getIndexFromLabel(table, Cs::columnLabel())->chunk(ci)...}; } @@ -1541,16 +1558,21 @@ typename C::type getSingleRowData(arrow::Table* table, T& rowIterator, uint64_t { using decayed = std::decay_t; if constexpr (decayed::persistent::value) { - return getSingleRowPersistentData(table, ci, ai); + auto val = getSingleRowPersistentData(table, ci, ai); + return val; } else if constexpr (o2::soa::is_dynamic_t()) { - return getSingleRowDynamicData(rowIterator, globalIndex); - } else if constexpr (o2::soa::is_index_column_v) { - return getSingleRowIndexData(rowIterator, globalIndex); + auto val = getSingleRowDynamicData(rowIterator, globalIndex); + return val; + } else if constexpr (o2::soa::is_index_t::value) { + auto val = getSingleRowIndexData(rowIterator, globalIndex); + return val; + } else { + static_assert(!sizeof(decayed*), "Unrecognized column kind"); // A trick to delay static_assert until we actually instantiate this branch } } template -std::tuple getRowData(arrow::Table* table, T rowIterator, uint64_t ci, uint64_t ai, uint64_t globalIndex) +std::tuple getRowData(arrow::Table* table, T rowIterator, framework::pack, uint64_t ci, uint64_t ai, uint64_t globalIndex) { return std::make_tuple(getSingleRowData(table, rowIterator, ci, ai, globalIndex)...); } diff --git a/Framework/Core/include/Framework/ASoAHelpers.h b/Framework/Core/include/Framework/ASoAHelpers.h index 19b1cd1408865..40ccda09fc4b3 100644 --- a/Framework/Core/include/Framework/ASoAHelpers.h +++ b/Framework/Core/include/Framework/ASoAHelpers.h @@ -125,9 +125,8 @@ std::vector groupTable(const T& table, const BP& binningPol selInd = selectedRows[ind]; } - auto rowData = o2::soa::row_helpers::getRowData(arrowTable, rowIterator, ci, ai, ind); - - int val = binningPolicy.getBin(rowData); + auto values = binningPolicy.getBinningValues(rowIterator, arrowTable, ci, ai, ind); + auto val = binningPolicy.getBin(values); if (val != outsider) { groupedIndices.emplace_back(val, ind); } diff --git a/Framework/Core/include/Framework/BinningPolicy.h b/Framework/Core/include/Framework/BinningPolicy.h index eb398e44b15cd..2769f3f399801 100644 --- a/Framework/Core/include/Framework/BinningPolicy.h +++ b/Framework/Core/include/Framework/BinningPolicy.h @@ -13,7 +13,6 @@ #define FRAMEWORK_BINNINGPOLICY_H #include "Framework/HistogramSpec.h" // only for VARIABLE_WIDTH -#include "Framework/ASoAHelpers.h" #include "Framework/Pack.h" #include "Framework/ArrowTypes.h" #include @@ -21,31 +20,89 @@ namespace o2::framework { -template -struct BinningPolicy { - BinningPolicy(std::array, sizeof...(Cs) + 1> bins, bool ignoreOverflows = true) : mBins(bins), mIgnoreOverflows(ignoreOverflows) +namespace binning_helpers +{ +void expandConstantBinning(std::vector const& bins, std::vector& expanded) +{ + if (bins[0] != VARIABLE_WIDTH) { + int nBins = static_cast(bins[0]); + expanded.clear(); + expanded.resize(nBins + 2); + expanded[0] = VARIABLE_WIDTH; + for (int i = 0; i <= nBins; i++) { + expanded[i + 1] = bins[1] + i * (bins[2] - bins[1]) / nBins; + } + } +} +} // namespace binning_helpers + +template +struct BinningPolicyBase { + BinningPolicyBase(std::array, N> bins, bool ignoreOverflows = true) : mBins(bins), mIgnoreOverflows(ignoreOverflows) + { + static_assert(N <= 3, "No default binning for more than 3 columns, you need to implement a binning class yourself"); + for (int i = 0; i < N; i++) { + binning_helpers::expandConstantBinning(bins[i], mBins[i]); + } + } + + // Note: Overflow / underflow bin -1 is not included + int getAllBinsCount() const + { + if constexpr (N == 1) { + return getBinsCount(mBins[0]); + } + if constexpr (N == 2) { + return getBinsCount(mBins[0]) * getBinsCount(mBins[1]); + } + if constexpr (N == 2) { + return getBinsCount(mBins[0]) * getBinsCount(mBins[1]) * getBinsCount(mBins[2]); + } + return -1; + } + + // Note: Overflow / underflow bin -1 is not included + int getXBinsCount() const + { + return getBinsCount(mBins[0]); + } + + // Note: Overflow / underflow bin -1 is not included + int getYBinsCount() const { - static_assert(sizeof...(Cs) < 3, "No default binning for more than 3 columns, you need to implement a binning class yourself"); - for (int i = 0; i < sizeof...(Cs) + 1; i++) { - expandConstantBinning(bins[i], i); + if constexpr (N == 1) { + return 0; } + return getBinsCount(mBins[1]); } - int getBin(std::tuple const& data) const + // Note: Overflow / underflow bin -1 is not included + int getZBinsCount() const { + if constexpr (N < 3) { + return 0; + } + return getBinsCount(mBins[2]); + } + + template + int getBin(std::tuple const& data) const + { + static_assert(sizeof...(Ts) == N, "There must be the same number of binning axes and data values/columns"); + unsigned int i = 2, j = 2, k = 2; - if (this->mIgnoreOverflows) { + if (mIgnoreOverflows) { // underflow - if (std::get<0>(data) < this->mBins[0][1]) { // xBins[0] is a dummy VARIABLE_WIDTH + if (std::get<0>(data) < mBins[0][1]) { // mBins[0][0] is a dummy VARIABLE_WIDTH return -1; } - if constexpr (sizeof...(Cs) > 0) { - if (std::get<1>(data) < this->mBins[1][1]) { // this->mBins[1][0] is a dummy VARIABLE_WIDTH + if constexpr (N > 1) { + if (std::get<1>(data) < mBins[1][1]) { // mBins[1][0] is a dummy VARIABLE_WIDTH return -1; } } - if constexpr (sizeof...(Cs) > 1) { - if (std::get<2>(data) < this->mBins[2][1]) { // this->mBins[2][0] is a dummy VARIABLE_WIDTH + if constexpr (N > 2) { + if (std::get<2>(data) < mBins[2][1]) { // mBins[2][0] is a dummy VARIABLE_WIDTH return -1; } } @@ -55,76 +112,76 @@ struct BinningPolicy { k = 1; } - for (; i < this->mBins[0].size(); i++) { - if (std::get<0>(data) < this->mBins[0][i]) { + for (; i < mBins[0].size(); i++) { + if (std::get<0>(data) < mBins[0][i]) { - if constexpr (sizeof...(Cs) > 0) { - for (; j < this->mBins[1].size(); j++) { - if (std::get<1>(data) < this->mBins[1][j]) { + if constexpr (N > 1) { + for (; j < mBins[1].size(); j++) { + if (std::get<1>(data) < mBins[1][j]) { - if constexpr (sizeof...(Cs) > 1) { - for (; k < this->mBins[2].size(); k++) { - if (std::get<2>(data) < this->mBins[2][k]) { + if constexpr (N > 2) { + for (; k < mBins[2].size(); k++) { + if (std::get<2>(data) < mBins[2][k]) { return getBinAt(i, j, k); } } - if (this->mIgnoreOverflows) { + if (mIgnoreOverflows) { return -1; } } - // overflow for this->mBins[2] only + // overflow for mBins[2] only return getBinAt(i, j, k); } } - if (this->mIgnoreOverflows) { + if (mIgnoreOverflows) { return -1; } - // overflow for this->mBins[1] only - if constexpr (sizeof...(Cs) > 1) { - for (k = 2; k < this->mBins[2].size(); k++) { - if (std::get<2>(data) < this->mBins[2][k]) { + // overflow for mBins[1] only + if constexpr (N > 2) { + for (k = 2; k < mBins[2].size(); k++) { + if (std::get<2>(data) < mBins[2][k]) { return getBinAt(i, j, k); } } } } - // overflow for this->mBins[2] and this->mBins[1] + // overflow for mBins[2] and mBins[1] return getBinAt(i, j, k); } } - if (this->mIgnoreOverflows) { + if (mIgnoreOverflows) { // overflow return -1; } - // overflow for this->mBins[0] only - if constexpr (sizeof...(Cs) > 0) { - for (j = 2; j < this->mBins[1].size(); j++) { - if (std::get<1>(data) < this->mBins[1][j]) { + // overflow for mBins[0] only + if constexpr (N > 1) { + for (j = 2; j < mBins[1].size(); j++) { + if (std::get<1>(data) < mBins[1][j]) { - if constexpr (sizeof...(Cs) > 1) { - for (k = 2; k < this->mBins[2].size(); k++) { - if (std::get<2>(data) < this->mBins[2][k]) { + if constexpr (N > 2) { + for (k = 2; k < mBins[2].size(); k++) { + if (std::get<2>(data) < mBins[2][k]) { return getBinAt(i, j, k); } } } - // overflow for this->mBins[0] and this->mBins[2] + // overflow for mBins[0] and mBins[2] return getBinAt(i, j, k); } } } - // overflow for this->mBins[0] and this->mBins[1] - if constexpr (sizeof...(Cs) > 1) { - for (k = 2; k < this->mBins[2].size(); k++) { - if (std::get<2>(data) < this->mBins[2][k]) { + // overflow for mBins[0] and mBins[1] + if constexpr (N > 2) { + for (k = 2; k < mBins[2].size(); k++) { + if (std::get<2>(data) < mBins[2][k]) { return getBinAt(i, j, k); } } @@ -134,89 +191,115 @@ struct BinningPolicy { return getBinAt(i, j, k); } - // Note: Overflow / underflow bin -1 is not included - int getXBinsCount() const + std::array, N> mBins; + bool mIgnoreOverflows; + + private: + // We substract 1 to account for VARIABLE_WIDTH in the bins vector + // We substract second 1 if we omit values below minima (underflow, mapped to -1) + // Otherwise we add 1 and we get the number of bins including those below and over the outer edges + int getBinAt(unsigned int iRaw, unsigned int jRaw, unsigned int kRaw) const { - return this->mBins[0].size() - 1 - getOverflowShift(); + int shiftBinsWithoutOverflow = getOverflowShift(); + unsigned int i = iRaw - 1 + shiftBinsWithoutOverflow; + unsigned int j = jRaw - 1 + shiftBinsWithoutOverflow; + unsigned int k = kRaw - 1 + shiftBinsWithoutOverflow; + auto xBinsCount = getXBinsCount(); + if constexpr (N == 1) { + return i; + } else if constexpr (N == 2) { + return i + j * xBinsCount; + } else if constexpr (N == 3) { + return i + j * xBinsCount + k * xBinsCount * getYBinsCount(); + } else { + return -1; + } } - // Note: Overflow / underflow bin -1 is not included - int getYBinsCount() const + int getOverflowShift() const { - if constexpr (sizeof...(Cs) == 0) { - return 0; - } - return this->mBins[1].size() - 1 - getOverflowShift(); + return mIgnoreOverflows ? -1 : 1; } // Note: Overflow / underflow bin -1 is not included - int getZBinsCount() const + int getBinsCount(std::vector const& bins) const { - if constexpr (sizeof...(Cs) < 2) { - return 0; - } - return this->mBins[2].size() - 1 - getOverflowShift(); + return bins.size() - 1 + getOverflowShift(); } +}; - // Note: Overflow / underflow bin -1 is not included - int getAllBinsCount() const +template +struct BinningPolicy; + +template +struct BinningPolicy, Ts...> : BinningPolicyBase { + BinningPolicy(std::tuple const& lambdaPtrs, std::array, sizeof...(Ts)> bins, bool ignoreOverflows = true) : BinningPolicyBase(bins, ignoreOverflows), mBinningFunctions{lambdaPtrs} { - if constexpr (sizeof...(Cs) == 0) { - return getXBinsCount(); - } - if constexpr (sizeof...(Cs) == 1) { - return getXBinsCount() * getYBinsCount(); + } + + template + auto getBinningValue(T& rowIterator, arrow::Table* table, uint64_t globalIndex = -1, uint64_t ci = -1, uint64_t ai = -1) const + { + using decayed = std::decay_t; + if (globalIndex == -1) { + globalIndex = *(std::get<0>(rowIterator.getIndices())); } - if constexpr (sizeof...(Cs) == 2) { - return getXBinsCount() * getYBinsCount() * getZBinsCount(); + + if constexpr (has_type_v>) { + rowIterator.setCursor(globalIndex); + return std::get(mBinningFunctions)(rowIterator); + } else { + if (ci == -1 && ai == -1) { + auto colIterator = static_cast(rowIterator).mColumnIterator; + ci = colIterator.mCurrentChunk; + ai = *(colIterator.mCurrentPos) - colIterator.mFirstIndex; + } + return soa::row_helpers::getSingleRowData(table, rowIterator, ci, ai, globalIndex); } - return -1; } - using persistent_columns_t = framework::selected_pack; + template + auto getBinningValues(T& rowIterator, arrow::Table* table, uint64_t globalIndex = -1, uint64_t ci = -1, uint64_t ai = -1) const + { + return std::make_tuple(getBinningValue(rowIterator, table, globalIndex, ci, ai)...); + } + + template + auto getBinningValues(typename T::iterator rowIterator, T& table, uint64_t globalIndex = -1, uint64_t ci = -1, uint64_t ai = -1) const + { + return getBinningValues(rowIterator, table.asArrowTable().get(), globalIndex, ci, ai); + } + + template + int getBin(std::tuple const& data) const + { + return BinningPolicyBase::template getBin(data); + } + + using persistent_columns_t = framework::selected_pack; private: - // We substract 1 to account for VARIABLE_WIDTH in the bins vector - // We substract second 1 if we omit values below minima (underflow, mapped to -1) - // Otherwise we add 1 and we get the number of bins including those below and over the outer edges - int getBinAt(unsigned int iRaw, unsigned int jRaw, unsigned int kRaw) const + std::tuple mBinningFunctions; +}; + +template +struct ColumnBinningPolicy : BinningPolicyBase { + ColumnBinningPolicy(std::array, sizeof...(Ts)> bins, bool ignoreOverflows = true) : BinningPolicyBase(bins, ignoreOverflows) { - int shiftBinsWithoutOverflow = getOverflowShift(); - unsigned int i = iRaw - 1 - shiftBinsWithoutOverflow; - unsigned int j = jRaw - 1 - shiftBinsWithoutOverflow; - unsigned int k = kRaw - 1 - shiftBinsWithoutOverflow; - auto xBinsCount = getXBinsCount(); - if constexpr (sizeof...(Cs) == 0) { - return i; - } else if constexpr (sizeof...(Cs) == 1) { - return i + j * xBinsCount; - } else if constexpr (sizeof...(Cs) == 2) { - return i + j * xBinsCount + k * xBinsCount * (this->mBins[1].size() - 1 - shiftBinsWithoutOverflow); - } else { - return -1; - } } - int getOverflowShift() const + template + auto getBinningValues(T& rowIterator, arrow::Table* table, uint64_t ci, uint64_t ai, uint64_t globalIndex) const { - return mIgnoreOverflows ? 1 : -1; + return std::make_tuple(soa::row_helpers::getSingleRowData(table, rowIterator, ci, ai, globalIndex)...); } - void expandConstantBinning(std::vector const& bins, int ind) + int getBin(std::tuple const& data) const { - if (bins[0] != VARIABLE_WIDTH) { - int nBins = static_cast(bins[0]); - this->mBins[ind].clear(); - this->mBins[ind].resize(nBins + 2); - this->mBins[ind][0] = VARIABLE_WIDTH; - for (int i = 0; i <= nBins; i++) { - this->mBins[ind][i + 1] = bins[1] + i * (bins[2] - bins[1]) / nBins; - } - } + return BinningPolicyBase::template getBin(data); } - std::array, sizeof...(Cs) + 1> mBins; - bool mIgnoreOverflows; + using persistent_columns_t = framework::selected_pack; }; template @@ -224,6 +307,12 @@ struct NoBinningPolicy { // Just take the bin number from the column data NoBinningPolicy() = default; + template + auto getBinningValues(T& rowIterator, arrow::Table* table, uint64_t ci, uint64_t ai, uint64_t globalIndex) const + { + return std::make_tuple(soa::row_helpers::getSingleRowData(table, rowIterator, ci, ai, globalIndex)); + } + int getBin(std::tuple const& data) const { return std::get<0>(data); diff --git a/Framework/Core/test/benchmark_EventMixing.cxx b/Framework/Core/test/benchmark_EventMixing.cxx index ddf6dbc20bdde..71f8d6ab2e0c7 100644 --- a/Framework/Core/test/benchmark_EventMixing.cxx +++ b/Framework/Core/test/benchmark_EventMixing.cxx @@ -48,7 +48,7 @@ static void BM_EventMixingTraditional(benchmark::State& state) std::vector xBins{VARIABLE_WIDTH, -0.064, -0.062, -0.060, 0.066, 0.068, 0.070, 0.072}; std::vector yBins{VARIABLE_WIDTH, -0.320, -0.301, -0.300, 0.330, 0.340, 0.350, 0.360}; - using BinningType = BinningPolicy; + using BinningType = ColumnBinningPolicy; BinningType binningOnPositions{{xBins, yBins}, true}; // true is for 'ignore overflows' (true by default) TableBuilder colBuilder, trackBuilder; @@ -135,7 +135,7 @@ static void BM_EventMixingCombinations(benchmark::State& state) std::vector xBins{VARIABLE_WIDTH, -0.064, -0.062, -0.060, 0.066, 0.068, 0.070, 0.072}; std::vector yBins{VARIABLE_WIDTH, -0.320, -0.301, -0.300, 0.330, 0.340, 0.350, 0.360}; - using BinningType = BinningPolicy; + using BinningType = ColumnBinningPolicy; BinningType binningOnPositions{{xBins, yBins}, true}; // true is for 'ignore overflows' (true by default) TableBuilder colBuilder, trackBuilder; diff --git a/Framework/Core/test/test_ASoAHelpers.cxx b/Framework/Core/test/test_ASoAHelpers.cxx index 1e4c963c44011..936f38ce248ad 100644 --- a/Framework/Core/test/test_ASoAHelpers.cxx +++ b/Framework/Core/test/test_ASoAHelpers.cxx @@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(CombinationsGeneratorConstruction) std::vector yBins{VARIABLE_WIDTH, 0, 5, 10, 20, 30, 40, 50, 101}; std::vector zBins{VARIABLE_WIDTH, -7.0, -5.0, -3.0, -1.0, 1.0, 3.0, 5.0, 7.0}; - BinningPolicy pairBinning{{yBins, zBins}, false}; + ColumnBinningPolicy pairBinning{{yBins, zBins}, false}; CombinationsGenerator>::CombinationsIterator combIt(CombinationsStrictlyUpperIndexPolicy(testsA, testsA)); BOOST_REQUIRE_NE(static_cast(std::get<0>(*(combIt))).getIterator().mCurrentPos, nullptr); @@ -293,8 +293,8 @@ BOOST_AUTO_TEST_CASE(CombinationsGeneratorConstruction) auto combBlock = combinations(CombinationsBlockStrictlyUpperSameIndexPolicy(pairBinning, 2, -1, testsA, testsA)); - static_assert(std::is_same_v, int32_t, TestA, TestA>>::CombinationsIterator>, "Wrong iterator type"); - static_assert(std::is_same_v, int32_t, TestA, TestA>::CombinationType&>, "Wrong combination type"); + static_assert(std::is_same_v, int32_t, TestA, TestA>>::CombinationsIterator>, "Wrong iterator type"); + static_assert(std::is_same_v, int32_t, TestA, TestA>::CombinationType&>, "Wrong combination type"); auto beginBlockCombination = *(combBlock.begin()); BOOST_REQUIRE_NE(static_cast(std::get<0>(beginBlockCombination)).getIterator().mCurrentPos, nullptr); @@ -948,8 +948,8 @@ BOOST_AUTO_TEST_CASE(BlockCombinations) std::vector yBins{VARIABLE_WIDTH, 0, 5, 10, 20, 30, 40, 50, 101}; std::vector zBins{VARIABLE_WIDTH, -7.0, -5.0, -3.0, -1.0, 1.0, 3.0, 5.0, 7.0}; - BinningPolicy pairBinning{{yBins, zBins}, false}; - BinningPolicy pairBinningNoOverflows{{yBins, zBins}, true}; + ColumnBinningPolicy pairBinning{{yBins, zBins}, false}; + ColumnBinningPolicy pairBinningNoOverflows{{yBins, zBins}, true}; // 2, 3, 5, 8, 9 have overflows in testA std::vector> expectedFullPairsNoOverflows{ @@ -1168,8 +1168,8 @@ BOOST_AUTO_TEST_CASE(BlockCombinations) // [3, 5] [0, 4], [7], [1, 6], [2], [8, 9] // Assuming bins intervals: [ , ) std::vector xBins{VARIABLE_WIDTH, 0, 7, 10}; - BinningPolicy tripleBinning{{xBins, yBins, zBins}, false}; - BinningPolicy tripleBinningNoOverflows{{xBins, yBins, zBins}, true}; + ColumnBinningPolicy tripleBinning{{xBins, yBins, zBins}, false}; + ColumnBinningPolicy tripleBinningNoOverflows{{xBins, yBins, zBins}, true}; // 2, 3, 5, 8, 9 have overflows in testA std::vector> expectedFullPairsTripleBinningNoOverflows{ @@ -1276,7 +1276,7 @@ BOOST_AUTO_TEST_CASE(CombinationsHelpers) std::vector yBins{VARIABLE_WIDTH, 0, 5, 10, 20, 30, 40, 50, 101}; std::vector zBins{VARIABLE_WIDTH, -7.0, -5.0, -3.0, -1.0, 1.0, 3.0, 5.0, 7.0}; - BinningPolicy pairBinning{{yBins, zBins}, false}; + ColumnBinningPolicy pairBinning{{yBins, zBins}, false}; std::vector> expectedStrictlyUpperPairs{ {0, 4}, {0, 7}, {4, 7}, {1, 6}, {3, 5}, {2, 8}, {2, 9}, {8, 9}};