diff --git a/PWGLF/DataModel/LFResonanceTables.h b/PWGLF/DataModel/LFResonanceTables.h index 1de673d77c4..11fd45842ef 100644 --- a/PWGLF/DataModel/LFResonanceTables.h +++ b/PWGLF/DataModel/LFResonanceTables.h @@ -83,6 +83,16 @@ DECLARE_SOA_COLUMN(ImpactParameter, impactParameter, float); //! ImpactParamete DECLARE_SOA_COLUMN(MCMultiplicity, mcMultiplicity, float); //! MC Multiplicity, o2-linter: disable=name/o2-column (pre-existing public column name kept for schema and API compatibility) } // namespace resocollision + +// Keep the established ResoCollisionColls schema above unchanged. Automatic +// GroupSlicer association to aod::Collisions requires the canonical physical +// column name fIndexCollisions, so the modular initializer writes this small +// companion table for the hybrid daughter process. +namespace resocollisiongroup +{ +DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(OriginalCollision, originalCollision, int, Collisions, "Collisions", ""); //! +} // namespace resocollisiongroup + DECLARE_SOA_TABLE(ResoCollisions, "AOD", "RESOCOLLISION", o2::soa::Index<>, o2::aod::mult::MultNTracksPV, @@ -100,6 +110,10 @@ DECLARE_SOA_TABLE(ResoCollisionColls, "AOD", "RESOCOLLISIONCOL", resocollision::CollisionId); using ResoCollisionColl = ResoCollisionColls::iterator; +DECLARE_SOA_TABLE(ResoCollisionGroups, "AOD", "RESOCOLLGROUP", + resocollisiongroup::OriginalCollisionId); +using ResoCollisionGroup = ResoCollisionGroups::iterator; + DECLARE_SOA_TABLE(ResoMCCollisions, "AOD", "RESOMCCOLLISION", o2::soa::Index<>, resocollision::IsVtxIn10, diff --git a/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx b/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx index 500e6219c01..1d748682f6c 100644 --- a/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx +++ b/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx @@ -86,9 +86,10 @@ struct ResonanceModuleInitializer { Service ccdb; ///< CCDB manager service Service pdg; ///< PDG database service - Produces resoCollisions; ///< Output table for resonance collisions - Produces resoCollisionColls; ///< Output table for collision references - Produces resoMCCollisions; ///< Output table for MC resonance collisions + Produces resoCollisions; ///< Output table for resonance collisions + Produces resoCollisionColls; ///< Output table for collision references + Produces resoCollisionGroups; ///< Canonical original-collision grouping references + Produces resoMCCollisions; ///< Output table for MC resonance collisions // CCDB options struct : ConfigurableGroup { @@ -492,6 +493,7 @@ struct ResonanceModuleInitializer { resoCollisions(collision.multNTracksPV(), collision.multNTracksPVeta1(), collision.multNTracksPVetaHalf(), collision.posX(), collision.posY(), collision.posZ(), centEst(collision), dBz, isRecINELgt0); resoCollisionColls(collision.globalIndex()); + resoCollisionGroups(collision.globalIndex()); } PROCESS_SWITCH(ResonanceModuleInitializer, processRun3, "Default process for RUN3", false); @@ -516,6 +518,7 @@ struct ResonanceModuleInitializer { resoCollisions(0, 0, 0, collision.posX(), collision.posY(), collision.posZ(), centrality, dBz, 0); resoCollisionColls(collision.globalIndex()); + resoCollisionGroups(collision.globalIndex()); } PROCESS_SWITCH(ResonanceModuleInitializer, processRun2, "process for RUN2", false); @@ -699,6 +702,7 @@ struct ResonanceDaughterInitializer { // Keep ResonanceModuleInitializer::cfgBypassCollIndexFill disabled and enable // the matching Run 2/Run 3 base event process for MC workflows. using ResoCollisionWithIndex = soa::Join; + using SelectedResoCollisions = soa::Join; /** * @brief Initializes the task @@ -707,9 +711,12 @@ struct ResonanceDaughterInitializer { */ void init(InitContext&) { - const bool processTrackDataEnabled = doprocessData || doprocessDataWithPairGate; + const bool processTrackDataEnabled = doprocessData || doprocessDataHybrid || doprocessDataWithPairGate; const bool processTrackMCEnabled = doprocessMC || doprocessMCWithPairGate; + const bool processV0DataEnabled = doprocessV0Data || doprocessV0DataHybrid; + const bool processCascDataEnabled = doprocessCascData || doprocessCascDataHybrid; const int enabledTrackProcesses = static_cast(doprocessData) + + static_cast(doprocessDataHybrid) + static_cast(doprocessDataWithPairGate) + static_cast(doprocessMC) + static_cast(doprocessMCWithPairGate); @@ -717,15 +724,21 @@ struct ResonanceDaughterInitializer { if (enabledTrackProcesses > 1) { LOGF(fatal, "Only one track process can be enabled in ResonanceDaughterInitializer"); } - if ((doprocessData || doprocessMC) && + if (static_cast(doprocessV0Data) + static_cast(doprocessV0DataHybrid) + static_cast(doprocessV0MC) > 1) { + LOGF(fatal, "Only one V0 process can be enabled in ResonanceDaughterInitializer"); + } + if (static_cast(doprocessCascData) + static_cast(doprocessCascDataHybrid) + static_cast(doprocessCascMC) > 1) { + LOGF(fatal, "Only one cascade process can be enabled in ResonanceDaughterInitializer"); + } + if ((doprocessData || doprocessDataHybrid || doprocessMC) && (FilterForDerivedTables.cfgBypassNoPairV0s || FilterForDerivedTables.cfgBypassNoPairCascades)) { - LOGF(warn, "Pair-gate options are ignored by processData/processMC; enable the matching *WithPairGate process to apply them"); + LOGF(warn, "Pair-gate options are ignored by processData/processDataHybrid/processMC; enable the matching *WithPairGate process to apply them"); } - if (doprocessDataWithPairGate && FilterForDerivedTables.cfgBypassNoPairV0s && !doprocessV0Data) { - LOGF(fatal, "cfgBypassNoPairV0s requires processV0Data so an accepted V0 is written for every retained collision"); + if (doprocessDataWithPairGate && FilterForDerivedTables.cfgBypassNoPairV0s && !processV0DataEnabled) { + LOGF(fatal, "cfgBypassNoPairV0s requires processV0Data or processV0DataHybrid so an accepted V0 is written for every retained collision"); } - if (doprocessDataWithPairGate && FilterForDerivedTables.cfgBypassNoPairCascades && !doprocessCascData) { - LOGF(fatal, "cfgBypassNoPairCascades requires processCascData so an accepted cascade is written for every retained collision"); + if (doprocessDataWithPairGate && FilterForDerivedTables.cfgBypassNoPairCascades && !processCascDataEnabled) { + LOGF(fatal, "cfgBypassNoPairCascades requires processCascData or processCascDataHybrid so an accepted cascade is written for every retained collision"); } if (!std::isfinite(TrackCuts.cfgCutMinPt.value) || @@ -860,7 +873,7 @@ struct ResonanceDaughterInitializer { } } - if (doprocessV0Data || doprocessV0MC) { + if (processV0DataEnabled || doprocessV0MC) { qaRegistry.add("QA/hGoodV0Indices", "hGoodV0Indices", kTH1D, {idxAxis}); if (doprocessV0MC) { qaRegistry.add("QA/hGoodMCV0Indices", "hGoodMCV0Indices", kTH1D, {idxAxis}); @@ -871,7 +884,7 @@ struct ResonanceDaughterInitializer { qaRegistry.add("QA/hV0CosPA", "V0 CosPA", kTH1F, {cosPAAxis}); } - if (doprocessCascData || doprocessCascMC) { + if (processCascDataEnabled || doprocessCascMC) { AxisSpec radiusAxis = {100, 0.0, 200.0, "Cascade Radius"}; AxisSpec cosPAAxis = {100, 0.97, 1.0, "Cascade CosPA"}; qaRegistry.add("QA/hGoodCascIndices", "hGoodCascIndices", kTH1D, {idxAxis}); @@ -885,19 +898,19 @@ struct ResonanceDaughterInitializer { if (processTrackDataEnabled || processTrackMCEnabled) { LOGF(info, "ResonanceDaughterInitializer initialized with tracks"); } - if (doprocessV0Data || doprocessV0MC) { + if (processV0DataEnabled || doprocessV0MC) { LOGF(info, "ResonanceDaughterInitializer initialized with V0s"); } - if (doprocessCascData || doprocessCascMC) { + if (processCascDataEnabled || doprocessCascMC) { LOGF(info, "ResonanceDaughterInitializer initialized with cascades"); } // Check if the module is initialized with both data and MC - if ((processTrackDataEnabled && processTrackMCEnabled) || (doprocessV0Data && doprocessV0MC) || (doprocessCascData && doprocessCascMC)) { + if ((processTrackDataEnabled && processTrackMCEnabled) || (processV0DataEnabled && doprocessV0MC) || (processCascDataEnabled && doprocessCascMC)) { LOGF(fatal, "ResonanceDaughterInitializer initialized with both data and MC"); } // Check if none of the processes are enabled - if (!doprocessDummy && !processTrackDataEnabled && !processTrackMCEnabled && !doprocessV0Data && !doprocessV0MC && !doprocessCascData && !doprocessCascMC) { + if (!doprocessDummy && !processTrackDataEnabled && !processTrackMCEnabled && !processV0DataEnabled && !doprocessV0MC && !processCascDataEnabled && !doprocessCascMC) { LOGF(fatal, "ResonanceDaughterInitializer not initialized, enable at least one process"); } } @@ -1874,6 +1887,25 @@ struct ResonanceDaughterInitializer { } PROCESS_SWITCH(ResonanceDaughterInitializer, processDummy, "Process dummy", true); + /** + * @brief Fills all enabled track tables from an already grouped track slice + * + * @tparam isMC Boolean indicating if it's MC + * @param collision Reduced collision used as the output foreign key + * @param tracks Tracks belonging to the corresponding original collision + */ + template + void fillTrackTables(CollisionType const& collision, TrackTableType const& tracks) + { + fillTracks(collision, tracks); + if (FilterForDerivedTables.cfgFillMicroTracks) { + fillMicroTracks(collision, tracks); + } + if (FilterForDerivedTables.cfgFillUltraMicroTracks) { + fillUltraMicroTracks(collision, tracks); + } + } + /** * @brief Fills track tables for one original collision * @@ -1888,13 +1920,7 @@ struct ResonanceDaughterInitializer { void fillTrackTablesForCollision(CollisionType const& collision, TrackTableType const& tracks, PresliceType const& perCollision) { auto tracksThisCollision = tracks.sliceBy(perCollision, collision.collisionId()); - fillTracks(collision, tracksThisCollision); - if (FilterForDerivedTables.cfgFillMicroTracks) { - fillMicroTracks(collision, tracksThisCollision); - } - if (FilterForDerivedTables.cfgFillUltraMicroTracks) { - fillUltraMicroTracks(collision, tracksThisCollision); - } + fillTrackTables(collision, tracksThisCollision); } /** @@ -1910,6 +1936,29 @@ struct ResonanceDaughterInitializer { } PROCESS_SWITCH(ResonanceDaughterInitializer, processData, "Process tracks for data", false); + /** + * @brief Processes data tracks using the two-stage hybrid grouping + * + * The canonical fIndexCollisions column in ResoCollisionGroups lets + * GroupSlicer associate both reduced collisions and tracks to the same + * original aod::Collision. The tracks argument is therefore already the + * selected slice for this collision and must not be sliced again. + */ + void processDataHybrid(aod::Collision const&, + soa::SmallGroups const& reducedCollisions, + soa::Filtered const& tracks) + { + if (reducedCollisions.size() == 0) { + return; + } + if (reducedCollisions.size() != 1) { + LOGF(fatal, "Expected exactly one reduced collision for an original collision, found %zu", reducedCollisions.size()); + } + auto reducedCollision = reducedCollisions.begin(); + fillTrackTables(reducedCollision, tracks); + } + PROCESS_SWITCH(ResonanceDaughterInitializer, processDataHybrid, "Process data tracks with the two-stage hybrid grouping", false); + /** * @brief Processes data tracks with configurable selected-V0 and selected-cascade gates */ @@ -1984,6 +2033,29 @@ struct ResonanceDaughterInitializer { } PROCESS_SWITCH(ResonanceDaughterInitializer, processV0Data, "Process V0s for data", false); + /** + * @brief Processes data V0s grouped automatically by their original collision + * + * Both V0s and tracks are already restricted to the current original + * aod::Collision by GroupSlicer. The unfiltered track table is required for + * resolving the positive and negative daughter indices. + */ + void processV0DataHybrid(aod::Collision const&, + soa::SmallGroups const& reducedCollisions, + aod::ResoV0Candidates const& v0s, + aod::ResoTrackCandidates const& tracks) + { + if (reducedCollisions.size() == 0) { + return; + } + if (reducedCollisions.size() != 1) { + LOGF(fatal, "Expected exactly one reduced collision for an original collision, found %zu", reducedCollisions.size()); + } + auto reducedCollision = reducedCollisions.begin(); + fillV0s(reducedCollision, v0s, tracks); + } + PROCESS_SWITCH(ResonanceDaughterInitializer, processV0DataHybrid, "Process data V0s with the two-stage hybrid grouping", false); + /** * @brief Processes MC V0 data * @@ -2012,6 +2084,29 @@ struct ResonanceDaughterInitializer { } PROCESS_SWITCH(ResonanceDaughterInitializer, processCascData, "Process Cascades for data", false); + /** + * @brief Processes data cascades grouped automatically by their original collision + * + * Cascades and tracks arrive as original-collision groups. Keeping this as a + * separate callback from V0 processing avoids enabling either upstream input + * dependency unless its process switch is selected. + */ + void processCascDataHybrid(aod::Collision const&, + soa::SmallGroups const& reducedCollisions, + aod::ResoCascadesCandidates const& cascades, + aod::ResoTrackCandidates const& tracks) + { + if (reducedCollisions.size() == 0) { + return; + } + if (reducedCollisions.size() != 1) { + LOGF(fatal, "Expected exactly one reduced collision for an original collision, found %zu", reducedCollisions.size()); + } + auto reducedCollision = reducedCollisions.begin(); + fillCascades(reducedCollision, cascades, tracks); + } + PROCESS_SWITCH(ResonanceDaughterInitializer, processCascDataHybrid, "Process data cascades with the two-stage hybrid grouping", false); + /** * @brief Processes MC cascade data *