diff --git a/PWGLF/Tasks/Resonances/doublephimeson.cxx b/PWGLF/Tasks/Resonances/doublephimeson.cxx index 27409836425..408eee907db 100644 --- a/PWGLF/Tasks/Resonances/doublephimeson.cxx +++ b/PWGLF/Tasks/Resonances/doublephimeson.cxx @@ -112,7 +112,7 @@ struct doublephimeson { histos.add("SEMassUnlike", "SEMassUnlike", HistType::kTHnSparseF, {thnAxisInvMass, thnAxisDeltaR, thnAxisPt, thnAxisDeltaR, thnAxisInvMassDeltaPhi, thnAxisPtCorr}); // histos.add("SEMassLike", "SEMassLike", HistType::kTHnSparseF, {thnAxisInvMass, thnAxisPt, thnAxisDeltaR, thnAxisInvMassPhi, thnAxisInvMassPhi, thnAxisNumPhi}); - histos.add("MEMassUnlike", "MEMassUnlike", HistType::kTHnSparseF, {thnAxisInvMass, thnAxisPt, thnAxisDeltaR, thnAxisInvMassDeltaPhi}); + histos.add("MEMassUnlike", "MEMassUnlike", HistType::kTHnSparseF, {thnAxisInvMass, thnAxisDeltaR, thnAxisPt, thnAxisDeltaR, thnAxisInvMassDeltaPhi, thnAxisPtCorr}); } // get kstar @@ -925,6 +925,194 @@ struct doublephimeson { } } PROCESS_SWITCH(doublephimeson, processopti3, "Process Optimized same event", false); + + SliceCache cache; + using BinningTypeVertexContributor = ColumnBinningPolicy; + + void processMixedEvent(aod::RedPhiEvents& collisions, aod::PhiTracks& phitracks) + { + auto tracksTuple = std::make_tuple(phitracks); + BinningTypeVertexContributor binningOnPositions{{CfgVtxBins, CfgMultBins}, true}; + SameKindPair pair{ + binningOnPositions, nEvtMixing, -1, collisions, tracksTuple, &cache}; + + // --- helpers (same as in processopti3) --- + constexpr double mPhiPDG = 1.019461; // GeV/c^2 + + const auto deltaMPhi = [=](double m1, double m2) { + const double d1 = m1 - mPhiPDG; + const double d2 = m2 - mPhiPDG; + return std::sqrt(d1 * d1 + d2 * d2); + }; + + const auto deltaR = [](double phi1, double eta1, double phi2, double eta2) { + const double dphi = TVector2::Phi_mpi_pi(phi1 - phi2); + const double deta = eta1 - eta2; + return std::sqrt(dphi * dphi + deta * deta); + }; + + const auto minKaonDeltaR = + [&](const ROOT::Math::PtEtaPhiMVector& kplusA, + const ROOT::Math::PtEtaPhiMVector& kplusB, + const ROOT::Math::PtEtaPhiMVector& kminusA, + const ROOT::Math::PtEtaPhiMVector& kminusB) { + // same-sign first (keep QA as in SE) + const double dRkplus = + deltaR(kplusA.Phi(), kplusA.Eta(), kplusB.Phi(), kplusB.Eta()); + const double dRkminus = + deltaR(kminusA.Phi(), kminusA.Eta(), kminusB.Phi(), kminusB.Eta()); + histos.fill(HIST("hDeltaRkaonplus"), dRkplus); + histos.fill(HIST("hDeltaRkaonminus"), dRkminus); + + // all other combinations + const double dR_k1p_k1m = + deltaR(kplusA.Phi(), kplusA.Eta(), kminusA.Phi(), kminusA.Eta()); + const double dR_k1p_k2m = + deltaR(kplusA.Phi(), kplusA.Eta(), kminusB.Phi(), kminusB.Eta()); + const double dR_k2p_k1m = + deltaR(kplusB.Phi(), kplusB.Eta(), kminusA.Phi(), kminusA.Eta()); + const double dR_k2p_k2m = + deltaR(kplusB.Phi(), kplusB.Eta(), kminusB.Phi(), kminusB.Eta()); + + double minDR = dRkplus; + minDR = std::min(minDR, dRkminus); + minDR = std::min(minDR, dR_k1p_k1m); + minDR = std::min(minDR, dR_k1p_k2m); + minDR = std::min(minDR, dR_k2p_k1m); + minDR = std::min(minDR, dR_k2p_k2m); + return minDR; + }; + + struct PhiCand { + ROOT::Math::PtEtaPhiMVector phi; + ROOT::Math::PtEtaPhiMVector kplus; + ROOT::Math::PtEtaPhiMVector kminus; + }; + + for (auto& [collision1, tracks1, collision2, tracks2] : pair) { + // safety: should never happen but keep it + if (collision1.index() == collision2.index()) { + continue; + } + + // optional event-level selection (same idea as in SE) + if (additionalEvsel) { + if (collision1.numPos() < 2 || collision1.numNeg() < 2) { + continue; + } + if (collision2.numPos() < 2 || collision2.numNeg() < 2) { + continue; + } + } + + std::vector cands1, cands2; + + // --- build φ candidates for event 1 (φ1) --- + for (auto const& t1 : tracks1) { + const double kplus1pt = std::hypot(t1.phid1Px(), t1.phid1Py()); + const double kminus1pt = std::hypot(t1.phid2Px(), t1.phid2Py()); + + if (kplus1pt > maxKaonPt || kminus1pt > maxKaonPt) + continue; + if (!selectionPID(t1.phid1TPC(), t1.phid1TOF(), t1.phid1TOFHit(), strategyPID1, kplus1pt)) + continue; + if (!selectionPID(t1.phid2TPC(), t1.phid2TOF(), t1.phid2TOFHit(), strategyPID2, kminus1pt)) + continue; + + TLorentzVector phi1, k1p, k1m; + phi1.SetXYZM(t1.phiPx(), t1.phiPy(), t1.phiPz(), t1.phiMass()); + k1p.SetXYZM(t1.phid1Px(), t1.phid1Py(), t1.phid1Pz(), 0.493); + k1m.SetXYZM(t1.phid2Px(), t1.phid2Py(), t1.phid2Pz(), 0.493); + + if (t1.phiMass() < minPhiMass1 || t1.phiMass() > maxPhiMass1) + continue; + if (phi1.Pt() < minPhiPt || phi1.Pt() > maxPhiPt) + continue; + + histos.fill(HIST("hPhiMass"), phi1.M(), phi1.Pt()); + + PhiCand cand; + cand.phi = ROOT::Math::PtEtaPhiMVector(phi1.Pt(), phi1.Eta(), phi1.Phi(), phi1.M()); + cand.kplus = ROOT::Math::PtEtaPhiMVector(k1p.Pt(), k1p.Eta(), k1p.Phi(), 0.493); + cand.kminus = ROOT::Math::PtEtaPhiMVector(k1m.Pt(), k1m.Eta(), k1m.Phi(), 0.493); + + cands1.emplace_back(std::move(cand)); + } + + // --- build φ candidates for event 2 (φ2) --- + for (auto const& t2 : tracks2) { + const double kplus2pt = std::hypot(t2.phid1Px(), t2.phid1Py()); + const double kminus2pt = std::hypot(t2.phid2Px(), t2.phid2Py()); + + if (kplus2pt > maxKaonPt || kminus2pt > maxKaonPt) + continue; + if (!selectionPID(t2.phid1TPC(), t2.phid1TOF(), t2.phid1TOFHit(), strategyPID1, kplus2pt)) + continue; + if (!selectionPID(t2.phid2TPC(), t2.phid2TOF(), t2.phid2TOFHit(), strategyPID2, kminus2pt)) + continue; + + TLorentzVector phi2, k2p, k2m; + phi2.SetXYZM(t2.phiPx(), t2.phiPy(), t2.phiPz(), t2.phiMass()); + k2p.SetXYZM(t2.phid1Px(), t2.phid1Py(), t2.phid1Pz(), 0.493); + k2m.SetXYZM(t2.phid2Px(), t2.phid2Py(), t2.phid2Pz(), 0.493); + + if (t2.phiMass() < minPhiMass2 || t2.phiMass() > maxPhiMass2) + continue; + if (phi2.Pt() < minPhiPt || phi2.Pt() > maxPhiPt) + continue; + + histos.fill(HIST("hPhiMass"), phi2.M(), phi2.Pt()); + + PhiCand cand; + cand.phi = ROOT::Math::PtEtaPhiMVector(phi2.Pt(), phi2.Eta(), phi2.Phi(), phi2.M()); + cand.kplus = ROOT::Math::PtEtaPhiMVector(k2p.Pt(), k2p.Eta(), k2p.Phi(), 0.493); + cand.kminus = ROOT::Math::PtEtaPhiMVector(k2m.Pt(), k2m.Eta(), k2m.Phi(), 0.493); + + cands2.emplace_back(std::move(cand)); + } + + if (cands1.empty() || cands2.empty()) { + continue; + } + + // --- build mixed-event pairs and fill MEMassUnlike --- + for (auto const& c1 : cands1) { + TLorentzVector phi1; + phi1.SetPtEtaPhiM(c1.phi.Pt(), c1.phi.Eta(), c1.phi.Phi(), c1.phi.M()); + + for (auto const& c2 : cands2) { + TLorentzVector phi2; + phi2.SetPtEtaPhiM(c2.phi.Pt(), c2.phi.Eta(), c2.phi.Phi(), c2.phi.M()); + + const double dM = deltaMPhi(phi1.M(), phi2.M()); + if (dM > maxDeltaMPhi) + continue; + + TLorentzVector pair = phi1 + phi2; + if (pair.M() < minExoticMass || pair.M() > maxExoticMass) + continue; + + const double minDR = minKaonDeltaR(c1.kplus, c2.kplus, c1.kminus, c2.kminus); + const double dR = deltaR(phi1.Phi(), phi1.Eta(), phi2.Phi(), phi2.Eta()); + + // same definition as SE + const double ptcorr = (pair.Pt() - phi1.Pt() != 0.) + ? phi1.Pt() / (pair.Pt() - phi1.Pt()) + : 0.; + + histos.fill(HIST("MEMassUnlike"), + pair.M(), // M(phi-phi) + minDR, // min ΔR among all kaon pairs + pair.Pt(), // pT(phi-phi) + dR, // ΔR(phi1, phi2) + dM, // Δm(phi) + ptcorr); // pT correlation + } + } + } + } + PROCESS_SWITCH(doublephimeson, processMixedEvent, + "Process EventMixing for combinatorial background", false); }; WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) { return WorkflowSpec{adaptAnalysisTask(cfgc)}; } diff --git a/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx b/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx index 00916c9c709..19f5d11e333 100644 --- a/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx +++ b/PWGLF/Tasks/Strangeness/lambdaspincorrderived.cxx @@ -531,7 +531,6 @@ struct lambdaspincorrderived { const ROOT::Math::PtEtaPhiMVector& daughpart1, const ROOT::Math::PtEtaPhiMVector& daughpart2, int datatype, float mixpairweight, int mixedLeg) { - auto lambda1Mass = 0.0; auto lambda2Mass = 0.0; if (!usePDGM) { @@ -622,35 +621,29 @@ struct lambdaspincorrderived { double epsWeightMixedLeg = 1.0; if (useweight && datatype == 1) { // only for ME if (mixedLeg == 1) { - // Only leg 1 is from the mixing pool double w1 = 1.0; - if (tag1 == 0) { // Λ - if (hweight1) { - w1 = hweight1->GetBinContent(hweight1->FindBin(dphi1, deta1, pt1)); - } - } else { // Λbar - if (hweight4) { - w1 = hweight4->GetBinContent(hweight4->FindBin(dphi1, deta1, pt1)); - } - } - if (w1 > 0.0 && std::isfinite(w1)) { - epsWeightMixedLeg = w1; + if (tag1 == 0 && tag2 == 0) { + w1 = hweight1->GetBinContent(hweight1->FindBin(dphi1, deta1, pt1)); + } else if (tag1 == 0 && tag2 == 1) { + w1 = hweight2->GetBinContent(hweight2->FindBin(dphi1, deta1, pt1)); + } else if (tag1 == 1 && tag2 == 0) { + w1 = hweight3->GetBinContent(hweight3->FindBin(dphi1, deta1, pt1)); + } else if (tag1 == 1 && tag2 == 1) { + w1 = hweight4->GetBinContent(hweight4->FindBin(dphi1, deta1, pt1)); } + epsWeightMixedLeg = w1; } else if (mixedLeg == 2) { - // Only leg 2 is from the mixing pool double w2 = 1.0; - if (tag2 == 0) { // Λ - if (hweight12) { - w2 = hweight12->GetBinContent(hweight12->FindBin(dphi2, deta2, pt2)); - } - } else { // Λbar - if (hweight42) { - w2 = hweight42->GetBinContent(hweight42->FindBin(dphi2, deta2, pt2)); - } - } - if (w2 > 0.0 && std::isfinite(w2)) { - epsWeightMixedLeg = w2; + if (tag1 == 0 && tag2 == 0) { + w2 = hweight12->GetBinContent(hweight12->FindBin(dphi2, deta1, pt2)); + } else if (tag1 == 0 && tag2 == 1) { + w2 = hweight22->GetBinContent(hweight22->FindBin(dphi2, deta1, pt2)); + } else if (tag1 == 1 && tag2 == 0) { + w2 = hweight32->GetBinContent(hweight32->FindBin(dphi2, deta1, pt2)); + } else if (tag1 == 1 && tag2 == 1) { + w2 = hweight42->GetBinContent(hweight42->FindBin(dphi2, deta2, pt2)); } + epsWeightMixedLeg = w2; } } @@ -697,19 +690,19 @@ struct lambdaspincorrderived { } else if (datatype == 1) { double weight = mixpairweight; if (useweight) { - if (usebothweight) { - weight = mixpairweight / (epsWeightMixedLeg); - } else { - weight = mixpairweight / (epsWeightMixedLeg); - } + weight = mixpairweight / (epsWeightMixedLeg); } if (weight <= 0.0) { weight = 1.0; } + // LOGF(info, Form("Getting alignment offsets from the CCDB...%2.2f",epsWeightMixedLeg)); histos.fill(HIST("hPtYMix"), particle1.Pt(), particle1.Rapidity(), weight); if (tag1 == 0 && tag2 == 0) { - histos.fill(HIST("ME_LL"), dphi1, deta1, pt1, mixpairweight); - histos.fill(HIST("ME_LL2"), dphi2, deta2, pt2, mixpairweight); + if (mixedLeg == 1) { + histos.fill(HIST("ME_LL"), dphi1, deta1, pt1, weight); + } else if (mixedLeg == 2) { + histos.fill(HIST("ME_LL2"), dphi2, deta2, pt2, weight); + } histos.fill(HIST("hSparseLambdaLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaR, weight); if (useAdditionalHisto) { histos.fill(HIST("hSparseRapLambdaLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaRap, weight); @@ -717,8 +710,11 @@ struct lambdaspincorrderived { histos.fill(HIST("hSparsePairMassLambdaLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, pairDummy.M(), weight); } } else if (tag1 == 0 && tag2 == 1) { - histos.fill(HIST("ME_LAL"), dphi1, deta1, pt1, mixpairweight); - histos.fill(HIST("ME_LAL2"), dphi2, deta2, pt2, mixpairweight); + if (mixedLeg == 1) { + histos.fill(HIST("ME_LAL"), dphi1, deta1, pt1, weight); + } else if (mixedLeg == 2) { + histos.fill(HIST("ME_LAL2"), dphi2, deta2, pt2, weight); + } histos.fill(HIST("hSparseLambdaAntiLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaR, weight); if (useAdditionalHisto) { histos.fill(HIST("hSparseRapLambdaAntiLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaRap, weight); @@ -726,8 +722,11 @@ struct lambdaspincorrderived { histos.fill(HIST("hSparsePairMassLambdaAntiLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, pairDummy.M(), weight); } } else if (tag1 == 1 && tag2 == 0) { - histos.fill(HIST("ME_ALL"), dphi1, deta1, pt1, mixpairweight); - histos.fill(HIST("ME_ALL2"), dphi2, deta2, pt2, mixpairweight); + if (mixedLeg == 1) { + histos.fill(HIST("ME_ALL"), dphi1, deta1, pt1, weight); + } else if (mixedLeg == 2) { + histos.fill(HIST("ME_ALL2"), dphi2, deta2, pt2, weight); + } histos.fill(HIST("hSparseAntiLambdaLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaR, weight); if (useAdditionalHisto) { histos.fill(HIST("hSparseRapAntiLambdaLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaRap, weight); @@ -735,8 +734,11 @@ struct lambdaspincorrderived { histos.fill(HIST("hSparsePairMassAntiLambdaLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, pairDummy.M(), weight); } } else if (tag1 == 1 && tag2 == 1) { - histos.fill(HIST("ME_ALAL"), dphi1, deta1, pt1, mixpairweight); - histos.fill(HIST("ME_ALAL2"), dphi2, deta2, pt2, mixpairweight); + if (mixedLeg == 1) { + histos.fill(HIST("ME_ALAL"), dphi1, deta1, pt1, weight); + } else if (mixedLeg == 2) { + histos.fill(HIST("ME_ALAL2"), dphi2, deta2, pt2, weight); + } histos.fill(HIST("hSparseAntiLambdaAntiLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaR, weight); if (useAdditionalHisto) { histos.fill(HIST("hSparseRapAntiLambdaAntiLambdaMixed"), particle1.M(), particle2.M(), cosThetaDiff, deltaRap, weight); @@ -1064,17 +1066,17 @@ struct lambdaspincorrderived { } PROCESS_SWITCH(lambdaspincorrderived, processMEV3, "Process data ME (first-leg, pair-3D maps)", false); + // ---------------------- minimal helpers you already use ---------------------- static constexpr int N_STATUS = 2; // v0Status ∈ {0,1} struct MixBinner { - // constructed from the task's configurables; φ is assumed already constrained into [0, 2π) float ptMin, ptMax, ptStep; float etaMin, etaMax, etaStep; float phiMin, phiMax, phiStep; - // Mass binning: [1.09, 1.14) with 50 bins (1e-3 GeV/c^2) + // if you want 1 mass bin (effectively “on/off”), keep nM_=1 static constexpr float mMin = 1.09f; - static constexpr float mMax = 1.14f; // exclusive + static constexpr float mMax = 1.14f; static constexpr int nM_ = 1; static constexpr float mStep = (mMax - mMin) / nM_; @@ -1088,7 +1090,6 @@ struct lambdaspincorrderived { ptStep = (ptStep > 0.f ? ptStep : 0.1f); etaStep = (etaStep > 0.f ? etaStep : 0.1f); phiStep = (phiStep > 0.f ? phiStep : 0.1f); - nPt_ = std::max(1, static_cast(std::floor((ptMax - ptMin) / ptStep + 0.5f))); nEta_ = std::max(1, static_cast(std::floor((etaMax - etaMin) / etaStep + 0.5f))); nPhi_ = std::max(1, static_cast(std::ceil((phiMax - phiMin) / phiStep))); @@ -1108,19 +1109,18 @@ struct lambdaspincorrderived { if (b < 0) return -1; if (b >= nBins) - b = nBins - 1; // clamp exact-top edge + b = nBins - 1; return b; } - inline int ptBin(float pt) const { return binFromValue(pt, ptMin, ptStep, nPt_); } - inline int etaBin(float eta) const { return binFromValue(eta, etaMin, etaStep, nEta_); } - inline int phiBin(float phi) const { return binFromValue(phi, phiMin, phiStep, nPhi_); } // φ already constrained upstream + inline int etaBin(float e) const { return binFromValue(e, etaMin, etaStep, nEta_); } + inline int phiBin(float ph) const { return binFromValue(ph, phiMin, phiStep, nPhi_); } inline int massBin(float m) const { return binFromValue(m, mMin, mStep, nM_); } }; struct BufferCand { - int64_t collisionIdx; // from col.index() - int64_t rowIndex; // global row id in V0s + int64_t collisionIdx; + int64_t rowIndex; uint8_t v0Status; uint16_t ptBin, etaBin, phiBin, mBin; }; @@ -1130,16 +1130,16 @@ struct lambdaspincorrderived { int64_t rowIndex; }; - // 6D key: (colBin, status, pt, eta, phi, mass) static inline size_t linearKey(int colBin, int statBin, int ptBin, int etaBin, int phiBin, int mBin, int nStatus, int nPt, int nEta, int nPhi, int nM) { return ((((((static_cast(colBin) * nStatus + statBin) * nPt + ptBin) * nEta + etaBin) * nPhi + phiBin) * nM + mBin)); } + + // ================================ processMEV4 ================================ void processMEV4(EventCandidates const& collisions, AllTrackCandidates const& V0s) { - // Build binner from your existing configurables MixBinner mb{ ptMin.value, ptMax.value, ptMix.value, // pT range & step v0eta.value, etaMix.value, // |eta| max & step @@ -1156,9 +1156,7 @@ struct lambdaspincorrderived { const size_t nKeys = static_cast(nCol) * nStat * nPt * nEta * nPhi * nM; std::vector> buffer(nKeys); - // ========================= - // PASS 1: fill 6D buffer - // ========================= + // ---------------- PASS 1: fill the 6D buffer ---------------- for (auto const& col : collisions) { const int colBin = colBinning.getBin(std::make_tuple(col.posz(), col.cent())); auto slice = V0s.sliceBy(tracksPerCollisionV0, col.index()); @@ -1178,9 +1176,7 @@ struct lambdaspincorrderived { if (ptB < 0 || etaB < 0 || phiB < 0 || mB < 0) continue; - const size_t key = linearKey(colBin, status, ptB, etaB, phiB, mB, - nStat, nPt, nEta, nPhi, nM); - + const size_t key = linearKey(colBin, status, ptB, etaB, phiB, mB, nStat, nPt, nEta, nPhi, nM); buffer[key].push_back(BufferCand{ .collisionIdx = static_cast(col.index()), .rowIndex = static_cast(t.globalIndex()), @@ -1192,7 +1188,7 @@ struct lambdaspincorrderived { } } - // Helper: get matches for a given candidate (for mixing one leg) + // small helper (kept local) to fetch matches for a given candidate from the same 6D bin auto makeMatchesFor = [&](auto const& cand, int colBinLocal, int64_t curColIdx) -> std::vector { @@ -1209,8 +1205,7 @@ struct lambdaspincorrderived { if (ptB < 0 || etaB < 0 || phiB < 0 || mB < 0) return matches; - const size_t key = linearKey(colBinLocal, status, ptB, etaB, phiB, mB, - nStat, nPt, nEta, nPhi, nM); + const size_t key = linearKey(colBinLocal, status, ptB, etaB, phiB, mB, nStat, nPt, nEta, nPhi, nM); auto const& binVec = buffer[key]; if (binVec.empty()) return matches; @@ -1218,24 +1213,13 @@ struct lambdaspincorrderived { matches.reserve(binVec.size()); for (const auto& bc : binVec) { if (bc.collisionIdx == curColIdx) - continue; // ensure different event + continue; // different event matches.push_back(MatchRef{bc.collisionIdx, bc.rowIndex}); } - - // Optionally cap number of matches by nEvtMixing - const int cap = nEvtMixing.value; - const int n = static_cast(matches.size()); - if (cap > 0 && n > cap) { - std::shuffle(matches.begin(), matches.end(), rng); - matches.resize(cap); - } - return matches; }; - // ========================= - // PASS 2: mixing over SE pairs (mix both legs) - // ========================= + // ---------------- PASS 2: loop over SE pairs and mix both legs ---------------- for (auto const& collision1 : collisions) { const int colBin = colBinning.getBin(std::make_tuple(collision1.posz(), collision1.cent())); auto poolA = V0s.sliceBy(tracksPerCollisionV0, collision1.index()); @@ -1259,20 +1243,38 @@ struct lambdaspincorrderived { if (t1.pionIndex() == t2.protonIndex()) continue; - // matches for replacing leg 1 and leg 2 - auto matches1 = makeMatchesFor(t1, colBin, curColId); // t1 -> tX - auto matches2 = makeMatchesFor(t2, colBin, curColId); // t2 -> tY + // gather matches for both legs from the same 6D bin + auto matches1 = makeMatchesFor(t1, colBin, curColId); // candidates to replace leg-1 + auto matches2 = makeMatchesFor(t2, colBin, curColId); // candidates to replace leg-2 - const int n1 = static_cast(matches1.size()); - const int n2 = static_cast(matches2.size()); - if (n1 == 0 && n2 == 0) + const int n1Eff = static_cast(matches1.size()); + const int n2Eff = static_cast(matches2.size()); + if (n1Eff == 0 && n2Eff == 0) continue; - // Split "unit" weight between the two modes - const float w1 = (n1 > 0 ? 0.5f / static_cast(n1) : 0.f); - const float w2 = (n2 > 0 ? 0.5f / static_cast(n2) : 0.f); + // per-leg depth (configurable) + int depth = nEvtMixing.value; + if (depth <= 0) + depth = 1; + + const int n1Take = std::min(depth, n1Eff); + const int n2Take = std::min(depth, n2Eff); - // --- Type A: replace leg 1 → (tX, t2) --- + // split unit weight between legs if both have mixes; else single leg gets full + const float w1 = (n1Take > 0 ? ((n2Take > 0 ? 0.5f : 1.0f) / static_cast(n1Take)) : 0.0f); + const float w2 = (n2Take > 0 ? ((n1Take > 0 ? 0.5f : 1.0f) / static_cast(n2Take)) : 0.0f); + + // randomize & truncate per-leg matches to requested depth + if (n1Take > 0) { + std::shuffle(matches1.begin(), matches1.end(), rng); + matches1.resize(n1Take); + } + if (n2Take > 0) { + std::shuffle(matches2.begin(), matches2.end(), rng); + matches2.resize(n2Take); + } + + // --- Type A: replace leg 1 (t1 -> tX), keep t2 for (const auto& m : matches1) { auto tX = V0s.iteratorAt(m.rowIndex); if (!selectionV0(tX)) @@ -1289,10 +1291,13 @@ struct lambdaspincorrderived { const float dPhi = std::fabs(RecoDecay::constrainAngle(lambda1.Phi() - lambda2.Phi(), 0.0F, harmonicDphi)); histos.fill(HIST("deltaPhiMix"), dPhi, w1); - fillHistograms2(tX.v0Status(), t2.v0Status(), lambda1, lambda2, proton1, proton2, 1, w1, 1); + // datatype=1 (ME), mixedLeg=1 + fillHistograms2(tX.v0Status(), t2.v0Status(), + lambda1, lambda2, proton1, proton2, + /*datatype=*/1, /*weight=*/w1, /*mixedLeg=*/1); } - // --- Type B: replace leg 2 → (t1, tY) --- + // --- Type B: replace leg 2 (t2 -> tY), keep t1 for (const auto& m : matches2) { auto tY = V0s.iteratorAt(m.rowIndex); if (!selectionV0(tY)) @@ -1309,13 +1314,15 @@ struct lambdaspincorrderived { const float dPhi = std::fabs(RecoDecay::constrainAngle(lambda1.Phi() - lambda2.Phi(), 0.0F, harmonicDphi)); histos.fill(HIST("deltaPhiMix"), dPhi, w2); - fillHistograms2(t1.v0Status(), tY.v0Status(), lambda1, lambda2, proton1, proton2, 1, w2, 2); + // datatype=1 (ME), mixedLeg=2 + fillHistograms2(t1.v0Status(), tY.v0Status(), + lambda1, lambda2, proton1, proton2, + /*datatype=*/1, /*weight=*/w2, /*mixedLeg=*/2); } } } } - - PROCESS_SWITCH(lambdaspincorrderived, processMEV4, "Process data ME (5d buffer)", false); + PROCESS_SWITCH(lambdaspincorrderived, processMEV4, "Process data ME (6D buffer, both legs mixed, depth-capped)", false); }; WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) {