From f3a321db527cb3f44899f27a38e4b4b96d47c899 Mon Sep 17 00:00:00 2001 From: mjkim525 Date: Mon, 24 Aug 2026 16:30:38 +0900 Subject: [PATCH 1/3] [PWGLF] Fix collision grouping in resonance module initializer Preserve the existing reduced-collision link schema while adding a canonical original-collision association for GroupSlicer. Add hybrid Track, V0, and cascade daughter paths that use automatic original-collision grouping and retain the current paths for comparison. --- PWGLF/DataModel/LFResonanceTables.h | 14 ++ .../Resonances/resonanceModuleInitializer.cxx | 156 +++++++++++++++--- 2 files changed, 147 insertions(+), 23 deletions(-) diff --git a/PWGLF/DataModel/LFResonanceTables.h b/PWGLF/DataModel/LFResonanceTables.h index 1de673d77c4..502a6853dd8 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 optimized 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..dd7e3aa73c7 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,13 @@ struct ResonanceDaughterInitializer { */ void init(InitContext&) { - const bool processTrackDataEnabled = doprocessData || doprocessDataWithPairGate; + const bool processTrackDataEnabled = doprocessData || doprocessDataOptimized || 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(doprocessDataOptimized) + + static_cast(doprocessDataHybrid) + static_cast(doprocessDataWithPairGate) + static_cast(doprocessMC) + static_cast(doprocessMCWithPairGate); @@ -717,15 +725,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 || doprocessDataOptimized || 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/processDataOptimized/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 +874,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 +885,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 +899,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 +1888,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 +1921,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 +1937,43 @@ struct ResonanceDaughterInitializer { } PROCESS_SWITCH(ResonanceDaughterInitializer, processData, "Process tracks for data", false); + /** + * @brief Processes data tracks grouped automatically by their original collision + * + * 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 processDataOptimized(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, processDataOptimized, "Process data tracks with original-collision grouping", false); + + /** + * @brief Production hybrid path using original-collision automatic grouping + * + * This named path preserves the existing optimized prototype for A/B + * compatibility while exposing the two-stage hybrid architecture explicitly. + */ + void processDataHybrid(aod::Collision const& originalCollision, + soa::SmallGroups const& reducedCollisions, + soa::Filtered const& tracks) + { + processDataOptimized(originalCollision, reducedCollisions, 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 +2048,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 +2099,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 * From 5060d6eb397e15e4682bd10d60ad7d8829adf89f Mon Sep 17 00:00:00 2001 From: mjkim525 Date: Mon, 24 Aug 2026 16:47:52 +0900 Subject: [PATCH 2/3] [PWGLF] Consolidate hybrid resonance daughter processing Remove the duplicate processDataOptimized switch and let processDataHybrid own the original-collision grouping implementation directly. Keep process validation and pair-gate diagnostics aligned with the consolidated path. --- PWGLF/DataModel/LFResonanceTables.h | 2 +- .../Resonances/resonanceModuleInitializer.cxx | 31 +++++-------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/PWGLF/DataModel/LFResonanceTables.h b/PWGLF/DataModel/LFResonanceTables.h index 502a6853dd8..11fd45842ef 100644 --- a/PWGLF/DataModel/LFResonanceTables.h +++ b/PWGLF/DataModel/LFResonanceTables.h @@ -87,7 +87,7 @@ DECLARE_SOA_COLUMN(MCMultiplicity, mcMultiplicity, float); //! MC Multiplicit // 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 optimized daughter process. +// companion table for the hybrid daughter process. namespace resocollisiongroup { DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(OriginalCollision, originalCollision, int, Collisions, "Collisions", ""); //! diff --git a/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx b/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx index dd7e3aa73c7..1d748682f6c 100644 --- a/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx +++ b/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx @@ -711,12 +711,11 @@ struct ResonanceDaughterInitializer { */ void init(InitContext&) { - const bool processTrackDataEnabled = doprocessData || doprocessDataOptimized || doprocessDataHybrid || 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(doprocessDataOptimized) + static_cast(doprocessDataHybrid) + static_cast(doprocessDataWithPairGate) + static_cast(doprocessMC) + @@ -731,9 +730,9 @@ struct 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 || doprocessDataOptimized || doprocessDataHybrid || doprocessMC) && + if ((doprocessData || doprocessDataHybrid || doprocessMC) && (FilterForDerivedTables.cfgBypassNoPairV0s || FilterForDerivedTables.cfgBypassNoPairCascades)) { - LOGF(warn, "Pair-gate options are ignored by processData/processDataOptimized/processDataHybrid/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 && !processV0DataEnabled) { LOGF(fatal, "cfgBypassNoPairV0s requires processV0Data or processV0DataHybrid so an accepted V0 is written for every retained collision"); @@ -1938,16 +1937,16 @@ struct ResonanceDaughterInitializer { PROCESS_SWITCH(ResonanceDaughterInitializer, processData, "Process tracks for data", false); /** - * @brief Processes data tracks grouped automatically by their original collision + * @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 + * original aod::Collision. The tracks argument is therefore already the * selected slice for this collision and must not be sliced again. */ - void processDataOptimized(aod::Collision const&, - soa::SmallGroups const& reducedCollisions, - soa::Filtered const& tracks) + void processDataHybrid(aod::Collision const&, + soa::SmallGroups const& reducedCollisions, + soa::Filtered const& tracks) { if (reducedCollisions.size() == 0) { return; @@ -1958,20 +1957,6 @@ struct ResonanceDaughterInitializer { auto reducedCollision = reducedCollisions.begin(); fillTrackTables(reducedCollision, tracks); } - PROCESS_SWITCH(ResonanceDaughterInitializer, processDataOptimized, "Process data tracks with original-collision grouping", false); - - /** - * @brief Production hybrid path using original-collision automatic grouping - * - * This named path preserves the existing optimized prototype for A/B - * compatibility while exposing the two-stage hybrid architecture explicitly. - */ - void processDataHybrid(aod::Collision const& originalCollision, - soa::SmallGroups const& reducedCollisions, - soa::Filtered const& tracks) - { - processDataOptimized(originalCollision, reducedCollisions, tracks); - } PROCESS_SWITCH(ResonanceDaughterInitializer, processDataHybrid, "Process data tracks with the two-stage hybrid grouping", false); /** From e0fb0fedefcfc0ac1295a1867e5b94cefab7665f Mon Sep 17 00:00:00 2001 From: mjkim525 Date: Mon, 24 Aug 2026 23:26:02 +0900 Subject: [PATCH 3/3] [PWGLF] Retrigger CI for resonance module initializer