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