Skip to content
Prev Previous commit
Next Next commit
Request slice infos for Preslice declarations
Non-optional Preslice declarations no longer fail when the column is missing
A declaration that cannot be used is skipped with a warning:
- the table is not an input and does not have the column
- the table is not an input (non-optional only)
- the table does not have the column (non-optional only)
  • Loading branch information
aalkin committed Sep 29, 2026
commit 6c167ed38d7597840c3db2d29509c7747c13d5e2
6 changes: 3 additions & 3 deletions Framework/Core/include/Framework/ASoA.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
#if defined(__CLING__)
#error "Please do not include this file in ROOT dictionary generation"
#endif
#include "Framework/InputSpec.h"
#include "Framework/Concepts.h"
#include "Framework/ConcreteDataMatcher.h"
#include "Framework/Pack.h" // IWYU pragma: export
#include "Framework/FunctionalHelpers.h" // IWYU pragma: export
#include "Headers/DataHeader.h" // IWYU pragma: export
Expand Down Expand Up @@ -1450,8 +1450,8 @@ struct PresliceBase : public Policy {

PresliceBase(expressions::BindingNode index_)
: Policy{Entry(
o2::soa::getLabelFromTypeForKey<T, OPT>(std::string{index_.name}),
o2::soa::getMatcherFromTypeForKey<T, OPT>(std::string{index_.name}),
o2::soa::getLabelFromTypeForKey<T, true>(std::string{index_.name}),
o2::soa::getMatcherFromTypeForKey<T, true>(std::string{index_.name}),
std::string{index_.name}
)}
{
Expand Down
65 changes: 65 additions & 0 deletions Framework/Core/include/Framework/AnalysisManagers.h
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,71 @@ bool replaceOrigin(T& presliceGroup, header::DataOrigin const& newOrigin)
return true;
}

template <typename T>
requires(!is_preslice<T> && !is_preslice_group<T>)
bool addSlicingInputs(T&, std::vector<InputSpec>&, header::DataOrigin const&)
{
return false;
}

/// check if any of the tables the sliced type is based on is an input of the task
template <soa::is_table T>
bool isSlicedTableInput(std::vector<InputSpec> const& inputs, header::DataOrigin const& newOrigin)
{
auto isInput = [&inputs, &newOrigin](ConcreteDataMatcher matcher) {
if ((matcher.origin == header::DataOrigin{"AOD"}) && (newOrigin != header::DataOrigin{"AOD"})) {
matcher = replaceOrigin(matcher, newOrigin);
}
return std::ranges::any_of(inputs, [&matcher](InputSpec const& input) { return DataSpecUtils::match(input, matcher); });
};
return [&isInput]<size_t... Is>(std::index_sequence<Is...>) {
return (isInput(o2::aod::matcher<T::originals[Is]>()) || ...);
}(std::make_index_sequence<T::originals.size()>{});
}

/// all the process function inputs are already added at this point, so a Preslice can only
/// amend them. Depending on whether the sliced table is an input of the task and whether it has
/// the index column, there are 4 cases:
/// 1. no table, no column - likely an incorrect declaration, warning for both Preslice and PresliceOptional
/// 2. no table, column - Preslice that never works, or a common declaration in a templated task that is
/// not effective in this specialization, warning for Preslice only
/// 3. table, no column - the intended case for PresliceOptional, a mistake for Preslice, warning for Preslice only
/// 4. table, column - slicing input is added
template<is_preslice T>
bool addSlicingInputs(T& preslice, std::vector<InputSpec>& inputs, header::DataOrigin const& newOrigin)
{
using target_t = typename T::target_t;
auto const& [binding, matcher, key, enabled] = preslice.bindingKey;
if (preslice.isMissing()) {
if (!isSlicedTableInput<target_t>(inputs, newOrigin)) {
LOGP(warn, "Preslice declared on {} is skipped: {} is not an input of any process function and does not have column {}, the declaration is likely incorrect",
o2::soa::getLabelFromType<target_t>(), o2::soa::getLabelFromType<target_t>(), key);
} else if constexpr (!T::optional) {
LOGP(warn, "Preslice declared on {} is skipped: it does not have column {}, use PresliceOptional if the column is not always expected",
o2::soa::getLabelFromType<target_t>(), key);
}
return true;
}
if (std::ranges::none_of(inputs, [&matcher](InputSpec const& input) { return DataSpecUtils::match(input, matcher); })) {
if constexpr (!T::optional) {
LOGP(warn, "Preslice declared on {}/{} ({}) is skipped: {} is not an input of any process function, use PresliceOptional if the declaration is not effective in every specialization of a templated task",
binding, key, DataSpecUtils::describe(matcher), binding);
}
return true;
}
DataSpecUtils::updateInputList(inputs, inputForEntry(preslice.bindingKey, std::same_as<typename T::policy_t, framework::PreslicePolicySorted>));
return true;
}

template <is_preslice_group T>
bool addSlicingInputs(T&& presliceGroup, std::vector<InputSpec>& inputs, header::DataOrigin const& newOrigin)
{
homogeneous_apply_refs<true>([&inputs, &newOrigin](auto& preslice){
return addSlicingInputs(preslice, inputs, newOrigin);
}, presliceGroup);
return true;
}

template <typename T>
requires(!is_preslice<T> && !is_preslice_group<T>)
bool registerCache(T&, Cache&, Cache&)
Expand Down
5 changes: 5 additions & 0 deletions Framework/Core/include/Framework/AnalysisTask.h
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,11 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args)
// replace origins in Preslice declarations
homogeneous_apply_refs_sized<numElements>([&newOrigin](auto& element) { return analysis_task_parsers::replaceOrigin(element, newOrigin); }, *task.get());

