From 4a5ab1fefd95ef671265784371fdde863703f6eb Mon Sep 17 00:00:00 2001 From: Marcello Di Costanzo Date: Thu, 8 Oct 2026 09:08:08 +0200 Subject: [PATCH 1/5] Cache soft pions --- PWGHF/TableProducer/trackIndexSkimCreator.cxx | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/PWGHF/TableProducer/trackIndexSkimCreator.cxx b/PWGHF/TableProducer/trackIndexSkimCreator.cxx index c8456183f31..8755e516bf4 100644 --- a/PWGHF/TableProducer/trackIndexSkimCreator.cxx +++ b/PWGHF/TableProducer/trackIndexSkimCreator.cxx @@ -1756,6 +1756,20 @@ struct HfTrackIndexSkimCreator { } } + /// Fill the per-collision cache of tracks propagated to the collision's primary vertex, so that + /// each track is propagated once per collision instead of once per pair or triplet. + /// \param collision is the collision under study + /// \param trackIndices are the track associations of this collision + /// \param prongs is the cache to be filled, in the order of trackIndices + template + void fillPropagatedProngCache(TCollision const& collision, std::optional const& trackIndices, std::vector& prongs) + { + prongs.clear(); + if (trackIndices.has_value()) { + fillPropagatedProngCache(collision, *trackIndices, prongs); // dispatches to the generic overload + } + } + /// Method to perform selections for 2-prong candidates before vertex reconstruction /// \param pVecTrack0 is the momentum array of the first daughter track /// \param pVecTrack1 is the momentum array of the second daughter track @@ -2356,6 +2370,10 @@ struct HfTrackIndexSkimCreator { using PropagatedProng = HfPropagatedProng; std::vector prongsPos{}; std::vector prongsNeg{}; + using SoftPionIterator = decltype(positiveSoftPions->sliceByCached(aod::track::collisionId, 0, cache).begin()); + using PropagatedSoftPi = HfPropagatedProng; + std::vector softPionsPos{}; + std::vector softPionsNeg{}; for (const auto& collision : collisions) { @@ -2435,11 +2453,18 @@ struct HfTrackIndexSkimCreator { const auto groupedTrackIndicesNeg1 = negativeFor2And3Prongs->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache); std::optionalsliceByCached(aod::track::collisionId, 0, cache))> groupedTrackIndicesSoftPionsPos; std::optionalsliceByCached(aod::track::collisionId, 0, cache))> groupedTrackIndicesSoftPionsNeg; + if (config.doDstar) { + groupedTrackIndicesSoftPionsPos.emplace(positiveSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); + groupedTrackIndicesSoftPionsNeg.emplace(negativeSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); + } int lastFilledD0 = -1; // index to be filled in table for D* mesons // propagate each track to this collision's PV once, instead of once per pair or triplet fillPropagatedProngCache(collision, groupedTrackIndicesPos1, prongsPos); fillPropagatedProngCache(collision, groupedTrackIndicesNeg1, prongsNeg); + // do the same for soft pions, using the more specialized function accepting the optional type for the grouped track indices + fillPropagatedProngCache(collision, groupedTrackIndicesSoftPionsPos, softPionsPos); + fillPropagatedProngCache(collision, groupedTrackIndicesSoftPionsNeg, softPionsNeg); for (std::size_t iPos1 = 0; iPos1 < prongsPos.size(); ++iPos1) { const auto& trackIndexPos1 = prongsPos[iPos1].trackIndex; @@ -3220,21 +3245,13 @@ struct HfTrackIndexSkimCreator { // if D* enabled and pt of the D0 is larger than the minimum of the D* one within 20% (D* and D0 momenta are very similar, always within 20% according to PYTHIA8) // second loop over positive tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 0) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexNeg1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0 candidates; moreover if kaon PID enabled, apply to the negative track - if (!groupedTrackIndicesSoftPionsPos) { - groupedTrackIndicesSoftPionsPos.emplace(positiveSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); - } - for (auto trackIndexPos2 = groupedTrackIndicesSoftPionsPos->begin(); trackIndexPos2 != groupedTrackIndicesSoftPionsPos->end(); ++trackIndexPos2) { - if (trackIndexPos2 == trackIndexPos1) { + for (std::size_t iPos2 = 0; iPos2 < softPionsPos.size(); ++iPos2) { + if (softPionsPos[iPos2].trackIndex == trackIndexPos1) { continue; } - auto trackPos2 = trackIndexPos2.template track_as(); - std::array pVecTrackPos2{trackPos2.pVector()}; - if (thisCollId != trackPos2.collisionId()) { // this is not the "default" collision for this track, we have to re-propagate it - auto trackParVarPos2 = getTrackParCov(trackPos2); - std::array dcaInfoPos2{trackPos2.dcaXY(), trackPos2.dcaZ()}; - o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, trackParVarPos2, 2.f, noMatCorr, &dcaInfoPos2); - getPxPyPz(trackParVarPos2, pVecTrackPos2); - } + + const auto& trackPos2 = softPionsPos[iPos2].track; + const auto& pVecTrackPos2 = softPionsPos[iPos2].pVec; uint8_t isSelectedDstar{0}; uint8_t cutStatus{BIT(kNCutsDstar) - 1}; @@ -3259,21 +3276,13 @@ struct HfTrackIndexSkimCreator { // second loop over negative tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 1) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexPos1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0bar candidates; moreover if kaon PID enabled, apply to the positive track - if (!groupedTrackIndicesSoftPionsNeg) { - groupedTrackIndicesSoftPionsNeg.emplace(negativeSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); - } - for (auto trackIndexNeg2 = groupedTrackIndicesSoftPionsNeg->begin(); trackIndexNeg2 != groupedTrackIndicesSoftPionsNeg->end(); ++trackIndexNeg2) { - if (trackIndexNeg1 == trackIndexNeg2) { + for (std::size_t iNeg2 = 0; iNeg2 < softPionsNeg.size(); ++iNeg2) { + if (trackIndexNeg1 == softPionsNeg[iNeg2].trackIndex) { continue; } - auto trackNeg2 = trackIndexNeg2.template track_as(); - std::array pVecTrackNeg2{trackNeg2.pVector()}; - if (thisCollId != trackNeg2.collisionId()) { // this is not the "default" collision for this track, we have to re-propagate it - auto trackParVarNeg2 = getTrackParCov(trackNeg2); - std::array dcaInfoNeg2{trackNeg2.dcaXY(), trackNeg2.dcaZ()}; - o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, trackParVarNeg2, 2.f, noMatCorr, &dcaInfoNeg2); - getPxPyPz(trackParVarNeg2, pVecTrackNeg2); - } + + const auto& trackNeg2 = softPionsNeg[iNeg2].track; + const auto& pVecTrackNeg2 = softPionsNeg[iNeg2].pVec; uint8_t isSelectedDstar{0}; uint8_t cutStatus{BIT(kNCutsDstar) - 1}; From 323853a53a7e1362c185796f298fa78d38f52514 Mon Sep 17 00:00:00 2001 From: Marcello Di Costanzo Date: Thu, 8 Oct 2026 12:03:08 +0200 Subject: [PATCH 2/5] Improve D* soft pions caching --- PWGHF/TableProducer/trackIndexSkimCreator.cxx | 117 ++++++++++++------ 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/PWGHF/TableProducer/trackIndexSkimCreator.cxx b/PWGHF/TableProducer/trackIndexSkimCreator.cxx index 8755e516bf4..35dac68d200 100644 --- a/PWGHF/TableProducer/trackIndexSkimCreator.cxx +++ b/PWGHF/TableProducer/trackIndexSkimCreator.cxx @@ -1506,6 +1506,8 @@ struct HfTrackIndexSkimCreator { std::array, kN2ProngDecays> binsPt2Prong{}; std::array, kN3ProngDecays> cut3Prong{}; std::array, kN3ProngDecays> binsPt3Prong{}; + LabeledArray cutsDstar{}; + std::vector binsPtDstar{}; // ML response o2::analysis::MlResponse hfMlResponse2Prongs; // only D0 @@ -1584,6 +1586,9 @@ struct HfTrackIndexSkimCreator { // cuts for 3-prong decays retrieved by json. the order must be then one in hf_cand_3prong::DecayType cut3Prong = {config.cutsDplusToPiKPi, config.cutsLcToPKPi, config.cutsDsToKKPi, config.cutsXicToPKPi, config.cutsCdToDeKPi, config.cutsCtToTrKPi, config.cutsChToHeKPi, config.cutsCaToAlKPi}; binsPt3Prong = {config.binsPtDplusToPiKPi, config.binsPtLcToPKPi, config.binsPtDsToKKPi, config.binsPtXicToPKPi, config.binsPtCdToDeKPi, config.binsPtCtToTrKPi, config.binsPtChToHeKPi, config.binsPtCaToAlKPi}; + // D* cuts retrieved by json + cutsDstar = config.cutsDstarToD0Pi; + binsPtDstar = config.binsPtDstarToD0Pi; df2.setPropagateToPCA(config.propagateToPCA); df2.setMaxR(config.maxR); @@ -1724,6 +1729,15 @@ struct HfTrackIndexSkimCreator { } } + /// One track of the collision under study, propagated to that collision's primary vertex. + /// \tparam TTrackIndex is the iterator type of the track-collision associations + template + struct HfPropagatedSoftPion { + TTrackIndex trackIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid + int64_t trackGlobalIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid + std::array pVec{}; ///< momentum at the PCA to the primary vertex + }; + /// One track of the collision under study, propagated to that collision's primary vertex. /// \tparam TTrackIndex is the iterator type of the track-collision associations /// \tparam TTrack is the iterator type of the track table @@ -1741,32 +1755,39 @@ struct HfTrackIndexSkimCreator { /// \param collision is the collision under study /// \param trackIndices are the track associations of this collision /// \param prongs is the cache to be filled, in the order of trackIndices - template + template void fillPropagatedProngCache(TCollision const& collision, TTrackIndices const& trackIndices, std::vector& prongs) { prongs.clear(); const auto thisCollId = collision.globalIndex(); for (auto trackIndex = trackIndices.begin(); trackIndex != trackIndices.end(); ++trackIndex) { const auto track = trackIndex.template track_as(); - auto& prong = prongs.emplace_back(TProng{trackIndex, track, getTrackParCov(track), track.pVector(), {track.dcaXY(), track.dcaZ()}}); - if (thisCollId != track.collisionId()) { // this is not the "default" collision for this track, we have to re-propagate it - o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, prong.trackParVar, 2.f, noMatCorr, &prong.dcaInfo); - getPxPyPz(prong.trackParVar, prong.pVec); + if constexpr (IsSoftPion) { + std::array pVec = track.pVector(); + if (thisCollId != track.collisionId()) { // rare: build the TrackParCov only here + auto trackParCov = getTrackParCov(track); + std::array dcaInfo{}; + o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, trackParCov, 2.f, noMatCorr, &dcaInfo); + getPxPyPz(trackParCov, pVec); + } + prongs.push_back(TProng{trackIndex, track.globalIndex(), pVec}); + } else { + const auto track = trackIndex.template track_as(); + auto& prong = prongs.emplace_back(TProng{trackIndex, track, getTrackParCov(track), track.pVector(), {track.dcaXY(), track.dcaZ()}}); + if (thisCollId != track.collisionId()) { // this is not the "default" collision for this track, we have to re-propagate it + o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, prong.trackParVar, 2.f, noMatCorr, &prong.dcaInfo); + getPxPyPz(prong.trackParVar, prong.pVec); + } } } } - /// Fill the per-collision cache of tracks propagated to the collision's primary vertex, so that - /// each track is propagated once per collision instead of once per pair or triplet. - /// \param collision is the collision under study - /// \param trackIndices are the track associations of this collision - /// \param prongs is the cache to be filled, in the order of trackIndices - template + // optional overload + template void fillPropagatedProngCache(TCollision const& collision, std::optional const& trackIndices, std::vector& prongs) { - prongs.clear(); if (trackIndices.has_value()) { - fillPropagatedProngCache(collision, *trackIndices, prongs); // dispatches to the generic overload + fillPropagatedProngCache(collision, *trackIndices, prongs); } } @@ -1841,10 +1862,10 @@ struct HfTrackIndexSkimCreator { // additional check for D0 to be used in D* finding if (iDecay2P == hf_cand_2prong::DecayType::D0ToPiK && config.doDstar && TESTBIT(isSelected, iDecay2P)) { - const auto binPtDstar = findBin(config.binsPtDstarToD0Pi, pt * 1.2); // assuming the D* pT about 20% higher than the one of the D0 to be safe + const auto binPtDstar = findBin(&binsPtDstar, pt * 1.2); // assuming the D* pT about 20% higher than the one of the D0 to be safe if (binPtDstar >= 0) { whichHypo[kN2ProngDecays] = whichHypo[hf_cand_2prong::DecayType::D0ToPiK]; - const double deltaMass = config.cutsDstarToD0Pi->get(binPtDstar, 1u); + const double deltaMass = cutsDstar.get(binPtDstar, 1u); if (TESTBIT(whichHypo[iDecay2P], 0) && (massHypos[0] > (MassD0 + deltaMass) * (MassD0 + deltaMass) || massHypos[0] < (MassD0 - deltaMass) * (MassD0 - deltaMass))) { CLRBIT(whichHypo[kN2ProngDecays], 0); @@ -2170,18 +2191,18 @@ struct HfTrackIndexSkimCreator { /// \param pVecTrack1 is the momentum array of the second daughter track (opposite charge) /// \param pVecTrack2 is the momentum array of the third daughter track (same charge) /// \param cutStatus is the cut status (filled only in case of debug) + /// \param invMassD0 is the invariant mass of the D0 candidate /// \param deltaMass is the M(Kpipi) - M(Kpi) value to be filled in the control histogram /// \return a bitmap with selection outcome template - uint8_t applySelectionDstar(T1 const& pVecTrack0, T1 const& pVecTrack1, T1 const& pVecTrack2, uint8_t& cutStatus, T2& deltaMass) + uint8_t applySelectionDstar(T1 const& pVecTrack0, T1 const& pVecTrack1, T1 const& pVecTrack2, uint8_t& cutStatus, T2 invMassD0, T2& deltaMass) { uint8_t isSelected{1}; const std::array arrMom{pVecTrack0, pVecTrack1, pVecTrack2}; - const std::array arrMomD0{pVecTrack0, pVecTrack1}; const auto pt = RecoDecay::pt(pVecTrack0, pVecTrack1, pVecTrack2) + config.ptTolerance; // add tolerance because of no reco decay vertex // pT - const auto binPt = findBin(config.binsPtDstarToD0Pi, pt); + const auto binPt = findBin(&binsPtDstar, pt); // return immediately if it is outside the defined pT bins if (binPt == -1) { isSelected = 0; @@ -2192,8 +2213,7 @@ struct HfTrackIndexSkimCreator { } // D0 mass - const double deltaMassD0 = config.cutsDstarToD0Pi->get(binPt, 1u); // 1u == deltaMassD0Index - const double invMassD0 = RecoDecay::m(arrMomD0, std::array{MassPiPlus, MassKPlus}); + const double deltaMassD0 = cutsDstar.get(binPt, 1u); // 1u == deltaMassD0Index if (std::abs(invMassD0 - MassD0) > deltaMassD0) { isSelected = 0; if (config.debug) { @@ -2203,7 +2223,7 @@ struct HfTrackIndexSkimCreator { } // D*+ mass - const double maxDeltaMass = config.cutsDstarToD0Pi->get(binPt, 0u); // 0u == deltaMassIndex + const double maxDeltaMass = cutsDstar.get(binPt, 0u); // 0u == deltaMassIndex const double invMassDstar = RecoDecay::m(arrMom, std::array{MassPiPlus, MassKPlus, MassPiPlus}); deltaMass = invMassDstar - invMassD0; if (deltaMass > maxDeltaMass) { @@ -2371,12 +2391,16 @@ struct HfTrackIndexSkimCreator { std::vector prongsPos{}; std::vector prongsNeg{}; using SoftPionIterator = decltype(positiveSoftPions->sliceByCached(aod::track::collisionId, 0, cache).begin()); - using PropagatedSoftPi = HfPropagatedProng; + using PropagatedSoftPi = HfPropagatedSoftPion; std::vector softPionsPos{}; std::vector softPionsNeg{}; + bool softPiPosCacheFilled{false}, softPiNegCacheFilled{false}; for (const auto& collision : collisions) { + softPiPosCacheFilled = false; + softPiNegCacheFilled = false; + /// retrieve PV contributors for the current collision std::vector vecPvContributorGlobId{}; std::vector vecPvContributorTrackParCov{}; @@ -2453,18 +2477,11 @@ struct HfTrackIndexSkimCreator { const auto groupedTrackIndicesNeg1 = negativeFor2And3Prongs->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache); std::optionalsliceByCached(aod::track::collisionId, 0, cache))> groupedTrackIndicesSoftPionsPos; std::optionalsliceByCached(aod::track::collisionId, 0, cache))> groupedTrackIndicesSoftPionsNeg; - if (config.doDstar) { - groupedTrackIndicesSoftPionsPos.emplace(positiveSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); - groupedTrackIndicesSoftPionsNeg.emplace(negativeSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); - } int lastFilledD0 = -1; // index to be filled in table for D* mesons // propagate each track to this collision's PV once, instead of once per pair or triplet - fillPropagatedProngCache(collision, groupedTrackIndicesPos1, prongsPos); - fillPropagatedProngCache(collision, groupedTrackIndicesNeg1, prongsNeg); - // do the same for soft pions, using the more specialized function accepting the optional type for the grouped track indices - fillPropagatedProngCache(collision, groupedTrackIndicesSoftPionsPos, softPionsPos); - fillPropagatedProngCache(collision, groupedTrackIndicesSoftPionsNeg, softPionsNeg); + fillPropagatedProngCache(collision, groupedTrackIndicesPos1, prongsPos); + fillPropagatedProngCache(collision, groupedTrackIndicesNeg1, prongsNeg); for (std::size_t iPos1 = 0; iPos1 < prongsPos.size(); ++iPos1) { const auto& trackIndexPos1 = prongsPos[iPos1].trackIndex; @@ -3241,24 +3258,35 @@ struct HfTrackIndexSkimCreator { } } - if (config.doDstar && TESTBIT(isSelected2ProngCand, hf_cand_2prong::DecayType::D0ToPiK) && (pt2Prong + config.ptTolerance) * 1.2 > config.binsPtDstarToD0Pi->at(0) && whichHypo2Prong[kN2ProngDecays] != 0) { // o2-linter: disable="magic-number" (see comment below) - // if D* enabled and pt of the D0 is larger than the minimum of the D* one within 20% (D* and D0 momenta are very similar, always within 20% according to PYTHIA8) + if (config.doDstar && TESTBIT(isSelected2ProngCand, hf_cand_2prong::DecayType::D0ToPiK) && (pt2Prong + config.ptTolerance) * 1.2 > binsPtDstar[0] && whichHypo2Prong[kN2ProngDecays] != 0) { // o2-linter: disable="magic-number" (see comment below) + // if D* enabled and pt of the D0 is larger than the minimum of the D* one within 20% (D* and D0 momenta are very similar, always within 20% according to PYTHIA8) // second loop over positive tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 0) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexNeg1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0 candidates; moreover if kaon PID enabled, apply to the negative track + + // Skip the processing if the D0 candidate mass is outside the maximum allowed window across pt + const std::array arrMomD0{pVecTrackPos1, pVecTrackNeg1}; + const float invMassD0 = RecoDecay::m(arrMomD0, std::array{MassPiPlus, MassKPlus}); + + if (!softPiPosCacheFilled) { + groupedTrackIndicesSoftPionsPos.emplace(positiveSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); + fillPropagatedProngCache(collision, groupedTrackIndicesSoftPionsPos, softPionsPos); + softPiPosCacheFilled = true; + } + for (std::size_t iPos2 = 0; iPos2 < softPionsPos.size(); ++iPos2) { if (softPionsPos[iPos2].trackIndex == trackIndexPos1) { continue; } - const auto& trackPos2 = softPionsPos[iPos2].track; + const auto& trackGlobalIndexPos2 = softPionsPos[iPos2].trackGlobalIndex; const auto& pVecTrackPos2 = softPionsPos[iPos2].pVec; uint8_t isSelectedDstar{0}; uint8_t cutStatus{BIT(kNCutsDstar) - 1}; float deltaMass{-1.}; - isSelectedDstar = applySelectionDstar(pVecTrackPos1, pVecTrackNeg1, pVecTrackPos2, cutStatus, deltaMass); // we do not compute the D* decay vertex at this stage because we are not interested in applying topological selections + isSelectedDstar = applySelectionDstar(pVecTrackPos1, pVecTrackNeg1, pVecTrackPos2, cutStatus, invMassD0, deltaMass); // we do not compute the D* decay vertex at this stage because we are not interested in applying topological selections if (isSelectedDstar) { - rowTrackIndexDstar(thisCollId, trackPos2.globalIndex(), lastFilledD0); + rowTrackIndexDstar(thisCollId, trackGlobalIndexPos2, lastFilledD0); if (config.fillHistograms) { registry.fill(HIST("hMassDstarToD0Pi"), deltaMass); } @@ -3276,20 +3304,31 @@ struct HfTrackIndexSkimCreator { // second loop over negative tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 1) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexPos1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0bar candidates; moreover if kaon PID enabled, apply to the positive track + + // Skip the processing if the D0 candidate mass is outside the maximum allowed window across pt + const std::array arrMomD0{pVecTrackNeg1, pVecTrackPos1}; + const float invMassD0 = RecoDecay::m(arrMomD0, std::array{MassPiPlus, MassKPlus}); + + if (!softPiNegCacheFilled) { + groupedTrackIndicesSoftPionsNeg.emplace(negativeSoftPions->sliceByCached(aod::track::collisionId, collision.globalIndex(), cache)); + fillPropagatedProngCache(collision, groupedTrackIndicesSoftPionsNeg, softPionsNeg); + softPiNegCacheFilled = true; + } + for (std::size_t iNeg2 = 0; iNeg2 < softPionsNeg.size(); ++iNeg2) { if (trackIndexNeg1 == softPionsNeg[iNeg2].trackIndex) { continue; } - const auto& trackNeg2 = softPionsNeg[iNeg2].track; + const auto& trackGlobalIndexNeg2 = softPionsNeg[iNeg2].trackGlobalIndex; const auto& pVecTrackNeg2 = softPionsNeg[iNeg2].pVec; uint8_t isSelectedDstar{0}; uint8_t cutStatus{BIT(kNCutsDstar) - 1}; float deltaMass{-1.}; - isSelectedDstar = applySelectionDstar(pVecTrackNeg1, pVecTrackPos1, pVecTrackNeg2, cutStatus, deltaMass); // we do not compute the D* decay vertex at this stage because we are not interested in applying topological selections + isSelectedDstar = applySelectionDstar(pVecTrackNeg1, pVecTrackPos1, pVecTrackNeg2, cutStatus, invMassD0, deltaMass); // we do not compute the D* decay vertex at this stage because we are not interested in applying topological selections if (isSelectedDstar) { - rowTrackIndexDstar(thisCollId, trackNeg2.globalIndex(), lastFilledD0); + rowTrackIndexDstar(thisCollId, trackGlobalIndexNeg2, lastFilledD0); if (config.fillHistograms) { registry.fill(HIST("hMassDstarToD0Pi"), deltaMass); } From 88ad3f33a32687dcfbd5b72b0cd5b17b9fcac27c Mon Sep 17 00:00:00 2001 From: Marcello Di Costanzo Date: Thu, 8 Oct 2026 12:14:04 +0200 Subject: [PATCH 3/5] Polish comments --- PWGHF/TableProducer/trackIndexSkimCreator.cxx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/PWGHF/TableProducer/trackIndexSkimCreator.cxx b/PWGHF/TableProducer/trackIndexSkimCreator.cxx index 35dac68d200..ccfa2d17581 100644 --- a/PWGHF/TableProducer/trackIndexSkimCreator.cxx +++ b/PWGHF/TableProducer/trackIndexSkimCreator.cxx @@ -1733,9 +1733,9 @@ struct HfTrackIndexSkimCreator { /// \tparam TTrackIndex is the iterator type of the track-collision associations template struct HfPropagatedSoftPion { - TTrackIndex trackIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid - int64_t trackGlobalIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid - std::array pVec{}; ///< momentum at the PCA to the primary vertex + TTrackIndex trackIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid + int64_t trackGlobalIndex; ///< track global index + std::array pVec{}; ///< momentum at the PCA to the primary vertex }; /// One track of the collision under study, propagated to that collision's primary vertex. @@ -1764,7 +1764,7 @@ struct HfTrackIndexSkimCreator { const auto track = trackIndex.template track_as(); if constexpr (IsSoftPion) { std::array pVec = track.pVector(); - if (thisCollId != track.collisionId()) { // rare: build the TrackParCov only here + if (thisCollId != track.collisionId()) { // this is not the "default" collision for this track, we have to re-propagate it auto trackParCov = getTrackParCov(track); std::array dcaInfo{}; o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, trackParCov, 2.f, noMatCorr, &dcaInfo); @@ -1782,7 +1782,10 @@ struct HfTrackIndexSkimCreator { } } - // optional overload + /// Overloaded function of fillPropagatedProngCache to handle optional track indices. + /// \param collision is the collision under study + /// \param trackIndices are the track associations of this collision + /// \param prongs is the cache to be filled, in the order of trackIndices template void fillPropagatedProngCache(TCollision const& collision, std::optional const& trackIndices, std::vector& prongs) { @@ -2480,8 +2483,8 @@ struct HfTrackIndexSkimCreator { int lastFilledD0 = -1; // index to be filled in table for D* mesons // propagate each track to this collision's PV once, instead of once per pair or triplet - fillPropagatedProngCache(collision, groupedTrackIndicesPos1, prongsPos); - fillPropagatedProngCache(collision, groupedTrackIndicesNeg1, prongsNeg); + fillPropagatedProngCache(collision, groupedTrackIndicesPos1, prongsPos); + fillPropagatedProngCache(collision, groupedTrackIndicesNeg1, prongsNeg); for (std::size_t iPos1 = 0; iPos1 < prongsPos.size(); ++iPos1) { const auto& trackIndexPos1 = prongsPos[iPos1].trackIndex; @@ -3263,7 +3266,7 @@ struct HfTrackIndexSkimCreator { // second loop over positive tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 0) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexNeg1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0 candidates; moreover if kaon PID enabled, apply to the negative track - // Skip the processing if the D0 candidate mass is outside the maximum allowed window across pt + // Compute D0 invariant mass for all D* candidates const std::array arrMomD0{pVecTrackPos1, pVecTrackNeg1}; const float invMassD0 = RecoDecay::m(arrMomD0, std::array{MassPiPlus, MassKPlus}); @@ -3305,7 +3308,7 @@ struct HfTrackIndexSkimCreator { // second loop over negative tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 1) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexPos1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0bar candidates; moreover if kaon PID enabled, apply to the positive track - // Skip the processing if the D0 candidate mass is outside the maximum allowed window across pt + // Compute D0 invariant mass for all D* candidates const std::array arrMomD0{pVecTrackNeg1, pVecTrackPos1}; const float invMassD0 = RecoDecay::m(arrMomD0, std::array{MassPiPlus, MassKPlus}); From adada7a1937f3f8d3cb8866ffd5be5fcce0f5d28 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Thu, 8 Oct 2026 10:25:57 +0000 Subject: [PATCH 4/5] Please consider the following formatting changes --- PWGHF/TableProducer/trackIndexSkimCreator.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PWGHF/TableProducer/trackIndexSkimCreator.cxx b/PWGHF/TableProducer/trackIndexSkimCreator.cxx index ccfa2d17581..40661296f53 100644 --- a/PWGHF/TableProducer/trackIndexSkimCreator.cxx +++ b/PWGHF/TableProducer/trackIndexSkimCreator.cxx @@ -3262,7 +3262,7 @@ struct HfTrackIndexSkimCreator { } if (config.doDstar && TESTBIT(isSelected2ProngCand, hf_cand_2prong::DecayType::D0ToPiK) && (pt2Prong + config.ptTolerance) * 1.2 > binsPtDstar[0] && whichHypo2Prong[kN2ProngDecays] != 0) { // o2-linter: disable="magic-number" (see comment below) - // if D* enabled and pt of the D0 is larger than the minimum of the D* one within 20% (D* and D0 momenta are very similar, always within 20% according to PYTHIA8) + // if D* enabled and pt of the D0 is larger than the minimum of the D* one within 20% (D* and D0 momenta are very similar, always within 20% according to PYTHIA8) // second loop over positive tracks if (TESTBIT(whichHypo2Prong[kN2ProngDecays], 0) && (!config.applyKaonPidIn3Prongs || TESTBIT(trackIndexNeg1.isIdentifiedPid(), ChannelKaonPid))) { // only for D0 candidates; moreover if kaon PID enabled, apply to the negative track From 9798609590aadef1573c30c1a8cd2b7fabfc6b39 Mon Sep 17 00:00:00 2001 From: Marcello Di Costanzo Date: Thu, 8 Oct 2026 12:39:10 +0200 Subject: [PATCH 5/5] Fix megalinter --- PWGHF/TableProducer/trackIndexSkimCreator.cxx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/PWGHF/TableProducer/trackIndexSkimCreator.cxx b/PWGHF/TableProducer/trackIndexSkimCreator.cxx index 40661296f53..f8d8a30a463 100644 --- a/PWGHF/TableProducer/trackIndexSkimCreator.cxx +++ b/PWGHF/TableProducer/trackIndexSkimCreator.cxx @@ -1733,9 +1733,9 @@ struct HfTrackIndexSkimCreator { /// \tparam TTrackIndex is the iterator type of the track-collision associations template struct HfPropagatedSoftPion { - TTrackIndex trackIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid - int64_t trackGlobalIndex; ///< track global index - std::array pVec{}; ///< momentum at the PCA to the primary vertex + TTrackIndex trackIndex; ///< track-collision association, carries isSelProng and isIdentifiedPid + int64_t trackGlobalIndex{-1}; ///< track global index + std::array pVec{}; ///< momentum at the PCA to the primary vertex }; /// One track of the collision under study, propagated to that collision's primary vertex. @@ -1772,7 +1772,6 @@ struct HfTrackIndexSkimCreator { } prongs.push_back(TProng{trackIndex, track.globalIndex(), pVec}); } else { - const auto track = trackIndex.template track_as(); auto& prong = prongs.emplace_back(TProng{trackIndex, track, getTrackParCov(track), track.pVector(), {track.dcaXY(), track.dcaZ()}}); if (thisCollId != track.collisionId()) { // this is not the "default" collision for this track, we have to re-propagate it o2::base::Propagator::Instance()->propagateToDCABxByBz({collision.posX(), collision.posY(), collision.posZ()}, prong.trackParVar, 2.f, noMatCorr, &prong.dcaInfo);