From 7e26f0c7df1e3ca962a36edbacf6071f95c9954d Mon Sep 17 00:00:00 2001 From: ptak Date: Mon, 3 Aug 2026 10:07:12 +0200 Subject: [PATCH 1/2] [PWGCF] Femto: Add D0-D0 correlation task --- PWGCF/Femto/Core/charmHadronBuilder.h | 8 +- PWGCF/Femto/Core/charmHadronHistManager.h | 2 + PWGCF/Femto/Core/closePairRejection.h | 8 + PWGCF/Femto/Core/pairBuilder.h | 281 ++++++++++++++++++++++ PWGCF/Femto/Core/pairHistManager.h | 2 + PWGCF/Femto/Core/trackHistManager.h | 6 + PWGCF/Femto/Tasks/CMakeLists.txt | 5 + PWGCF/Femto/Tasks/femtoD0Qa.cxx | 2 +- PWGCF/Femto/Tasks/femtoPairD0D0.cxx | 220 +++++++++++++++++ PWGCF/Femto/Tasks/femtoPairTrackD0.cxx | 2 +- 10 files changed, 533 insertions(+), 3 deletions(-) create mode 100644 PWGCF/Femto/Tasks/femtoPairD0D0.cxx diff --git a/PWGCF/Femto/Core/charmHadronBuilder.h b/PWGCF/Femto/Core/charmHadronBuilder.h index 1b139a7cb52..25664974273 100644 --- a/PWGCF/Femto/Core/charmHadronBuilder.h +++ b/PWGCF/Femto/Core/charmHadronBuilder.h @@ -73,8 +73,9 @@ struct ConfD0Bits : o2::framework::ConfigurableGroup { }; // base selection for analysis task for D0s +template struct ConfD0Selection : o2::framework::ConfigurableGroup { - std::string prefix = std::string("D0Selection"); + std::string prefix = Prefix; o2::framework::Configurable pdgCodeAbs{"pdgCodeAbs", o2::constants::physics::Pdg::kD0, "PDG code (D0)"}; o2::framework::Configurable sign{"sign", 0, "Particle sign (+1: D0; -1: D0bar; 0: both)"}; o2::framework::Configurable ptMin{"ptMin", 1.f, "Minimum pT"}; @@ -91,6 +92,11 @@ struct ConfD0Selection : o2::framework::ConfigurableGroup { o2::framework::Configurable mask{"mask", 0, "Bitmask for D0 selection"}; }; +constexpr const char PrefixD0Selection1[] = "D0Selection1"; +constexpr const char PrefixD0Selection2[] = "D0Selection2"; +using ConfD0Selection1 = ConfD0Selection; +using ConfD0Selection2 = ConfD0Selection; + /// The different selections for D0s enum D0Sels { // topological selections diff --git a/PWGCF/Femto/Core/charmHadronHistManager.h b/PWGCF/Femto/Core/charmHadronHistManager.h index 6b839b90ca3..f17219d3bd7 100644 --- a/PWGCF/Femto/Core/charmHadronHistManager.h +++ b/PWGCF/Femto/Core/charmHadronHistManager.h @@ -94,7 +94,9 @@ struct ConfD0Binning : o2::framework::ConfigurableGroup { #undef CHARMHADRON_DEFAULT_BINNING constexpr const char PrefixD0Binning1[] = "D0Binning1"; +constexpr const char PrefixD0Binning2[] = "D0Binning2"; using ConfD0Binning1 = ConfD0Binning; +using ConfD0Binning2 = ConfD0Binning; template struct ConfD0QaBinning : o2::framework::ConfigurableGroup { diff --git a/PWGCF/Femto/Core/closePairRejection.h b/PWGCF/Femto/Core/closePairRejection.h index df8309dfba7..9460fad49dc 100644 --- a/PWGCF/Femto/Core/closePairRejection.h +++ b/PWGCF/Femto/Core/closePairRejection.h @@ -84,6 +84,8 @@ struct ConfCpr : o2::framework::ConfigurableGroup { constexpr const char PrefixCprTrackTrack[] = "CprTrackTrack"; constexpr const char PrefixCprTrackV0Daughter[] = "CprTrackV0Daughter"; constexpr const char PrefixCprTrackD0Daughter[] = "CprTrackD0Daughter"; +constexpr const char PrefixCprD0DaughterD0DaughterPos[] = "CprD0DaughterD0DaughterPos"; +constexpr const char PrefixCprD0DaughterD0DaughterNeg[] = "CprD0DaughterD0DaughterNeg"; constexpr const char PrefixCprTrackResonanceDaughter[] = "CprTrackResonanceDaughter"; constexpr const char PrefixCprTrackKinkDaughter[] = "CprTrackKinkDaughter"; constexpr const char PrefixCprV0DaughterV0DaughterPos[] = "CprV0DaughterV0DaughterPos"; @@ -96,6 +98,8 @@ constexpr const char PrefixCprTrackCascadeBachelor[] = "CprTrackCascadeBachelor" using ConfCprTrackTrack = ConfCpr; using ConfCprTrackV0Daughter = ConfCpr; using ConfCprTrackD0Daughter = ConfCpr; +using ConfCprD0DaugherD0DaughterPos = ConfCpr; +using ConfCprD0DaugherD0DaughterNeg = ConfCpr; using ConfCprTrackResonanceDaughter = ConfCpr; using ConfCprTrackKinkDaughter = ConfCpr; using ConfCprV0DaugherV0DaughterPos = ConfCpr; @@ -115,6 +119,10 @@ constexpr char PrefixTrackV0DaughterSe[] = "CPR_TrackV0Dau/SE/"; constexpr char PrefixTrackV0DaughterMe[] = "CPR_TrackV0Dau/ME/"; constexpr char PrefixTrackD0DaughterSe[] = "CPR_TrackD0Dau/SE/"; constexpr char PrefixTrackD0DaughterMe[] = "CPR_TrackD0Dau/ME/"; +constexpr char PrefixD0D0PosSe[] = "CPR_D0D0_PosDau/SE/"; +constexpr char PrefixD0D0NegSe[] = "CPR_D0D0_NegDau/SE/"; +constexpr char PrefixD0D0PosMe[] = "CPR_D0D0_PosDau/ME/"; +constexpr char PrefixD0D0NegMe[] = "CPR_D0D0_NegDau/ME/"; constexpr char PrefixV0V0PosSe[] = "CPR_V0V0_PosDau/SE/"; constexpr char PrefixV0V0NegSe[] = "CPR_V0V0_NegDau/SE/"; constexpr char PrefixV0V0PosMe[] = "CPR_V0V0_PosDau/ME/"; diff --git a/PWGCF/Femto/Core/pairBuilder.h b/PWGCF/Femto/Core/pairBuilder.h index b7a181ebca6..4d7cbed796a 100644 --- a/PWGCF/Femto/Core/pairBuilder.h +++ b/PWGCF/Femto/Core/pairBuilder.h @@ -42,6 +42,7 @@ #include #include #include +#include #include namespace o2::analysis::femto::pairbuilder @@ -563,6 +564,286 @@ class PairV0V0Builder static_cast(pairprocesshelpers::kOrder21)}; }; +template +class PairD0D0Builder +{ + public: + PairD0D0Builder() = default; + ~PairD0D0Builder() = default; + + template + void init(o2::framework::HistogramRegistry* registry, + T1 const& confCollisionBinning, + T2 const& confD0Selection1, + T3 const& confD0Selection2, + T4 const& confD0Cleaner1, + T5 const& confD0Cleaner2, + T6 const& confCprPos, + T7 const& confCprNeg, + T8 const& confMixing, + T9 const& confPairBinning, + T10 const& confPairCuts, + std::map> const& colHistSpec, + std::map> const& D0HistSpec1, + std::map> const& D0HistSpec2, + std::map> const& PosDauHistSpec1, + std::map> const& NegDauHistSpec1, + std::map> const& PosDauHistSpec2, + std::map> const& NegDauHistSpec2, + std::map> const& pairHistSpec, + std::map> const& cprHistSpecPos, + std::map> const& cprHistSpecNeg) + { + mSameSpecies = confMixing.sameSpecies.value; + + if (mSameSpecies && confD0Selection1.sign.value != confD0Selection2.sign.value) { + LOG(warn) << "sameSpecies=true, but the two selections have different signs (" + << confD0Selection1.sign.value << " vs " << confD0Selection2.sign.value + << "). D0 and D0bar are not identical particles. Overriding sameSpecies to false."; + mSameSpecies = false; + } + if (!mSameSpecies && confD0Selection1.sign.value == confD0Selection2.sign.value) { + LOG(warn) << "sameSpecies=false for identical selections with identical sign (" + << confD0Selection1.sign.value << "). If both selections match the same " + << "candidates, this produces self-pairs and double counting."; + } + if (confD0Selection1.sign.value == 0 || confD0Selection2.sign.value == 0) { + LOG(warn) << "sign=0 accepts both the D0 and the D0bar hypothesis of the same candidate. " + << "Set an explicit sign (+1: D0, -1: D0bar) for both slots in a D0-D0 analysis."; + } + mColHistManager.template init(registry, colHistSpec, confCollisionBinning); + mPairHistManagerSe.template init(registry, pairHistSpec, confPairBinning, confPairCuts, confMixing); + mPairHistManagerMe.template init(registry, pairHistSpec, confPairBinning, confPairCuts, confMixing); + mPc.template init(confPairCuts); + + mD0Cleaner1.init(confD0Cleaner1); + mD0Cleaner2.init(confD0Cleaner2); + + mD0HistManager1.template init(registry, D0HistSpec1, confD0Selection1, PosDauHistSpec1, NegDauHistSpec1); + + auto prongPdgCodes = [](int sign) -> std::pair { + if (sign > 0) { + return {PDG_t::kPiPlus, PDG_t::kKMinus}; + } + return {PDG_t::kKPlus, PDG_t::kPiMinus}; + }; + auto [posDauPdg1, negDauPdg1] = prongPdgCodes(confD0Selection1.sign.value); + + if (mSameSpecies) { + mPairHistManagerSe.setMass(confD0Selection1.pdgCodeAbs.value, posDauPdg1, negDauPdg1, confD0Selection1.pdgCodeAbs.value, posDauPdg1, negDauPdg1); + mPairHistManagerSe.setCharge(1, 1); + mCprSe.init(registry, cprHistSpecPos, cprHistSpecNeg, confCprPos, confCprNeg); + + mPairHistManagerMe.setMass(confD0Selection1.pdgCodeAbs.value, posDauPdg1, negDauPdg1, confD0Selection1.pdgCodeAbs.value, posDauPdg1, negDauPdg1); + mPairHistManagerMe.setCharge(1, 1); + mCprMe.init(registry, cprHistSpecPos, cprHistSpecNeg, confCprPos, confCprNeg); + } else { + mD0HistManager2.template init(registry, D0HistSpec2, confD0Selection2, PosDauHistSpec2, NegDauHistSpec2); + auto [posDauPdg2, negDauPdg2] = prongPdgCodes(confD0Selection2.sign.value); + + mPairHistManagerSe.setMass(confD0Selection1.pdgCodeAbs.value, posDauPdg1, negDauPdg1, confD0Selection2.pdgCodeAbs.value, posDauPdg2, negDauPdg2); + mPairHistManagerSe.setCharge(1, 1); + mCprSe.init(registry, cprHistSpecPos, cprHistSpecNeg, confCprPos, confCprNeg); + + mPairHistManagerMe.setMass(confD0Selection1.pdgCodeAbs.value, posDauPdg1, negDauPdg1, confD0Selection2.pdgCodeAbs.value, posDauPdg2, negDauPdg2); + mPairHistManagerMe.setCharge(1, 1); + mCprMe.init(registry, cprHistSpecPos, cprHistSpecNeg, confCprPos, confCprNeg); + } + + // setup mixing + mMixingPolicy = static_cast(confMixing.policy.value); + mMixingDepth = confMixing.depth.value; + // setup rng if necessary + // mDist is bounded at declaration, so it stays valid even if this branch is not taken + if (confMixing.seed.value >= 0) { + uint64_t randomSeed = 0; + mMixIdenticalParticles = true; + if (confMixing.seed.value == 0) { + randomSeed = static_cast(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); + } else { + randomSeed = static_cast(confMixing.seed.value); + } + mRng = std::mt19937(randomSeed); + } + } + + template + void processSameEvent(T1 const& col, T2 const& trackTable, T3& /*d0table*/, T4& partition1, T5& partition2, T6& cache) + { + if (mSameSpecies) { + auto d0Slice1 = partition1->sliceByCached(o2::aod::femtobase::stored::fColId, col.globalIndex(), cache); + if (d0Slice1.size() < nLimitPartitionIdenticalParticles) { + return; + } + mColHistManager.template fill(col); + mCprSe.setMagField(col.magField()); + pairprocesshelpers::PairOrder pairOrder = pairprocesshelpers::kOrder12; + if (mMixIdenticalParticles) { + pairOrder = static_cast(mDist(mRng)); + } + pairprocesshelpers::processSameEvent(d0Slice1, trackTable, col, mD0HistManager1, mPairHistManagerSe, mCprSe, mPc, pairOrder); + } else { + auto d0Slice1 = partition1->sliceByCached(o2::aod::femtobase::stored::fColId, col.globalIndex(), cache); + auto d0Slice2 = partition2->sliceByCached(o2::aod::femtobase::stored::fColId, col.globalIndex(), cache); + if (d0Slice1.size() < nLimitPartitionParticles || d0Slice2.size() < nLimitPartitionParticles) { + return; + } + mColHistManager.template fill(col); + mCprSe.setMagField(col.magField()); + pairprocesshelpers::processSameEvent(d0Slice1, d0Slice2, trackTable, col, mD0HistManager1, mD0HistManager2, mPairHistManagerSe, mCprSe, mPc); + } + } + + // mc + template + void processSameEvent(T1 const& col, T2 const& mcCols, T3 const& trackTable, T4 const& /*d0table*/, T5& partition1, T6& partition2, T7 const& mcParticles, T8 const& mcMothers, T9 const& mcPartonicMothers, T10& cache) + { + if (mSameSpecies) { + auto d0Slice1 = partition1->sliceByCached(o2::aod::femtobase::stored::fColId, col.globalIndex(), cache); + if (d0Slice1.size() < nLimitPartitionIdenticalParticles) { + return; + } + mColHistManager.template fill(col, mcCols); + mCprSe.setMagField(col.magField()); + pairprocesshelpers::PairOrder pairOrder = pairprocesshelpers::kOrder12; + if (mMixIdenticalParticles) { + pairOrder = static_cast(mDist(mRng)); + } + pairprocesshelpers::processSameEvent(d0Slice1, trackTable, mcParticles, mcMothers, mcPartonicMothers, col, mcCols, mD0HistManager1, mPairHistManagerSe, mD0Cleaner1, mCprSe, mPc, pairOrder); + } else { + auto d0Slice1 = partition1->sliceByCached(o2::aod::femtobase::stored::fColId, col.globalIndex(), cache); + auto d0Slice2 = partition2->sliceByCached(o2::aod::femtobase::stored::fColId, col.globalIndex(), cache); + if (d0Slice1.size() < nLimitPartitionParticles || d0Slice2.size() < nLimitPartitionParticles) { + return; + } + mColHistManager.template fill(col, mcCols); + mCprSe.setMagField(col.magField()); + pairprocesshelpers::processSameEvent(d0Slice1, d0Slice2, trackTable, mcParticles, mcMothers, mcPartonicMothers, col, mcCols, mD0HistManager1, mD0HistManager2, mPairHistManagerSe, mD0Cleaner1, mD0Cleaner2, mCprSe, mPc); + } + } + + template + void processMixedEvent(T1 const& cols, T2 const& trackTable, T3& partition1, T4& partition2, T5& cache, T6& binsVtxMult, T7& binsVtxCent, T8& binsVtxMultCent) + { + if (mSameSpecies) { + switch (mMixingPolicy) { + case static_cast(pairhistmanager::kVtxMult): + pairprocesshelpers::processMixedEvent(cols, partition1, partition1, trackTable, cache, binsVtxMult, mMixingDepth, mPairHistManagerMe, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxCent): + pairprocesshelpers::processMixedEvent(cols, partition1, partition1, trackTable, cache, binsVtxCent, mMixingDepth, mPairHistManagerMe, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxMultCent): + pairprocesshelpers::processMixedEvent(cols, partition1, partition1, trackTable, cache, binsVtxMultCent, mMixingDepth, mPairHistManagerMe, mCprMe, mPc); + break; + default: + LOG(fatal) << "Invalid binning policiy specifed. Breaking..."; + } + } else { + switch (mMixingPolicy) { + case static_cast(pairhistmanager::kVtxMult): + pairprocesshelpers::processMixedEvent(cols, partition1, partition2, trackTable, cache, binsVtxMult, mMixingDepth, mPairHistManagerMe, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxCent): + pairprocesshelpers::processMixedEvent(cols, partition1, partition2, trackTable, cache, binsVtxCent, mMixingDepth, mPairHistManagerMe, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxMultCent): + pairprocesshelpers::processMixedEvent(cols, partition1, partition2, trackTable, cache, binsVtxMultCent, mMixingDepth, mPairHistManagerMe, mCprMe, mPc); + break; + default: + LOG(fatal) << "Invalid binning policiy specifed. Breaking..."; + } + } + } + + template + void processMixedEvent(T1 const& cols, T2 const& mcCols, T3& trackTable, T4& partition1, T5& partition2, T6 const& mcParticles, T7 const& mcMothers, T8 const& mcPartonicMothers, T9& cache, T10& binsVtxMult, T11& binsVtxCent, T12& binsVtxMultCent) + { + if (mSameSpecies) { + switch (mMixingPolicy) { + case static_cast(pairhistmanager::kVtxMult): + pairprocesshelpers::processMixedEvent(cols, mcCols, partition1, partition1, trackTable, mcParticles, mcMothers, mcPartonicMothers, cache, binsVtxMult, mMixingDepth, mPairHistManagerMe, mD0Cleaner1, mD0Cleaner1, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxCent): + pairprocesshelpers::processMixedEvent(cols, mcCols, partition1, partition1, trackTable, mcParticles, mcMothers, mcPartonicMothers, cache, binsVtxCent, mMixingDepth, mPairHistManagerMe, mD0Cleaner1, mD0Cleaner1, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxMultCent): + pairprocesshelpers::processMixedEvent(cols, mcCols, partition1, partition1, trackTable, mcParticles, mcMothers, mcPartonicMothers, cache, binsVtxMultCent, mMixingDepth, mPairHistManagerMe, mD0Cleaner1, mD0Cleaner1, mCprMe, mPc); + break; + default: + LOG(fatal) << "Invalid binning policiy specifed. Breaking..."; + } + } else { + switch (mMixingPolicy) { + case static_cast(pairhistmanager::kVtxMult): + pairprocesshelpers::processMixedEvent(cols, mcCols, partition1, partition2, trackTable, mcParticles, mcMothers, mcPartonicMothers, cache, binsVtxMult, mMixingDepth, mPairHistManagerMe, mD0Cleaner1, mD0Cleaner2, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxCent): + pairprocesshelpers::processMixedEvent(cols, mcCols, partition1, partition2, trackTable, mcParticles, mcMothers, mcPartonicMothers, cache, binsVtxCent, mMixingDepth, mPairHistManagerMe, mD0Cleaner1, mD0Cleaner2, mCprMe, mPc); + break; + case static_cast(pairhistmanager::kVtxMultCent): + pairprocesshelpers::processMixedEvent(cols, mcCols, partition1, partition2, trackTable, mcParticles, mcMothers, mcPartonicMothers, cache, binsVtxMultCent, mMixingDepth, mPairHistManagerMe, mD0Cleaner1, mD0Cleaner2, mCprMe, mPc); + break; + default: + LOG(fatal) << "Invalid binning policiy specifed. Breaking..."; + } + } + } + + private: + colhistmanager::CollisionHistManager mColHistManager; + charmhadronhistmanager::CharmHadronHistManager mD0HistManager1; + charmhadronhistmanager::CharmHadronHistManager mD0HistManager2; + particlecleaner::ParticleCleaner mD0Cleaner1; + particlecleaner::ParticleCleaner mD0Cleaner2; + pairhistmanager::PairHistManager mPairHistManagerSe; + pairhistmanager::PairHistManager mPairHistManagerMe; + closepairrejection::ClosePairRejectionV0V0 mCprSe; + closepairrejection::ClosePairRejectionV0V0 mCprMe; + paircleaner::V0V0PairCleaner mPc; + pairhistmanager::MixingPolicy mMixingPolicy = pairhistmanager::MixingPolicy::kVtxMult; + bool mSameSpecies = false; + int mMixingDepth = 5; + bool mMixIdenticalParticles = false; + std::mt19937 mRng; + std::uniform_int_distribution<> mDist{static_cast(pairprocesshelpers::kOrder12), + static_cast(pairprocesshelpers::kOrder21)}; +}; + template ; using ConfV02NegDauBinning = ConfTrackBinning; using ConfD01PosDauBinning = ConfTrackBinning; using ConfD01NegDauBinning = ConfTrackBinning; +using ConfD02PosDauBinning = ConfTrackBinning; +using ConfD02NegDauBinning = ConfTrackBinning; using ConfCascadePosDauBinning = ConfTrackBinning; using ConfCascadeNegDauBinning = ConfTrackBinning; using ConfCascadeBachelorBinning = ConfTrackBinning; @@ -527,6 +531,8 @@ constexpr char PrefixResonanceNegDaughterQa[] = "ResonanceNegDauQa/"; constexpr char PrefixD01PosDaughter[] = "D01PosDau/"; constexpr char PrefixD01NegDaughter[] = "D01NegDau/"; +constexpr char PrefixD02PosDaughter[] = "D02PosDau/"; +constexpr char PrefixD02NegDaughter[] = "D02NegDau/"; constexpr char PrefixD01PosDaughterQa[] = "D01PosDauQa/"; constexpr char PrefixD01NegDaughterQa[] = "D01NegDauQa/"; diff --git a/PWGCF/Femto/Tasks/CMakeLists.txt b/PWGCF/Femto/Tasks/CMakeLists.txt index 6ca8f2cd90a..593e560699d 100644 --- a/PWGCF/Femto/Tasks/CMakeLists.txt +++ b/PWGCF/Femto/Tasks/CMakeLists.txt @@ -54,6 +54,11 @@ o2physics_add_dpl_workflow(femto-pair-track-d0 PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore COMPONENT_NAME Analysis) +o2physics_add_dpl_workflow(femto-pair-d0-d0 + SOURCES femtoPairD0D0.cxx + PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore + COMPONENT_NAME Analysis) + o2physics_add_dpl_workflow(femto-d0-qa SOURCES femtoD0Qa.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore diff --git a/PWGCF/Femto/Tasks/femtoD0Qa.cxx b/PWGCF/Femto/Tasks/femtoD0Qa.cxx index fdc9ecc6873..7a3f9330722 100644 --- a/PWGCF/Femto/Tasks/femtoD0Qa.cxx +++ b/PWGCF/Femto/Tasks/femtoD0Qa.cxx @@ -69,7 +69,7 @@ struct FemtoD0Qa { colhistmanager::ConfCollisionQaBinning confCollisionQaBinning; // setup for D0s - charmhadronbuilder::ConfD0Selection confD0Selection; + charmhadronbuilder::ConfD0Selection1 confD0Selection; o2::framework::Partition d0Partition = MAKE_D0_PARTITION(confD0Selection); o2::framework::Preslice perColD0s = o2::aod::femtobase::stored::fColId; diff --git a/PWGCF/Femto/Tasks/femtoPairD0D0.cxx b/PWGCF/Femto/Tasks/femtoPairD0D0.cxx new file mode 100644 index 00000000000..47a5959f2ca --- /dev/null +++ b/PWGCF/Femto/Tasks/femtoPairD0D0.cxx @@ -0,0 +1,220 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file femtoPairD0D0.cxx +/// \brief Tasks that computes correlation between two D0 mesons +/// \author Igor Ptak, WUT, igor.tomasz.ptak@cern.ch + +#include "PWGCF/Femto/Core/charmHadronBuilder.h" +#include "PWGCF/Femto/Core/charmHadronHistManager.h" +#include "PWGCF/Femto/Core/closePairRejection.h" +#include "PWGCF/Femto/Core/collisionBuilder.h" +#include "PWGCF/Femto/Core/collisionHistManager.h" +#include "PWGCF/Femto/Core/modes.h" +#include "PWGCF/Femto/Core/pairBuilder.h" +#include "PWGCF/Femto/Core/pairHistManager.h" +#include "PWGCF/Femto/Core/particleCleaner.h" +#include "PWGCF/Femto/Core/partitions.h" +#include "PWGCF/Femto/Core/trackHistManager.h" +#include "PWGCF/Femto/DataModel/FemtoTables.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace o2::analysis::femto; + +// Each pair slot (1 and 2) has its own selection, cleaner, binning and partition. +// The sign of the selection picks the decay hypothesis: +// d0-d0 : D0Selection1.sign = +1, D0Selection2.sign = +1, Mixing.sameSpecies = true +// d0bar-d0bar : D0Selection1.sign = -1, D0Selection2.sign = -1, Mixing.sameSpecies = true +// d0-d0bar : D0Selection1.sign = +1, D0Selection2.sign = -1, Mixing.sameSpecies = false +struct FemtoPairD0D0 { + + // setup tables + using FemtoCollisions = o2::soa::Join; + using FilteredFemtoCollisions = o2::soa::Filtered; + using FilteredFemtoCollision = FilteredFemtoCollisions::iterator; + + using FemtoCollisionsWithLabel = o2::soa::Join; + using FilteredFemtoCollisionsWithLabel = o2::soa::Filtered; + using FilteredFemtoCollisionWithLabel = FilteredFemtoCollisionsWithLabel::iterator; + + using FemtoTracks = o2::aod::FTracks; + using FemtoD0s = o2::soa::Join; + + using FemtoTracksWithLabel = o2::soa::Join; + using FemtoD0sWithLabel = o2::soa::Join; + + using FemtoMcParticlesWithLabel = o2::soa::Join; + + o2::framework::SliceCache cache; + + // setup collisions + collisionbuilder::ConfCollisionSelection collisionSelection; + o2::framework::expressions::Filter collisionFilter = MAKE_COLLISION_FILTER(collisionSelection); + colhistmanager::ConfCollisionBinning confCollisionBinning; + + // setup for daughters + // separate binning per d0 slot, so that both hypotheses can be binned independently + trackhistmanager::ConfD01PosDauBinning confD01PosDauBinning; + trackhistmanager::ConfD01NegDauBinning confD01NegDauBinning; + trackhistmanager::ConfD02PosDauBinning confD02PosDauBinning; + trackhistmanager::ConfD02NegDauBinning confD02NegDauBinning; + + // setup d0s + charmhadronbuilder::ConfD0Selection1 confD0Selection1; + charmhadronbuilder::ConfD0Selection2 confD0Selection2; + particlecleaner::ConfD0Cleaner1 confD0Cleaner1; + particlecleaner::ConfD0Cleaner2 confD0Cleaner2; + charmhadronhistmanager::ConfD0Binning1 confD0Binning1; + charmhadronhistmanager::ConfD0Binning2 confD0Binning2; + + o2::framework::Partition d0Partition1 = MAKE_D0_PARTITION(confD0Selection1); + o2::framework::Partition d0Partition2 = MAKE_D0_PARTITION(confD0Selection2); + o2::framework::Preslice perColD0s = o2::aod::femtobase::stored::fColId; + + o2::framework::Partition d0WithLabelPartition1 = MAKE_D0_PARTITION(confD0Selection1); + o2::framework::Partition d0WithLabelPartition2 = MAKE_D0_PARTITION(confD0Selection2); + o2::framework::Preslice perColD0sWithLabel = o2::aod::femtobase::stored::fColId; + + // setup pairs + pairhistmanager::ConfPairBinning confPairBinning; + pairhistmanager::ConfPairCuts confPairCuts; + + pairbuilder::PairD0D0Builder< + charmhadronhistmanager::PrefixD01, + trackhistmanager::PrefixD01PosDaughter, + trackhistmanager::PrefixD01NegDaughter, + charmhadronhistmanager::PrefixD02, + trackhistmanager::PrefixD02PosDaughter, + trackhistmanager::PrefixD02NegDaughter, + pairhistmanager::PrefixD0D0Se, + pairhistmanager::PrefixD0D0Me, + closepairrejection::PrefixD0D0PosSe, + closepairrejection::PrefixD0D0NegSe, + closepairrejection::PrefixD0D0PosMe, + closepairrejection::PrefixD0D0NegMe, + modes::CharmHadron::kD0, + modes::CharmHadron::kD0> + pairD0D0Builder; + + // setup mixing + std::vector defaultVtxBins{10, -10, 10}; + std::vector defaultMultBins{50, 0, 200}; + std::vector defaultCentBins{10, 0, 100}; + o2::framework::ColumnBinningPolicy mixBinsVtxMult{{defaultVtxBins, defaultMultBins}, true}; + o2::framework::ColumnBinningPolicy mixBinsVtxCent{{defaultVtxBins, defaultCentBins}, true}; + o2::framework::ColumnBinningPolicy mixBinsVtxMultCent{{defaultVtxBins, defaultMultBins, defaultCentBins}, true}; + pairhistmanager::ConfMixing confMixing; + + o2::framework::HistogramRegistry hRegistry{"FemtoD0D0", {}, o2::framework::OutputObjHandlingPolicy::AnalysisObject}; + + // setup cpr + closepairrejection::ConfCprD0DaugherD0DaughterPos confCprPos; + closepairrejection::ConfCprD0DaugherD0DaughterNeg confCprNeg; + + void init(o2::framework::InitContext&) + { + bool processData = doprocessSameEvent || doprocessMixedEvent; + bool processMc = doprocessSameEventMc || doprocessMixedEventMc; + + if (processData && processMc) { + LOG(fatal) << "Both data and mc processing is enabled. Breaking..."; + } + if (!processData && !processMc) { + LOG(fatal) << "Neither data nor mc processing is enabled. Breaking..."; + } + + // setup columnpolicy for binning + // default values are used during instantiation, so we need to explicity update them here + mixBinsVtxMult = {{confMixing.vtxBins.value, confMixing.multBins.value}, true}; + mixBinsVtxCent = {{confMixing.vtxBins.value, confMixing.centBins.value}, true}; + mixBinsVtxMultCent = {{confMixing.vtxBins.value, confMixing.multBins.value, confMixing.centBins.value}, true}; + + // setup histograms + std::map> colHistSpec; + std::map> posDauSpec1; + std::map> negDauSpec1; + std::map> posDauSpec2; + std::map> negDauSpec2; + std::map> d0HistSpec1; + std::map> d0HistSpec2; + std::map> pairD0D0HistSpec; + std::map> cprHistSpecPos = closepairrejection::makeCprHistSpecMap(confCprPos); + std::map> cprHistSpecNeg = closepairrejection::makeCprHistSpecMap(confCprNeg); + + if (processData) { + colHistSpec = colhistmanager::makeColHistSpecMap(confCollisionBinning); + posDauSpec1 = trackhistmanager::makeTrackHistSpecMap(confD01PosDauBinning); + negDauSpec1 = trackhistmanager::makeTrackHistSpecMap(confD01NegDauBinning); + posDauSpec2 = trackhistmanager::makeTrackHistSpecMap(confD02PosDauBinning); + negDauSpec2 = trackhistmanager::makeTrackHistSpecMap(confD02NegDauBinning); + d0HistSpec1 = charmhadronhistmanager::makeD0HistSpecMap(confD0Binning1); + d0HistSpec2 = charmhadronhistmanager::makeD0HistSpecMap(confD0Binning2); + pairD0D0HistSpec = pairhistmanager::makePairHistSpecMap(confPairBinning, confMixing); + pairD0D0Builder.init(&hRegistry, confCollisionBinning, confD0Selection1, confD0Selection2, confD0Cleaner1, confD0Cleaner2, confCprPos, confCprNeg, confMixing, confPairBinning, confPairCuts, colHistSpec, d0HistSpec1, d0HistSpec2, posDauSpec1, negDauSpec1, posDauSpec2, negDauSpec2, pairD0D0HistSpec, cprHistSpecPos, cprHistSpecNeg); + } else { + colHistSpec = colhistmanager::makeColMcHistSpecMap(confCollisionBinning); + posDauSpec1 = trackhistmanager::makeTrackMcHistSpecMap(confD01PosDauBinning); + negDauSpec1 = trackhistmanager::makeTrackMcHistSpecMap(confD01NegDauBinning); + posDauSpec2 = trackhistmanager::makeTrackMcHistSpecMap(confD02PosDauBinning); + negDauSpec2 = trackhistmanager::makeTrackMcHistSpecMap(confD02NegDauBinning); + d0HistSpec1 = charmhadronhistmanager::makeD0McHistSpecMap(confD0Binning1); + d0HistSpec2 = charmhadronhistmanager::makeD0McHistSpecMap(confD0Binning2); + pairD0D0HistSpec = pairhistmanager::makePairMcHistSpecMap(confPairBinning, confMixing); + pairD0D0Builder.init(&hRegistry, confCollisionBinning, confD0Selection1, confD0Selection2, confD0Cleaner1, confD0Cleaner2, confCprPos, confCprNeg, confMixing, confPairBinning, confPairCuts, colHistSpec, d0HistSpec1, d0HistSpec2, posDauSpec1, negDauSpec1, posDauSpec2, negDauSpec2, pairD0D0HistSpec, cprHistSpecPos, cprHistSpecNeg); + } + } + + void processSameEvent(FilteredFemtoCollision const& col, FemtoTracks const& tracks, FemtoD0s const& d0s) + { + pairD0D0Builder.processSameEvent(col, tracks, d0s, d0Partition1, d0Partition2, cache); + } + PROCESS_SWITCH(FemtoPairD0D0, processSameEvent, "Enable processing same event processing for d0-d0", true); + + void processSameEventMc(FilteredFemtoCollisionWithLabel const& col, o2::aod::FMcCols const& mcCols, FemtoTracksWithLabel const& tracks, FemtoD0sWithLabel const& d0s, FemtoMcParticlesWithLabel const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) + { + pairD0D0Builder.processSameEvent(col, mcCols, tracks, d0s, d0WithLabelPartition1, d0WithLabelPartition2, mcParticles, mcMothers, mcPartonicMothers, cache); + } + PROCESS_SWITCH(FemtoPairD0D0, processSameEventMc, "Enable processing same event processing for d0-d0 with mc information", false); + + void processMixedEvent(FilteredFemtoCollisions const& cols, FemtoTracks const& tracks, FemtoD0s const& /*d0s*/) + { + pairD0D0Builder.processMixedEvent(cols, tracks, d0Partition1, d0Partition2, cache, mixBinsVtxMult, mixBinsVtxCent, mixBinsVtxMultCent); + } + PROCESS_SWITCH(FemtoPairD0D0, processMixedEvent, "Enable processing mixed event processing for d0-d0", true); + + void processMixedEventMc(FilteredFemtoCollisionsWithLabel const& cols, o2::aod::FMcCols const& mcCols, FemtoTracksWithLabel const& tracks, FemtoD0sWithLabel const& /*d0s*/, FemtoMcParticlesWithLabel const& mcParticles, o2::aod::FMcMothers const& mcMothers, o2::aod::FMcPartMoths const& mcPartonicMothers) + { + pairD0D0Builder.processMixedEvent(cols, mcCols, tracks, d0WithLabelPartition1, d0WithLabelPartition2, mcParticles, mcMothers, mcPartonicMothers, cache, mixBinsVtxMult, mixBinsVtxCent, mixBinsVtxMultCent); + } + PROCESS_SWITCH(FemtoPairD0D0, processMixedEventMc, "Enable processing mixed event processing for d0-d0 with mc information", false); +}; + +o2::framework::WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& context) +{ + o2::framework::WorkflowSpec workflow{ + adaptAnalysisTask(context), + }; + return workflow; +} diff --git a/PWGCF/Femto/Tasks/femtoPairTrackD0.cxx b/PWGCF/Femto/Tasks/femtoPairTrackD0.cxx index 24f056a9659..61ab3340f73 100644 --- a/PWGCF/Femto/Tasks/femtoPairTrackD0.cxx +++ b/PWGCF/Femto/Tasks/femtoPairTrackD0.cxx @@ -85,7 +85,7 @@ struct FemtoPairTrackD0 { trackhistmanager::ConfD01NegDauBinning confNegDauBinning; // setup D0s - charmhadronbuilder::ConfD0Selection d0Selection; + charmhadronbuilder::ConfD0Selection1 d0Selection; charmhadronhistmanager::ConfD0Binning1 confD0Binning; particlecleaner::ConfD0Cleaner1 confD0Cleaner; From c7720913ad75e49ca51e067714f214724145d955 Mon Sep 17 00:00:00 2001 From: ptak Date: Mon, 3 Aug 2026 17:01:10 +0200 Subject: [PATCH 2/2] [PWGCF] Femto: Reject ML-ambiguous D0/D0bar candidates --- PWGCF/Femto/Core/charmHadronBuilder.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/PWGCF/Femto/Core/charmHadronBuilder.h b/PWGCF/Femto/Core/charmHadronBuilder.h index 25664974273..83b2121fcbf 100644 --- a/PWGCF/Femto/Core/charmHadronBuilder.h +++ b/PWGCF/Femto/Core/charmHadronBuilder.h @@ -60,6 +60,7 @@ struct ConfD0Filters : o2::framework::ConfigurableGroup { // invariant-mass window o2::framework::Configurable massMin{"massMin", 1.7f, "Minimum invariant mass for D0"}; o2::framework::Configurable massMax{"massMax", 2.0f, "Maximum invariant mass for D0"}; + o2::framework::Configurable rejectAmbiguousHypothesis{"rejectAmbiguousHypothesis", false, "Reject candidates selected under both D0 and D0bar hypotheses"}; }; // derived selection bits for D0s @@ -131,6 +132,7 @@ enum D0Filters { kYMax, kMassMin, kMassMax, + kRejectAmbiguous, kD0FiltersMax }; @@ -148,7 +150,8 @@ const std::unordered_map d0FilterNames = { {kYMin, "Minimum rapidity"}, {kYMax, "Maximum rapidity"}, {kMassMin, "Minimum invariant mass"}, - {kMassMax, "Maximum invariant mass"}}; + {kMassMax, "Maximum invariant mass"}, + {kRejectAmbiguous, "Reject ambiguous D0/D0bar hypothesis"}}; template class D0Selection : public baseselection::BaseSelection @@ -173,6 +176,7 @@ class D0Selection : public baseselection::BaseSelectionaddSelection(kCpaMin, d0SelectionNames.at(kCpaMin), config.cpaMin.value, limits::kLowerLimit, true, true, false); this->addSelection(kDecayLengthMin, d0SelectionNames.at(kDecayLengthMin), config.decayLengthMin.value, limits::kLowerLimit, true, true, false); @@ -195,6 +199,7 @@ class D0Selection : public baseselection::BaseSelection(mRejectAmbiguousHypothesis)}, }); } @@ -229,6 +234,16 @@ class D0Selection : public baseselection::BaseSelectiontemplate fillFilter(kHypothesis, p); pass &= p; + bool competing = false; + if constexpr (modes::isEqual(hadronType, modes::CharmHadron::kD0)) { + competing = d0candidate.isSelD0bar(); + } else { + competing = d0candidate.isSelD0(); + } + p = !mRejectAmbiguousHypothesis || !competing; + this->template fillFilter(kRejectAmbiguous, p); + pass &= p; + p = d0candidate.pt() > mPtMin; this->template fillFilter(kPtMin, p); pass &= p; @@ -292,6 +307,7 @@ class D0Selection : public baseselection::BaseSelection