Skip to content

Commit 37a9123

Browse files
Implement candidate mixing configuration and processing
Added mixing configuration for candidate creation and updated related processing functions.
1 parent eaf70c7 commit 37a9123

1 file changed

Lines changed: 231 additions & 3 deletions

File tree

PWGHF/TableProducer/candidateCreator3Prong.cxx

Lines changed: 231 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,21 @@
6969

7070
#include <Rtypes.h>
7171

72+
#include <algorithm>
7273
#include <array>
74+
#include <cmath>
7375
#include <cstddef>
7476
#include <cstdint>
7577
#include <cstdlib>
78+
#include <deque>
79+
#include <map>
7680
#include <memory>
7781
#include <numeric>
82+
#include <set>
7883
#include <stdexcept>
7984
#include <string>
85+
#include <tuple>
86+
#include <type_traits>
8087
#include <utility>
8188
#include <vector>
8289

@@ -140,6 +147,16 @@ struct HfCandidateCreator3Prong {
140147
Configurable<bool> createLc{"createLc", false, "enable Lc+/- candidate creation"};
141148
Configurable<bool> createXic{"createXic", false, "enable Xic+/- candidate creation"};
142149
Configurable<bool> createCharmNuclei{"createCharmNuclei", false, "enable createCharmNuclei candidate creation"};
150+
struct : ConfigurableGroup {
151+
std::string prefix = "mixing";
152+
Configurable<bool> enabled{"enabled", false, "Enable Cd event mixing in the existing no-PV-refit DCA data process"};
153+
Configurable<int> type{"type", 0, "Replace 0: deuteron, 1: kaon, 2: pion"};
154+
Configurable<int> depth{"depth", 5, "Previous distinct events per pool within a dataframe"};
155+
Configurable<float> zBinWidth{"zBinWidth", 1.f, "PV z pool width (cm); no track translation"};
156+
Configurable<int> pvMultBinWidth{"pvMultBinWidth", 20, "PV track multiplicity pool width; used only without centrality"};
157+
Configurable<float> centralityBinWidth{"centralityBinWidth", 10.f, "Centrality pool width (%); replaces PV multiplicity binning in FT0C/FT0M modes"};
158+
} mixing;
159+
143160
// KF
144161
Configurable<bool> applyTopoConstraint{"applyTopoConstraint", false, "apply origin from PV hypothesis for created candidate, works only in KF mode"};
145162
Configurable<bool> applyInvMassConstraint{"applyInvMassConstraint", false, "apply particle type hypothesis to recalculate created candidate's momentum, works only in KF mode"};
@@ -178,6 +195,31 @@ struct HfCandidateCreator3Prong {
178195
if ((std::accumulate(doprocessDF.begin(), doprocessDF.end(), 0) + std::accumulate(doprocessKF.begin(), doprocessKF.end(), 0)) != 1) {
179196
LOGP(fatal, "One and only one process function must be enabled at a time.");
180197
}
198+
if (mixing.enabled) {
199+
constexpr int LastMixingType{2};
200+
if (!(doprocessNoPvRefitWithDCAFitterN || doprocessNoPvRefitWithDCAFitterNCentFT0C || doprocessNoPvRefitWithDCAFitterNCentFT0M) ||
201+
!createCharmNuclei || createDplus || createDs || createLc || createXic) {
202+
LOGP(fatal, "Cd mixing requires a no-PV-refit DCA data process and only createCharmNuclei enabled.");
203+
}
204+
if (mixing.type < 0 || mixing.type > LastMixingType || mixing.depth <= 0 ||
205+
!std::isfinite(mixing.zBinWidth.value) || mixing.zBinWidth <= 0.f || (doprocessNoPvRefitWithDCAFitterN && mixing.pvMultBinWidth <= 0) ||
206+
!std::isfinite(mixing.centralityBinWidth.value) || mixing.centralityBinWidth <= 0.f) {
207+
LOGP(fatal, "Invalid mixing configuration.");
208+
}
209+
registry.add("Mixing/hCounter", "Cd mixing;stage;Entries", HistType::kTH1D, {{5, 0., 5.}});
210+
registry.get<TH1>(HIST("Mixing/hCounter"))->GetXaxis()->SetBinLabel(1, "seed candidates");
211+
registry.get<TH1>(HIST("Mixing/hCounter"))->GetXaxis()->SetBinLabel(2, "accepted events");
212+
registry.get<TH1>(HIST("Mixing/hCounter"))->GetXaxis()->SetBinLabel(3, "candidate pairs");
213+
registry.get<TH1>(HIST("Mixing/hCounter"))->GetXaxis()->SetBinLabel(4, "unique fit attempts");
214+
registry.get<TH1>(HIST("Mixing/hCounter"))->GetXaxis()->SetBinLabel(5, "written candidates");
215+
const AxisSpec partnerAxis{mixing.depth.value + 1, -0.5, mixing.depth.value + 0.5};
216+
registry.add("Mixing/hPoolSize", "Buffered events before insertion;Pool size;Event-charge entries", HistType::kTH1D, {partnerAxis});
217+
registry.add("Mixing/hNPartners", "Partners with at least one usable candidate pair;Partners;Event-charge entries", HistType::kTH1D, {partnerAxis});
218+
registry.add("Mixing/hEventPairDeltaPVZ", "One entry per usable event pair and charge;z_{current}-z_{previous} (cm);Event pairs", HistType::kTH1D, {{200, -10., 10.}});
219+
registry.add("Mixing/hEventPairCentrality", "One entry per usable event pair and charge;Current centrality (%);Previous centrality (%)", HistType::kTH2D, {{102, -1.5, 100.5}, {102, -1.5, 100.5}});
220+
registry.add("Mixing/hPoolOccupancy", "Occupancy at end of dataframe;Buffered events;Pools", HistType::kTH1D, {partnerAxis});
221+
registry.add("Mixing/hDeltaPVZ", "Mixed event PV difference;z_{A}-z_{B} (cm);Candidate pairs", HistType::kTH1F, {{200, -10., 10.}});
222+
}
181223
std::array<bool, 4> processesCollisions = {doprocessCollisions, doprocessCollisionsCentFT0C, doprocessCollisionsCentFT0M, doprocessCollisionsUpc};
182224
const int nProcessesCollisions = std::accumulate(processesCollisions.begin(), processesCollisions.end(), 0);
183225

@@ -715,6 +757,180 @@ struct HfCandidateCreator3Prong {
715757
/// ///
716758
///////////////////////////////////
717759

760+
// In-memory adapter only: no new AOD table and no changes to the ordinary fitter.
761+
template <typename TSeed, typename TTracks>
762+
struct HfMixed3ProngSeed {
763+
TSeed source;
764+
TTracks const* tracks;
765+
std::array<int64_t, 3> ids;
766+
uint8_t flag;
767+
template <typename T>
768+
auto collision_as() const
769+
{
770+
return source.template collision_as<T>();
771+
}
772+
template <typename T>
773+
auto prong0_as() const
774+
{
775+
return tracks->rawIteratorAt(ids[0]);
776+
}
777+
template <typename T>
778+
auto prong1_as() const
779+
{
780+
return tracks->rawIteratorAt(ids[1]);
781+
}
782+
template <typename T>
783+
auto prong2_as() const
784+
{
785+
return tracks->rawIteratorAt(ids[2]);
786+
}
787+
int64_t prong0Id() const { return ids[0]; }
788+
int64_t prong1Id() const { return ids[1]; }
789+
int64_t prong2Id() const { return ids[2]; }
790+
uint8_t hfflag() const { return flag; }
791+
};
792+
793+
template <CentralityEstimator CentEstimator, typename TCollisions, typename TCandidates, typename TTracks, typename TBCs>
794+
void runCreator3ProngMixedWithDCAFitterN(TCollisions const& collisions, TCandidates const& candidates, TTracks const& tracks, TBCs const& bcs, uint8_t channelFlag)
795+
{
796+
constexpr int LastProng{2};
797+
using Seed = std::decay_t<decltype(*candidates.begin())>;
798+
using Event = std::vector<Seed>;
799+
using PoolKey = std::tuple<int, int, int, int, int>; // run, charge, PV z, PV multiplicity OR centrality (unused bin is zero)
800+
std::map<int64_t, Event> events;
801+
std::map<PoolKey, std::deque<Event>> pools;
802+
std::set<std::array<int64_t, 4>> seedKeys;
803+
std::map<std::array<int64_t, 4>, Seed> mixedCandidates; // reference collision and ordered prong track IDs
804+
805+
// These are skim candidates, before final topology/PID/BDT selection.
806+
// Enumerate both d hypotheses for each source below, including cross-prong exchanges.
807+
for (const auto& seed : candidates) {
808+
if (!(seed.hfflag() & channelFlag)) {
809+
continue;
810+
}
811+
std::array<int64_t, 4> key{seed.collisionId(), seed.prong0Id(), seed.prong1Id(), seed.prong2Id()};
812+
if (seedKeys.insert(key).second) {
813+
events[seed.collisionId()].push_back(seed);
814+
registry.fill(HIST("Mixing/hCounter"), 0.5);
815+
}
816+
}
817+
818+
for (const auto& [collisionId, event] : events) {
819+
auto collision = event.front().template collision_as<TCollisions>();
820+
float centrality{-1.f};
821+
if (hfEvSel.getHfCollisionRejectionMask<true, CentEstimator, TBCs>(collision, centrality, ccdb, registry) != 0) {
822+
continue;
823+
}
824+
if (!std::isfinite(collision.posZ())) {
825+
continue;
826+
}
827+
registry.fill(HIST("Mixing/hCounter"), 1.5);
828+
const int eventRun = collision.template bc_as<TBCs>().runNumber();
829+
const int zBin = static_cast<int>(std::floor(collision.posZ() / mixing.zBinWidth));
830+
int multBin = 0;
831+
if constexpr (CentEstimator == CentralityEstimator::None) {
832+
multBin = collision.multNTracksPV() / mixing.pvMultBinWidth;
833+
}
834+
int centralityBin = 0;
835+
if constexpr (CentEstimator != CentralityEstimator::None) {
836+
if (!std::isfinite(centrality) || centrality < 0.f) {
837+
continue;
838+
}
839+
centralityBin = static_cast<int>(std::floor(centrality / mixing.centralityBinWidth));
840+
}
841+
842+
// Separate matter/antimatter within each accepted event before adding it to pools.
843+
for (const int& charge : {-1, 1}) {
844+
Event seeds;
845+
for (const auto& seed : event) {
846+
auto t0 = seed.template prong0_as<TTracks>();
847+
auto t1 = seed.template prong1_as<TTracks>();
848+
auto t2 = seed.template prong2_as<TTracks>();
849+
if (t0.sign() == charge && t1.sign() == -charge && t2.sign() == charge &&
850+
t0.globalIndex() != t2.globalIndex()) {
851+
seeds.push_back(seed);
852+
}
853+
}
854+
if (seeds.empty()) {
855+
continue;
856+
}
857+
auto& pool = pools[PoolKey{eventRun, charge, zBin, multBin, centralityBin}];
858+
registry.fill(HIST("Mixing/hPoolSize"), pool.size());
859+
int nPartners = 0;
860+
for (const auto& previousEvent : pool) {
861+
bool hasUsablePair = false;
862+
for (const auto& seedA : seeds) {
863+
for (const auto& seedB : previousEvent) {
864+
if (seedA.collisionId() == seedB.collisionId()) {
865+
continue;
866+
}
867+
std::array<int64_t, 3> idsA{seedA.prong0Id(), seedA.prong1Id(), seedA.prong2Id()};
868+
std::array<int64_t, 3> idsB{seedB.prong0Id(), seedB.prong1Id(), seedB.prong2Id()};
869+
bool sharesTrack = false;
870+
for (const auto& id : idsA) {
871+
sharesTrack |= std::find(idsB.begin(), idsB.end(), id) != idsB.end();
872+
}
873+
if (sharesTrack) {
874+
continue;
875+
}
876+
hasUsablePair = true;
877+
registry.fill(HIST("Mixing/hCounter"), 2.5);
878+
auto collisionB = seedB.template collision_as<TCollisions>();
879+
registry.fill(HIST("Mixing/hDeltaPVZ"), collision.posZ() - collisionB.posZ());
880+
881+
auto buildMixed = [&](auto const& referenceCollision, auto const& referenceSeed,
882+
std::array<int64_t, 3> ids, std::array<int64_t, 3> const& donorIds,
883+
int deuteronProng, int donorDeuteronProng) {
884+
const int replacedProng = mixing.type == 0 ? deuteronProng : (mixing.type == 1 ? 1 : LastProng - deuteronProng);
885+
const int donorProng = mixing.type == 0 ? donorDeuteronProng : (mixing.type == 1 ? 1 : LastProng - donorDeuteronProng);
886+
ids[replacedProng] = donorIds[donorProng];
887+
// Preserve prong positions; the selector evaluates both mass hypotheses.
888+
std::array<int64_t, 4> key{referenceCollision.globalIndex(), ids[0], ids[1], ids[2]};
889+
mixedCandidates.try_emplace(key, referenceSeed);
890+
};
891+
for (const int& deuteronProngA : {0, LastProng}) {
892+
for (const int& deuteronProngB : {0, LastProng}) {
893+
buildMixed(collision, seedA, idsA, idsB, deuteronProngA, deuteronProngB);
894+
buildMixed(collisionB, seedB, idsB, idsA, deuteronProngB, deuteronProngA);
895+
}
896+
}
897+
}
898+
}
899+
if (hasUsablePair) {
900+
++nPartners;
901+
auto previousCollision = previousEvent.front().template collision_as<TCollisions>();
902+
registry.fill(HIST("Mixing/hEventPairDeltaPVZ"), collision.posZ() - previousCollision.posZ());
903+
float previousCentrality = -1.f;
904+
if constexpr (CentEstimator == CentralityEstimator::FT0C) {
905+
previousCentrality = previousCollision.centFT0C();
906+
} else if constexpr (CentEstimator == CentralityEstimator::FT0M) {
907+
previousCentrality = previousCollision.centFT0M();
908+
}
909+
registry.fill(HIST("Mixing/hEventPairCentrality"), centrality, previousCentrality);
910+
}
911+
}
912+
registry.fill(HIST("Mixing/hNPartners"), nPartners);
913+
pool.push_back(std::move(seeds));
914+
if (pool.size() > static_cast<size_t>(mixing.depth.value)) {
915+
pool.pop_front();
916+
}
917+
}
918+
}
919+
for (const auto& entry : pools) {
920+
registry.fill(HIST("Mixing/hPoolOccupancy"), entry.second.size());
921+
}
922+
// The ordinary reconstruction function consumes these adapters without any changes.
923+
std::vector<HfMixed3ProngSeed<Seed, TTracks>> mixedSeeds;
924+
mixedSeeds.reserve(mixedCandidates.size());
925+
for (const auto& [key, source] : mixedCandidates) {
926+
mixedSeeds.push_back({source, &tracks, {key[1], key[2], key[3]}, channelFlag});
927+
registry.fill(HIST("Mixing/hCounter"), 3.5);
928+
}
929+
const auto firstIndex = rowCandidateBase.lastIndex();
930+
runCreator3ProngWithDCAFitterN<false, false, CentEstimator>(collisions, mixedSeeds, tracks, bcs);
931+
registry.fill(HIST("Mixing/hCounter"), 4.5, rowCandidateBase.lastIndex() - firstIndex);
932+
}
933+
718934
/// @brief process function using DCA fitter w/ PV refit and w/o centrality selections
719935
void processPvRefitWithDCAFitterN(soa::Join<aod::Collisions, aod::EvSels, aod::PVMults> const& collisions,
720936
FilteredPvRefitHf3Prongs const& rowsTrackIndexProng3,
@@ -731,7 +947,11 @@ struct HfCandidateCreator3Prong {
731947
TracksWCovExtraPidPiKaPrLightNuclei const& tracks,
732948
aod::BCsWithTimestamps const& bcWithTimeStamps)
733949
{
734-
runCreator3ProngWithDCAFitterN</*doPvRefit*/ false, false, CentralityEstimator::None>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps);
950+
if (mixing.enabled) {
951+
runCreator3ProngMixedWithDCAFitterN<CentralityEstimator::None>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps, static_cast<uint8_t>(BIT(DecayType::CdToDeKPi)));
952+
} else {
953+
runCreator3ProngWithDCAFitterN</*doPvRefit*/ false, /*applyUpcSel*/ false, CentralityEstimator::None>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps);
954+
}
735955
}
736956
PROCESS_SWITCH(HfCandidateCreator3Prong, processNoPvRefitWithDCAFitterN, "Run candidate creator using DCA fitter without PV refit and w/o centrality selections", true);
737957

@@ -777,7 +997,11 @@ struct HfCandidateCreator3Prong {
777997
TracksWCovExtraPidPiKaPrLightNuclei const& tracks,
778998
aod::BCsWithTimestamps const& bcWithTimeStamps)
779999
{
780-
runCreator3ProngWithDCAFitterN</*doPvRefit*/ false, false, CentralityEstimator::FT0C>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps);
1000+
if (mixing.enabled) {
1001+
runCreator3ProngMixedWithDCAFitterN<CentralityEstimator::FT0C>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps, static_cast<uint8_t>(BIT(DecayType::CdToDeKPi)));
1002+
} else {
1003+
runCreator3ProngWithDCAFitterN</*doPvRefit*/ false, /*applyUpcSel*/ false, CentralityEstimator::FT0C>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps);
1004+
}
7811005
}
7821006
PROCESS_SWITCH(HfCandidateCreator3Prong, processNoPvRefitWithDCAFitterNCentFT0C, "Run candidate creator using DCA fitter without PV refit and w/ centrality selection on FT0C", false);
7831007

@@ -823,7 +1047,11 @@ struct HfCandidateCreator3Prong {
8231047
TracksWCovExtraPidPiKaPrLightNuclei const& tracks,
8241048
aod::BCsWithTimestamps const& bcWithTimeStamps)
8251049
{
826-
runCreator3ProngWithDCAFitterN</*doPvRefit*/ false, false, CentralityEstimator::FT0M>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps);
1050+
if (mixing.enabled) {
1051+
runCreator3ProngMixedWithDCAFitterN<CentralityEstimator::FT0M>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps, static_cast<uint8_t>(BIT(DecayType::CdToDeKPi)));
1052+
} else {
1053+
runCreator3ProngWithDCAFitterN</*doPvRefit*/ false, /*applyUpcSel*/ false, CentralityEstimator::FT0M>(collisions, rowsTrackIndexProng3, tracks, bcWithTimeStamps);
1054+
}
8271055
}
8281056
PROCESS_SWITCH(HfCandidateCreator3Prong, processNoPvRefitWithDCAFitterNCentFT0M, "Run candidate creator using DCA fitter without PV refit and w/ centrality selection on FT0M", false);
8291057

0 commit comments

Comments
 (0)