From 6f89cf97012e4e9658179f0a51de15ea26a4b60f Mon Sep 17 00:00:00 2001 From: Yunfan Liu Date: Sun, 15 Nov 2026 15:21:24 +0100 Subject: [PATCH 1/4] [PWGHF] Add generated Dstar-proton pairing using existing tables --- .../producerCharmHadronsTrackFemtoDream.cxx | 112 +++++++++++-- .../Tasks/taskCharmHadronsTrackFemtoDream.cxx | 158 +++++++++++++++++- 2 files changed, 259 insertions(+), 11 deletions(-) diff --git a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx index 0b1102163c7..bbf3a46cd17 100644 --- a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx @@ -113,6 +113,11 @@ enum class D0CandFlag : uint8_t { struct HfProducerCharmHadronsTrackFemtoDream { + enum TrackCharge { + PositiveCharge = 1, + NegativeCharge = -1 + }; + struct : ProducesGroup { Produces outputCollision; Produces rowMasks; @@ -255,6 +260,8 @@ struct HfProducerCharmHadronsTrackFemtoDream { Filter filterSelectCandidateLc = (aod::hf_sel_candidate_lc::isSelLcToPKPi >= generalCfg.selectionFlagHadron || aod::hf_sel_candidate_lc::isSelLcToPiKP >= generalCfg.selectionFlagHadron); Filter filterSelectCandidateXic = aod::hf_sel_candidate_xic::isSelXicToXiPiPi >= generalCfg.selectionFlagHadron; + Preslice perMcCollision = aod::mcparticle::mcCollisionId; + HistogramRegistry qaRegistry{"QAHistos", {}, OutputObjHandlingPolicy::AnalysisObject}; HistogramRegistry trackRegistry{"Tracks", {}, OutputObjHandlingPolicy::AnalysisObject}; OutputObj zorroSummary{"zorroSummary"}; @@ -1095,7 +1102,7 @@ struct HfProducerCharmHadronsTrackFemtoDream { } template - void fillCharmHadMcGen(const ParticleType& particles) + void fillCharmHadMcGen(const ParticleType& particles, FemtoFullMcgenCollisions const* collisions = nullptr) { // Filling particle properties tables.rowCandCharmHadGen.reserve(particles.size() + 1); @@ -1127,13 +1134,99 @@ struct HfProducerCharmHadronsTrackFemtoDream { } } } else if constexpr (Channel == DecayChannel::DstarToD0Pi) { - for (const auto& particle : particles) { - if (std::abs(particle.flagMcMatchGen()) == hf_decay::hf_cand_dstar::DecayChannelMain::DstarToPiKPi) { - tables.rowCandCharmHadGen( - particle.mcCollisionId(), - particle.flagMcMatchGen(), - particle.originMcGen()); + constexpr int InvalidMcIndex = -1; + const int64_t timeStamp = -1; // No need for Gen process. + const float bdtScore = -1.f; // No need for Gen particles. + const float dcaXY = 999.f; // No need for Gen particles. + const float sphericity = 2.f; // Not computed. + const aod::femtodreamparticle::cutContainerType bitPos = 1u << 1; + const aod::femtodreamparticle::cutContainerType bitNeg = 1u << 0; + std::vector daughterIds; + std::vector const childIds = {InvalidMcIndex, InvalidMcIndex}; + for (const auto& col : *collisions) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::All); + if (col.posZ() < hfEvSel.zPvPosMin || col.posZ() > hfEvSel.zPvPosMax) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::RejEveSel); + continue; } + auto groupedMCParticles = particles.sliceBy(perMcCollision, col.globalIndex()); + // MC provides particle multiplicity, but no reconstructed centrality or magnetic field. + const float multiplicityPercentile = 0.f; + const float magneticField = 0.f; + tables.outputCollision(col.posZ(), multiplicityPercentile, col.multMCNParticlesEta08(), sphericity, magneticField); + tables.outputMcCollision(col.multMCNParticlesEta08()); + tables.outputCollsMcLabels(tables.outputMcCollision.lastIndex()); + + bool isTrackFilled = false; + bool isCharmFilled = false; + for (const auto& particle : groupedMCParticles) { + if (std::abs(particle.pdgCode()) != o2::constants::physics::Pdg::kDStar || + std::abs(particle.flagMcMatchGen()) != hf_decay::hf_cand_dstar::DecayChannelMain::DstarToPiKPi || + !particle.producedByGenerator()) { + continue; + } + daughterIds.clear(); + RecoDecay::getDaughters(particle, &daughterIds, std::array{kPiPlus, kKPlus}); + int prongPosId = InvalidMcIndex; + int prongNegId = InvalidMcIndex; + int softPionId = InvalidMcIndex; + for (const auto& daughterId : daughterIds) { + const auto daughter = particles.rawIteratorAt(daughterId); + if (std::abs(daughter.pdgCode()) != kPiPlus && std::abs(daughter.pdgCode()) != kKPlus) { + continue; + } + const auto mother = daughter.template mothers_first_as(); + if (std::abs(mother.pdgCode()) == o2::constants::physics::Pdg::kDStar) { + softPionId = daughter.globalIndex(); + } else if (daughter.pdgCode() > 0) { + prongPosId = daughter.globalIndex(); + } else { + prongNegId = daughter.globalIndex(); + } + } + const auto prongPos = particles.rawIteratorAt(prongPosId); + const auto prongNeg = particles.rawIteratorAt(prongNegId); + const auto softPion = particles.rawIteratorAt(softPionId); + // The existing pair-mass helper selects the pion/kaon hypothesis with candidateSelFlag == 1. + tables.rowCandCharmDstar(tables.outputCollision.lastIndex(), timeStamp, + particle.pdgCode() > 0 ? PositiveCharge : NegativeCharge, + prongPosId, prongNegId, softPionId, + prongPos.pt(), prongNeg.pt(), softPion.pt(), + prongPos.eta(), prongNeg.eta(), softPion.eta(), + prongPos.phi(), prongNeg.phi(), softPion.phi(), + particle.pdgCode() > 0, bdtScore, bdtScore, bdtScore); + const auto particleOrigin = particle.originMcGen() == RecoDecay::OriginType::Prompt + ? aod::femtodreamMCparticle::ParticleOriginMCTruth::kPrimary + : aod::femtodreamMCparticle::ParticleOriginMCTruth::kSecondaryFromHF; + tables.outputPartsMc(particleOrigin, particle.pdgCode(), particle.pt(), particle.eta(), particle.phi()); + tables.rowCandCharmHadGen(tables.outputPartsMc.lastIndex(), particle.flagMcMatchGen(), particle.originMcGen()); + tables.rowCandMcCharmHad(particle.flagMcMatchGen(), particle.originMcGen()); + tables.outputPartsMcLabels(tables.outputPartsMc.lastIndex()); + isCharmFilled = true; + } + for (const auto& particle : groupedMCParticles) { + if (std::abs(particle.pdgCode()) == std::abs(trackCfg.trkPDGCode.value) && particle.isPhysicalPrimary() && particle.producedByGenerator()) { + tables.outputPartsIndex(particle.globalIndex()); + tables.outputPartsTime(timeStamp); + // Store the PDG code instead of detector PID bits, as in the femto truth producer. + tables.outputParts(tables.outputCollision.lastIndex(), + particle.pt(), particle.eta(), particle.phi(), + aod::femtodreamparticle::ParticleType::kTrack, + particle.pdgCode() > 0 ? bitPos : bitNeg, + std::abs(particle.pdgCode()), dcaXY, childIds, 0.f, 0.f); + isTrackFilled = true; + } + } + if (isTrackFilled) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::TrackSelected); + } + if (isCharmFilled) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::CharmSelected); + } + if (isTrackFilled && isCharmFilled) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::PairSelected); + } + tables.rowMasks(isTrackFilled, isCharmFilled, false); } } } @@ -1257,10 +1350,9 @@ struct HfProducerCharmHadronsTrackFemtoDream { } PROCESS_SWITCH(HfProducerCharmHadronsTrackFemtoDream, processMcDstarToD0PiWithML, "Provide Mc for DstarToD0Pi with ml", false); - void processMcDstarToD0PiGen(GeneratedDstarMc const& particles) + void processMcDstarToD0PiGen(FemtoFullMcgenCollisions const& collisions, GeneratedDstarMc const& particles) { - - fillCharmHadMcGen(particles); + fillCharmHadMcGen(particles, &collisions); } PROCESS_SWITCH(HfProducerCharmHadronsTrackFemtoDream, processMcDstarToD0PiGen, "Provide Mc Generated DstarToD0Pi", false); diff --git a/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx index cfbd216ec22..ee27975fabd 100644 --- a/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx @@ -186,6 +186,9 @@ struct HfTaskCharmHadronsTrackFemtoDream { using FilteredCharmMcCandDstars = soa::Filtered>; using FilteredCharmMcCandDstar = FilteredCharmMcCandDstars::iterator; + using CharmGenCandDstars = soa::Join; + using FilteredFDGenParts = soa::Filtered>; + using FilteredCollisions = soa::Filtered>; using FilteredCollision = FilteredCollisions::iterator; @@ -225,6 +228,8 @@ struct HfTaskCharmHadronsTrackFemtoDream { Partition partitionMcTrk1Ka = (aod::femtodreamparticle::partType == static_cast(aod::femtodreamparticle::ParticleType::kTrack)) && (ncheckbit(aod::femtodreamparticle::cut, trackSel.cutBitTrack1)) && (aod::femtodreamparticle::pidcut == PidTrackPass); + Partition partitionGenTrk1 = (aod::femtodreamparticle::partType == static_cast(aod::femtodreamparticle::ParticleType::kTrack)) && (aod::femtodreamparticle::pidcut == static_cast(kProton)); + /// Partition for particle 2 Partition partitionCharmHadron3Prong = aod::fdhf::bdtBkg < charmSel.charmHadBkgBDTmax && aod::fdhf::bdtFD < charmSel.charmHadFdBDTmax && aod::fdhf::bdtFD > charmSel.charmHadFdBDTmin&& aod::fdhf::bdtPrompt charmSel.charmHadPromptBDTmin; Partition partitionCharmHadron3ProngXic = aod::fdhf::bdtBkg < charmSel.charmHadBkgBDTmax && aod::fdhf::bdtFD < charmSel.charmHadFdBDTmax && aod::fdhf::bdtFD > charmSel.charmHadFdBDTmin&& aod::fdhf::bdtPrompt charmSel.charmHadPromptBDTmin; @@ -300,7 +305,7 @@ struct HfTaskCharmHadronsTrackFemtoDream { void init(InitContext& /*context*/) { - std::array processes = {doprocessDataLcTrk, doprocessDataDplusTrk, doprocessDataD0Trk, doprocessDataDstarTrk, doprocessDataXicTrk, doprocessMcLcTrk, doprocessMcDplusTrk, doprocessMcD0Trk, doprocessMcDstarTrk, doprocessMcXicTrk}; + std::array processes = {doprocessDataLcTrk, doprocessDataDplusTrk, doprocessDataD0Trk, doprocessDataDstarTrk, doprocessDataXicTrk, doprocessMcLcTrk, doprocessMcDplusTrk, doprocessMcD0Trk, doprocessMcDstarTrk, doprocessMcXicTrk, doprocessMcDstarTrkGen}; if (std::accumulate(processes.begin(), processes.end(), 0) != 1) { LOGP(fatal, "One and only one process function must be enabled at a time."); } @@ -1308,6 +1313,157 @@ struct HfTaskCharmHadronsTrackFemtoDream { } PROCESS_SWITCH(HfTaskCharmHadronsTrackFemtoDream, processMcDstarTrk, "Enable processing DstarToD0Pi and Tracks correlation for Monte Carlo", false); + void processMcDstarTrkGen(FilteredMcColisions const& cols, + FilteredFDGenParts const&, + CharmGenCandDstars const& candidates, + aod::FDMCParticles const&) + { + constexpr int SameEvent = 1; + constexpr int MixedEvent = 2; + constexpr int InvalidMixingBin = -1; + const int64_t timeStamp = -1; // No timestamp is available in this Gen process. + const uint8_t nCls = 0; // Gen particles have no detector clusters. + const float nSigma = 999.f; // Gen particles have no detector PID response. + + auto fillPairs = [&](auto const& sliceGenTrk1, auto const& sliceGenCharmHad, auto const& col, auto& container) { + for (auto const& [p1, p2] : combinations(CombinationsFullIndexPolicy(sliceGenTrk1, sliceGenCharmHad))) { + const auto particle = p2.fdMCParticle(); + float kstar = FemtoDreamMath::getkstar(p1, massOne, particle, massTwo); + if (kstar > highkstarCut) { + continue; + } + if (particle.pt() < charmSel.charmHadMinPt || particle.pt() > charmSel.charmHadMaxPt) { + continue; + } + float invMass = getCharmHadronMass(p2); + float deltaInvMassPair = getCharmHadronTrackMass(p2, p1, trackSel.pdgCodeTrack1.value) - invMass; + float chargeTrack = 0.; + if ((p1.cut() & CutBitChargePositive) == CutBitChargePositive) { + chargeTrack = PositiveCharge; + } else { + chargeTrack = NegativeCharge; + } + int pairSign = PairNotDefined; + if (chargeTrack == p2.charge()) { + pairSign = LikeSignPair; + } else if (chargeTrack == -p2.charge()) { + pairSign = UnLikeSignPair; + } else { + pairSign = ReflectedPair; + } + rowFemtoResultPairs( + invMass, + particle.pt(), + p1.pt(), + p2.bdtBkg(), + p2.bdtPrompt(), + p2.bdtFD(), + kstar, + FemtoDreamMath::getkT(p1, massOne, particle, massTwo), + FemtoDreamMath::getmT(p1, massOne, particle, massTwo), + col.multNtr(), + col.multV0M(), + p2.charge(), + pairSign, + deltaInvMassPair, + processType, + p2.flagMc(), + p2.originMcRec()); + container.template setPair_base(kstar, FemtoDreamMath::getmT(p1, massOne, particle, massTwo), p1, particle, col.multNtr(), col.multV0M(), pairQASetting.use4D, pairQASetting.extendedPlots); + } + }; + + for (const auto& col : cols) { + eventHisto.fillQA(col); + auto sliceGenTrk1 = partitionGenTrk1->sliceByCached(aod::femtodreamparticle::fdCollisionId, col.globalIndex(), cache); + auto sliceGenCharmHad = candidates.sliceByCached(aod::femtodreamparticle::fdCollisionId, col.globalIndex(), cache); + if ((col.bitmaskTrackOne() & bitMask) != bitMask || (col.bitmaskTrackTwo() & bitMask) != bitMask) { + continue; + } + if (fillTableWithCharm.value && sliceGenCharmHad.size() == 0) { + continue; + } + for (auto const& part : sliceGenCharmHad) { + const auto particle = part.fdMCParticle(); + float invMass = getCharmHadronMass(part); + float invMassD0 = getCharmHadronMass(part, true); + registryCharmHadronQa.fill(HIST("CharmHadronQA/hPtVsMass"), particle.pt(), invMass); + rowFemtoResultCharmDstar( + col.globalIndex(), + part.timeStamp(), + invMass, + invMassD0, + particle.pt(), + particle.eta(), + particle.phi(), + part.prong0Id(), + part.prong1Id(), + part.prong2Id(), + part.charge(), + part.bdtBkg(), + part.bdtPrompt(), + part.bdtFD(), + part.flagMc(), + part.originMcRec()); + } + for (auto const& part : sliceGenTrk1) { + float chargeTrack = ((part.cut() & CutBitChargePositive) == CutBitChargePositive) + ? PositiveCharge + : NegativeCharge; + rowFemtoResultTrk( + col.globalIndex(), + part.timeStamp(), + part.pt(), + part.eta(), + part.phi(), + part.trackId(), + chargeTrack, + nCls, + nCls, + nCls, + nSigma, + nSigma); + } + if (sliceGenCharmHad.size() > 0 || sliceGenTrk1.size() > 0) { + rowFemtoResultColl(col.globalIndex(), timeStamp, col.posZ(), col.multNtr()); + } + fillCollision(col); + processType = SameEvent; + fillPairs(sliceGenTrk1, sliceGenCharmHad, col, sameEventCont); + } + + if (mixSetting.doMixEvent) { + Partition partitionMaskedCol1 = (aod::femtodreamcollision::bitmaskTrackOne & bitMask) == bitMask; + partitionMaskedCol1.bindTable(cols); + Partition partitionMaskedCol2 = (aod::femtodreamcollision::bitmaskTrackTwo & bitMask) == bitMask; + partitionMaskedCol2.bindTable(cols); + auto mixEvents = [&](auto const& policy) { + processType = MixedEvent; + for (auto const& [collision1, collision2] : combinations(soa::CombinationsBlockFullIndexPolicy(policy, mixSetting.mixingDepth, InvalidMixingBin, *partitionMaskedCol1.mFiltered, *partitionMaskedCol2.mFiltered))) { + if (collision1.globalIndex() == collision2.globalIndex()) { + continue; + } + registryMixQa.fill(HIST("MixingQA/hMECollisionBins"), colBinningMult.getBin({collision1.posZ(), collision1.multNtr()})); + auto sliceGenTrk1 = partitionGenTrk1->sliceByCached(aod::femtodreamparticle::fdCollisionId, collision1.globalIndex(), cache); + auto sliceGenCharmHad = candidates.sliceByCached(aod::femtodreamparticle::fdCollisionId, collision2.globalIndex(), cache); + fillPairs(sliceGenTrk1, sliceGenCharmHad, collision1, mixedEventCont); + } + }; + switch (mixSetting.mixingBinPolicy) { + case femtodreamcollision::kMult: + mixEvents(colBinningMult); + break; + case femtodreamcollision::kMultPercentile: + mixEvents(colBinningMultPercentile); + break; + case femtodreamcollision::kMultMultPercentile: + mixEvents(colBinningMultMultPercentile); + break; + } + } + } + PROCESS_SWITCH(HfTaskCharmHadronsTrackFemtoDream, processMcDstarTrkGen, "Enable generated Dstar and proton correlation for Monte Carlo", false); + void processMcXicTrk(FilteredMcColisions const& cols, FilteredFDMcParts const& parts, o2::aod::FDMCParticles const&, From e46b37ec73cda98c910487bae19fbb69d4e57ca9 Mon Sep 17 00:00:00 2001 From: Yunfan Liu Date: Sun, 15 Nov 2026 17:23:20 +0100 Subject: [PATCH 2/4] --- .../producerCharmHadronsTrackFemtoDream.cxx | 109 +++++++++++++++--- .../Tasks/taskCharmHadronsTrackFemtoDream.cxx | 87 +++++++++----- 2 files changed, 157 insertions(+), 39 deletions(-) diff --git a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx index bbf3a46cd17..4bfde69ab29 100644 --- a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx @@ -242,8 +242,8 @@ struct HfProducerCharmHadronsTrackFemtoDream { using FemtoFullCollision = soa::Join::iterator; using FemtoFullCollisionMc = soa::Join::iterator; - using FemtoFullMcgenCollisions = soa::Join; - using FemtoFullMcgenCollision = FemtoFullMcgenCollisions::iterator; + using FemtoMcgenCollisions = soa::Join; + using FemtoMcgenCollision = FemtoMcgenCollisions::iterator; using FemtoHFTracks = soa::Join; using FemtoHFTrack = FemtoHFTracks::iterator; using FemtoHFMcTracks = soa::Join; @@ -562,7 +562,7 @@ struct HfProducerCharmHadronsTrackFemtoDream { void fillMcCollision(CollisionType const& col) { if (col.has_mcCollision()) { - // auto genMCcol = col.template mcCollision_as(); + // auto genMCcol = col.template mcCollision_as(); // tables.outputMcCollision(genMCcol.multMCNParticlesEta08()); tables.outputCollsMcLabels(tables.outputMcCollision.lastIndex()); } else { @@ -1102,18 +1102,101 @@ struct HfProducerCharmHadronsTrackFemtoDream { } template - void fillCharmHadMcGen(const ParticleType& particles, FemtoFullMcgenCollisions const* collisions = nullptr) + void fillCharmHadMcGen(const ParticleType& particles, FemtoMcgenCollisions const* collisions = nullptr) { // Filling particle properties tables.rowCandCharmHadGen.reserve(particles.size() + 1); if constexpr (Channel == DecayChannel::DplusToPiKPi) { - for (const auto& particle : particles) { - if (std::abs(particle.flagMcMatchGen()) == hf_decay::hf_cand_3prong::DecayChannelMain::DplusToPiKPi) { - tables.rowCandCharmHadGen( - particle.mcCollisionId(), - particle.flagMcMatchGen(), - particle.originMcGen()); + constexpr int InvalidMcIndex = -1; + const int64_t timeStamp = -1; // No need for Gen process. + const float bdtScore = -1.f; // No need for Gen particles. + const float dcaXY = 999.f; // No need for Gen particles. + const float sphericity = 2.f; // Not computed. + const aod::femtodreamparticle::cutContainerType bitPos = 1u << 1; + const aod::femtodreamparticle::cutContainerType bitNeg = 1u << 0; + std::vector daughterIds; + std::vector const childIds = {InvalidMcIndex, InvalidMcIndex}; + for (const auto& col : *collisions) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::All); + if (col.posZ() < hfEvSel.zPvPosMin || col.posZ() > hfEvSel.zPvPosMax) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::RejEveSel); + continue; + } + auto groupedMCParticles = particles.sliceBy(perMcCollision, col.globalIndex()); + // MC provides particle multiplicity, but no reconstructed centrality or magnetic field. + const float multiplicityPercentile = 0.f; + const float magneticField = 0.f; + tables.outputCollision(col.posZ(), multiplicityPercentile, col.multMCNParticlesEta08(), sphericity, magneticField); + tables.outputMcCollision(col.multMCNParticlesEta08()); + tables.outputCollsMcLabels(tables.outputMcCollision.lastIndex()); + + bool isTrackFilled = false; + bool isCharmFilled = false; + for (const auto& particle : groupedMCParticles) { + if (std::abs(particle.pdgCode()) != o2::constants::physics::Pdg::kDPlus || + std::abs(particle.flagMcMatchGen()) != hf_decay::hf_cand_3prong::DecayChannelMain::DplusToPiKPi || + !particle.producedByGenerator()) { + continue; + } + daughterIds.clear(); + RecoDecay::getDaughters(particle, &daughterIds, std::array{kPiPlus, kKPlus}); + int prong0Id = InvalidMcIndex; + int prong1Id = InvalidMcIndex; + int prong2Id = InvalidMcIndex; + for (const auto& daughterId : daughterIds) { + const auto daughter = particles.rawIteratorAt(daughterId); + if (std::abs(daughter.pdgCode()) == kKPlus) { + prong1Id = daughter.globalIndex(); + } else if (std::abs(daughter.pdgCode()) == kPiPlus) { + if (prong0Id == InvalidMcIndex) { + prong0Id = daughter.globalIndex(); + } else { + prong2Id = daughter.globalIndex(); + } + } + } + const auto prong0 = particles.rawIteratorAt(prong0Id); + const auto prong1 = particles.rawIteratorAt(prong1Id); + const auto prong2 = particles.rawIteratorAt(prong2Id); + tables.rowCandCharm3Prong(tables.outputCollision.lastIndex(), timeStamp, + particle.pdgCode() > 0 ? PositiveCharge : NegativeCharge, + prong0Id, prong1Id, prong2Id, + prong0.pt(), prong1.pt(), prong2.pt(), + prong0.eta(), prong1.eta(), prong2.eta(), + prong0.phi(), prong1.phi(), prong2.phi(), + true, bdtScore, bdtScore, bdtScore); + const auto particleOrigin = particle.originMcGen() == RecoDecay::OriginType::Prompt + ? aod::femtodreamMCparticle::ParticleOriginMCTruth::kPrimary + : aod::femtodreamMCparticle::ParticleOriginMCTruth::kSecondaryFromHF; + tables.outputPartsMc(particleOrigin, particle.pdgCode(), particle.pt(), particle.eta(), particle.phi()); + tables.rowCandCharmHadGen(tables.outputPartsMc.lastIndex(), particle.flagMcMatchGen(), particle.originMcGen()); + tables.rowCandMcCharmHad(particle.flagMcMatchGen(), particle.originMcGen()); + tables.outputPartsMcLabels(tables.outputPartsMc.lastIndex()); + isCharmFilled = true; + } + for (const auto& particle : groupedMCParticles) { + if (std::abs(particle.pdgCode()) == std::abs(trackCfg.trkPDGCode.value) && particle.isPhysicalPrimary() && particle.producedByGenerator()) { + tables.outputPartsIndex(particle.globalIndex()); + tables.outputPartsTime(timeStamp); + // Store the PDG code instead of detector PID bits, as in the femto truth producer. + tables.outputParts(tables.outputCollision.lastIndex(), + particle.pt(), particle.eta(), particle.phi(), + aod::femtodreamparticle::ParticleType::kTrack, + particle.pdgCode() > 0 ? bitPos : bitNeg, + std::abs(particle.pdgCode()), dcaXY, childIds, 0.f, 0.f); + isTrackFilled = true; + } + } + if (isTrackFilled) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::TrackSelected); + } + if (isCharmFilled) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::CharmSelected); } + if (isTrackFilled && isCharmFilled) { + qaRegistry.fill(HIST("hEventQA"), 1 + Event::PairSelected); + } + tables.rowMasks(isTrackFilled, isCharmFilled, false); } } else if constexpr (Channel == DecayChannel::LcToPKPi) { for (const auto& particle : particles) { @@ -1350,7 +1433,7 @@ struct HfProducerCharmHadronsTrackFemtoDream { } PROCESS_SWITCH(HfProducerCharmHadronsTrackFemtoDream, processMcDstarToD0PiWithML, "Provide Mc for DstarToD0Pi with ml", false); - void processMcDstarToD0PiGen(FemtoFullMcgenCollisions const& collisions, GeneratedDstarMc const& particles) + void processMcDstarToD0PiGen(FemtoMcgenCollisions const& collisions, GeneratedDstarMc const& particles) { fillCharmHadMcGen(particles, &collisions); } @@ -1406,10 +1489,10 @@ struct HfProducerCharmHadronsTrackFemtoDream { } PROCESS_SWITCH(HfProducerCharmHadronsTrackFemtoDream, processMcDplusToPiKPiWithML, "Provide Mc for DplusToPiKPi with ml", false); - void processMcDplusToPiKPiGen(Generated3ProngMc const& particles) + void processMcDplusToPiKPiGen(FemtoMcgenCollisions const& collisions, Generated3ProngMc const& particles) { - fillCharmHadMcGen(particles); + fillCharmHadMcGen(particles, &collisions); } PROCESS_SWITCH(HfProducerCharmHadronsTrackFemtoDream, processMcDplusToPiKPiGen, "Provide Mc Generated DplusToPiKPi", false); diff --git a/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx index ee27975fabd..c870b41da81 100644 --- a/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx @@ -186,6 +186,7 @@ struct HfTaskCharmHadronsTrackFemtoDream { using FilteredCharmMcCandDstars = soa::Filtered>; using FilteredCharmMcCandDstar = FilteredCharmMcCandDstars::iterator; + using CharmGenCand3Prongs = soa::Join; using CharmGenCandDstars = soa::Join; using FilteredFDGenParts = soa::Filtered>; @@ -305,7 +306,7 @@ struct HfTaskCharmHadronsTrackFemtoDream { void init(InitContext& /*context*/) { - std::array processes = {doprocessDataLcTrk, doprocessDataDplusTrk, doprocessDataD0Trk, doprocessDataDstarTrk, doprocessDataXicTrk, doprocessMcLcTrk, doprocessMcDplusTrk, doprocessMcD0Trk, doprocessMcDstarTrk, doprocessMcXicTrk, doprocessMcDstarTrkGen}; + std::array processes = {doprocessDataLcTrk, doprocessDataDplusTrk, doprocessDataD0Trk, doprocessDataDstarTrk, doprocessDataXicTrk, doprocessMcLcTrk, doprocessMcDplusTrk, doprocessMcD0Trk, doprocessMcDstarTrk, doprocessMcXicTrk, doprocessMcDstarTrkGen, doprocessMcDplusTrkGen}; if (std::accumulate(processes.begin(), processes.end(), 0) != 1) { LOGP(fatal, "One and only one process function must be enabled at a time."); } @@ -1313,10 +1314,8 @@ struct HfTaskCharmHadronsTrackFemtoDream { } PROCESS_SWITCH(HfTaskCharmHadronsTrackFemtoDream, processMcDstarTrk, "Enable processing DstarToD0Pi and Tracks correlation for Monte Carlo", false); - void processMcDstarTrkGen(FilteredMcColisions const& cols, - FilteredFDGenParts const&, - CharmGenCandDstars const& candidates, - aod::FDMCParticles const&) + template + void processMcGen(FilteredMcColisions const& cols, CandidateType const& candidates) { constexpr int SameEvent = 1; constexpr int MixedEvent = 2; @@ -1335,8 +1334,8 @@ struct HfTaskCharmHadronsTrackFemtoDream { if (particle.pt() < charmSel.charmHadMinPt || particle.pt() > charmSel.charmHadMaxPt) { continue; } - float invMass = getCharmHadronMass(p2); - float deltaInvMassPair = getCharmHadronTrackMass(p2, p1, trackSel.pdgCodeTrack1.value) - invMass; + float invMass = getCharmHadronMass(p2); + float deltaInvMassPair = getCharmHadronTrackMass(p2, p1, trackSel.pdgCodeTrack1.value) - invMass; float chargeTrack = 0.; if ((p1.cut() & CutBitChargePositive) == CutBitChargePositive) { chargeTrack = PositiveCharge; @@ -1385,26 +1384,45 @@ struct HfTaskCharmHadronsTrackFemtoDream { } for (auto const& part : sliceGenCharmHad) { const auto particle = part.fdMCParticle(); - float invMass = getCharmHadronMass(part); - float invMassD0 = getCharmHadronMass(part, true); + float invMass = getCharmHadronMass(part); registryCharmHadronQa.fill(HIST("CharmHadronQA/hPtVsMass"), particle.pt(), invMass); - rowFemtoResultCharmDstar( - col.globalIndex(), - part.timeStamp(), - invMass, - invMassD0, - particle.pt(), - particle.eta(), - particle.phi(), - part.prong0Id(), - part.prong1Id(), - part.prong2Id(), - part.charge(), - part.bdtBkg(), - part.bdtPrompt(), - part.bdtFD(), - part.flagMc(), - part.originMcRec()); + if constexpr (Channel == DecayChannel::DstarToD0Pi) { + float invMassD0 = getCharmHadronMass(part, true); + rowFemtoResultCharmDstar( + col.globalIndex(), + part.timeStamp(), + invMass, + invMassD0, + particle.pt(), + particle.eta(), + particle.phi(), + part.prong0Id(), + part.prong1Id(), + part.prong2Id(), + part.charge(), + part.bdtBkg(), + part.bdtPrompt(), + part.bdtFD(), + part.flagMc(), + part.originMcRec()); + } else { + rowFemtoResultCharm3Prong( + col.globalIndex(), + part.timeStamp(), + invMass, + particle.pt(), + particle.eta(), + particle.phi(), + part.prong0Id(), + part.prong1Id(), + part.prong2Id(), + part.charge(), + part.bdtBkg(), + part.bdtPrompt(), + part.bdtFD(), + part.flagMc(), + part.originMcRec()); + } } for (auto const& part : sliceGenTrk1) { float chargeTrack = ((part.cut() & CutBitChargePositive) == CutBitChargePositive) @@ -1462,8 +1480,25 @@ struct HfTaskCharmHadronsTrackFemtoDream { } } } + + void processMcDstarTrkGen(FilteredMcColisions const& cols, + FilteredFDGenParts const&, + CharmGenCandDstars const& candidates, + aod::FDMCParticles const&) + { + processMcGen(cols, candidates); + } PROCESS_SWITCH(HfTaskCharmHadronsTrackFemtoDream, processMcDstarTrkGen, "Enable generated Dstar and proton correlation for Monte Carlo", false); + void processMcDplusTrkGen(FilteredMcColisions const& cols, + FilteredFDGenParts const&, + CharmGenCand3Prongs const& candidates, + aod::FDMCParticles const&) + { + processMcGen(cols, candidates); + } + PROCESS_SWITCH(HfTaskCharmHadronsTrackFemtoDream, processMcDplusTrkGen, "Enable generated Dplus and proton correlation for Monte Carlo", false); + void processMcXicTrk(FilteredMcColisions const& cols, FilteredFDMcParts const& parts, o2::aod::FDMCParticles const&, From 29eb044ad49ac45f2c2516deef06f0341a117259 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Sun, 15 Nov 2026 16:42:20 +0000 Subject: [PATCH 3/4] Please consider the following formatting changes --- .../producerCharmHadronsTrackFemtoDream.cxx | 20 +++++++++---------- .../Tasks/taskCharmHadronsTrackFemtoDream.cxx | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx index 4bfde69ab29..7ebec107a4e 100644 --- a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx @@ -1109,8 +1109,8 @@ struct HfProducerCharmHadronsTrackFemtoDream { if constexpr (Channel == DecayChannel::DplusToPiKPi) { constexpr int InvalidMcIndex = -1; const int64_t timeStamp = -1; // No need for Gen process. - const float bdtScore = -1.f; // No need for Gen particles. - const float dcaXY = 999.f; // No need for Gen particles. + const float bdtScore = -1.f; // No need for Gen particles. + const float dcaXY = 999.f; // No need for Gen particles. const float sphericity = 2.f; // Not computed. const aod::femtodreamparticle::cutContainerType bitPos = 1u << 1; const aod::femtodreamparticle::cutContainerType bitNeg = 1u << 0; @@ -1159,12 +1159,12 @@ struct HfProducerCharmHadronsTrackFemtoDream { const auto prong1 = particles.rawIteratorAt(prong1Id); const auto prong2 = particles.rawIteratorAt(prong2Id); tables.rowCandCharm3Prong(tables.outputCollision.lastIndex(), timeStamp, - particle.pdgCode() > 0 ? PositiveCharge : NegativeCharge, - prong0Id, prong1Id, prong2Id, - prong0.pt(), prong1.pt(), prong2.pt(), - prong0.eta(), prong1.eta(), prong2.eta(), - prong0.phi(), prong1.phi(), prong2.phi(), - true, bdtScore, bdtScore, bdtScore); + particle.pdgCode() > 0 ? PositiveCharge : NegativeCharge, + prong0Id, prong1Id, prong2Id, + prong0.pt(), prong1.pt(), prong2.pt(), + prong0.eta(), prong1.eta(), prong2.eta(), + prong0.phi(), prong1.phi(), prong2.phi(), + true, bdtScore, bdtScore, bdtScore); const auto particleOrigin = particle.originMcGen() == RecoDecay::OriginType::Prompt ? aod::femtodreamMCparticle::ParticleOriginMCTruth::kPrimary : aod::femtodreamMCparticle::ParticleOriginMCTruth::kSecondaryFromHF; @@ -1219,8 +1219,8 @@ struct HfProducerCharmHadronsTrackFemtoDream { } else if constexpr (Channel == DecayChannel::DstarToD0Pi) { constexpr int InvalidMcIndex = -1; const int64_t timeStamp = -1; // No need for Gen process. - const float bdtScore = -1.f; // No need for Gen particles. - const float dcaXY = 999.f; // No need for Gen particles. + const float bdtScore = -1.f; // No need for Gen particles. + const float dcaXY = 999.f; // No need for Gen particles. const float sphericity = 2.f; // Not computed. const aod::femtodreamparticle::cutContainerType bitPos = 1u << 1; const aod::femtodreamparticle::cutContainerType bitNeg = 1u << 0; diff --git a/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx index c870b41da81..93e90ce8c74 100644 --- a/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/Tasks/taskCharmHadronsTrackFemtoDream.cxx @@ -1321,8 +1321,8 @@ struct HfTaskCharmHadronsTrackFemtoDream { constexpr int MixedEvent = 2; constexpr int InvalidMixingBin = -1; const int64_t timeStamp = -1; // No timestamp is available in this Gen process. - const uint8_t nCls = 0; // Gen particles have no detector clusters. - const float nSigma = 999.f; // Gen particles have no detector PID response. + const uint8_t nCls = 0; // Gen particles have no detector clusters. + const float nSigma = 999.f; // Gen particles have no detector PID response. auto fillPairs = [&](auto const& sliceGenTrk1, auto const& sliceGenCharmHad, auto const& col, auto& container) { for (auto const& [p1, p2] : combinations(CombinationsFullIndexPolicy(sliceGenTrk1, sliceGenCharmHad))) { From d2b1aba38396106a7e9e47db53261be024a156f6 Mon Sep 17 00:00:00 2001 From: Yunfan Liu Date: Sun, 15 Nov 2026 18:12:41 +0100 Subject: [PATCH 4/4] Rename trackCuts to trackSelections in isNoSelectedTracks --- .../HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx index 7ebec107a4e..b0681d1768a 100644 --- a/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx +++ b/PWGHF/HFC/TableProducer/producerCharmHadronsTrackFemtoDream.cxx @@ -1091,10 +1091,10 @@ struct HfProducerCharmHadronsTrackFemtoDream { /// \param TC type of the femto track cuts /// \return whether or not the tracks fulfills the all selections template - bool isNoSelectedTracks(C const& /*col*/, T const& tracks, TC& trackCuts) + bool isNoSelectedTracks(C const& /*col*/, T const& tracks, TC& trackSelections) { for (auto const& track : tracks) { - if (trackCuts.isSelectedMinimal(track)) { + if (trackSelections.isSelectedMinimal(track)) { return false; } }