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,6 +121,8 @@ 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 1 + 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-collision Poisson multiplicity sampling" );
123126
124127 // Filters and input definitions
125128 Filter collisionZVtxFilter = nabs(aod::collision::posZ) < cfgCutVertex;
@@ -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
@@ -389,11 +393,12 @@ struct FilterCF {
389393 }
390394
391395 template <typename TCollision, typename TTracks>
392- float getCorrectedMultiplicity (const TCollision& collision, const TTracks& tracks, uint64_t timestamp)
396+ float getCorrectedMultiplicity (const TCollision& collision, const TTracks& tracks, uint64_t timestamp, uint64_t globalBC, int runNumber )
393397 {
394398 auto * efficiency = loadMultiplicityEfficiency (timestamp);
395399 double correctedMultiplicity = 0 .;
396400 size_t skippedTracks = 0 ;
401+ double totalMeanMissing = 0 .;
397402 for (const auto & track : tracks) {
398403 // Match the tracks written by the corresponding data/MC producer path.
399404 if (!isTrackSelected (track, true )) {
@@ -403,14 +408,39 @@ struct FilterCF {
403408 // Keep the original estimator as the map coordinate, including for centrality.
404409 const std::array<double , MultiplicityEfficiencyDimensions> values{track.eta (), track.pt (), collision.multiplicity (), collision.posZ ()};
405410 const double eff = efficiency->GetBinContent (efficiency->GetBin (values.data ()));
406- if (!std::isfinite (eff) || eff <= 0 .) {
411+ if (!std::isfinite (eff) || eff <= 0 . || (cfgMultiplicityPoisson && eff > 1 .) ) {
407412 ++skippedTracks;
408413 continue ;
409414 }
410- correctedMultiplicity += 1 . / eff;
415+ if (cfgMultiplicityPoisson) {
416+ correctedMultiplicity += 1 .;
417+ totalMeanMissing += 1 . / eff - 1 .;
418+ } else {
419+ correctedMultiplicity += 1 . / eff;
420+ }
421+ }
422+ if (cfgMultiplicityPoisson) {
423+ if (!std::isfinite (totalMeanMissing) || totalMeanMissing >= static_cast <double >(std::numeric_limits<uint64_t >::max ())) {
424+ LOGF (fatal, " Poisson multiplicity mean exceeds the sampling range: %g" , totalMeanMissing);
425+ return 0 .f ;
426+ }
427+ // Sample from sum of missing count (Poisson sum rule)
428+ if (totalMeanMissing > 0 .) {
429+ // Reseed per collision
430+ const auto collisionIndex = static_cast <uint64_t >(collision.globalIndex ());
431+ std::seed_seq seed{cfgMultiplicityPoissonSeed.value , static_cast <uint32_t >(runNumber),
432+ static_cast <uint32_t >(globalBC), static_cast <uint32_t >(globalBC >> 32 ),
433+ static_cast <uint32_t >(collisionIndex), static_cast <uint32_t >(collisionIndex >> 32 )};
434+ // Speed up the seeding by creating one 64bit seed from the event information
435+ std::array<uint32_t , 2 > seedWords{};
436+ seed.generate (seedWords.begin (), seedWords.end ());
437+ const uint64_t eventSeed = (static_cast <uint64_t >(seedWords[0 ]) << std::numeric_limits<uint32_t >::digits) | seedWords[1 ];
438+ mMultiplicityGenerator .seed (eventSeed);
439+ correctedMultiplicity += std::poisson_distribution<uint64_t >(totalMeanMissing)(mMultiplicityGenerator );
440+ }
411441 }
412442 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 ()));
443+ LOGF (warning, " Skipped %zu tracks with invalid or unsupported efficiency while correcting collision %lld" , skippedTracks, static_cast <int64_t >(collision.globalIndex ()));
414444 }
415445 if (!std::isfinite (correctedMultiplicity) || correctedMultiplicity > std::numeric_limits<float >::max ()) {
416446 LOGF (fatal, " Corrected multiplicity cannot be represented as a float: %g" , correctedMultiplicity);
@@ -454,7 +484,7 @@ struct FilterCF {
454484 auto bc = collision.template bc_as <aod::BCsWithTimestamps>();
455485 outputCollisions (bc.runNumber (), collision.posZ (), collision.multiplicity (), bc.timestamp ());
456486 if (!cfgEfficiencyMultiplicity.value .empty ()) {
457- outputCollisionsExtra (getCorrectedMultiplicity (collision, tracks, bc.timestamp ()));
487+ outputCollisionsExtra (getCorrectedMultiplicity (collision, tracks, bc.timestamp (), bc. globalBC (), bc. runNumber () ));
458488 }
459489
460490 if constexpr (std::experimental::is_detected<HasMultTables, C1 >::value) {
@@ -652,7 +682,7 @@ struct FilterCF {
652682 // NOTE works only when we store all MC collisions (as we do here)
653683 outputCollisions (bc.runNumber (), collision.posZ (), collision.multiplicity (), bc.timestamp ());
654684 if (!cfgEfficiencyMultiplicity.value .empty ()) {
655- outputCollisionsExtra (getCorrectedMultiplicity (collision, groupedTracks, bc.timestamp ()));
685+ outputCollisionsExtra (getCorrectedMultiplicity (collision, groupedTracks, bc.timestamp (), bc. globalBC (), bc. runNumber () ));
656686 }
657687
658688 const auto mcCollisionId = collision.mcCollisionId ();
0 commit comments