From 175098b4f08fa8b1a663213a36dec70ca037793b Mon Sep 17 00:00:00 2001 From: suyoupeng <109774812+15071832337@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:23:13 +0800 Subject: [PATCH 1/3] Add files via upload --- PWGLF/DataModel/LFSpincorrelationTables.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/PWGLF/DataModel/LFSpincorrelationTables.h b/PWGLF/DataModel/LFSpincorrelationTables.h index d4fdffd2442..fa7e2061f3e 100644 --- a/PWGLF/DataModel/LFSpincorrelationTables.h +++ b/PWGLF/DataModel/LFSpincorrelationTables.h @@ -53,6 +53,12 @@ DECLARE_SOA_COLUMN(ProtonPhi, protonPhi, float); //! Proton Ph DECLARE_SOA_COLUMN(ProtonIndex, protonIndex, int); //! Proton index DECLARE_SOA_COLUMN(PionIndex, pionIndex, int); //! Pion index DECLARE_SOA_COLUMN(DcaV0ToPV, dcaV0ToPV, float); //! DCA of V0 to primary vertex +DECLARE_SOA_COLUMN(TpcPosNClsCrossedRows, tpcPosNClsCrossedRows, float); +DECLARE_SOA_COLUMN(TpcNegNClsCrossedRows, tpcNegNClsCrossedRows, float); +DECLARE_SOA_COLUMN(Ctau, ctau, float); +DECLARE_SOA_COLUMN(TpcNsigmaPos, tpcNsigmaPos, float); +DECLARE_SOA_COLUMN(TpcNsigmaNeg, tpcNsigmaNeg, float); +DECLARE_SOA_COLUMN(MK0Short, mK0Short, float); } // namespace lambdapair DECLARE_SOA_TABLE(LambdaPairs, "AOD", "LAMBDAPAIR", @@ -74,7 +80,13 @@ DECLARE_SOA_TABLE(LambdaPairs, "AOD", "LAMBDAPAIR", lambdapair::ProtonPhi, lambdapair::ProtonIndex, lambdapair::PionIndex, - lambdapair::DcaV0ToPV); + lambdapair::DcaV0ToPV, + lambdapair::TpcPosNClsCrossedRows, + lambdapair::TpcNegNClsCrossedRows, + lambdapair::Ctau, + lambdapair::TpcNsigmaPos, + lambdapair::TpcNsigmaNeg, + lambdapair::MK0Short); using LambdaPair = LambdaPairs::iterator; @@ -109,6 +121,8 @@ DECLARE_SOA_COLUMN(ProtonPhimc, protonPhimc, float); //! Proto DECLARE_SOA_COLUMN(ProtonIndexmc, protonIndexmc, int); //! Proton index in montecarlo DECLARE_SOA_COLUMN(PionIndexmc, pionIndexmc, int); //! Pion index in montecarlo DECLARE_SOA_COLUMN(DcaV0ToPVmc, dcaV0ToPVmc, float); //! DCA of V0 to primary vertex + +//! TPC n sigma } // namespace lambdapairmc DECLARE_SOA_TABLE(LambdaPairmcs, "AOD", "LAMBDAPAIRMC", o2::soa::Index<>, From f6b70ee4a7948a5646119076f9074c4d16fb0bcb Mon Sep 17 00:00:00 2001 From: suyoupeng <109774812+15071832337@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:24:00 +0800 Subject: [PATCH 2/3] Add files via upload --- .../Strangeness/lambdaspincorrelation.cxx | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/PWGLF/TableProducer/Strangeness/lambdaspincorrelation.cxx b/PWGLF/TableProducer/Strangeness/lambdaspincorrelation.cxx index d9a3d8cca27..615c4be269d 100644 --- a/PWGLF/TableProducer/Strangeness/lambdaspincorrelation.cxx +++ b/PWGLF/TableProducer/Strangeness/lambdaspincorrelation.cxx @@ -95,21 +95,14 @@ struct lambdaspincorrelation { Configurable cMaxV0DCA{"cMaxV0DCA", 1.2, "Maximum V0 DCA to PV"}; Configurable cMinV0DCAPr{"cMinV0DCAPr", 0.05, "Minimum V0 daughters DCA to PV for Pr"}; Configurable cMinV0DCAPi{"cMinV0DCAPi", 0.05, "Minimum V0 daughters DCA to PV for Pi"}; + Configurable cMaxV0LifeTime{"cMaxV0LifeTime", 50, "Maximum V0 life time"}; // config for V0 daughters Configurable confDaughEta{"confDaughEta", 0.8f, "V0 Daugh sel: max eta"}; Configurable cfgDaughPrPt{"cfgDaughPrPt", 0.2, "minimum daughter proton pt"}; Configurable cfgDaughPiPt{"cfgDaughPiPt", 0.2, "minimum daughter pion pt"}; Configurable confDaughTPCnclsMin{"confDaughTPCnclsMin", 50.f, "V0 Daugh sel: Min. nCls TPC"}; - - struct : ConfigurableGroup { - std::string prefix = "v0Configuration"; - Configurable cfgDaughTPCCrossedRows{"cfgDaughTPCCrossedRows", 70, "Min. TPC crossed rows for V0 daughters"}; - - Configurable cMaxV0LifeTime{"cMaxV0LifeTime", 30, "Maximum V0 life time"}; - Configurable confDaughPIDCuts{"confDaughPIDCuts", 4, "PID selections for Lambda daughters"}; - - } v0Configurations; + Configurable confDaughPIDCuts{"confDaughPIDCuts", 3, "PID selections for Lambda daughters"}; Configurable iMNbins{"iMNbins", 50, "Number of bins in invariant mass"}; Configurable lbinIM{"lbinIM", 1.09, "lower bin value in IM histograms"}; @@ -156,10 +149,9 @@ struct lambdaspincorrelation { if (tranRad > confV0TranRadV0Max) { return false; } - if (std::abs(ctauLambda) > v0Configurations.cMaxV0LifeTime) { + if (std::abs(ctauLambda) > cMaxV0LifeTime) { return false; } - // if (std::abs(candidate.yLambda()) > confV0Rap) { // return false; // } @@ -170,9 +162,10 @@ struct lambdaspincorrelation { bool isSelectedV0Daughter(V0 const& candidate, T const& track, int pid) { const auto tpcNClsF = track.tpcNClsFound(); + const auto ncr = 70; const auto ncrfc = 0.8; - if (track.tpcNClsCrossedRows() < v0Configurations.cfgDaughTPCCrossedRows) { + if (track.tpcNClsCrossedRows() < ncr) { return false; } if (tpcNClsF < confDaughTPCnclsMin) { @@ -182,10 +175,10 @@ struct lambdaspincorrelation { return false; } - if (pid == 0 && std::abs(track.tpcNSigmaPr()) > v0Configurations.confDaughPIDCuts) { + if (pid == 0 && std::abs(track.tpcNSigmaPr()) > confDaughPIDCuts) { return false; } - if (pid == 1 && std::abs(track.tpcNSigmaPi()) > v0Configurations.confDaughPIDCuts) { + if (pid == 1 && std::abs(track.tpcNSigmaPi()) > confDaughPIDCuts) { return false; } if (pid == 0 && (candidate.positivept() < cfgDaughPrPt || candidate.negativept() < cfgDaughPiPt)) { @@ -306,6 +299,13 @@ struct lambdaspincorrelation { std::vector negativeIndex = {}; std::vector dcaBetweenDaughter = {}; std::vector dcaV0ToPV = {}; + std::vector tpcPosNClsCrossedRows = {}; + std::vector tpcNegNClsCrossedRows = {}; + std::vector ctau = {}; + std::vector tpcNsigmaPos = {}; + std::vector tpcNsigmaNeg = {}; + std::vector mK0Short = {}; + int numbV0 = 0; // LOGF(info, "event collisions: (%d)", collision.index()); auto centrality = collision.centFT0C(); @@ -357,6 +357,9 @@ struct lambdaspincorrelation { if (aLambdaTag) { histos.fill(HIST("hV0Info"), 3.5); } + + float ctauLambda = v0.distovertotmom(collision.posX(), collision.posY(), collision.posZ()) * (o2::constants::physics::MassLambda); + // LOGF(info, "v0 index2: (%d)", v0.index()); auto postrack1 = v0.template posTrack_as(); auto negtrack1 = v0.template negTrack_as(); @@ -364,6 +367,16 @@ struct lambdaspincorrelation { negativeIndex.push_back(negtrack1.globalIndex()); v0Cospa.push_back(v0.v0cosPA()); dcaV0ToPV.push_back(std::abs(v0.dcav0topv())); + + tpcPosNClsCrossedRows.push_back(postrack1.tpcNClsCrossedRows()); + tpcNegNClsCrossedRows.push_back(negtrack1.tpcNClsCrossedRows()); + ctau.push_back(ctauLambda); + + tpcNsigmaPos.push_back(std::abs(postrack1.tpcNSigmaPr())); + tpcNsigmaNeg.push_back(std::abs(negtrack1.tpcNSigmaPi())); + + mK0Short.push_back(v0.mK0Short()); + v0Radius.push_back(v0.v0radius()); dcaPositive.push_back(std::abs(v0.dcapostopv())); dcaNegative.push_back(std::abs(v0.dcanegtopv())); @@ -400,7 +413,7 @@ struct lambdaspincorrelation { lambdaDummy = lambdaMother.at(i5); protonDummy = protonDaughter.at(i5); pionDummy = pionDaughter.at(i5); - lambdaPair(indexEvent, v0Status.at(i5), doubleStatus.at(i5), v0Cospa.at(i5), v0Radius.at(i5), dcaPositive.at(i5), dcaNegative.at(i5), dcaBetweenDaughter.at(i5), lambdaDummy.Pt(), lambdaDummy.Eta(), lambdaDummy.Phi(), lambdaDummy.M(), protonDummy.Pt(), protonDummy.Eta(), protonDummy.Phi(), positiveIndex.at(i5), negativeIndex.at(i5), dcaV0ToPV.at(i5)); + lambdaPair(indexEvent, v0Status.at(i5), doubleStatus.at(i5), v0Cospa.at(i5), v0Radius.at(i5), dcaPositive.at(i5), dcaNegative.at(i5), dcaBetweenDaughter.at(i5), lambdaDummy.Pt(), lambdaDummy.Eta(), lambdaDummy.Phi(), lambdaDummy.M(), protonDummy.Pt(), protonDummy.Eta(), protonDummy.Phi(), positiveIndex.at(i5), negativeIndex.at(i5), dcaV0ToPV.at(i5), tpcPosNClsCrossedRows.at(i5), tpcNegNClsCrossedRows.at(i5), ctau.at(i5), tpcNsigmaPos.at(i5), tpcNsigmaNeg.at(i5), mK0Short.at(i5)); } } } From 70f3a7acfb47e93dc1c83dc7baadbabbedfc4f88 Mon Sep 17 00:00:00 2001 From: suyoupeng <109774812+15071832337@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:24:41 +0800 Subject: [PATCH 3/3] Add files via upload --- .../Strangeness/lambdaspincorrderived.cxx | 774 +----------------- 1 file changed, 33 insertions(+), 741 deletions(-) diff --git a/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx b/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx index 98167f6fe0d..e150c3644cc 100644 --- a/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx +++ b/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx @@ -70,9 +70,8 @@ static double getK0sCompetingMass(T const& v0) v0.protonPt(), v0.protonEta(), v0.protonPhi(), o2::constants::physics::MassProton); - const auto negDaugh = lambda - posDaugh; // 反推 neg + const auto negDaugh = lambda - posDaugh; - // const ROOT::Math::PtEtaPhiMVector pi1( posDaugh.Pt(), posDaugh.Eta(), posDaugh.Phi(), o2::constants::physics::MassPionCharged); @@ -416,6 +415,15 @@ struct lambdaspincorrderived { Configurable useK0sRejection{"useK0sRejection", true, "Apply K0s competing mass rejection"}; Configurable confK0sMassWindow{"confK0sMassWindow", 0.01f, "K0s competing mass rejection window (GeV/c^2)"}; + Configurable useAdditionalCut{"useAdditionalCut", false, "Apply additional cut"}; + + // tpcnclustercorssedrows () + Configurable minTpcNClsCrossedRows{"minTpcNClsCrossedRows", 70.f, "Minimum TPC crossed rows for daughter"}; + // ctau (lifetime) + Configurable maxCtau{"maxCtau", 30.f, "Maximum ctau of Lambda candidate (cm)"}; + // TPC nsigma + Configurable maxTpcNsigma{"maxTpcNsigma", 4.f, "Maximum |TPC nsigma| for Lambda daughters"}; + } v0Configurations; // Event Mixing @@ -931,6 +939,28 @@ struct lambdaspincorrderived { } } + if (v0Configurations.useAdditionalCut) { + // additional cuts + if (candidate.v0Status() == 0 && (std::abs(candidate.tpcNsigmaPos()) > v0Configurations.maxTpcNsigma || std::abs(candidate.tpcNsigmaNeg()) > v0Configurations.maxTpcNsigma)) { + return false; + } + if (candidate.v0Status() == 1 && (std::abs(candidate.tpcNsigmaNeg()) > v0Configurations.maxTpcNsigma || std::abs(candidate.tpcNsigmaPos()) > v0Configurations.maxTpcNsigma)) { + return false; + } + + if (candidate.tpcPosNClsCrossedRows() < v0Configurations.minTpcNClsCrossedRows) { + return false; + } + + if (candidate.tpcNegNClsCrossedRows() < v0Configurations.minTpcNClsCrossedRows) { + return false; + } + + if (candidate.ctau() > v0Configurations.maxCtau) { + return false; + } + } + return true; } @@ -1822,744 +1852,6 @@ struct lambdaspincorrderived { } PROCESS_SWITCH(lambdaspincorrderived, processData, "Process data", true); - void processDataShareDaughter(EventCandidates::iterator const& collision, AllTrackCandidates const& V0s) - { - auto centrality = collision.cent(); - - // ------------------------------------------------------------ - // Candidate-level shared-daughter rejection - // - // First collect all candidates in this event that pass selectionV0, - // then flag every candidate that shares a daughter track with any - // other selected candidate. Only candidates that are not flagged - // are kept for the subsequent SE pairing. - // - // This corresponds to the lambdaSharingDauFlag + trueLambdaFlag - // approach used in Subhadeep Roy's analysis: a candidate that - // shares a daughter track is removed entirely, rather than only - // vetoing the specific pair that shares the track. - // - // Implementation note: we store index() (not globalIndex()) because - // V0s.iteratorAt() expects the row index within the current table. - // ------------------------------------------------------------ - std::vector selected; - selected.reserve(V0s.size()); - - for (auto const& v0 : V0s) { - if (selectionV0(v0)) { - selected.push_back(v0.index()); - } - } - - std::vector isDirty(selected.size(), false); - for (size_t i = 0; i < selected.size(); ++i) { - for (size_t j = i + 1; j < selected.size(); ++j) { - auto a = V0s.iteratorAt(static_cast(selected[i])); - auto b = V0s.iteratorAt(static_cast(selected[j])); - if (hasSharedDaughters(a, b)) { - isDirty[i] = true; - isDirty[j] = true; - } - } - } - - std::vector clean; - clean.reserve(selected.size()); - for (size_t i = 0; i < selected.size(); ++i) { - if (!isDirty[i]) { - clean.push_back(selected[i]); - } - } - - // ------------------------------------------------------------ - // SE pairing on clean candidates only - // ------------------------------------------------------------ - for (size_t i = 0; i < clean.size(); ++i) { - const auto& v0 = V0s.iteratorAt(static_cast(clean[i])); - - if (fillHistConfig.fillBasicQAHistos) { - histos.fill(HIST("hPtRadiusV0"), v0.lambdaPt(), v0.v0Radius()); - } - if (fillHistConfig.fillBasicQAHistos) { - histos.fill(HIST("ptCent"), v0.lambdaPt(), centrality); - } - if (fillHistConfig.fillBasicQAHistos) { - histos.fill(HIST("etaCent"), v0.lambdaEta(), centrality); - } - - proton = ROOT::Math::PtEtaPhiMVector(v0.protonPt(), v0.protonEta(), v0.protonPhi(), - o2::constants::physics::MassProton); - lambda = ROOT::Math::PtEtaPhiMVector(v0.lambdaPt(), v0.lambdaEta(), v0.lambdaPhi(), - v0.lambdaMass()); - - const double phi = RecoDecay::constrainAngle(v0.lambdaPhi(), 0.0F, harmonic); - const double eta = v0.lambdaEta(); - - if (v0.v0Status() == 0) { - histos.fill(HIST("hEtaPhiLambdaRaw"), phi, eta, - getNUAWeight(0, v0.lambdaPhi(), v0.lambdaEta())); - } else { - histos.fill(HIST("hEtaPhiAntiLambdaRaw"), phi, eta, - getNUAWeight(1, v0.lambdaPhi(), v0.lambdaEta())); - } - - for (size_t j = i + 1; j < clean.size(); ++j) { - const auto& v02 = V0s.iteratorAt(static_cast(clean[j])); - - // Note: hasSharedDaughters(v0, v02) is no longer needed here. - // All clean candidates are already guaranteed not to share a - // daughter track with any other selected candidate. - - proton2 = ROOT::Math::PtEtaPhiMVector(v02.protonPt(), v02.protonEta(), v02.protonPhi(), - o2::constants::physics::MassProton); - lambda2 = ROOT::Math::PtEtaPhiMVector(v02.lambdaPt(), v02.lambdaEta(), v02.lambdaPhi(), - v02.lambdaMass()); - - if ((v0.v0Status() == 0 && v02.v0Status() == 1) || - (v0.v0Status() == 1 && v02.v0Status() == 0)) { - if (fillHistConfig.fillBasicQAHistos) { - histos.fill(HIST("deltaPhiSame"), - RecoDecay::constrainAngle(v0.lambdaPhi() - v02.lambdaPhi(), - -TMath::Pi(), harmonicDphi)); - } - } - - // const int ptype = pairTypeCode(v0.v0Status(), v02.v0Status()); - if (v0.v0Status() == 0 && v02.v0Status() == 0) { - fillHistograms(0, 0, lambda, lambda2, proton, proton2, 0, 1.0); - } - if (v0.v0Status() == 0 && v02.v0Status() == 1) { - fillHistograms(0, 1, lambda, lambda2, proton, proton2, 0, 1.0); - } - if (v0.v0Status() == 1 && v02.v0Status() == 0) { - fillHistograms(0, 1, lambda2, lambda, proton2, proton, 0, 1.0); - } - if (v0.v0Status() == 1 && v02.v0Status() == 1) { - fillHistograms(1, 1, lambda, lambda2, proton, proton2, 0, 1.0); - } - } - } - } - PROCESS_SWITCH(lambdaspincorrderived, processDataShareDaughter, "Process data share daughter", true); - - void processMEV6ShareDaughter(EventCandidates const& collisions, AllTrackCandidates const& V0s) - { - MixBinnerR mb{ - ptMin.value, - ptMax.value, - ptMix.value, - v0Configurations.v0etaMixBuffer.value, - etaMix.value, - phiMix.value, - MassMin.value, - MassMax.value, - cfgV5MassBins.value, - cfgMixRadiusParam.cfgMixRadiusBins.value}; - - const int nCol = colBinning.getAllBinsCount(); - const int nStat = N_STATUS; - const int nPt = mb.nPt(); - const int nEta = mb.nEta(); - const int nPhi = mb.nPhi(); - const int nM = mb.nM(); - const int nR = mb.nR(); - - const size_t nKeys = static_cast(nCol) * nStat * nPt * nEta * nPhi * nM * nR; - std::vector> buffer(nKeys); - - // -------- PASS 1: fill buffer -------- - for (auto const& col : collisions) { - const int colBin = colBinning.getBin(std::make_tuple(col.posz(), col.cent())); - if (colBin < 0) { - continue; - } - - auto slice = V0s.sliceBy(tracksPerCollisionV0, col.index()); - - // ------------------------------------------------------------ - // Candidate-level shared-daughter rejection (PASS 1) - // - // Only clean candidates are inserted into the mixing buffer. - // A candidate is dirty if it shares a daughter track with any - // other selected candidate in the same event. - // - // Implementation note: we store index() (not globalIndex()) because - // slice.iteratorAt() expects the row index within the current table. - // ------------------------------------------------------------ - std::vector selected; - selected.reserve(slice.size()); - for (auto const& t : slice) { - if (selectionV0(t)) { - selected.push_back(t.index()); - } - } - - std::vector isDirty(selected.size(), false); - for (size_t i = 0; i < selected.size(); ++i) { - for (size_t j = i + 1; j < selected.size(); ++j) { - auto a = slice.iteratorAt(static_cast(selected[i])); - auto b = slice.iteratorAt(static_cast(selected[j])); - if (hasSharedDaughters(a, b)) { - isDirty[i] = true; - isDirty[j] = true; - } - } - } - - for (size_t i = 0; i < selected.size(); ++i) { - if (isDirty[i]) { - continue; - } - const auto& t = slice.iteratorAt(static_cast(selected[i])); - - const int status = static_cast(t.v0Status()); - if (status < 0 || status >= nStat) { - continue; - } - - const int ptB = mb.ptBin(t.lambdaPt()); - - int etaB = mb.etaBin(t.lambdaEta()); - if (userapidity) { - const auto lv = ROOT::Math::PtEtaPhiMVector(t.lambdaPt(), t.lambdaEta(), t.lambdaPhi(), t.lambdaMass()); - etaB = mb.etaBin(lv.Rapidity()); - } - - const int phiB = mb.phiBin(RecoDecay::constrainAngle(t.lambdaPhi(), 0.0, harmonic)); - const int mB = getMassMixClassFromEdges(t.lambdaMass(), massMixEdges.value); - const int rB = mb.radiusBin(t.v0Radius()); - - if (ptB < 0 || etaB < 0 || phiB < 0 || mB < 0 || rB < 0) { - continue; - } - - const size_t key = linearKeyR(colBin, status, ptB, etaB, phiB, mB, rB, - nStat, nPt, nEta, nPhi, nM, nR); - - buffer[key].push_back(BufferCandR{ - .collisionIdx = static_cast(col.index()), - .rowIndex = static_cast(t.globalIndex()), - .v0Status = static_cast(status), - .ptBin = static_cast(ptB), - .etaBin = static_cast(etaB), - .phiBin = static_cast(phiB), - .mBin = static_cast(mB), - .rBin = static_cast(rB)}); - } - } - - const int nN_pt = std::max(0, cfgV5NeighborPt.value); - const int nN_eta = std::max(0, cfgV5NeighborEta.value); - const int nN_phi = std::max(0, cfgV5NeighborPhi.value); - - std::vector ptBins, etaBins, phiBins; - std::vector matches1, matches2; - matches1.reserve(256); - matches2.reserve(256); - - auto collectMatchesForReplacedLeg = [&](auto const& tRep, auto const& tKeep, int colBin, int64_t curColIdx, std::vector& matches) { - matches.clear(); - - const int status = static_cast(tRep.v0Status()); - if (status < 0 || status >= nStat) { - return; - } - - const int ptB = mb.ptBin(tRep.lambdaPt()); - - int etaB = mb.etaBin(tRep.lambdaEta()); - if (userapidity) { - const auto lv = ROOT::Math::PtEtaPhiMVector(tRep.lambdaPt(), tRep.lambdaEta(), tRep.lambdaPhi(), tRep.lambdaMass()); - etaB = mb.etaBin(lv.Rapidity()); - } - - const int phiB = mb.phiBin(RecoDecay::constrainAngle(tRep.lambdaPhi(), 0.0, harmonic)); - const int mB = getMassMixClassFromEdges(tRep.lambdaMass(), massMixEdges.value); - const int rB = mb.radiusBin(tRep.v0Radius()); - - if (ptB < 0 || etaB < 0 || phiB < 0 || mB < 0 || rB < 0) { - return; - } - auto collectFromBins = [&](const std::vector& ptUseBins, - const std::vector& etaUseBins, - const std::vector& phiUseBins) { - for (const auto& ptUse : ptUseBins) { - for (const auto& etaUse : etaUseBins) { - for (const auto& phiUse : phiUseBins) { - const auto& vec = buffer[linearKeyR(colBin, status, ptUse, etaUse, phiUse, mB, rB, - nStat, nPt, nEta, nPhi, nM, nR)]; - - for (auto const& bc : vec) { - if (bc.collisionIdx == curColIdx) - continue; - - auto tX = V0s.iteratorAt(static_cast(bc.rowIndex)); - - if (!selectionV0(tX)) - continue; - if (!checkKinematics(tRep, tX)) - continue; - - if (tX.globalIndex() == tRep.globalIndex()) - continue; - if (tX.globalIndex() == tKeep.globalIndex()) - continue; - - if (hasSharedDaughters(tX, tKeep)) - continue; - if (hasSharedDaughters(tX, tRep)) - continue; - - matches.push_back(MatchRef{bc.collisionIdx, bc.rowIndex}); - } - } - } - } - }; - - matches.clear(); - - // 1) exact bin first - ptBins.clear(); - etaBins.clear(); - phiBins.clear(); - - ptBins.push_back(ptB); - etaBins.push_back(etaB); - phiBins.push_back(phiB); - - collectFromBins(ptBins, etaBins, phiBins); - - // 2) if exact bin gives fewer than required matches, also search neighbors - const int targetMatches = (cfgV5MaxMatches.value > 0) ? cfgV5MaxMatches.value : 1; - - if ((int)matches.size() < targetMatches) { - std::vector ptBinsN, etaBinsN, phiBinsN; - collectNeighborBinsClamp(ptB, nPt, nN_pt, ptBinsN); - collectNeighborBinsClamp(etaB, nEta, nN_eta, etaBinsN); - collectNeighborBinsPhi(phiB, nPhi, nN_phi, phiBinsN); - - for (const auto& ptUse : ptBinsN) { - for (const auto& etaUse : etaBinsN) { - for (const auto& phiUse : phiBinsN) { - if (ptUse == ptB && etaUse == etaB && phiUse == phiB) - continue; - - const auto& vec = buffer[linearKeyR(colBin, status, ptUse, etaUse, phiUse, mB, rB, - nStat, nPt, nEta, nPhi, nM, nR)]; - - for (auto const& bc : vec) { - if (bc.collisionIdx == curColIdx) - continue; - - auto tX = V0s.iteratorAt(static_cast(bc.rowIndex)); - - if (!selectionV0(tX)) - continue; - if (!checkKinematics(tRep, tX)) - continue; - - if (tX.globalIndex() == tRep.globalIndex()) - continue; - if (tX.globalIndex() == tKeep.globalIndex()) - continue; - - if (hasSharedDaughters(tX, tKeep)) - continue; - if (hasSharedDaughters(tX, tRep)) - continue; - - matches.push_back(MatchRef{bc.collisionIdx, bc.rowIndex}); - } - } - } - } - } - - std::sort(matches.begin(), matches.end(), - [](auto const& a, auto const& b) { - return std::tie(a.collisionIdx, a.rowIndex) < std::tie(b.collisionIdx, b.rowIndex); - }); - matches.erase(std::unique(matches.begin(), matches.end(), - [](auto const& a, auto const& b) { - return a.collisionIdx == b.collisionIdx && a.rowIndex == b.rowIndex; - }), - matches.end()); - }; - - auto downsampleMatches = [&](std::vector& matches, uint64_t seedBase) { - if (cfgV5MaxMatches.value > 0 && (int)matches.size() > cfgV5MaxMatches.value) { - uint64_t seed = cfgMixSeed.value ^ splitmix64(seedBase); - const int K = cfgV5MaxMatches.value; - for (int i = 0; i < K; ++i) { - seed = splitmix64(seed); - const int j = i + (int)(seed % (uint64_t)(matches.size() - i)); - std::swap(matches[i], matches[j]); - } - matches.resize(K); - } - }; - - const size_t pendingAtStart = v6Pending.data.size(); - size_t pendingMatched = 0; - size_t pendingExpired = 0; - size_t pendingAdded = 0; - if (!cfgV6CarryUnmatched) { - v6Pending.data.clear(); - } else { - for (auto it = v6Pending.data.begin(); it != v6Pending.data.end();) { - auto& pending = *it; - ++pending.age; - if (cfgV6MaxPendingAge.value > 0 && pending.age > cfgV6MaxPendingAge.value) { - ++pendingExpired; - it = v6Pending.data.erase(it); - continue; - } - - auto& matches = pending.replacedLeg == 1 ? matches1 : matches2; - collectMatchesForReplacedLeg(pending.target, pending.fixed, pending.colBin, -1, matches); - limitMatchesToNEvents(matches, nEvtMixing.value); - downsampleMatches(matches, pending.seed ^ splitmix64(static_cast(pending.age))); - - int nAccepted = 0; - for (auto const& m : matches) { - auto replacement = V0s.iteratorAt(static_cast(m.rowIndex)); - if (!selectionV0(replacement) || !checkKinematics(pending.target, replacement)) { - continue; - } - if (replacement.globalIndex() == pending.target.globalIndex() || replacement.globalIndex() == pending.fixed.globalIndex()) { - continue; - } - if (hasSharedDaughters(replacement, pending.target) || hasSharedDaughters(replacement, pending.fixed)) { - continue; - } - ++nAccepted; - } - - if (nAccepted == 0) { - ++it; - continue; - } - - const float controlWeight = 1.0f / static_cast(nAccepted); - const float branchNorm = cfgMixLegMode.value == 2 ? 0.5f : 1.0f; - const float mixWeight = branchNorm * controlWeight; - for (auto const& m : matches) { - auto replacement = V0s.iteratorAt(static_cast(m.rowIndex)); - if (!selectionV0(replacement) || !checkKinematics(pending.target, replacement)) { - continue; - } - if (replacement.globalIndex() == pending.target.globalIndex() || replacement.globalIndex() == pending.fixed.globalIndex()) { - continue; - } - if (hasSharedDaughters(replacement, pending.target) || hasSharedDaughters(replacement, pending.fixed)) { - continue; - } - fillV6MixedBranch(replacement, pending.fixed, pending.replacedLeg, controlWeight, mixWeight); - } - ++pendingMatched; - it = v6Pending.data.erase(it); - } - } - - // -------- PASS 2: configurable one-leg / two-leg mixing -------- - for (auto const& col1 : collisions) { - const int colBin = colBinning.getBin(std::make_tuple(col1.posz(), col1.cent())); - if (colBin < 0) { - continue; - } - - const int64_t curColIdx = static_cast(col1.index()); - auto poolA = V0s.sliceBy(tracksPerCollisionV0, col1.index()); - - // ------------------------------------------------------------ - // Candidate-level shared-daughter rejection (PASS 2) - // - // Build the clean set for this event so that SE pairing only - // uses clean candidates. The mixing buffer already contains - // only clean candidates, so matched replacements are clean. - // - // Implementation note: we store index() (not globalIndex()) because - // poolA.iteratorAt() expects the row index within the current table. - // ------------------------------------------------------------ - std::vector selectedA; - selectedA.reserve(poolA.size()); - for (auto const& t : poolA) { - if (selectionV0(t)) { - selectedA.push_back(t.index()); - } - } - - std::vector dirtyA(selectedA.size(), false); - for (size_t i = 0; i < selectedA.size(); ++i) { - for (size_t j = i + 1; j < selectedA.size(); ++j) { - auto a = poolA.iteratorAt(static_cast(selectedA[i])); - auto b = poolA.iteratorAt(static_cast(selectedA[j])); - if (hasSharedDaughters(a, b)) { - dirtyA[i] = true; - dirtyA[j] = true; - } - } - } - - // Build a set of clean row indices for fast lookup in the pairing loop. - // We use index() here, matching what soa::combinations yields via t.index(). - std::unordered_set cleanIdxSet; - cleanIdxSet.reserve(selectedA.size()); - for (size_t i = 0; i < selectedA.size(); ++i) { - if (!dirtyA[i]) { - cleanIdxSet.insert(selectedA[i]); - } - } - - for (auto const& [t1, t2] : soa::combinations(o2::soa::CombinationsFullIndexPolicy(poolA, poolA))) { - if (!selectionV0(t1) || !selectionV0(t2)) { - continue; - } - if (!cleanIdxSet.count(t1.index()) || !cleanIdxSet.count(t2.index())) { - continue; - } - if (t2.index() <= t1.index()) { - continue; - } - - // Note: hasSharedDaughters(t1, t2) is no longer needed here. - // Both t1 and t2 are guaranteed to be clean candidates. - - const bool doMixLeg1 = (cfgMixLegMode.value == 0 || cfgMixLegMode.value == 2); - const bool doMixLeg2 = (cfgMixLegMode.value == 1 || cfgMixLegMode.value == 2); - - // Fill TGT maps before searching for replacements. This makes TGT the - // true same-event target phase space for the selected pair, matching the - // hPtYSame definition. REP below is filled only for accepted replacements. - if (doMixLeg1) { - fillReplacementControlMap(t1.v0Status(), t2.v0Status(), 1, true, - ROOT::Math::PtEtaPhiMVector(t1.lambdaPt(), t1.lambdaEta(), t1.lambdaPhi(), t1.lambdaMass()), - 1.0f); - fillFixedLegControlMap(t1.v0Status(), t2.v0Status(), 1, true, - ROOT::Math::PtEtaPhiMVector(t2.lambdaPt(), t2.lambdaEta(), t2.lambdaPhi(), t2.lambdaMass()), - 1.0f); - } - if (doMixLeg2) { - fillReplacementControlMap(t1.v0Status(), t2.v0Status(), 2, true, - ROOT::Math::PtEtaPhiMVector(t2.lambdaPt(), t2.lambdaEta(), t2.lambdaPhi(), t2.lambdaMass()), - 1.0f); - fillFixedLegControlMap(t1.v0Status(), t2.v0Status(), 2, true, - ROOT::Math::PtEtaPhiMVector(t1.lambdaPt(), t1.lambdaEta(), t1.lambdaPhi(), t1.lambdaMass()), - 1.0f); - } - - if (doMixLeg1) { - collectMatchesForReplacedLeg(t1, t2, colBin, curColIdx, matches1); - limitMatchesToNEvents(matches1, nEvtMixing.value); - downsampleMatches(matches1, (uint64_t)t1.globalIndex() ^ (splitmix64((uint64_t)t2.globalIndex()) + 0x111ULL) ^ splitmix64((uint64_t)curColIdx)); - } else { - matches1.clear(); - } - - if (doMixLeg2) { - collectMatchesForReplacedLeg(t2, t1, colBin, curColIdx, matches2); - limitMatchesToNEvents(matches2, nEvtMixing.value); - downsampleMatches(matches2, (uint64_t)t2.globalIndex() ^ (splitmix64((uint64_t)t1.globalIndex()) + 0x222ULL) ^ splitmix64((uint64_t)curColIdx)); - } else { - matches2.clear(); - } - - // Do not fill TGT here. TGT has already been filled above as the - // selected same-event target phase space, independent of replacement success. - int nFill1 = 0; - int nFill2 = 0; - // count actual accepted fills for leg-1 replacement - if (doMixLeg1) { - for (auto const& m : matches1) { - auto tX = V0s.iteratorAt(static_cast(m.rowIndex)); - if (!selectionV0(tX)) - continue; - if (tX.v0Status() != t1.v0Status()) - continue; - if (!checkKinematics(t1, tX)) - continue; - if (tX.globalIndex() == t1.globalIndex()) - continue; - if (tX.globalIndex() == t2.globalIndex()) - continue; - if (hasSharedDaughters(tX, t2)) - continue; - if (hasSharedDaughters(tX, t1)) - continue; - ++nFill1; - } - } - - // count actual accepted fills for leg-2 replacement - if (doMixLeg2) { - for (auto const& m : matches2) { - auto tY = V0s.iteratorAt(static_cast(m.rowIndex)); - if (!selectionV0(tY)) - continue; - if (tY.v0Status() != t2.v0Status()) - continue; - if (!checkKinematics(t2, tY)) - continue; - if (tY.globalIndex() == t2.globalIndex()) - continue; - if (tY.globalIndex() == t1.globalIndex()) - continue; - if (hasSharedDaughters(tY, t2)) - continue; - if (hasSharedDaughters(tY, t1)) - continue; - ++nFill2; - } - } - - if (cfgV6CarryUnmatched) { - const auto hasPendingSpace = [&]() { - return cfgV6MaxPendingBranches.value <= 0 || static_cast(v6Pending.data.size()) < cfgV6MaxPendingBranches.value; - }; - if (doMixLeg1 && nFill1 == 0 && hasPendingSpace()) { - v6Pending.data.push_back({storeV6Candidate(t1, curColIdx), storeV6Candidate(t2, curColIdx), colBin, 1, 0, - static_cast(t1.globalIndex()) ^ splitmix64(static_cast(t2.globalIndex())) ^ splitmix64(static_cast(curColIdx))}); - ++pendingAdded; - } - if (doMixLeg2 && nFill2 == 0 && hasPendingSpace()) { - v6Pending.data.push_back({storeV6Candidate(t2, curColIdx), storeV6Candidate(t1, curColIdx), colBin, 2, 0, - static_cast(t2.globalIndex()) ^ splitmix64(static_cast(t1.globalIndex())) ^ splitmix64(static_cast(curColIdx))}); - ++pendingAdded; - } - } - histos.fill(HIST("hNMEperSE_leg2"), nFill2); - histos.fill(HIST("hNMEperSE_leg1"), nFill1); - - if (nFill1 <= 0 && nFill2 <= 0) { - continue; - } - // Residual-weight QA needs a leg-specific normalization: - // TGT_leg is filled once per same-event target candidate with weight 1. - // REP_leg is the average replacement distribution for that same target. - const float wSELeg1 = (nFill1 > 0) ? 1.0f / static_cast(nFill1) : 0.0f; - const float wSELeg2 = (nFill2 > 0) ? 1.0f / static_cast(nFill2) : 0.0f; - - const int nActiveMixBranches = ((doMixLeg1 && nFill1 > 0) ? 1 : 0) + - ((doMixLeg2 && nFill2 > 0) ? 1 : 0); - float branchNorm = 1.0f; - if (cfgMixLegMode.value == 2) { - branchNorm = cfgV6CarryUnmatched ? 0.5f : 1.0f / static_cast(nActiveMixBranches); - } - const float finalMixWeightLeg1 = branchNorm * wSELeg1; - const float finalMixWeightLeg2 = branchNorm * wSELeg2; - - // Do not fill target maps here: TGT has already been filled above, - // before searching for replacements, so that TGT represents all selected - // same-event targets rather than only targets with successful replacements. - - if (doMixLeg1 && nFill1 > 0) { - for (auto const& m : matches1) { - auto tX = V0s.iteratorAt(static_cast(m.rowIndex)); - if (!selectionV0(tX)) { - continue; - } - if (tX.v0Status() != t1.v0Status()) - continue; - if (!checkKinematics(t1, tX)) - continue; - if (tX.globalIndex() == t1.globalIndex()) - continue; - if (tX.globalIndex() == t2.globalIndex()) - continue; - if (hasSharedDaughters(tX, t1)) - continue; - if (hasSharedDaughters(tX, t2)) - continue; - fillReplacementControlMap(tX.v0Status(), t2.v0Status(), 1, false, - ROOT::Math::PtEtaPhiMVector(tX.lambdaPt(), tX.lambdaEta(), tX.lambdaPhi(), tX.lambdaMass()), - wSELeg1); - // Fixed leg for leg1 replacement is original t2. - // Fill only for successful replacement. - fillFixedLegControlMap(tX.v0Status(), t2.v0Status(), 1, false, - ROOT::Math::PtEtaPhiMVector(t2.lambdaPt(), t2.lambdaEta(), t2.lambdaPhi(), t2.lambdaMass()), - wSELeg1); - auto proton = ROOT::Math::PtEtaPhiMVector(tX.protonPt(), tX.protonEta(), tX.protonPhi(), o2::constants::physics::MassProton); - auto lambda = ROOT::Math::PtEtaPhiMVector(tX.lambdaPt(), tX.lambdaEta(), tX.lambdaPhi(), tX.lambdaMass()); - auto proton2 = ROOT::Math::PtEtaPhiMVector(t2.protonPt(), t2.protonEta(), t2.protonPhi(), o2::constants::physics::MassProton); - auto lambda2 = ROOT::Math::PtEtaPhiMVector(t2.lambdaPt(), t2.lambdaEta(), t2.lambdaPhi(), t2.lambdaMass()); - - // const int ptype = pairTypeCode(tX.v0Status(), t2.v0Status()); - - const float meWeight = finalMixWeightLeg1; - const float dPhi = deltaPhiMinusPiToPi((float)lambda.Phi(), (float)lambda2.Phi()); - if ((tX.v0Status() == 0 && t2.v0Status() == 1) || (tX.v0Status() == 1 && t2.v0Status() == 0)) - if (fillHistConfig.fillBasicQAHistos) - histos.fill(HIST("deltaPhiMix"), dPhi, meWeight); - const int s1 = tX.v0Status(); - const int s2 = t2.v0Status(); - - if (s1 == 0 && s2 == 1) { - fillHistograms(0, 1, lambda, lambda2, proton, proton2, 1, meWeight, 1, 1); - } else if (s1 == 1 && s2 == 0) { - fillHistograms(0, 1, lambda2, lambda, proton2, proton, 1, meWeight, 2, 1); - } else { - fillHistograms(s1, s2, lambda, lambda2, proton, proton2, 1, meWeight, 1, 1); - } - } - } - - if (doMixLeg2 && nFill2 > 0) { - for (auto const& m : matches2) { - auto tY = V0s.iteratorAt(static_cast(m.rowIndex)); - if (!selectionV0(tY)) { - continue; - } - if (tY.v0Status() != t2.v0Status()) - continue; - if (!checkKinematics(t2, tY)) - continue; - if (tY.globalIndex() == t2.globalIndex()) - continue; - if (tY.globalIndex() == t1.globalIndex()) - continue; - if (hasSharedDaughters(tY, t1)) - continue; - if (hasSharedDaughters(tY, t2)) - continue; - fillReplacementControlMap(t1.v0Status(), tY.v0Status(), 2, false, - ROOT::Math::PtEtaPhiMVector(tY.lambdaPt(), tY.lambdaEta(), tY.lambdaPhi(), tY.lambdaMass()), - wSELeg2); - // Fixed leg for leg2 replacement is original t1. - // Fill only for successful replacement. - fillFixedLegControlMap(t1.v0Status(), tY.v0Status(), 2, false, - ROOT::Math::PtEtaPhiMVector(t1.lambdaPt(), t1.lambdaEta(), t1.lambdaPhi(), t1.lambdaMass()), - wSELeg2); - auto proton = ROOT::Math::PtEtaPhiMVector(t1.protonPt(), t1.protonEta(), t1.protonPhi(), o2::constants::physics::MassProton); - auto lambda = ROOT::Math::PtEtaPhiMVector(t1.lambdaPt(), t1.lambdaEta(), t1.lambdaPhi(), t1.lambdaMass()); - auto proton2 = ROOT::Math::PtEtaPhiMVector(tY.protonPt(), tY.protonEta(), tY.protonPhi(), o2::constants::physics::MassProton); - auto lambda2 = ROOT::Math::PtEtaPhiMVector(tY.lambdaPt(), tY.lambdaEta(), tY.lambdaPhi(), tY.lambdaMass()); - - // const int ptype = pairTypeCode(t1.v0Status(), tY.v0Status()); - const float meWeight = finalMixWeightLeg2; - const float dPhi = deltaPhiMinusPiToPi((float)lambda.Phi(), (float)lambda2.Phi()); - if (fillHistConfig.fillBasicQAHistos) - histos.fill(HIST("deltaPhiMix"), dPhi, meWeight); - const int s1 = t1.v0Status(); - const int s2 = tY.v0Status(); - if (s1 == 0 && s2 == 1) { - fillHistograms(0, 1, lambda, lambda2, proton, proton2, 1, meWeight, 2, 2); - } else if (s1 == 1 && s2 == 0) { - fillHistograms(0, 1, lambda2, lambda, proton2, proton, 1, meWeight, 1, 2); - } else { - fillHistograms(s1, s2, lambda, lambda2, proton, proton2, 1, meWeight, 2, 2); - } - } - } - } - } - if (cfgV6LogPending) { - LOGF(info, "MEV6 data pending branches: carriedIn=%zu matched=%zu expired=%zu newlyPropagated=%zu carriedToNext=%zu", - pendingAtStart, pendingMatched, pendingExpired, pendingAdded, v6Pending.data.size()); - } - } - PROCESS_SWITCH(lambdaspincorrderived, processMEV6ShareDaughter, "Process data ME v6 with radius buffer share daughter", true); - void processDataSys(EventCandidates::iterator const&, AllTrackCandidates const& V0s) { for (const auto& v0 : V0s) { @@ -2762,7 +2054,7 @@ struct lambdaspincorrderived { // Processing Event Mixing SliceCache cache; using BinningType = ColumnBinningPolicy; - BinningType colBinning{{CfgVtxBins, CfgMultBins}}; + BinningType colBinning{{CfgVtxBins, CfgMultBins}, true}; Preslice tracksPerCollisionV0 = aod::lambdapair::lambdaeventId; void processMEV3(EventCandidates const& collisions, AllTrackCandidates const& V0s)