diff --git a/PWGLF/DataModel/LFResonanceTables.h b/PWGLF/DataModel/LFResonanceTables.h index 673d2ae8393..4c8f3ab7836 100644 --- a/PWGLF/DataModel/LFResonanceTables.h +++ b/PWGLF/DataModel/LFResonanceTables.h @@ -800,6 +800,14 @@ DECLARE_SOA_DYNAMIC_COLUMN(PassedPtDependentDCAxy, passedPtDependentDCAxy, [](uint8_t trackSelectionFlags) { return DCAEncoding::testPtDependentDCAxy(trackSelectionFlags); }); DECLARE_SOA_DYNAMIC_COLUMN(PassedPtDependentDCAz, passedPtDependentDCAz, [](uint8_t trackSelectionFlags) { return DCAEncoding::testPtDependentDCAz(trackSelectionFlags); }); +DECLARE_SOA_DYNAMIC_COLUMN(HasITSHitInLayer, hasITSHitInLayer, + [](uint8_t itsClusterMap, int layer) -> bool { + constexpr int NumberOfITSLayers = 7; + if (layer < 0 || layer >= NumberOfITSLayers) { + return false; + } + return (itsClusterMap & (1u << layer)) != 0; + }); } // namespace resomicrodaughter001 // Ultra-micro track representation. The momentum components are quantised @@ -1036,6 +1044,8 @@ using ResoMicroTrack = ResoMicroTracks::iterator; // Keep ResoMicroTracks as the version-0 API for existing producers and // consumers. Version-1 users must request ResoMicroTracks_001 explicitly. +// Version 1 includes TPC crossed rows and the ITS cluster map; older version-1 +// files without these columns must be regenerated or explicitly converted. DECLARE_SOA_TABLE_VERSIONED(ResoMicroTracks_001, "AOD", "RESOMICROTRACK", 1, o2::soa::Index<>, resodaughter::ResoCollisionId, @@ -1048,6 +1058,8 @@ DECLARE_SOA_TABLE_VERSIONED(ResoMicroTracks_001, "AOD", "RESOMICROTRACK", 1, resomicrodaughter::PidNSigmaPrFlag, resomicrodaughter001::TrackSelectionFlags, resodaughter::TrackFlags, + resodaughter::TpcNClsCrossedRows, + track::ITSClusterMap, // Dynamic columns resomicrodaughter::Pt, resodaughter::Eta, @@ -1069,7 +1081,10 @@ DECLARE_SOA_TABLE_VERSIONED(ResoMicroTracks_001, "AOD", "RESOMICROTRACK", 1, resodaughter::IsPrimaryTrack, resodaughter::IsPVContributor, resodaughter::HasTOF, - resodaughter::Sign); + resodaughter::Sign, + track::ITSNCls, + track::ITSNClsInnerBarrel, + resomicrodaughter001::HasITSHitInLayer); // Positional soft-link side table retained for ResoMicroTracks version 000. // Version 001 stores the same row number as a scalar and should be consumed // without joining this side table, since both columns expose trackId(). diff --git a/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx b/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx index 21e534db7fc..d5a43cf1028 100644 --- a/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx +++ b/PWGLF/TableProducer/Resonances/resonanceModuleInitializer.cxx @@ -1008,6 +1008,7 @@ struct ResonanceDaughterInitializer { static constexpr float MomentumQuantizationScale = 1000.f; static constexpr std::size_t StoredMCRelationCount = 2; static constexpr std::size_t MaxCandidateDaughters = 3; + static constexpr int NumberOfITSLayers = 7; /// Selected-candidate state and the optional global daughter-ID veto set. /// By default only candidate existence is recorded and daughter reuse is @@ -1132,6 +1133,8 @@ struct ResonanceDaughterInitializer { // General daughter output options Configurable cfgFillQA{"cfgFillQA", false, "Fill QA histograms"}; Configurable cfgDetailTrackQA{"cfgDetailTrackQA", false, "Fill detailed QA histograms for enabled track output tables"}; + Configurable cfgQAITS{"cfgQAITS", false, "Fill Micro001 ITS quality vs pT (requires cfgFillQA and cfgFillMicroTracks)"}; + Configurable cfgQATPC{"cfgQATPC", false, "Fill Micro001 TPC crossed rows vs pT (requires cfgFillQA and cfgFillMicroTracks)"}; // Track pre-selection and DCA cuts struct : ConfigurableGroup { @@ -1243,13 +1246,12 @@ struct ResonanceDaughterInitializer { // data-model relation keeps its legacy v000 default target; v001 consumers // must use resoCollision_as() with the exact bound v001 table type for // dereferencing (aod::ResoCollisions_001 when the parent is not joined). - // Original-collision grouping is provided by the scalar-only version-1 - // ResoCollisionGroups_001 table, avoiding a hard source-AO2D relation. + // Daughter callbacks use the original collision ID from ResoCollisionColls + // to slice input candidates explicitly. The scalar ResoCollisionGroups_001 + // output is retained for consumers that do not need source-AO2D dereferencing. // Collision mappings are always written; cfgBypassCollIndexFill is retained // only so existing configuration files remain accepted. using ResoCollisionWithIndex = soa::Join; - using SelectedResoCollisions = soa::Join; - PresliceUnsorted reducedCollisionsPerOriginalCollision = aod::resocollisiongroup001::originalCollisionId; /** * @brief Initializes the task @@ -1263,7 +1265,7 @@ struct ResonanceDaughterInitializer { LOGF(fatal, "cfgPairGateMode must be 0 (configured gates) or 1 (V0 or cascade)"); } const bool useEitherPairGate = FilterForDerivedTables.cfgPairGateMode.value == PairGateModeEither; - const bool processTrackDataEnabled = doprocessData || doprocessDataHybrid || doprocessDataWithPairGate || + const bool processTrackDataEnabled = doprocessData || doprocessDataWithPairGate || doprocessDataWithV0PairGate || doprocessDataWithCascPairGate; const bool processTrackMCEnabled = doprocessMC || doprocessMCWithPairGate || doprocessMCWithV0PairGate || doprocessMCWithCascPairGate; @@ -1275,7 +1277,6 @@ struct ResonanceDaughterInitializer { const bool anyDataProcessEnabled = processTrackDataEnabled || processV0DataEnabled || processCascDataEnabled; const bool anyMCProcessEnabled = processTrackMCEnabled || doprocessV0MC || doprocessCascMC; const int enabledTrackProcesses = static_cast(doprocessData) + - static_cast(doprocessDataHybrid) + static_cast(doprocessDataWithPairGate) + static_cast(doprocessDataWithV0PairGate) + static_cast(doprocessDataWithCascPairGate) + @@ -1311,10 +1312,10 @@ struct ResonanceDaughterInitializer { if (static_cast(doprocessCascData) + static_cast(doprocessCascMC) > 1) { LOGF(fatal, "Only one cascade process can be enabled in ResonanceDaughterInitializer"); } - if ((doprocessData || doprocessDataHybrid || doprocessMC) && + if ((doprocessData || doprocessMC) && (useEitherPairGate || FilterForDerivedTables.cfgBypassNoPairV0s || FilterForDerivedTables.cfgBypassNoPairCascades || FilterForDerivedTables.cfgGlobalDaughterVeto)) { - LOGF(warn, "Pair-gate options are ignored by processData/processDataHybrid/processMC; enable the matching *WithPairGate process to apply them"); + LOGF(warn, "Pair-gate options are ignored by processData/processMC; enable the matching *WithPairGate process to apply them"); } const auto validatePairGateOutputs = [&](bool pairProcessEnabled, bool v0OutputEnabled, @@ -1513,6 +1514,24 @@ struct ResonanceDaughterInitializer { } } + if ((processTrackDataEnabled || processTrackMCEnabled) && FilterForDerivedTables.cfgFillMicroTracks) { + AxisSpec qualityPtAxis = {300, 0.f, 30.f, "#it{p}_{T} (GeV/#it{c})"}; + if (cfgQAITS) { + AxisSpec itsMapAxis = {128, -0.5, 127.5, "ITS cluster map"}; + AxisSpec itsNClsAxis = {8, -0.5, 7.5, "ITS occupied layers"}; + AxisSpec itsInnerBarrelNClsAxis = {4, -0.5, 3.5, "ITS inner-barrel occupied layers"}; + AxisSpec itsLayerAxis = {NumberOfITSLayers, -0.5, NumberOfITSLayers - 0.5, "ITS layer (0 = innermost)"}; + qaRegistry.add("QA/h2MicroTrackITSClusterMapVsPt", "ResoMicroTracks ITS cluster map vs pT", kTH2D, {qualityPtAxis, itsMapAxis}); + qaRegistry.add("QA/h2MicroTrackITSNClsVsPt", "ResoMicroTracks ITS occupied layers vs pT", kTH2D, {qualityPtAxis, itsNClsAxis}); + qaRegistry.add("QA/h2MicroTrackITSNClsInnerBarrelVsPt", "ResoMicroTracks ITS inner-barrel occupied layers vs pT", kTH2D, {qualityPtAxis, itsInnerBarrelNClsAxis}); + qaRegistry.add("QA/h2MicroTrackITSLayerHitsVsPt", "ResoMicroTracks ITS hits vs pT (one entry per occupied layer)", kTH2D, {qualityPtAxis, itsLayerAxis}); + } + if (cfgQATPC) { + AxisSpec tpcCrossedRowsAxis = {256, -0.5, 255.5, "TPC crossed rows"}; + qaRegistry.add("QA/h2MicroTrackTPCCrossedRowsVsPt", "ResoMicroTracks TPC crossed rows vs pT", kTH2D, {qualityPtAxis, tpcCrossedRowsAxis}); + } + } + if (processV0DataEnabled || doprocessV0MC) { qaRegistry.add("QA/hGoodV0Indices", "hGoodV0Indices", kTH1D, {idxAxis}); auto v0Selection = qaRegistry.get(HIST("QA/hGoodV0Indices")); @@ -2195,6 +2214,22 @@ struct ResonanceDaughterInitializer { qaRegistry.fill(HIST("QA/h4MicroTrackTOFnSigma"), track.pt(), track.tofNSigmaPi(), track.tofNSigmaKa(), track.tofNSigmaPr()); } } + // Quality QA follows the same selected rows as Micro001, independently + // of the detailed PID/DCA QA. ITS count getters count occupied layers. + if (cfgFillQA && cfgQAITS) { + const auto itsClusterMap = track.itsClusterMap(); + qaRegistry.fill(HIST("QA/h2MicroTrackITSClusterMapVsPt"), track.pt(), itsClusterMap); + qaRegistry.fill(HIST("QA/h2MicroTrackITSNClsVsPt"), track.pt(), track.itsNCls()); + qaRegistry.fill(HIST("QA/h2MicroTrackITSNClsInnerBarrelVsPt"), track.pt(), track.itsNClsInnerBarrel()); + for (int layer = 0; layer < NumberOfITSLayers; ++layer) { + if ((itsClusterMap & (1u << layer)) != 0) { + qaRegistry.fill(HIST("QA/h2MicroTrackITSLayerHitsVsPt"), track.pt(), layer); + } + } + } + if (cfgFillQA && cfgQATPC) { + qaRegistry.fill(HIST("QA/h2MicroTrackTPCCrossedRowsVsPt"), track.pt(), track.tpcNClsCrossedRows()); + } reso2microtrks(collision.globalIndex(), track.globalIndex(), track.px(), @@ -2204,7 +2239,9 @@ struct ResonanceDaughterInitializer { static_cast(o2::aod::resomicrodaughter001::PidNSigma(track.tpcNSigmaKa(), track.tofNSigmaKa(), track.hasTOF())), static_cast(o2::aod::resomicrodaughter001::PidNSigma(track.tpcNSigmaPr(), track.tofNSigmaPr(), track.hasTOF())), static_cast(trackSelFlag), - trackFlags); + trackFlags, + static_cast(track.tpcNClsCrossedRows()), + track.itsClusterMap()); if (!FilterForDerivedTables.cfgBypassTrackIndexFill) { resoMicroTrackTracks(track.globalIndex()); } @@ -2783,31 +2820,6 @@ struct ResonanceDaughterInitializer { } PROCESS_SWITCH(ResonanceDaughterInitializer, processData, "Process tracks for data", false); - /** - * @brief Processes data tracks using the two-stage hybrid grouping - * - * GroupSlicer associates tracks automatically to the original - * aod::Collision. Reduced collisions retain a scalar original-collision row - * number and are explicitly sliced from the much smaller mapping table. The - * tracks argument is already the selected slice and must not be sliced again. - */ - void processDataHybrid(aod::Collision const& originalCollision, - SelectedResoCollisions const& reducedCollisions, - soa::Filtered const& tracks) - { - auto reducedCollisionsThisCollision = reducedCollisions.sliceBy(reducedCollisionsPerOriginalCollision, originalCollision.globalIndex()); - if (reducedCollisionsThisCollision.size() == 0) { - return; - } - if (reducedCollisionsThisCollision.size() > 1) { - LOGF(error, "Found %zu reduced collisions for one original collision; skipping the ambiguous association", reducedCollisionsThisCollision.size()); - return; - } - auto reducedCollision = reducedCollisionsThisCollision.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 cascade gates */