5555#include < experimental/type_traits> // required for is_detected
5656#include < limits>
5757#include < memory>
58+ #include < random>
5859#include < string>
5960#include < type_traits>
6061#include < vector>
@@ -120,14 +121,16 @@ struct FilterCF {
120121 O2_DEFINE_CONFIGURABLE (cfgEfficiencyMultiplicity, std::string, " " , " Multiplicity efficiency (RecoAll / MC): CCDB path or local ROOT file with a 4D ccdb_object (eta, pT, multiplicity, z-vtx); empty disables CFCollisionsExtra output" );
121122 O2_DEFINE_CONFIGURABLE (cfgLocalEfficiency, int , 0 , " 0 = CCDB efficiency, 1 = local ROOT efficiency" );
122123 O2_DEFINE_CONFIGURABLE (cfgMultiplicityTrackBitMask, uint16_t , 0 , " Required track-type bits for corrected multiplicity; match cfgTrackBitMask used to produce the efficiency (0 = all stored tracks)" );
124+ O2_DEFINE_CONFIGURABLE (cfgMultiplicityPoisson, bool , false , " Use reproducibly seeded Nvalid + Poisson(sum(1/eff - 1)) for multiplicity correction; skips eff > 1 tracks" );
125+ O2_DEFINE_CONFIGURABLE (cfgMultiplicityPoissonSeed, uint32_t , 1 , " Base seed for reproducible per-dataframe Poisson multiplicity sampling" );
123126
124127 // Filters and input definitions
125128 Filter collisionZVtxFilter = nabs(aod::collision::posZ) < cfgCutVertex;
126129 Filter collisionVertexTypeFilter = (cfgCollisionFlags == 0 ) || ((aod::collision::flags & cfgCollisionFlags) == cfgCollisionFlags);
127130
128131 // TODO how to have this in the second task? For now they are copied
129132 Filter trackFilter = (nabs(aod::track::eta) < cfgCutEta) && (aod::track::pt > cfgCutPt);
130- Filter trackSelection = (requireGlobalTrackInFilter()) || (aod::track::isGlobalTrackSDD == ( uint8_t ) true );
133+ Filter trackSelection = (requireGlobalTrackInFilter()) || (aod::track::isGlobalTrackSDD == static_cast < uint8_t >( true ) );
131134
132135 Filter mcCollisionFilter = nabs(aod::mccollision::posZ) < cfgCutVertex;
133136
@@ -160,6 +163,7 @@ struct FilterCF {
160163 // Own local histograms independently of their input file. CCDB owns its objects.
161164 std::unique_ptr<THn> localMultiplicityEfficiency;
162165 THn* mEfficiency = nullptr ;
166+ std::mt19937_64 mMultiplicityGenerator ;
163167 static constexpr int MultiplicityEfficiencyDimensions = 4 ;
164168
165169 // persistent caches
@@ -168,6 +172,9 @@ struct FilterCF {
168172
169173 void init (InitContext& initContext)
170174 {
175+ if (cfgMultiplicityPoisson && !doprocessDataframeSeed) {
176+ LOGF (fatal, " Poisson multiplicity sampling requires processDataframeSeed" );
177+ }
171178 if (!cfgEfficiencyMultiplicity.value .empty ()) {
172179 bool processTracksEnabled = false ;
173180 if (!o2::common::core::getTaskOptionValue (initContext, " multiplicity-selector" , " processTracks" , processTracksEnabled, false )) {
@@ -394,6 +401,7 @@ struct FilterCF {
394401 auto * efficiency = loadMultiplicityEfficiency (timestamp);
395402 double correctedMultiplicity = 0 .;
396403 size_t skippedTracks = 0 ;
404+ double totalMeanMissing = 0 .;
397405 for (const auto & track : tracks) {
398406 // Match the tracks written by the corresponding data/MC producer path.
399407 if (!isTrackSelected (track, true )) {
@@ -403,14 +411,29 @@ struct FilterCF {
403411 // Keep the original estimator as the map coordinate, including for centrality.
404412 const std::array<double , MultiplicityEfficiencyDimensions> values{track.eta (), track.pt (), collision.multiplicity (), collision.posZ ()};
405413 const double eff = efficiency->GetBinContent (efficiency->GetBin (values.data ()));
406- if (!std::isfinite (eff) || eff <= 0 .) {
414+ if (!std::isfinite (eff) || eff <= 0 . || (cfgMultiplicityPoisson && eff > 1 .) ) {
407415 ++skippedTracks;
408416 continue ;
409417 }
410- correctedMultiplicity += 1 . / eff;
418+ if (cfgMultiplicityPoisson) {
419+ correctedMultiplicity += 1 .;
420+ totalMeanMissing += 1 . / eff - 1 .;
421+ } else {
422+ correctedMultiplicity += 1 . / eff;
423+ }
424+ }
425+ if (cfgMultiplicityPoisson) {
426+ if (!std::isfinite (totalMeanMissing) || totalMeanMissing >= static_cast <double >(std::numeric_limits<uint64_t >::max ())) {
427+ LOGF (fatal, " Poisson multiplicity mean exceeds the sampling range: %g" , totalMeanMissing);
428+ return 0 .f ;
429+ }
430+ // Sample from sum of missing count (Poisson sum rule)
431+ if (totalMeanMissing > 0 .) {
432+ correctedMultiplicity += std::poisson_distribution<uint64_t >(totalMeanMissing)(mMultiplicityGenerator );
433+ }
411434 }
412435 if (cfgVerbosity > 0 && skippedTracks > 0 ) {
413- LOGF (warning, " Skipped %zu tracks with invalid efficiency while correcting collision %lld" , skippedTracks, static_cast <int64_t >(collision.globalIndex ()));
436+ LOGF (warning, " Skipped %zu tracks with invalid or unsupported efficiency while correcting collision %lld" , skippedTracks, static_cast <int64_t >(collision.globalIndex ()));
414437 }
415438 if (!std::isfinite (correctedMultiplicity) || correctedMultiplicity > std::numeric_limits<float >::max ()) {
416439 LOGF (fatal, " Corrected multiplicity cannot be represented as a float: %g" , correctedMultiplicity);
@@ -434,6 +457,27 @@ struct FilterCF {
434457 return true ;
435458 }
436459
460+ // This process must run before the collision processes below.
461+ void processDataframeSeed (aod::Origins const & origins)
462+ {
463+ if (!cfgMultiplicityPoisson) {
464+ return ;
465+ }
466+ if (origins.size () == 0 ) {
467+ LOGF (fatal, " Cannot seed Poisson multiplicity sampling without a dataframe ID" );
468+ return ;
469+ }
470+ const uint64_t dataframeId = origins.iteratorAt (0 ).dataframeID ();
471+ std::seed_seq seed{cfgMultiplicityPoissonSeed.value , static_cast <uint32_t >(dataframeId),
472+ static_cast <uint32_t >(dataframeId >> std::numeric_limits<uint32_t >::digits)};
473+ std::array<uint32_t , 2 > seedWords{};
474+ seed.generate (seedWords.begin (), seedWords.end ());
475+ // Speed up seeding by creating one 64-bit seed from the dataframe information.
476+ const uint64_t dataframeSeed = (static_cast <uint64_t >(seedWords[0 ]) << std::numeric_limits<uint32_t >::digits) | seedWords[1 ];
477+ mMultiplicityGenerator .seed (dataframeSeed);
478+ }
479+ PROCESS_SWITCH (FilterCF, processDataframeSeed, " Seed Poisson multiplicity sampling for each dataframe" , false );
480+
437481 template <class T >
438482 using HasMultTables = decltype (std::declval<T&>().multNTracksPV());
439483
@@ -815,7 +859,7 @@ struct MultiplicitySelector {
815859 O2_DEFINE_CONFIGURABLE (cfgCutEta, float , 0 .8f , " Eta range for tracks" );
816860
817861 Filter trackFilter = (nabs(aod::track::eta) < cfgCutEta) && (aod::track::pt > cfgCutPt);
818- Filter trackSelection = (requireGlobalTrackInFilter()) || (aod::track::isGlobalTrackSDD == ( uint8_t ) true );
862+ Filter trackSelection = (requireGlobalTrackInFilter()) || (aod::track::isGlobalTrackSDD == static_cast < uint8_t >( true ) );
819863
820864 void init (InitContext&)
821865 {
0 commit comments