diff --git a/PWGCF/TwoParticleCorrelations/Tasks/lambdaProtonBalanceFunction.cxx b/PWGCF/TwoParticleCorrelations/Tasks/lambdaProtonBalanceFunction.cxx index 7dc7595998b..4c1c3be98df 100644 --- a/PWGCF/TwoParticleCorrelations/Tasks/lambdaProtonBalanceFunction.cxx +++ b/PWGCF/TwoParticleCorrelations/Tasks/lambdaProtonBalanceFunction.cxx @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -1297,14 +1298,6 @@ struct LambdaProtonBalanceFunction { void init(InitContext const&) { - if (doprocessMCComposition.value) { - if (doprocessData.value || doprocessMCRecBF.value || doprocessMCGenBF.value || doprocessMCEfficiency.value) { - LOGF(fatal, "processMCComposition requires processData, processMCRecBF, processMCGenBF and processMCEfficiency=false. processData defaults to true and must be disabled explicitly."); - } - if (cApplyEffCorr.value) { - LOGF(fatal, "processMCComposition requires cApplyEffCorr=false: composition stores unweighted counts and must not load efficiency maps."); - } - } if (static_cast(doprocessData.value) + static_cast(doprocessMCRecBF.value) + static_cast(doprocessMCGenBF.value) > 1) { @@ -1366,9 +1359,6 @@ struct LambdaProtonBalanceFunction { registryEff.add(name, name, {HistType::kTH3F, {axCent, axPt, axY}}); } } - if (doprocessMCComposition.value) { - bookMCComposition(); - } registryOther.add("hEffWeightProton", "Efficiency weight 1/#epsilon, protons", {HistType::kTH1F, {{200, 0.0f, 20.0f}}}); registryOther.add("hEffWeightLambda", "Efficiency weight 1/#epsilon, #Lambda", {HistType::kTH1F, {{200, 0.0f, 20.0f}}}); registryOther.add("hEffWeightZeroCount", "Particles with efficiency weight 0 (1=p, 2=#bar{p}, 3=#Lambda, 4=#bar{#Lambda})", {HistType::kTH1F, {{4, 0.5f, 4.5f}}}); @@ -1376,6 +1366,18 @@ struct LambdaProtonBalanceFunction { registryOther.add("hNSelectedPrimAntiProtons", "Number of selected primary antiprotons per event", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); registryOther.add("hNSelLambda", "Number of selected Lambda candidates per event", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); registryOther.add("hNSelAntiLambda", "Number of selected AntiLambda candidates per event", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_Lambda_reco1", "Stage 1: selected reconstructed Lambda per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_AntiLambda_reco1", "Stage 1: selected reconstructed AntiLambda per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_Proton_reco1", "Stage 1: selected reconstructed Proton per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_AntiProton_reco1", "Stage 1: selected reconstructed AntiProton per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_Lambda_PrimSec_reco", "Stage 2: primary and secondary truth-matched Lambda per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_AntiLambda_PrimSec_reco", "Stage 2: primary and secondary truth-matched AntiLambda per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_Proton_PrimSec_reco", "Stage 2: primary and secondary truth-matched Proton per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_AntiProton_PrimSec_reco", "Stage 2: primary and secondary truth-matched AntiProton per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_Lambda_prim_reco", "Stage 3: physical-primary truth-matched Lambda per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_AntiLambda_prim_reco", "Stage 3: physical-primary truth-matched AntiLambda per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_Proton_prim_reco", "Stage 3: physical-primary truth-matched Proton per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); + registryOther.add("hN_AntiProton_prim_reco", "Stage 3: physical-primary truth-matched AntiProton per event (unweighted);N;Events", {HistType::kTH1F, {{100, 0.0f, 100.0f}}}); // ── V0-daughter veto QA counters ───────────────────────────────────── // Proton veto counters registryOther.add("hNPrimProtonBeforeVeto", "Proton candidates before V0-daughter veto;N;Events", {HistType::kTH1F, {{200, 0.0f, 200.0f}}}); @@ -2205,10 +2207,9 @@ struct LambdaProtonBalanceFunction { for (int i = 0; i < 4; ++i) { hDaughters->GetXaxis()->SetBinLabel(i + 1, daughterLabels[i]); } - const std::string mode = doprocessData.value ? "processData" : doprocessMCRecBF.value ? "processMCRecBF" - : doprocessMCGenBF.value ? "processMCGenBF" - : doprocessMCComposition.value ? "processMCComposition" - : "none"; + const std::string mode = doprocessData.value ? "processData" : doprocessMCRecBF.value ? "processMCRecBF" + : doprocessMCGenBF.value ? "processMCGenBF" + : "none"; std::vector configLabels = { "activeBFMode=" + mode, "cUseFT0C=" + std::to_string(static_cast(cUseFT0C.value)), @@ -3185,19 +3186,141 @@ struct LambdaProtonBalanceFunction { // ───────────────────────────────────────────────────────────────────── } + void fillStage23Counts(int64_t /*mcCollisionId*/, + std::vector const& /*selectedLambdas*/, + std::vector const& /*selectedAntiLambdas*/, + std::vector const& /*selectedPrimProtons*/, + std::vector const& /*selectedPrimAntiProtons*/) + { + } + + void fillStage23Counts(int64_t mcCollisionId, + std::vector const& selectedLambdas, + std::vector const& selectedAntiLambdas, + std::vector const& selectedPrimProtons, + std::vector const& selectedPrimAntiProtons) + { + // Stage 2 and Stage 3 counting; the selected reconstructed containers remain unchanged. + int nLambdaPrimSec = 0; + int nAntiLambdaPrimSec = 0; + int nProtonPrimSec = 0; + int nAntiProtonPrimSec = 0; + int nLambdaPrim = 0; + int nAntiLambdaPrim = 0; + int nProtonPrim = 0; + int nAntiProtonPrim = 0; + + for (const auto& candidate : selectedLambdas) { + if (!candidate.has_mcParticle()) { + continue; + } + const auto* truth = static_cast(candidate).getCurrent(); + const int64_t id = candidate.mcParticleId(); + if (!usableMCIndex(truth, id)) { + continue; + } + const auto mc = truth->rawIteratorAt(id - truth->offset()); + if (mc.mcCollisionId() != mcCollisionId) { + continue; + } + if (mc.pdgCode() != PDG_t::kLambda0) { + continue; + } + ++nLambdaPrimSec; + if (mc.isPhysicalPrimary()) { + ++nLambdaPrim; + } + } + + for (const auto& candidate : selectedAntiLambdas) { + if (!candidate.has_mcParticle()) { + continue; + } + const auto* truth = static_cast(candidate).getCurrent(); + const int64_t id = candidate.mcParticleId(); + if (!usableMCIndex(truth, id)) { + continue; + } + const auto mc = truth->rawIteratorAt(id - truth->offset()); + if (mc.mcCollisionId() != mcCollisionId) { + continue; + } + if (mc.pdgCode() != PDG_t::kLambda0Bar) { + continue; + } + ++nAntiLambdaPrimSec; + if (mc.isPhysicalPrimary()) { + ++nAntiLambdaPrim; + } + } + + for (const auto& candidate : selectedPrimProtons) { + if (!candidate.has_mcParticle()) { + continue; + } + const auto* truth = static_cast(candidate).getCurrent(); + const int64_t id = candidate.mcParticleId(); + if (!usableMCIndex(truth, id)) { + continue; + } + const auto mc = truth->rawIteratorAt(id - truth->offset()); + if (mc.mcCollisionId() != mcCollisionId) { + continue; + } + if (mc.pdgCode() != PDG_t::kProton) { + continue; + } + ++nProtonPrimSec; + if (mc.isPhysicalPrimary()) { + ++nProtonPrim; + } + } + + for (const auto& candidate : selectedPrimAntiProtons) { + if (!candidate.has_mcParticle()) { + continue; + } + const auto* truth = static_cast(candidate).getCurrent(); + const int64_t id = candidate.mcParticleId(); + if (!usableMCIndex(truth, id)) { + continue; + } + const auto mc = truth->rawIteratorAt(id - truth->offset()); + if (mc.mcCollisionId() != mcCollisionId) { + continue; + } + if (mc.pdgCode() != PDG_t::kProtonBar) { + continue; + } + ++nAntiProtonPrimSec; + if (mc.isPhysicalPrimary()) { + ++nAntiProtonPrim; + } + } + + registryOther.fill(HIST("hN_Lambda_PrimSec_reco"), static_cast(nLambdaPrimSec)); + registryOther.fill(HIST("hN_AntiLambda_PrimSec_reco"), static_cast(nAntiLambdaPrimSec)); + registryOther.fill(HIST("hN_Proton_PrimSec_reco"), static_cast(nProtonPrimSec)); + registryOther.fill(HIST("hN_AntiProton_PrimSec_reco"), static_cast(nAntiProtonPrimSec)); + registryOther.fill(HIST("hN_Lambda_prim_reco"), static_cast(nLambdaPrim)); + registryOther.fill(HIST("hN_AntiLambda_prim_reco"), static_cast(nAntiLambdaPrim)); + registryOther.fill(HIST("hN_Proton_prim_reco"), static_cast(nProtonPrim)); + registryOther.fill(HIST("hN_AntiProton_prim_reco"), static_cast(nAntiProtonPrim)); + } + // Reconstructed candidates are compared with generated primary particles; // residual background and feed-down can prevent full closure. - template - void runRecoAnalysis(TCollision const& collision, aod::V0Datas const& V0s, - MyTracks const& tracks, float cent, CentRho2Set* cs) + template + void runRecoAnalysis(TCollision const& collision, TV0s const& V0s, + TTracks const& tracks, float cent, CentRho2Set* cs, [[maybe_unused]] int64_t mcCollisionId = -1) { // ====== Step 1: Initialise containers ====================================== std::unordered_set v0DaughterTrackIds; v0DaughterTrackIds.reserve(64); - std::vector selectedPrimProtons; - std::vector selectedPrimAntiProtons; - std::vector selectedLambdas; - std::vector selectedAntiLambdas; + std::vector selectedPrimProtons; + std::vector selectedPrimAntiProtons; + std::vector selectedLambdas; + std::vector selectedAntiLambdas; int nSelLambda = 0; int nSelAntiLambda = 0; @@ -3206,7 +3329,7 @@ struct LambdaProtonBalanceFunction { int nPairs_AntiLambda_PrimProton = 0; int nPairs_AntiLambda_PrimAntiProton = 0; - selectRecoV0s(collision, V0s, selectedLambdas, selectedAntiLambdas, v0DaughterTrackIds); + selectRecoV0s(collision, V0s, selectedLambdas, selectedAntiLambdas, v0DaughterTrackIds); nSelLambda = static_cast(selectedLambdas.size()); nSelAntiLambda = static_cast(selectedAntiLambdas.size()); constexpr float mLambdaPDG = o2::constants::physics::MassLambda; @@ -3217,8 +3340,8 @@ struct LambdaProtonBalanceFunction { { const float ctauFinal = v0.distovertotmom(collision.posX(), collision.posY(), collision.posZ()) * mLambdaPDG; const float bgctauFinal = ctauFinal * v0.p() / mLambdaPDG; - const auto& dauPos = v0.posTrack_as(); - const auto& dauNeg = v0.negTrack_as(); + const auto& dauPos = v0.template posTrack_as(); + const auto& dauNeg = v0.template negTrack_as(); fillLambdaQA(LambdaQAStage::Final, v0, ctauFinal, bgctauFinal); fillDaughterQA(DaughterQAStage::Final, dauPos); fillDaughterQA(DaughterQAStage::Final, dauNeg); @@ -3240,8 +3363,8 @@ struct LambdaProtonBalanceFunction { { const float ctauFinal = v0.distovertotmom(collision.posX(), collision.posY(), collision.posZ()) * mLambdaPDG; const float bgctauFinal = ctauFinal * v0.p() / mLambdaPDG; - const auto& dauPos = v0.posTrack_as(); - const auto& dauNeg = v0.negTrack_as(); + const auto& dauPos = v0.template posTrack_as(); + const auto& dauNeg = v0.template negTrack_as(); fillLambdaQA(LambdaQAStage::Final, v0, ctauFinal, bgctauFinal); fillDaughterQA(DaughterQAStage::Final, dauNeg); fillDaughterQA(DaughterQAStage::Final, dauPos); @@ -3442,7 +3565,7 @@ struct LambdaProtonBalanceFunction { // now fully populated. Veto set was built from validated daughters only. for (const auto& v0 : selectedLambdas) { - const auto& v0PosDau = v0.posTrack_as(); + const auto& v0PosDau = v0.template posTrack_as(); const float pt = v0.pt(); int bin = -1; for (size_t i = 0; i < state.ptEdges.size() - 1; ++i) { @@ -3697,7 +3820,7 @@ struct LambdaProtonBalanceFunction { } for (const auto& v0 : selectedAntiLambdas) { - const auto& v0NegDau = v0.negTrack_as(); + const auto& v0NegDau = v0.template negTrack_as(); const float pt = v0.pt(); int bin = -1; for (size_t i = 0; i < state.ptEdges.size() - 1; ++i) { @@ -4159,6 +4282,13 @@ struct LambdaProtonBalanceFunction { registryOther.fill(HIST("hNPairs_Lambda_PrimAntiProton"), static_cast(nPairs_Lambda_PrimAntiProton)); registryOther.fill(HIST("hNPairs_AntiLambda_PrimProton"), static_cast(nPairs_AntiLambda_PrimProton)); registryOther.fill(HIST("hNPairs_AntiLambda_PrimAntiProton"), static_cast(nPairs_AntiLambda_PrimAntiProton)); + + // Stage 1 counting. + registryOther.fill(HIST("hN_Lambda_reco1"), static_cast(selectedLambdas.size())); + registryOther.fill(HIST("hN_AntiLambda_reco1"), static_cast(selectedAntiLambdas.size())); + registryOther.fill(HIST("hN_Proton_reco1"), static_cast(selectedPrimProtons.size())); + registryOther.fill(HIST("hN_AntiProton_reco1"), static_cast(selectedPrimAntiProtons.size())); + fillStage23Counts(mcCollisionId, selectedLambdas, selectedAntiLambdas, selectedPrimProtons, selectedPrimAntiProtons); } void processData(soa::Filtered>::iterator const& collision, aod::V0Datas const& V0s, @@ -4544,15 +4674,15 @@ struct LambdaProtonBalanceFunction { // ══ Reconstructed BF in matched selected MC events ══ void processMCRecBF(aod::McCollisions::iterator const& mcCollision, soa::SmallGroups const& collisions, - aod::V0Datas const& allV0s, - MyTracks const& allTracks) + MyV0sMC const& allV0s, + MyTracksMC const& allTracks, + aod::McParticles const& /*mcParticles*/) { - // Unlabelled candidate tables preserve the complete data-like reco sample. - withMatchedBFEvent(mcCollision, collisions, allTracks, - [&](auto const& collision, MyTracks const& tracks, float cent, CentRho2Set* cs) { - auto V0s = allV0s.sliceBy(v0sPerCollisionData, collision.globalIndex()); - runRecoAnalysis(collision, V0s, tracks, cent, cs); - }); + withMatchedEvent(mcCollision, collisions, allTracks, tracksPerCollision, + [&](auto const& collision, MyTracksMC const& tracks, float cent, CentRho2Set* cs) { + auto V0s = allV0s.sliceBy(v0sPerCollision, collision.globalIndex()); + runRecoAnalysis(collision, V0s, tracks, cent, cs, mcCollision.globalIndex()); + }); } PROCESS_SWITCH(LambdaProtonBalanceFunction, processMCRecBF, "Reconstructed correlations in matched selected MC events", false); @@ -4677,83 +4807,6 @@ struct LambdaProtonBalanceFunction { PROCESS_SWITCH(LambdaProtonBalanceFunction, processMCGenBF, "Generated correlations in matched selected MC events", false); - // Book only for the exclusive composition mode; reuse Other_Hists. - void bookMCComposition() - { - // Identical axes to the efficiency maps: reconstructed centrality, pT, rapidity. - // Equal binning does not imply equal selections or establish valid closure. - const AxisSpec axCent(cCentBins, "reconstructed centrality (%)"); - const AxisSpec axPt{60, 0.0, 6.0, "reconstructed p_{T} (GeV/c)"}; - const AxisSpec axY{cRhoYBins.value, cRhoYMin.value, cRhoYMax.value, "reconstructed y"}; - // First/last bins are reserved for PDG codes below -10000 / above +10000. - const AxisSpec axMother{20003, -10001.5, 10001.5, "signed direct-mother PDG (out-of-range codes mapped to endpoints)"}; - const std::array categories = {"AllCandidates", "PrimarySignal", "SecondarySignal", - "WrongSpeciesOrSign", "Unmatched", "OtherMCCollision"}; - for (const char* species : {"Lambda", "AntiLambda", "Proton", "AntiProton"}) { - const std::string base = std::string("MCComposition/") + species + "/"; - for (const char* category : categories) { - const std::string name = base + "h3_" + category; - registryOther.add(name.c_str(), name.c_str(), {HistType::kTH3F, {axCent, axPt, axY}}); - } - auto totals = registryOther.add((base + "hCategoryTotals").c_str(), - "Selected reconstructed candidates;Category;Candidates", - {HistType::kTH1F, {{6, 0.5, 6.5}}}); - for (int i = 0; i < 6; ++i) { - totals->GetXaxis()->SetBinLabel(i + 1, categories[i]); - } - for (const char* signalClass : {"PrimarySignal", "SecondarySignal"}) { - const std::string name = base + "hMotherPDG_" + signalClass; - registryOther.add(name.c_str(), "Direct-mother associations (not exclusive candidate sources)", - {HistType::kTHnSparseF, {axMother, axPt, axCent}}); - auto multiplicity = registryOther.add((base + "hMotherMultiplicity_" + signalClass).c_str(), - "Valid recorded direct mothers per signal candidate;Mothers;Candidates", - {HistType::kTH1F, {{3, -0.5, 2.5}}}); - multiplicity->GetXaxis()->SetBinLabel(1, "0"); - multiplicity->GetXaxis()->SetBinLabel(2, "1"); - multiplicity->GetXaxis()->SetBinLabel(3, "2 or more"); - } - } - for (const char* species : {"Proton", "AntiProton"}) { - const std::string name = std::string("MCComposition/") + species + "/hMcMask"; - registryOther.add(name.c_str(), "Selected track MC mask (diagnostic only);mcMask;Candidates", - {HistType::kTH1F, {{65536, -0.5, 65535.5}}}); - } - registryOther.add("MCComposition/hAcceptedEvents", "Accepted composition events, including zero candidates;Events;Counts", - {HistType::kTH1F, {{1, 0.0, 1.0}}}); - // Store raw counts only, for merging and later bin-by-bin fractions: - // fSecondaryCandidates = Secondary / All - // fSecondarySignal = Secondary / (Primary + Secondary) - // fPrimaryCandidates = Primary / All - // fSignal = (Primary + Secondary) / All - // These denominators are not interchangeable; no fractions are calculated here. - } - - template - void fillCompositionCategory(TPrefix prefix, int category, float cent, float pt, float y) - { - // Bins 2..6 match the five exclusive categories in hCategoryTotals. - registryOther.fill(prefix + HIST("hCategoryTotals"), static_cast(category + 1)); - switch (category) { - case 1: - registryOther.fill(prefix + HIST("h3_PrimarySignal"), cent, pt, y); - break; - case 2: - registryOther.fill(prefix + HIST("h3_SecondarySignal"), cent, pt, y); - break; - case 3: - registryOther.fill(prefix + HIST("h3_WrongSpeciesOrSign"), cent, pt, y); - break; - case 4: - registryOther.fill(prefix + HIST("h3_Unmatched"), cent, pt, y); - break; - case 5: - registryOther.fill(prefix + HIST("h3_OtherMCCollision"), cent, pt, y); - break; - default: - break; - } - } - static bool usableMCIndex(aod::McParticles const* table, int64_t id) { // has_mcParticle() tests only id >= 0 in ASoA; also check the bound table range. @@ -4761,136 +4814,11 @@ struct LambdaProtonBalanceFunction { id - static_cast(table->offset()) < table->size(); } - template - void fillCompositionMothers(TPrefix prefix, TMc const& mc, bool primary, float pt, float cent) - { - int nMothers = 0; - // MothersIds is the actual direct-mother array in AnalysisDataModel.h. - // mothers_as() dereferences every stored index without range checks. - // Read mothersIds() and its bound table instead, validating each index first. - const auto* mothers = static_cast(mc) - .getCurrentRaw() - .template get(); - if (mc.has_mothers() && mothers) { - for (const auto motherId : mc.mothersIds()) { - if (!usableMCIndex(mothers, motherId)) { - continue; - } - const auto mother = mothers->rawIteratorAt(motherId - static_cast(mothers->offset())); - const int pdg = mother.pdgCode(); - const int recordedPDG = pdg < -10000 ? -10001 : pdg > 10000 ? 10001 - : pdg; - if (primary) { - registryOther.fill(prefix + HIST("hMotherPDG_PrimarySignal"), recordedPDG, pt, cent); - } else { - registryOther.fill(prefix + HIST("hMotherPDG_SecondarySignal"), recordedPDG, pt, cent); - } - ++nMothers; - } - } - // Count mother associations, not exclusive candidate sources: totals can exceed - // particle counts. No recorded mother does not imply primary; having a mother - // does not imply secondary. Only isPhysicalPrimary() defines that classification. - if (primary) { - registryOther.fill(prefix + HIST("hMotherMultiplicity_PrimarySignal"), std::min(nMothers, 2)); - } else { - registryOther.fill(prefix + HIST("hMotherMultiplicity_SecondarySignal"), std::min(nMothers, 2)); - } - } - - template - void fillMCCompositionCandidate(TPrefix prefix, TCandidate const& candidate, - int expectedPDG, int64_t mcCollisionId, - float cent, float pt, float y) - { - // Count candidates as selected, without deduplicating by truth ID. Ordinary - // TH3 under/overflow is retained; no extra acceptance or rho-grid cut is applied. - registryOther.fill(prefix + HIST("h3_AllCandidates"), cent, pt, y); - registryOther.fill(prefix + HIST("hCategoryTotals"), 1.f); - if (!candidate.has_mcParticle()) { - fillCompositionCategory(prefix, 4, cent, pt, y); - return; - } - // The label column binds the full truth table, whereas the process argument - // can be sliced to the current MC collision. Use the binding so a valid label - // pointing to another collision is OtherMCCollision rather than Unmatched. - const auto* truth = static_cast(candidate).getCurrent(); - const int64_t id = candidate.mcParticleId(); - if (!usableMCIndex(truth, id)) { - fillCompositionCategory(prefix, 4, cent, pt, y); - return; - } - const auto mc = truth->rawIteratorAt(id - static_cast(truth->offset())); - if (mc.mcCollisionId() != mcCollisionId) { - fillCompositionCategory(prefix, 5, cent, pt, y); - return; - } - if (mc.pdgCode() != expectedPDG) { - fillCompositionCategory(prefix, 3, cent, pt, y); - return; - } - const bool primary = mc.isPhysicalPrimary(); - // SecondarySignal means correctly identified and non-primary, not material-produced. - fillCompositionCategory(prefix, primary ? 1 : 2, cent, pt, y); - fillCompositionMothers(prefix, mc, primary, pt, cent); - // One All entry and exactly one exclusive category entry per selected candidate: - // All = Primary + Secondary + WrongSpeciesOrSign + Unmatched + OtherMCCollision. - } - - void processMCComposition(aod::McCollisions::iterator const& mcCollision, - soa::SmallGroups const& collisions, - MyV0sMC const& allV0s, - MyTracksMC const& allTracks, - aod::McParticles const& /*mcParticles*/) - { - // The McParticles input supplies framework label bindings; no truth filtering - // enters either shared reconstructed selection. All fills have unit weight. - withMatchedEvent(mcCollision, collisions, allTracks, tracksPerCollision, - [&](auto const& collision, MyTracksMC const& tracks, float cent, CentRho2Set*) { - registryOther.fill(HIST("MCComposition/hAcceptedEvents"), 0.5f); - fillRunConfig(); - auto V0s = allV0s.sliceBy(v0sPerCollision, collision.globalIndex()); - std::unordered_set v0DaughterTrackIds; - v0DaughterTrackIds.reserve(64); - std::vector lambdas, antiLambdas; - std::vector protons, antiProtons; - selectRecoV0s(collision, V0s, lambdas, antiLambdas, v0DaughterTrackIds); - // Reuse the pre-cleaning daughter set, including rejected shared-daughter V0s. - selectRecoProtons(tracks, v0DaughterTrackIds, protons, antiProtons); - for (const auto& v0 : lambdas) { - fillMCCompositionCandidate(HIST("MCComposition/Lambda/"), - v0, 3122, mcCollision.globalIndex(), cent, v0.pt(), v0.rapidity(1)); - } - for (const auto& v0 : antiLambdas) { - fillMCCompositionCandidate(HIST("MCComposition/AntiLambda/"), - v0, -3122, mcCollision.globalIndex(), cent, v0.pt(), v0.rapidity(2)); - } - // McTrackLabels::mcMask() is uint16_t: set bits indicate mismatches; - // bits 0..6 ITS layers, 12 ITSAB tracklet, 13 ITS-TPC, 14 noise, 15 fake. - // Record the complete mask for every selected track, never use it as a cut. - for (const auto& trk : protons) { - registryOther.fill(HIST("MCComposition/Proton/hMcMask"), trk.mcMask()); - fillMCCompositionCandidate(HIST("MCComposition/Proton/"), - trk, 2212, mcCollision.globalIndex(), cent, trk.pt(), protonRapidity(trk)); - } - for (const auto& trk : antiProtons) { - registryOther.fill(HIST("MCComposition/AntiProton/hMcMask"), trk.mcMask()); - fillMCCompositionCandidate(HIST("MCComposition/AntiProton/"), - trk, -2212, mcCollision.globalIndex(), cent, trk.pt(), protonRapidity(trk)); - } - }); - } - PROCESS_SWITCH(LambdaProtonBalanceFunction, processMCComposition, - "Reconstructed MC candidate composition and mother origins", false); - // ── Run-level summary printed once at the end of the analysis job ───── // Called automatically by the O2 framework after all events are processed. // Prints totals accumulated in runTotalProtonBeforeVeto / runTotalProtonRemovedVeto. void endOfStream(EndOfStreamContext const&) { - if (doprocessMCComposition.value) { - return; // Composition persists raw counts only; no BF veto-fraction summary. - } const int64_t totalBefore = state.runTotalProtonBeforeVeto; const int64_t totalRemoved = state.runTotalProtonRemovedVeto; const int64_t totalAfter = totalBefore - totalRemoved;