@@ -186,7 +186,7 @@ struct RadialFlowDecorr {
186186
187187 Configurable<int > cfgSys{" cfgSys" , 1 , " Which collision system? 1-->PbPb, 2-->NeNe, 3-->OO, 4-->pp" };
188188 Configurable<int > cfgSystType{" cfgSystType" , 0 , " Systematic variation: 0=Base,1=systDCA,2=systEff,3=systFlat,4=systNEta,5=systNITS,6=systNTPC,7=systPileup,8=systVz,9=systEtaBinning" };
189- Configurable<int > cfgNBootstrap{" cfgNBootstrap" , 30 , " Number of Poisson bootstrap samples (base data run only)" };
189+ Configurable<int > cfgNBootstrap{" cfgNBootstrap" , 16 , " Number of Poisson bootstrap samples (base data run only)" };
190190 Configurable<int > cfgBootstrapSeed{" cfgBootstrapSeed" , 0 , " TRandom3 seed for bootstrap (0 = machine-random per job)" };
191191
192192 Configurable<bool > cfgFlat{" cfgFlat" , false , " Whether to use flattening weights" };
@@ -462,23 +462,68 @@ struct RadialFlowDecorr {
462462 }
463463
464464 // Inclusive efficiency/fake lookup (no species dependence).
465- float getEfficiency (float mult, float pt, float eta, int effidx, bool useEff) const
465+ float getEfficiency (float mult, float pt, float eta,
466+ int effidx, bool useEff) const
466467 {
467468 if (!useEff) {
468469 return (effidx == 0 ) ? 1 .0f : 0 .0f ;
469470 }
471+
472+ const float invalid =
473+ std::numeric_limits<float >::quiet_NaN ();
474+
470475 TH3F * h = (effidx == 0 ) ? state.hEff : state.hFake ;
471- if (!h) {
472- return -1 ;
476+
477+ if (!h || !std::isfinite (mult) ||
478+ !std::isfinite (pt) || !std::isfinite (eta)) {
479+ return invalid;
480+ }
481+
482+ int ibx = h->GetXaxis ()->FindFixBin (mult);
483+
484+ // Reject Nch underflow, clamp overflow.
485+ if (ibx < 1 ) {
486+ return invalid;
487+ }
488+
489+ ibx = std::min (ibx, h->GetNbinsX ());
490+
491+ int iby = h->GetYaxis ()->FindFixBin (pt);
492+ int ibz = h->GetZaxis ()->FindFixBin (eta);
493+
494+ // Do not extrapolate in pT or eta.
495+ if (iby < 1 || iby > h->GetNbinsY () ||
496+ ibz < 1 || ibz > h->GetNbinsZ ()) {
497+ return invalid;
498+ }
499+
500+ const double val = h->GetBinContent (ibx, iby, ibz);
501+ const double err = h->GetBinError (ibx, iby, ibz);
502+
503+ // A bin without a positive error is not a valid measurement.
504+ if (!std::isfinite (val) ||
505+ !std::isfinite (err) || err <= 0.0 ) {
506+ return invalid;
473507 }
474- int ibx = h->GetXaxis ()->FindBin (mult);
475- int iby = h->GetYaxis ()->FindBin (pt);
476- int ibz = h->GetZaxis ()->FindBin (eta);
477- float val = h->GetBinContent (ibx, iby, ibz);
478- if (effidx == 0 ) {
479- return (val > 0 .f ) ? val : 1 .0f ;
508+
509+ // Physical-domain checks.
510+ if (val < 0.0 || val > 1.0 ||
511+ (effidx == 0 && val == 0.0 )) {
512+ return invalid;
480513 }
481- return val;
514+
515+ return static_cast <float >(val);
516+ }
517+
518+ // Reject missing or invalid efficiency/fake corrections consistently in all passes.
519+ bool getValidEffFake (float mult, float pt, float eta, bool useEff,
520+ float & eff, float & fake) const
521+ {
522+ eff = getEfficiency (mult, pt, eta, 0 , useEff);
523+ fake = getEfficiency (mult, pt, eta, 1 , useEff);
524+ return std::isfinite (eff) && std::isfinite (fake) &&
525+ eff > KFloatEpsilon && eff <= 1 .f &&
526+ fake >= 0 .f && fake < 1 .f ;
482527 }
483528
484529 float getFlatteningWeight (float vz, float chg, float pt, float eta, float phi, bool useFlat) const
@@ -499,6 +544,139 @@ struct RadialFlowDecorr {
499544 return h->GetBinContent (bins.data ());
500545 }
501546
547+ TH3F * rebinNchMap (const TH3F * h, int sysConfig,
548+ const char * newName) const
549+ {
550+ if (!h) {
551+ return nullptr ;
552+ }
553+
554+ // PbPb: 100 tracks; NeNe, OO, pp: 10 tracks
555+ const int trackStep = (sysConfig == 1 ) ? 100 : 10 ;
556+
557+ const int nx = h->GetNbinsX ();
558+ const int ny = h->GetNbinsY ();
559+ const int nz = h->GetNbinsZ ();
560+
561+ const double originalWidth = h->GetXaxis ()->GetBinWidth (1 );
562+
563+ const int groupSize =
564+ static_cast <int >(std::lround (trackStep / originalWidth));
565+
566+ // Ensure the input binning allows exact 10/100-track groups
567+ if (groupSize < 1 ||
568+ std::abs (groupSize * originalWidth - trackStep) > 1 .e -6 ) {
569+ LOGF (fatal, " Nch bin width is incompatible with %d-track rebinning" ,
570+ trackStep);
571+ return nullptr ;
572+ }
573+
574+ for (int ix = 1 ; ix <= nx; ++ix) {
575+ if (std::abs (h->GetXaxis ()->GetBinWidth (ix) -
576+ originalWidth) > 1 .e -6 ) {
577+ LOGF (fatal, " Input Nch axis must have uniform bin widths" );
578+ return nullptr ;
579+ }
580+ }
581+
582+ // Construct the new Nch bin edges
583+ std::vector<double > xEdges;
584+ xEdges.push_back (h->GetXaxis ()->GetBinLowEdge (1 ));
585+
586+ for (int ix = 1 ; ix <= nx; ix += groupSize) {
587+ const int last = std::min (ix + groupSize - 1 , nx);
588+ xEdges.push_back (h->GetXaxis ()->GetBinUpEdge (last));
589+ }
590+
591+ // Preserve the original pT and eta axes exactly
592+ auto copyEdges = [](const TAxis* axis) {
593+ std::vector<double > edges;
594+
595+ for (int i = 1 ; i <= axis->GetNbins (); ++i) {
596+ edges.push_back (axis->GetBinLowEdge (i));
597+ }
598+
599+ edges.push_back (axis->GetBinUpEdge (axis->GetNbins ()));
600+ return edges;
601+ };
602+
603+ const auto yEdges = copyEdges (h->GetYaxis ());
604+ const auto zEdges = copyEdges (h->GetZaxis ());
605+
606+ const int nNewX = static_cast <int >(xEdges.size ()) - 1 ;
607+
608+ auto * rebinned = new TH3F (
609+ newName, h->GetTitle (),
610+ nNewX, xEdges.data (),
611+ ny, yEdges.data (),
612+ nz, zEdges.data ());
613+
614+ rebinned->SetDirectory (nullptr );
615+ rebinned->Sumw2 ();
616+
617+ // Rebin independently for each (pT, eta) slice
618+ for (int iy = 1 ; iy <= ny; ++iy) {
619+ for (int iz = 1 ; iz <= nz; ++iz) {
620+
621+ int lastValid = 0 ;
622+ double lastValue = 0 .;
623+ double lastError = 0 .;
624+
625+ for (int ib = 1 ; ib <= nNewX; ++ib) {
626+
627+ const int first = (ib - 1 ) * groupSize + 1 ;
628+ const int last = std::min (first + groupSize - 1 , nx);
629+
630+ double sumW = 0 .;
631+ double sumVW = 0 .;
632+
633+ for (int ix = first; ix <= last; ++ix) {
634+
635+ const double v = h->GetBinContent (ix, iy, iz);
636+ const double e = h->GetBinError (ix, iy, iz);
637+
638+ // Inverse-variance weighting requires a positive error
639+ if (!std::isfinite (v) ||
640+ !std::isfinite (e) || e <= 0 .) {
641+ continue ;
642+ }
643+
644+ const double weight = 1 . / (e * e);
645+
646+ sumW += weight;
647+ sumVW += v * weight;
648+ }
649+
650+ if (sumW > 0 .) {
651+
652+ const double avg = sumVW / sumW;
653+ const double err = std::sqrt (1 . / sumW);
654+
655+ rebinned->SetBinContent (ib, iy, iz, avg);
656+ rebinned->SetBinError (ib, iy, iz, err);
657+
658+ lastValid = ib;
659+ lastValue = avg;
660+ lastError = err;
661+ }
662+ }
663+
664+ // Extend the final valid value into trailing empty bins.
665+ // This also allows safe overflow clamping at readout.
666+ for (int ib = lastValid + 1 ; ib <= nNewX; ++ib) {
667+ if (lastValid == 0 ) {
668+ break ;
669+ }
670+
671+ rebinned->SetBinContent (ib, iy, iz, lastValue);
672+ rebinned->SetBinError (ib, iy, iz, lastError);
673+ }
674+ }
675+ }
676+
677+ return rebinned;
678+ }
679+
502680 std::vector<o2::detectors::AlignParam>* offsetFT0 = nullptr ;
503681 uint64_t mLastTimestamp = 0 ;
504682 double getEtaFT0 (uint64_t globalChno, int i)
@@ -937,6 +1115,7 @@ struct RadialFlowDecorr {
9371115 // ===========================================================================
9381116 void init (InitContext&)
9391117 {
1118+ TH1::SetDefaultSumw2 (kTRUE );
9401119 // Nch axes by system
9411120 if (cfgSys == kPbPb ) {
9421121 nChAxis = {cfgNchPbMax / 2 , KBinOffset, cfgNchPbMax + KBinOffset, " Nch" , " PV-contributor track multiplicity" };
@@ -1082,26 +1261,50 @@ struct RadialFlowDecorr {
10821261 return ;
10831262 }
10841263
1264+ // 1. Process Efficiency Map
10851265 auto * hNum = dynamic_cast <TH3F *>(lst->FindObject (" h3_RecoMatchedToPrimary" ));
10861266 auto * hDen = dynamic_cast <TH3F *>(lst->FindObject (" h3_AllPrimary" ));
10871267 if (hNum && hDen) {
10881268 state.hEff = dynamic_cast <TH3F *>(hNum->Clone (" hEff" ));
10891269 state.hEff ->SetDirectory (nullptr );
1270+ state.hEff ->Sumw2 ();
10901271 state.hEff ->Divide (hDen);
1272+
1273+ TH3F * rebinnedEff = rebinNchMap (state.hEff , cfgSys.value , " hEffRebinned" );
1274+ if (!rebinnedEff) {
1275+ LOGF (fatal, " Failed to rebin efficiency map" );
1276+ return ;
1277+ }
1278+
1279+ delete state.hEff ;
1280+ state.hEff = rebinnedEff;
1281+
10911282 } else {
1092- LOGF (error , " Missing CCDB objects for efficiency (h3_RecoMatchedToPrimary / h3_AllPrimary)." );
1283+ LOGF (fatal , " Missing CCDB objects for efficiency (h3_RecoMatchedToPrimary / h3_AllPrimary)." );
10931284 }
10941285
1286+ // 2. Process Fake Map
10951287 auto * hNumS = dynamic_cast <TH3F *>(lst->FindObject (" h3_RecoUnMatchedToPrimary_Secondary" ));
10961288 auto * hNumF = dynamic_cast <TH3F *>(lst->FindObject (" h3_RecoUnMatchedToPrimary_Fake" ));
10971289 auto * hDenF = dynamic_cast <TH3F *>(lst->FindObject (" h3_AllReco" ));
10981290 if (hNumS && hNumF && hDenF) {
10991291 state.hFake = dynamic_cast <TH3F *>(hNumS->Clone (" hFake" ));
1100- state.hFake ->Add (hNumF);
11011292 state.hFake ->SetDirectory (nullptr );
1293+ state.hFake ->Sumw2 ();
1294+ state.hFake ->Add (hNumF); // Secondary + fake: add exactly once.
11021295 state.hFake ->Divide (hDenF);
1296+
1297+ TH3F * rebinnedFake = rebinNchMap (state.hFake , cfgSys.value , " hFakeRebinned" );
1298+ if (!rebinnedFake) {
1299+ LOGF (fatal, " Failed to rebin fake map" );
1300+ return ;
1301+ }
1302+
1303+ delete state.hFake ;
1304+ state.hFake = rebinnedFake;
1305+
11031306 } else {
1104- LOGF (error , " Missing CCDB objects for fakes." );
1307+ LOGF (fatal , " Missing CCDB objects for fakes." );
11051308 }
11061309 }
11071310
@@ -1333,9 +1536,11 @@ struct RadialFlowDecorr {
13331536 histos.fill (HIST (" hEta" ), eta);
13341537 histos.fill (HIST (" hPhi" ), phi);
13351538
1336- float eff = getEfficiency (multPV, pt, eta, 0 , cfgEff);
1337- float fake = getEfficiency (multPV, pt, eta, 1 , cfgEff);
1338- float w = (eff > KFloatEpsilon) ? (1 .0f - fake) / eff : 0 .0f ;
1539+ float eff = 1 .f , fake = 0 .f ;
1540+ if (!getValidEffFake (multPV, pt, eta, cfgEff, eff, fake)) {
1541+ continue ;
1542+ }
1543+ float w = (1 .0f - fake) / eff;
13391544 if (std::isfinite (w) && w > 0 .f ) {
13401545 histos.fill (HIST (" MCReco/hEtaPhiRecoEffWtd" ), vz, sign, pt, eta, phi, w);
13411546 histos.fill (HIST (" MCReco/hEtaPhiReco" ), vz, sign, pt, eta, phi, 1.0 );
@@ -1420,8 +1625,10 @@ struct RadialFlowDecorr {
14201625 histos.fill (HIST (" hEta" ), eta);
14211626 histos.fill (HIST (" hPhi" ), phi);
14221627
1423- float eff = getEfficiency (multPV, pt, eta, 0 , cfgEff);
1424- float fake = getEfficiency (multPV, pt, eta, 1 , cfgEff);
1628+ float eff = 1 .f , fake = 0 .f ;
1629+ if (!getValidEffFake (multPV, pt, eta, cfgEff, eff, fake)) {
1630+ continue ;
1631+ }
14251632 float flatW = getFlatteningWeight (vz, sign, pt, eta, phi, cfgFlat);
14261633 float w = flatW * (1.0 - fake) / eff;
14271634 if (!std::isfinite (w) || w <= 0 .f || eff <= KFloatEpsilon) {
@@ -1609,8 +1816,10 @@ struct RadialFlowDecorr {
16091816 histos.fill (HIST (" hEta" ), eta);
16101817 histos.fill (HIST (" hPhi" ), phi);
16111818
1612- float eff = getEfficiency (multPV, pt, eta, 0 , cfgEff);
1613- float fake = getEfficiency (multPV, pt, eta, 1 , cfgEff);
1819+ float eff = 1 .f , fake = 0 .f ;
1820+ if (!getValidEffFake (multPV, pt, eta, cfgEff, eff, fake)) {
1821+ continue ;
1822+ }
16141823 float flatW = getFlatteningWeight (vz, sign, pt, eta, phi, cfgFlat);
16151824 float w = flatW * (1.0 - fake) / eff;
16161825 if (!std::isfinite (w) || w <= 0 .f || eff <= KFloatEpsilon) {
@@ -2012,11 +2221,10 @@ struct RadialFlowDecorr {
20122221 ntrk++;
20132222 }
20142223
2015- float eff = getEfficiency (coll. multNTracksPV (), pt, eta, 0 , cfgEff) ;
2016- if (eff <= KFloatEpsilon ) {
2224+ float eff = 1 . f , fake = 0 . f ;
2225+ if (! getValidEffFake (coll. multNTracksPV (), pt, eta, cfgEff, eff, fake) ) {
20172226 continue ;
20182227 }
2019- float fake = getEfficiency (coll.multNTracksPV (), pt, eta, 1 , cfgEff);
20202228 float w = (1 .0f - fake) / eff;
20212229 if (!std::isfinite (w) || w <= 0 .f ) {
20222230 continue ;
@@ -2087,8 +2295,10 @@ struct RadialFlowDecorr {
20872295 histos.fill (HIST (" hEta" ), eta);
20882296 histos.fill (HIST (" hPhi" ), phi);
20892297
2090- float eff = getEfficiency (coll.multNTracksPV (), pt, eta, 0 , cfgEff);
2091- float fake = getEfficiency (coll.multNTracksPV (), pt, eta, 1 , cfgEff);
2298+ float eff = 1 .f , fake = 0 .f ;
2299+ if (!getValidEffFake (coll.multNTracksPV (), pt, eta, cfgEff, eff, fake)) {
2300+ continue ;
2301+ }
20922302 float flatWeight = getFlatteningWeight (vz, sign, pt, eta, phi, cfgFlat);
20932303
20942304 histos.fill (HIST (" pEffWeight_pt_eta_cent" ), pt, eta, cent, eff);
@@ -2201,8 +2411,13 @@ struct RadialFlowDecorr {
22012411 LOGF (warning, " Data fluc: mean pT or mult map missing" );
22022412 return ;
22032413 }
2204- if ((cfgEff || cfgFlat) && (!state.hEff || !state.hFake || !state.hFlatWeight )) {
2205- LOGF (warning, " Data fluc: correction maps requested but not all present." );
2414+ if (cfgEff && (!state.hEff || !state.hFake )) {
2415+ LOGF (warning, " Data fluc: Efficiency maps requested but not present." );
2416+ return ;
2417+ }
2418+
2419+ if (cfgFlat && !state.hFlatWeight ) {
2420+ LOGF (warning, " Data fluc: Flattening map requested but not present." );
22062421 return ;
22072422 }
22082423
@@ -2260,11 +2475,10 @@ struct RadialFlowDecorr {
22602475 continue ;
22612476 }
22622477
2263- float eff = getEfficiency (coll. multNTracksPV (), pt, eta, 0 , cfgEff) ;
2264- if (eff <= KFloatEpsilon ) {
2478+ float eff = 1 . f , fake = 0 . f ;
2479+ if (! getValidEffFake (coll. multNTracksPV (), pt, eta, cfgEff, eff, fake) ) {
22652480 continue ;
22662481 }
2267- float fake = getEfficiency (coll.multNTracksPV (), pt, eta, 1 , cfgEff);
22682482 float flatWeight = getFlatteningWeight (vz, sign, pt, eta, phi, cfgFlat);
22692483 float w = flatWeight * (1 .0f - fake) / eff;
22702484 if (!std::isfinite (w) || w <= 0 .f ) {
0 commit comments