// add slicing inputs from Preslice declarations
homogeneous_apply_refs_sized<numElements>([&inputs, &newOrigin](auto& element){
return analysis_task_parsers::addSlicingInputs(element, inputs, newOrigin);
}, *task.get());

auto algo = AlgorithmSpec::InitCallback
{
[task = task, expressionInfos, inputInfos, newOrigin, newOriginStr](InitContext& ic) mutable {
Expand Down
32 changes: 28 additions & 4 deletions Framework/Core/test/test_AnalysisTask.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,11 @@ struct KTask {
std::shared_ptr<int> someSharedInt;
};


// PresliceOptional ignores situations where the table is present, but does
// not have the requested column
struct LTask {
SliceCache cache;
Preslice<aod::Tracks> perCol = aod::track::collisionId;
PresliceOptional<aod::Tracks> perPart = aod::mctracklabel::mcParticleId;
PresliceUnsorted<aod::McCollisionLabels> perMcCol = aod::mccollisionlabel::mcCollisionId;
PresliceUnsortedOptional<aod::Collisions> perMcColopt = aod::mccollisionlabel::mcCollisionId;
void process(aod::McCollision const&, soa::SmallGroups<soa::Join<aod::Collisions, aod::McCollisionLabels>> const&) {}
Expand All @@ -189,14 +190,31 @@ struct LTask {
struct MTask {
SliceCache cache;
struct : public PresliceGroup {
Preslice<aod::Tracks> perCol = aod::track::collisionId;
PresliceOptional<aod::Tracks> perPart = aod::mctracklabel::mcParticleId;
PresliceUnsorted<aod::McCollisionLabels> perMcCol = aod::mccollisionlabel::mcCollisionId;
PresliceUnsortedOptional<aod::Collisions> perMcColopt = aod::mccollisionlabel::mcCollisionId;
} foo;
void process(aod::McCollision const&, soa::SmallGroups<soa::Join<aod::Collisions, aod::McCollisionLabels>> const&) {}
};

// Preslices that cannot be used are skipped with a warning:
// - table is not an input and does not have the column
// - table is not an input
// - table does not have the column
struct NTask {
SliceCache cache;
Preslice<aod::Tracks> perPart = aod::mctracklabel::mcParticleId;
Preslice<aod::Tracks> perCol = aod::track::collisionId;
Preslice<aod::Collisions> perMcCol = aod::mccollisionlabel::mcCollisionId;
void process(aod::McCollision const&, soa::SmallGroups<soa::Join<aod::Collisions, aod::McCollisionLabels>> const&) {}
};

// optional Preslice on a table that is not an input of any process function is skipped silently
struct OTask {
SliceCache cache;
PresliceOptional<aod::Tracks> perCol = aod::track::collisionId;
void process(aod::McCollision const&, soa::SmallGroups<soa::Join<aod::Collisions, aod::McCollisionLabels>> const&) {}
};

TEST_CASE("AdaptorCompilation")
{
auto cfgc = makeEmptyConfigContext();
Expand Down Expand Up @@ -276,6 +294,12 @@ TEST_CASE("AdaptorCompilation")

auto task13 = adaptAnalysisTask<MTask>(*cfgc, TaskName{"test13"});
REQUIRE(task13.inputs.size() == 4); // 3 base + 1 actual slice

auto task14 = adaptAnalysisTask<NTask>(*cfgc, TaskName{"test14"});
REQUIRE(task14.inputs.size() == 4); // 3 base + 1 actual slice, all Preslices skipped

auto task15 = adaptAnalysisTask<OTask>(*cfgc, TaskName{"test15"});
REQUIRE(task15.inputs.size() == 4); // 3 base + 1 actual slice, optional Preslice skipped
}

TEST_CASE("TestPartitionIteration")
Expand Down