From a06f2b0174ef53f76ba454e2400c5ccdfabc4773 Mon Sep 17 00:00:00 2001 From: Somadutta Bhatta Date: Fri, 23 Oct 2026 23:32:04 +0200 Subject: [PATCH 1/3] [PWGCF] radialFlowDecorr: Edits for QM Prep-->all systematic variations in one wagon --- .../Tasks/radialFlowDecorr.cxx | 4086 ++++++++++------- 1 file changed, 2524 insertions(+), 1562 deletions(-) diff --git a/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx b/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx index e917af7fb09..7478e2a9bc1 100644 --- a/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx +++ b/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx @@ -21,6 +21,9 @@ #include #include +#include +#include +#include #include #include #include @@ -33,10 +36,14 @@ #include #include +#include +#include #include #include #include +#include #include +#include #include #include #include @@ -69,6 +76,10 @@ struct RadialFlowDecorr { static constexpr int KNEtaHalfBinsMax = 8; // 0.8 / 0.1: finest (DATA) bins per side static constexpr int KNEtaMax = 2 * KNEtaHalfBinsMax + 1; // + index-0 full-range reference bin + // Bootstrap: at most KMaxBoot replicas of 8 profiles each, in one registry of at most + // 512 objects. nBoot = min(cfgNBootstrap, KMaxBoot) replicas are filled. + static constexpr int KMaxBoot = 60; + static constexpr float KFloatEpsilon = 1e-6f; static constexpr float KEtaEdgeTolerance = 1e-3f; // slack for cfgCutEta being an integer multiple of the bin width static constexpr float KBinOffset = 0.5f; @@ -91,11 +102,21 @@ struct RadialFlowDecorr { static constexpr float KVzMax = 40.f; static constexpr int KNbinsEtaFine = 20; static constexpr float KEtaFineMax = 1.f; - static constexpr float KCentMax = 90; - - // Bootstrap: KMaxBoot is the compile-time storage cap; the number actually - // filled is nBoot = min(cfgNBootstrap, KMaxBoot). - static constexpr int KMaxBoot = 64; + // N_ch bin width (tracks) of the efficiency maps + static constexpr int KEffNchWidthPbPb = 100; + static constexpr int KEffNchWidth = 10; + // occupancy QA axis + static constexpr int KNbinsOccQA = 150; + static constexpr float KOccQAMax = 15000.f; + // mcMask bits of McTrackLabels (bit ON = mismatch) + static constexpr int KMcMaskItsTpcMismatch = 13; + static constexpr int KMcMaskFake = 15; + + // Forward detectors: FT0 channels 0-95 are A side, 96-207 C side; FV0-A has 48 cells. + static constexpr int KNChFT0A = 96; + static constexpr int KNChFT0 = 208; + static constexpr int KNChFV0 = 48; + static constexpr int KNFwdMax = 32; // detector totals + eta sub-bins enum ECentralityEstimator { kCentFT0C = 1, @@ -109,29 +130,229 @@ struct RadialFlowDecorr { kOO = 3, kpp = 4 }; + enum EITSLayerCut { + kITSLayerAuto = -1, // nominal: pp kIsGoodITSLayer0123, other systems kIsGoodITSLayersAll + kITSLayerNone = 0, + kITSLayer3 = 1, + kITSLayer0123 = 2, + kITSLayersAll = 3 + }; + enum EFwdDet { + kFT0A = 0, + kFT0C, + kFV0A, + kNFwdDet + }; + // Histogram level: DATA uses level 0 only; MC fills truth, reco and efficiency-corrected reco. + enum ELevel { + kGen = 0, + kReco, + kRecoEffCorr, + kNLevels + }; + // Last axis of the multi-dimensional objects. + enum EMeanObs { // Prof_Mean_Nch_etabin_obs + kMeanPt = 0, + kMeanN, + kNMeanObs + }; + enum EFlucObs { // Prof_Obs_Nch_etabin_obs + kObsN = 0, // N(eta), efficiency-corrected + kObsMeanPt, // [pT](eta) + kObsC2, // c2 within the bin + kObsC3, // c3 within the bin + kObsC2Sub, // , mirror pair, at the A bin + kObsC3Sub, // c2_A _C at A (AAB) and c2_C _A at C (ABB), mirror pair + kNFlucObs + }; + enum EEffCat { // hEffMaps + kEffAllPrim = 0, // charged primaries of the MC collision + kEffRecoMatchedPrim, // reco tracks of primaries of the same MC collision, at the MC pT and eta + kEffAllReco, + kEffRecoSec, // secondaries of the same MC collision + kEffRecoOtherColl, // particles of another MC collision (pile-up, wrong association) + kEffRecoFake, // no label, fake flag or ITS-TPC mismatch (mcMask) + kNEffCat + }; + enum ETrackOrigin { // MCQA/Hist3D_Nch_pT_origin + kOriginPrimary = 0, + kOriginSecondary, + kOriginOtherColl, + kOriginNoLabel, + kOriginFakeFlag, + kOriginItsTpcMismatch, + kNOrigin + }; + enum EVtxQA { // MCQA/Hist2D_dVz_type + kVtxSingle = 0, // the only reco collision of the MC collision + kVtxSplitBest, // the one used among several + kVtxSplitOther, // the others (skipped) + kNVtxQA + }; + enum EFlatType { // hFlatMaps + kFlatRaw = 0, + kFlatEffWtd, // (1 - fake) / eff (Base only) + kFlatWtd, // all weights of the pass + kNFlatType + }; + enum EChanQA { // FwdQA/Prof_Channel_qa + kChAmpRaw = 0, + kChAmpCorr, + kChEta, + kNChQA + }; + // Event cut flow (hEvtCount, x axis); the last two bins count selected events only + enum EEvtCut { + kEvAll = 0, + kEvSel8, + kEvVtxZ, + kEvGoodZvtx, + kEvSameBunch, + kEvTimeRange, + kEvITSLayer, + kEvAddPileup, + kEvVariationCut, + kEvCent, + kEvInfoPrevRof, // selected events passing kNoHighMultCollInPrevRof + kEvInfoOccUnknown, // selected events with occupancy < 0 (time-frame border) + kNEvCut + }; + // Track cut flow (hTrkCount, x axis) + enum ETrkCut { + kTrkAll = 0, + kTrkSign, + kTrkHasColl, + kTrkPVContrib, + kTrkPtEta, + kTrkGlobal, + kTrkITS, + kTrkTPC, + kTrkDCAz, + kTrkVariationCut, + kTrkWeight, // valid efficiency (and flattening) weight: used + kNTrkCut + }; + + // Cut QA (processCutQA): each quantity the Base selection cuts on, before and after it. + enum ECutQAStage { + kQABefore = 0, // events: all collisions; tracks: all tracks of the selected events + kQAAfter, // Base-selected events / tracks + kNQAStage + }; + enum ECutQAEvt1D { + kQAVtxZ = 0, + kQACent, + kQAOccupancy, + kNQAEvt1D + }; + enum ECutQAEvt2D { + kQANchNPV = 0, + kQANchCent, + kNQAEvt2D + }; + enum ECutQATrk1D { + kQAPt = 0, + kQAEta, + kQAItsNCls, + kQAItsChi2, + kQATpcNClsFound, + kQATpcCrossedRows, + kQATpcCrossedRowsOverFindable, + kQATpcChi2, + kQAPVContributor, + kQAGlobalTrack, + kNQATrk1D + }; + enum ECutQATrk2D { + kQADcaXYPt = 0, + kQADcaZPt, + kNQATrk2D + }; - // Systematic-variation selector for DATA. Base is the main measurement; each - // variation reads its own DataMean object (systSuffix) for the correlation step. - // kSystEff and kSystEffPtDep scale the efficiency by cfgEffVarPercent; kSystEffPtDep - // always uses the pT-dependent shape. kSystCent is run with a different - // cfgCentralityChoice than the base (base FT0C, variation FT0M). + // Systematic variations, all evaluated in every job. Base is the nominal measurement. + // The cut values are the K* constants below; only the occupancy limits are configurable. + // Cent : FT0M instead of the nominal estimator (cfgCentralityChoice, FT0C) + // Chi2TPC : additionally chi2/TPC cluster < 3 + // DCAxy : |DCAxy| < 5/7 x (globalTrack pT-dependent DCAxy cut), i.e. 5 instead of 7 sigma + // DCAz : |DCAz| < 1 cm instead of cfgCutTracKDcaMaxZ + // DCAxy and DCAz are null checks: with the nominal isPVContributor requirement even + // 5 sigma DCAxy or |DCAz| < 0.05 cm removes only ~0.2% of the selected tracks + // Eff : efficiency scaled by -10% at all pT (cancels in [pT]: a check) + // EffpTDep : efficiency scaled by -10% at cfgPtMin, linearly to 0 at 10 GeV/c + // Flat : no acceptance (flattening) weights + // ITSLayer : ITS good-layer requirement one step looser (All -> 0123, 0123 -> Layer3) + // NITS : itsNCls > 3 instead of cfgITScluster + // NTPC : tpcNClsFound >= 90 instead of cfgTPCcluster + // Pileup : additionally kNoCollInRofStandard + // Vz : |z_vtx| < 8 cm instead of cfgVtxZCut + // NchBin2 : Base selection and inputs, observables (and its own mean pass) in N_ch bins + // of 2 (PbPb, OO) or 1 (pp) tracks: cross-check of the N_ch binning of the mean subtraction + // Occupancy : 0 <= trackOccupancyInTimeRange < cfgSystOccMax* of the system + // PileupNSigma : n-sigma pile-up bands narrower by 1 (mean and fluctuation passes) + // Since the observables are stored versus N_ch, Cent differs from Base only in the events + // kept (cent < cfgCentMax) and in its (centrality, N_ch) map, which defines its classes. enum ESystType { kSystBase = 0, kSystCent, - kSystDCA, + kSystChi2TPC, + kSystDCAxy, + kSystDCAz, kSystEff, kSystEffPtDep, kSystFlat, + kSystITSLayer, kSystNITS, kSystNTPC, kSystPileup, kSystVz, - kNSystType + kSystNchBin, + kSystOccupancy, + kSystPileupNSigma, + kNSyst }; - // Suffix appended to the DataMean CCDB path per systematic (Base -> no suffix). - inline static const std::vector systSuffix = { - "", "_systCent", "_systDCA", "_systEff", "_systEffpTDep", "_systFlat", - "_systNITS", "_systNTPC", "_systPileup", "_systVz"}; + inline static const std::array systName = { + "Base", "systCent", "systChi2TPC", "systDCAxy", "systDCAz", "systEff", "systEffpTDep", + "systFlat", "systITSLayer", "systNITS", "systNTPC", "systPileup", "systVz", "systNchBin2", + "systOccupancy", "systPileupNSigma"}; + static constexpr uint32_t KAllSystMask = (1u << kNSyst) - 1u; + static constexpr int KNSystPerRegistry = 4; // variations 1..15 are spread over 4 registries + // Cut of each variation (fixed; the occupancy limits are configurables, cfgSystOccMax*). + // Pileup adds kNoCollInRofStandard; ITSLayer loosens the nominal ITS good-layer requirement by one step. + static constexpr int KSystCentralityChoice = 2; // Cent: FT0M + static constexpr float KSystChi2TPC = 3.f; // Chi2TPC: chi2 per TPC cluster < 3 (nominal 4) + static constexpr float KSystDcaXyScale = 5.f / 7.f; // DCAxy: 5 sigma (nominal 7 sigma) + static constexpr float KSystDcaMaxZ = 1.f; // DCAz: |DCAz| < 1 cm (nominal 2 cm) + static constexpr float KEffVarPercent = -10.f; // Eff, EffpTDep: efficiency scaled by -10% + static constexpr float KEffVarPtZero = 10.f; // EffpTDep: variation falls linearly to 0 at 10 GeV/c + static constexpr int KSystITScluster = 3; // NITS: itsNCls > 3 (nominal > 5) + static constexpr int KSystTPCcluster = 90; // NTPC: tpcNClsFound >= 90 (nominal 70) + static constexpr float KSystVtxZCut = 8.f; // Vz: |z_vtx| < 8 cm (nominal 10 cm) + static constexpr int KSystNchBinWidthPbPb = 2; // NchBin2: N_ch bin width, PbPb (nominal 10) + static constexpr int KSystNchBinWidthOO = 2; // NchBin2: OO and NeNe (nominal 5) + static constexpr int KSystNchBinWidthPP = 1; // NchBin2: pp (nominal 2) + static constexpr float KSystPupnSigStep = 1.f; // PileupNSigma: n-sigma bands narrower by 1 + static constexpr double KMinBandEntries = 50.; // n-sigma band bins with fewer flattening-pass events apply no band cut + + // Output folder of a variation (Base keeps the top level). + static std::string systPrefix(int v) { return v == kSystBase ? std::string{} : systName[v] + "/"; } + // Suffix appended to every CCDB object path of a variation. + static std::string systSuffix(int v) { return v == kSystBase ? std::string{} : "_" + systName[v]; } + // Variation whose efficiency map v uses. Cent, Eff, EffpTDep, Flat, NchBin2 and PileupNSigma + // have the Base selection in the efficiency pass and use the Base map; Eff and EffpTDep scale + // it when applied. (The n-sigma bands are applied only in the mean and fluctuation passes.) + static constexpr int effSource(int v) + { + return (v == kSystCent || v == kSystEff || v == kSystEffPtDep || v == kSystFlat || v == kSystNchBin || v == kSystPileupNSigma) ? static_cast(kSystBase) : v; + } + // Variation whose flattening map v uses. Cent, NchBin2 and PileupNSigma use the Base map; + // Flat uses none (-1). + static constexpr int flatSource(int v) + { + if (v == kSystFlat) { + return -1; + } + return (v == kSystCent || v == kSystNchBin || v == kSystPileupNSigma) ? static_cast(kSystBase) : v; + } static constexpr float KinvalidCentrality = -1.0f; @@ -142,68 +363,122 @@ struct RadialFlowDecorr { int nBoot = 0; // active bootstrap samples (set in init()) bool doBoot = false; // bootstrap active for this run (base data fluc only) + // Variations filled by each pass (set in init()): maskEff in the MC efficiency pass, + // maskFlat in the flattening passes, maskAll in the mean and fluctuation passes. + uint32_t maskAll = 1u; + uint32_t maskEff = 1u; + uint32_t maskFlat = 1u; + int itsLayerNom = kITSLayersAll; + int itsLayerVar = kITSLayer0123; + int occupancyMax = 0; // Occupancy variation cut of this system + float pupNSigma = 5.f; // width of the n-sigma pile-up bands of this system + int nchWidth = 10; // N_ch bin width of the observables of this system + int nchWidthNchBin2 = 2; // N_ch bin width of NchBin2 for this system + std::vector effOccEdges; // occupancy bins of the efficiency maps + + // Forward bins: 0-2 detector totals (FT0A, FT0C, FV0A), then the eta sub-bins of each detector. + int nFwd = kNFwdDet; + std::array, kNFwdDet> fwdEdges; + std::array fwdOffset{}; + std::vector fwdLabels; + // --- configurables ----------------------------------------------------------- - Configurable cfgVtxZCut{"cfgVtxZCut", 10.f, "|z_{vtx}| acceptance (cm): collision filter + explicit event/particle vertex checks"}; + // Event selection (nominal) + struct : ConfigurableGroup { + Configurable cfgVtxZCut{"cfgVtxZCut", 10.f, "|z_{vtx}| acceptance (cm)"}; + Configurable cfgCentralityChoice{"cfgCentralityChoice", 1, "Which centrality estimator? 1-->FT0C, 2-->FT0M, 3-->FDDM, 4-->FV0A"}; + Configurable cfgCentMax{"cfgCentMax", 99.f, "Events with centrality below this value (%) are kept"}; + Configurable cfgIsGoodZvtxFT0VsPV{"cfgIsGoodZvtxFT0VsPV", true, "Good Vertexing cut"}; + Configurable cfgEvSelNoSameBunchPileup{"cfgEvSelNoSameBunchPileup", true, "Pileup removal: kNoSameBunchPileup"}; + Configurable cfgEvSelNoCollInTimeRangeStd{"cfgEvSelNoCollInTimeRangeStd", true, "Pileup removal: kNoCollInTimeRangeStandard"}; + Configurable cfgITSLayerCut{"cfgITSLayerCut", -1, "ITS good-layer cut: -1 auto (pp 0123, else All), 0 none, 1 Layer3, 2 Layer0123, 3 LayersAll"}; + } cfgEvSel; + + // Pile-up cuts on the correlations of N_ch (global tracks) with N_PV and centrality. + // The linear cut is applied in every pass. The flattening passes (MC and DATA) fill + // Hist2D_globalTracks_cent and Hist2D_globalTracks_PVTracks after it; the mean +- n sigma + // bands of N_ch from these maps are then applied in the mean and fluctuation passes. + struct : ConfigurableGroup { + Configurable cfgPupnSigPbPb{"cfgPupnSigPbPb", 5.f, "Width of the n-sigma bands (number of RMS), PbPb"}; + Configurable cfgPupnSigOO{"cfgPupnSigOO", 5.f, "Width of the n-sigma bands (number of RMS), OO and NeNe"}; + Configurable cfgPupnSigPP{"cfgPupnSigPP", 6.f, "Width of the n-sigma bands (number of RMS), pp"}; + Configurable cfgApplySigPupCut{"cfgApplySigPupCut", 0, "Mean/Fluc: n-sigma band of N_ch versus centrality"}; + Configurable cfgApplySigPupCutPV{"cfgApplySigPupCutPV", 0, "Mean/Fluc: n-sigma band of N_ch versus N_PV"}; + Configurable cfgApplyLinPupCut{"cfgApplyLinPupCut", 0, "Linear cut of N_ch versus N_PV (every pass)"}; + Configurable cfgLinPupParam0{"cfgLinPupParam0", 3.0f, "(Upper) Linear Pileup Cut Const"}; + Configurable cfgLinPupParam1{"cfgLinPupParam1", 3.0f, "(Upper) Linear Pileup Slope"}; + Configurable cfgLinPupParam2{"cfgLinPupParam2", 3.0f, "(Lower) Linear Pileup Cut Const"}; + Configurable cfgLinPupParam3{"cfgLinPupParam3", 3.0f, "(Lower) Linear Pileup Slope"}; + } cfgPileup; + + // Track selection (nominal), on top of isGlobalTrack (Run 3 globalTrack, incl. its 7 sigma pT-dependent DCAxy cut) Configurable cfgPtMin{"cfgPtMin", 0.2f, "min pT for observables"}; Configurable cfgPtMax{"cfgPtMax", 5.0f, "max pT for observables"}; - Configurable cfgCutTracKDcaMaxZ{"cfgCutTracKDcaMaxZ", 2.0f, "Maximum DcaZ"}; - Configurable cfgCutTracKDcaMaxXY{"cfgCutTracKDcaMaxXY", 0.2f, "Maximum DcaXY"}; - - Configurable cfgPtDepDCAxy{"cfgPtDepDCAxy", false, "Use pt-dependent DCAxy cut"}; - Configurable cfgDcaXyP0{"cfgDcaXyP0", 0.0026f, "p0 for DCAxy"}; - Configurable cfgDcaXyP1{"cfgDcaXyP1", 0.005f, "p1 for DCAxy"}; - Configurable cfgDcaXyP2{"cfgDcaXyP2", 1.01f, "p2 for DCAxy"}; - - Configurable cfgPtDepDCAz{"cfgPtDepDCAz", false, "Use pt-dependent DCAz cut"}; - Configurable cfgDcaZP0{"cfgDcaZP0", 0.0026f, "p0 for DCAz"}; - Configurable cfgDcaZP1{"cfgDcaZP1", 0.005f, "p1 for DCAz"}; - Configurable cfgDcaZP2{"cfgDcaZP2", 1.01f, "p2 for DCAz"}; - - Configurable cfgITScluster{"cfgITScluster", 1, "Minimum Number of ITS cluster"}; - Configurable cfgTPCcluster{"cfgTPCcluster", 80, "Minimum Number of TPC cluster"}; - Configurable cfgTPCnCrossedRows{"cfgTPCnCrossedRows", 70, "Minimum Number of TPC crossed-rows"}; - Configurable cfgCutPtLower{"cfgCutPtLower", 0.2f, "Lower pT cut (track selection)"}; Configurable cfgCutPtUpper{"cfgCutPtUpper", 10.0f, "Higher pT cut (track selection)"}; Configurable cfgCutEta{"cfgCutEta", 0.8f, "absolute Eta cut"}; Configurable cfgEtaBinWidth{"cfgEtaBinWidth", 0.1f, "DATA only: width of each narrow observable eta bin (eta units); cfgCutEta must be an integer multiple so eta=0 is a bin edge. MC is pinned to 0.2."}; - Configurable cfgCentralityChoice{"cfgCentralityChoice", 1, "Which centrality estimator? 1-->FT0C, 2-->FT0M, 3-->FDDM, 4-->FV0A"}; - Configurable cfgEvSelNoSameBunchPileup{"cfgEvSelNoSameBunchPileup", true, "Pileup removal"}; - Configurable cfgUseGoodITSLayerAllCut{"cfgUseGoodITSLayerAllCut", true, "Remove time interval with dead ITS zone"}; - Configurable cfgIsGoodZvtxFT0VsPV{"cfgIsGoodZvtxFT0VsPV", true, "Good Vertexing cut"}; - - Configurable cfgPupnSig{"cfgPupnSig", 6.0f, "Additional Pileup Cut"}; - Configurable cfgApplySigPupCut{"cfgApplySigPupCut", 0, "nSig Pileup Cut"}; - Configurable cfgApplyLinPupCut{"cfgApplyLinPupCut", 0, "Lin Pileup Cut"}; - Configurable cfgLinPupParam0{"cfgLinPupParam0", 3.0f, "(Upper) Linear Pileup Cut Const"}; - Configurable cfgLinPupParam1{"cfgLinPupParam1", 3.0f, "(Upper) Linear Pileup Slope"}; - Configurable cfgLinPupParam2{"cfgLinPupParam2", 3.0f, "(Lower) Linear Pileup Cut Const"}; - Configurable cfgLinPupParam3{"cfgLinPupParam3", 3.0f, "(Lower) Linear Pileup Slope"}; - - Configurable cfgNchPbMax{"cfgNchPbMax", 5000, "Max Nch range for PbPb collisions"}; - Configurable cfgNchOMax{"cfgNchOMax", 800, "Max Nch range for OO collisions"}; + Configurable cfgCutTracKDcaMaxZ{"cfgCutTracKDcaMaxZ", 2.0f, "Maximum |DCAz| (cm)"}; + Configurable cfgITScluster{"cfgITScluster", 5, "Nominal: itsNCls > value"}; + Configurable cfgTPCcluster{"cfgTPCcluster", 70, "Nominal: tpcNClsFound >= value"}; + Configurable cfgTPCnCrossedRows{"cfgTPCnCrossedRows", 70, "Minimum Number of TPC crossed-rows"}; + + // Systematic variations (names stay flat in the JSON) + struct : ConfigurableGroup { + Configurable cfgSystMask{"cfgSystMask", static_cast(KAllSystMask), "Bitmask of active variations, bit i = ESystType i: 0 Base (always on), 1 Cent, 2 Chi2TPC, 3 DCAxy, 4 DCAz, 5 Eff, 6 EffpTDep, 7 Flat, 8 ITSLayer, 9 NITS, 10 NTPC, 11 Pileup, 12 Vz, 13 NchBin2, 14 Occupancy, 15 PileupNSigma"}; + Configurable cfgDcaXyP0{"cfgDcaXyP0", 0.0105f, "globalTrack DCAxy cut p0 + p1 / pT^p2 (trackselection dcaSetup=0; dcaSetup=1: 0.004, 0.013, 1)"}; + Configurable cfgDcaXyP1{"cfgDcaXyP1", 0.035f, "globalTrack DCAxy cut p1"}; + Configurable cfgDcaXyP2{"cfgDcaXyP2", 1.1f, "globalTrack DCAxy cut p2"}; + Configurable cfgSystOccMaxPbPb{"cfgSystOccMaxPbPb", 2000, "Occupancy: maximum trackOccupancyInTimeRange, PbPb"}; + Configurable cfgSystOccMaxOO{"cfgSystOccMaxOO", 1000, "Occupancy: maximum trackOccupancyInTimeRange, OO and NeNe"}; + Configurable cfgSystOccMaxPP{"cfgSystOccMaxPP", 500, "Occupancy: maximum trackOccupancyInTimeRange, pp"}; + } cfgSyst; + + // Corrections + struct : ConfigurableGroup { + Configurable> cfgEffOccupancyEdges{"cfgEffOccupancyEdges", {0.f, 500.f, 1000.f, 2000.f, 4000.f, 100000.f}, "trackOccupancyInTimeRange bin edges of the efficiency maps (data and MC); occupancy < 0 goes to the first bin"}; + Configurable cfgMcItsTpcMismatchAsFake{"cfgMcItsTpcMismatchAsFake", true, "MC: tracks with the ITS-TPC mismatch bit of mcMask count as fakes"}; + } cfgCorr; + + // N_ch axes. Largest N_ch (global tracks) seen in the data: 3542 (PbPb), 328 (OO), 72 (pp) + struct : ConfigurableGroup { + Configurable cfgNchPbMax{"cfgNchPbMax", 4000, "Max N_ch (global tracks) and N_PV of the axes, PbPb"}; + Configurable cfgNchOMax{"cfgNchOMax", 400, "Max N_ch (global tracks) and N_PV of the axes, OO and NeNe"}; + Configurable cfgNchPPMax{"cfgNchPPMax", 100, "Max N_ch (global tracks) and N_PV of the axes, pp"}; + Configurable cfgNchBinWidthPbPb{"cfgNchBinWidthPbPb", 10, "N_ch bin width (tracks) of the observables, PbPb; Mean and Fluc passes must use the same value"}; + Configurable cfgNchBinWidthOO{"cfgNchBinWidthOO", 5, "N_ch bin width (tracks) of the observables, OO and NeNe"}; + Configurable cfgNchBinWidthPP{"cfgNchBinWidthPP", 2, "N_ch bin width (tracks) of the observables, pp"}; + } cfgNch; Configurable cfgSys{"cfgSys", 1, "Which collision system? 1-->PbPb, 2-->NeNe, 3-->OO, 4-->pp"}; - Configurable cfgSystType{"cfgSystType", 0, "Systematic variation: 0=Base,1=systCent,2=systDCA,3=systEff,4=systEffpTDep,5=systFlat,6=systNITS,7=systNTPC,8=systPileup,9=systVz"}; - Configurable cfgNBootstrap{"cfgNBootstrap", 20, "Number of Poisson bootstrap samples (base data run only)"}; + Configurable cfgNBootstrap{"cfgNBootstrap", 20, "Number of Poisson bootstrap samples (Base, DataFluc only)"}; Configurable cfgBootstrapSeed{"cfgBootstrapSeed", 0, "TRandom3 seed for bootstrap (0 = machine-random per job)"}; Configurable cfgFlat{"cfgFlat", false, "Whether to use flattening weights"}; Configurable cfgEff{"cfgEff", false, "Whether to use Efficiency weights"}; - Configurable cfgEffVarPercent{"cfgEffVarPercent", 0.f, "Signed relative efficiency variation (%): +10 or -10"}; - Configurable cfgEffVarPtDep{"cfgEffVarPtDep", false, "false: constant variation; true: linear decrease with pT"}; - Configurable cfgZDC{"cfgZDC", false, "Whether to use ZDC for pileup histograms"}; + + // Forward multiplicity (FT0A, FT0C, FV0A amplitudes) correlated with mid-rapidity [pT] and N + struct : ConfigurableGroup { + Configurable cfgFwd{"cfgFwd", true, "Mean/Fluc: forward amplitudes and their covariance with [pT](eta) and N(eta)"}; + Configurable cfgFwdGainEq{"cfgFwdGainEq", true, "DATA: apply the FT0/FV0 gain equalisation from cfgGainEqPath"}; + Configurable cfgGainEqPath{"cfgGainEqPath", "Analysis/EventPlane/GainEq", "CCDB path of the FT0/FV0 gain equalisation (+ /FT0, /FV0); central objects per run for LHC23zzh Pb-Pb and 2025 O-O/Ne-Ne"}; + Configurable cfgFwdVzCorr{"cfgFwdVzCorr", true, "Channel eta relative to the primary vertex (else to z = 0)"}; + Configurable> cfgFwdEtaEdgesFT0A{"cfgFwdEtaEdgesFT0A", {3.5f, 4.2f, 4.9f}, "FT0A eta sub-bin edges"}; + Configurable> cfgFwdEtaEdgesFT0C{"cfgFwdEtaEdgesFT0C", {-3.3f, -2.7f, -2.1f}, "FT0C eta sub-bin edges"}; + Configurable> cfgFwdEtaEdgesFV0A{"cfgFwdEtaEdgesFV0A", {2.2f, 2.7f, 3.2f, 3.9f, 4.5f, 5.1f}, "FV0A eta sub-bin edges"}; + } cfgFwdOpt; Configurable cfgCCDBurl{"cfgCCDBurl", "https://alice-ccdb.cern.ch", "ccdb url"}; Configurable cfgCCDBUserPath{"cfgCCDBUserPath", "/Users/s/somadutt", "Base CCDB path"}; - ConfigurableAxis cfgAxisCent{"cfgAxisCent", {0.0, 1.0, 5.0, 10, 20, 40, 60, 80, 100}, "centrality axis (percentile)"}; - // --- axes -------------------------------------------------------------------- - AxisSpec centAxis{cfgAxisCent, "Centrality (%)"}; AxisSpec centAxis1Per{100, 0.0, 100.0, "Centrality (%)"}; - AxisSpec nChAxis{1, 0., 1., "Nch", "Nch"}; - AxisSpec nChAxis2{1, 0., 1., "Nch", "Nch"}; + AxisSpec centAxisBoot{20, 0.0, 100.0, "Centrality (%)"}; // bootstrap replicas: final 5% classes + AxisSpec multQAAxis{1, 0., 1., "Nch", "Nch"}; // QA (N_PV correlations) + AxisSpec nchAxisObs{1, 0., 1., "N_{ch}"}; // every observable (bins of nchWidth) + AxisSpec nchAxisNchBin2{1, 0., 1., "N_{ch}"}; // NchBin2 (bins of nchWidthNchBin2) + AxisSpec nchAxisEff{1, 0., 1., "N_{ch}"}; // efficiency maps + AxisSpec occAxisEff{1, 0., 1., "occupancy"}; // efficiency maps AxisSpec vzAxis{5, -12.5, 12.5, "Vz"}; AxisSpec chgAxis{3, -1.5, 1.5}; @@ -220,6 +495,7 @@ struct RadialFlowDecorr { AxisSpec etaBinAxis{10, -0.5, 9.5, "#eta bin Number"}; AxisSpec gapAxis{15, -1.5, 1.5, "#Delta#eta"}; AxisSpec sumAxis{15, -1.5, 1.5, "#Sigma#eta"}; + AxisSpec fwdAxis{kNFwdDet, -0.5, kNFwdDet - 0.5, "forward bin"}; // --- process switches -------------------------------------------------------- Configurable cfgRunGetEff{"cfgRunGetEff", false, "Run MC pass to build efficiency/fake maps"}; @@ -233,174 +509,422 @@ struct RadialFlowDecorr { Service ccdb{}; Service pdg{}; + + // histos: Base and QA; histosBoot: bootstrap replicas; histosSystA-D: the variations, + // 4 per registry (a registry holds at most 512 objects). HistogramRegistry histos{"Histos", {}, OutputObjHandlingPolicy::AnalysisObject}; + HistogramRegistry histosBoot{"HistosBoot", {}, OutputObjHandlingPolicy::AnalysisObject}; + HistogramRegistry histosSystA{"HistosSystA", {}, OutputObjHandlingPolicy::AnalysisObject}; + HistogramRegistry histosSystB{"HistosSystB", {}, OutputObjHandlingPolicy::AnalysisObject}; + HistogramRegistry histosSystC{"HistosSystC", {}, OutputObjHandlingPolicy::AnalysisObject}; + HistogramRegistry histosSystD{"HistosSystD", {}, OutputObjHandlingPolicy::AnalysisObject}; + + HistogramRegistry& reg(int v) + { + if (v == kSystBase) { + return histos; + } + switch ((v - 1) / KNSystPerRegistry) { + case 0: + return histosSystA; + case 1: + return histosSystB; + case 2: + return histosSystC; + default: + return histosSystD; + } + } + + const AxisSpec& nchAxisOf(int v) const { return v == kSystNchBin ? nchAxisNchBin2 : nchAxisObs; } + int nchBinWidthOf(int v) const { return v == kSystNchBin ? nchWidthNchBin2 : nchWidth; } TRandom3 rng; // bootstrap Poisson weights - // --- persistent state -------------------------------------------------------- - struct InternalState { - TH3F* hEff = nullptr; - TH3F* hFake = nullptr; - THnSparseF* hFlatWeight = nullptr; - - std::vector> mLimitsNchCent; - float mMinXNchCent = 0, mMaxXNchCent = 0; - - // MC mean maps (per eta bin): truth / reco / reco-eff-corrected - TProfile2D* pmeanTruNchEtabinStep2 = nullptr; - TProfile2D* pmeanRecoNchEtabinStep2 = nullptr; - TProfile2D* pmeanRecoEffcorrNchEtabinStep2 = nullptr; - - // Data mean maps (per eta bin) - TProfile2D* pmeanNchEtabinStep2 = nullptr; - - } state; - - // --- bootstrap replica storage (base data fluc only) ------------------------- - struct BootstrapHists { - std::array, KMaxBoot> multCent{}; - std::array, KMaxBoot> multMult{}; - std::array, KMaxBoot> meanpTCent{}; - std::array, KMaxBoot> meanpTMult{}; - std::array, KMaxBoot> c2Cent{}; - std::array, KMaxBoot> c2Mult{}; - std::array, KMaxBoot> c3Cent{}; - std::array, KMaxBoot> c3Mult{}; - std::array, KMaxBoot> c2SubCent{}; - std::array, KMaxBoot> c2SubMult{}; - std::array, KMaxBoot> c3SubCent{}; - std::array, KMaxBoot> c3SubMult{}; - std::array, KMaxBoot> c2Sub2D{}; - std::array, KMaxBoot> c3Sub2D{}; - std::array, KMaxBoot> gapSum2D{}; - std::array, KMaxBoot> c3GapSum2D{}; - } bs; + // --- per-variation inputs ---------------------------------------------------- + struct SystInputs { + // efficiency and fake rate versus (N_ch, pT, eta), one map per occupancy bin, and integrated + // over occupancy (used where the cell of the occupancy bin is empty or not a valid efficiency) + std::vector hEff; + std::vector hFake; + TH3* hEffAllOcc = nullptr; + TH3* hFakeAllOcc = nullptr; + // integrated over occupancy and N_ch (one N_ch bin): last resort for sparse cells + TH3* hEffPtEta = nullptr; + TH3* hFakePtEta = nullptr; + THnSparseF* hFlat = nullptr; + // mean pass, per level: <[pT]> and vs (N_ch, eta bin), vs (N_ch, forward bin) + std::array pmean{}; + std::array pmeanFwd{}; + }; + std::array inputs{}; + + // mean +- n sigma of N_ch per bin of x (centrality or N_PV), from the flattening pass + struct NSigmaBand { + std::vector> meanRms; // per bin of x; rms < 0 for an empty bin + float xMin = 0.f, xMax = 0.f; + bool applied = false; + }; + enum EPileupBand { + kBandCent = 0, + kBandPV, + kNBands + }; + std::array pileupBands{}; + + // --- per-variation histograms, filled through pointers (HIST() needs names known at compile time) --- + struct EffHists { + std::shared_ptr maps; // (N_ch, pT, eta, occupancy, EEffCat) + }; + struct FlatHists { + std::shared_ptr maps; // (vz, sign, pT, eta, phi, EFlatType) + }; + struct MeanHists { + std::shared_ptr mean; // (N_ch, eta bin, EMeanObs) + std::shared_ptr fwdMean; // (N_ch, forward bin): + }; + struct FlucHists { + std::shared_ptr obs; // (N_ch, eta bin, EFlucObs) + std::shared_ptr c2Sub2D; // (N_ch, eta_A, eta_C) + std::shared_ptr c3Sub2D; // (N_ch, eta_A pair, eta_C single) + std::shared_ptr gapSum2D; // (N_ch, Delta eta, Sigma eta) of the C2 map + std::shared_ptr c3GapSum2D; // (N_ch, eta_pair - eta_single, eta_pair + eta_single) + std::shared_ptr fwdVar; // (N_ch, forward bin): + std::shared_ptr covFwdPt; // (N_ch, eta bin, forward bin): + std::shared_ptr covFwdN; // (N_ch, eta bin, forward bin): + }; + struct { + std::shared_ptr evtCount; // (EEvtCut, variation) + std::shared_ptr trkCount; // (ETrkCut, variation) + std::array, kNSyst> evtCount1D{}; // EEvtCut, one per variation + std::array, kNSyst> trkCount1D{}; // ETrkCut, one per variation + std::shared_ptr nchOccupancy; // (N_ch, occupancy) of the Base events + std::array, kNQAStage>, kNQAEvt1D> qaEvt1D{}; + std::array, kNQAStage>, kNQAEvt2D> qaEvt2D{}; + std::array, kNQAStage>, kNQATrk1D> qaTrk1D{}; + std::array, kNQAStage>, kNQATrk2D> qaTrk2D{}; + std::array, kNSyst> nchCent{}; // (centrality, N_ch) per variation + std::array eff{}; + std::array flat{}; + std::array, kNSyst> mean{}; + std::array, kNSyst> fluc{}; + std::array boot{}; + std::shared_ptr fwdChannel; // (channel: FT0 0-207, FV0 208-255; EChanQA) + std::shared_ptr fwdEta; // amplitude-weighted eta per forward bin + std::shared_ptr mcTrackOrigin; // (N_ch, pT, ETrackOrigin) of the Base tracks + std::shared_ptr mcVtx; // (z_reco - z_true, EVtxQA) + } hists; + + // --- forward detector calibration (per run) ---------------------------------- + struct { + o2::ft0::Geometry ft0Det; + o2::fv0::Geometry* fv0Det = nullptr; + int run = -1; + std::array, KNChFT0> posFT0{}; + std::array, KNChFV0> posFV0{}; + std::array gainFT0{}; + std::array gainFV0{}; + } fwdCalib; + + // --- per-event work arrays --------------------------------------------------- + // Power sums S_{m,k} = sum w^k pT^m per eta bin. + struct PowerSums { + std::array, KIntM>, KNEtaMax> pmwk{}; + std::array, KNEtaMax> wk{}; + void reset() + { + for (auto& a : pmwk) { + for (auto& b : a) { + b.fill(0.); + } + } + for (auto& a : wk) { + a.fill(0.); + } + } + void add(int ieta, double pt, double w) + { + double wPow = 1.; + for (int k = 0; k < KIntK; ++k) { + double ptPow = 1.; + for (int m = 0; m < KIntM; ++m) { + pmwk[ieta][m][k] += wPow * ptPow; + ptPow *= pt; + } + wk[ieta][k] += wPow; + wPow *= w; + } + } + }; + std::array, kNSyst> sums{}; + // track cut flow of the current event, added to hTrkCount once per event + std::array, kNSyst> trkCounts{}; + + struct FwdSignal { + std::array ampl{}; // amplitude (data, MC reco) or number of particles (MC truth) + std::array valid{}; // detector read out in this event + }; + + struct EventSelection { + uint32_t mask = 0; + std::array cent{}; + int occupancy = -1; + int occBin = 0; // occupancy bin of the efficiency maps + }; + + // Per-event observables of one (variation, level). + struct EventObs { + float nCh = 0.f; + std::array nW{}, mean{}, c2{}, c3{}, p1kBar{}, dN{}; + std::array fwd{}, dFwd{}; + std::array fwdValid{}; + }; + + static bool hasBit(uint32_t mask, int v) { return (mask >> v) & 1u; } // =========================================================================== // Selection helpers // =========================================================================== template - bool isEventSelected(const T& col) + static bool passItsLayerCut(int cut, const T& col) { - histos.fill(HIST("hEvtCount"), 0.5); - if (!col.sel8()) { - return false; + switch (cut) { + case kITSLayer3: + return col.selection_bit(o2::aod::evsel::kIsGoodITSLayer3); + case kITSLayer0123: + return col.selection_bit(o2::aod::evsel::kIsGoodITSLayer0123); + case kITSLayersAll: + return col.selection_bit(o2::aod::evsel::kIsGoodITSLayersAll); + default: + return true; } - histos.fill(HIST("hEvtCount"), 1.5); - if (std::abs(col.posZ()) > cfgVtxZCut) { + } + + // N_ch within mean +- nSigma RMS at x (events outside the x range of the band are rejected, + // empty bins of the band accept every event). + static bool insideBand(const NSigmaBand& band, float x, float nCh, float nSigma) + { + const auto& mr = band.meanRms; + int bin = 1 + static_cast((x - band.xMin) / (band.xMax - band.xMin) * (mr.size() - 2)); + if (bin < 1 || bin >= static_cast(mr.size() - 1)) { return false; } - histos.fill(HIST("hEvtCount"), 2.5); - if (cfgEvSelNoSameBunchPileup && !col.selection_bit(o2::aod::evsel::kNoSameBunchPileup)) { - return false; + if (mr[bin].second < 0.f) { + return true; } - histos.fill(HIST("hEvtCount"), 3.5); - if (cfgUseGoodITSLayerAllCut && !col.selection_bit(o2::aod::evsel::kIsGoodITSLayersAll)) { + return std::abs(nCh - mr[bin].first) <= nSigma * mr[bin].second; + } + + // Linear cut and n-sigma bands of width nSigma. + bool isPassAddPileup(float multPV, int trksize, float cent, float nSigma) + { + if (pileupBands[kBandCent].applied && !insideBand(pileupBands[kBandCent], cent, trksize, nSigma)) { return false; } - histos.fill(HIST("hEvtCount"), 4.5); - if (cfgIsGoodZvtxFT0VsPV && !col.selection_bit(o2::aod::evsel::kIsGoodZvtxFT0vsPV)) { + if (pileupBands[kBandPV].applied && !insideBand(pileupBands[kBandPV], multPV, trksize, nSigma)) { return false; } - histos.fill(HIST("hEvtCount"), 5.5); + if (cfgPileup.cfgApplyLinPupCut) { + if (trksize > (cfgPileup.cfgLinPupParam0 + cfgPileup.cfgLinPupParam1 * multPV)) { + return false; + } + if (trksize < (cfgPileup.cfgLinPupParam2 + cfgPileup.cfgLinPupParam3 * multPV)) { + return false; + } + } return true; } - bool isPassAddPileup(float multPV, int trksize, float cent) + // Occupancy bin of the efficiency maps; occupancy < 0 (time-frame border) goes to the first bin. + int effOccBin(int occupancy) const { - auto checkLimits = [](float x, float y, const std::vector>& limits, float xM, float xMx) { - if (limits.empty()) { - return true; - } - int bin = 1 + static_cast((x - xM) / (xMx - xM) * (limits.size() - 2)); - if (bin < 1 || bin >= static_cast(limits.size() - 1)) { - return false; + const int n = static_cast(effOccEdges.size()) - 1; + const int i = static_cast(std::upper_bound(effOccEdges.begin(), effOccEdges.end(), static_cast(occupancy)) - effOccEdges.begin()) - 1; + return std::clamp(i, 0, n - 1); + } + double effOccValue(int occBin) const { return 0.5 * (effOccEdges[occBin] + effOccEdges[occBin + 1]); } + + // One event at cut-flow stage s of variation v: hEvtCountSyst and that variation's hEvtCount. + void countEvent(int s, int v) + { + hists.evtCount->Fill(s, v); + if (hists.evtCount1D[v]) { + hists.evtCount1D[v]->Fill(s); + } + } + + // Event selection of all active variations: returns the variations the event passes and its + // centrality for each of them. The cuts common to all variations come first. The event cut + // flow of every active variation is filled unless count is false (cut QA). + template + EventSelection isEventSelected(const T& col, int nCh, uint32_t active, bool count = true) + { + EventSelection ev; + auto countAll = [&](int s) { + for (int v = 0; v < kNSyst && count; ++v) { + if (hasBit(active, v)) { + countEvent(s, v); + } } - return (y >= limits[bin].first && y <= limits[bin].second); }; - if (cfgApplySigPupCut) { - if (!checkLimits(cent, trksize, state.mLimitsNchCent, state.mMinXNchCent, state.mMaxXNchCent)) { - return false; - } - histos.fill(HIST("hEvtCount"), 6.5); + countAll(kEvAll); + if (!col.sel8()) { + return ev; } - if (cfgApplyLinPupCut) { - if (trksize > (cfgLinPupParam0 + cfgLinPupParam1 * multPV)) { - return false; + countAll(kEvSel8); + if (std::abs(col.posZ()) > cfgEvSel.cfgVtxZCut) { + return ev; + } + countAll(kEvVtxZ); + if (cfgEvSel.cfgIsGoodZvtxFT0VsPV && !col.selection_bit(o2::aod::evsel::kIsGoodZvtxFT0vsPV)) { + return ev; + } + countAll(kEvGoodZvtx); + + const float centBase = getCentrality(col, cfgEvSel.cfgCentralityChoice); + const float multPV = col.multNTracksPV(); + ev.occupancy = col.trackOccupancyInTimeRange(); + ev.occBin = effOccBin(ev.occupancy); + const bool passAddPileup = isPassAddPileup(multPV, nCh, centBase, pupNSigma); + const bool passAddPileupTight = isPassAddPileup(multPV, nCh, centBase, pupNSigma - KSystPupnSigStep); + + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(active, v)) { + continue; } - histos.fill(HIST("hEvtCount"), 7.5); - if (trksize < (cfgLinPupParam2 + cfgLinPupParam3 * multPV)) { - return false; + auto countOne = [&](int s) { + if (count) { + countEvent(s, v); + } + }; + const float cent = (v == kSystCent) ? getCentrality(col, KSystCentralityChoice) : centBase; + ev.cent[v] = cent; + if (cfgEvSel.cfgEvSelNoSameBunchPileup && !col.selection_bit(o2::aod::evsel::kNoSameBunchPileup)) { + continue; + } + countOne(kEvSameBunch); + if (cfgEvSel.cfgEvSelNoCollInTimeRangeStd && !col.selection_bit(o2::aod::evsel::kNoCollInTimeRangeStandard)) { + continue; + } + countOne(kEvTimeRange); + if (!passItsLayerCut(v == kSystITSLayer ? itsLayerVar : itsLayerNom, col)) { + continue; + } + countOne(kEvITSLayer); + if (!(v == kSystPileupNSigma ? passAddPileupTight : passAddPileup)) { + continue; + } + countOne(kEvAddPileup); + // the cut of the event-selection variations + if (v == kSystPileup && !col.selection_bit(o2::aod::evsel::kNoCollInRofStandard)) { + continue; + } + if (v == kSystVz && !(std::abs(col.posZ()) < KSystVtxZCut)) { + continue; + } + if (v == kSystOccupancy && !(ev.occupancy >= 0 && ev.occupancy < occupancyMax)) { + continue; + } + countOne(kEvVariationCut); + if (!(cent < cfgEvSel.cfgCentMax)) { + continue; + } + countOne(kEvCent); + ev.mask |= (1u << v); + if (col.selection_bit(o2::aod::evsel::kNoHighMultCollInPrevRof)) { + countOne(kEvInfoPrevRof); + } + if (ev.occupancy < 0) { + countOne(kEvInfoOccUnknown); } - histos.fill(HIST("hEvtCount"), 8.5); } - return true; + return ev; } + // Track selection of all variations the event passed (evMask): returns the variations the + // track passes. The cuts common to all variations come first. The cut flow is collected in + // trkCounts (unless count is false: cut QA) and added to the track counters by flushTrackCounts(). template - bool isTrackSelected(const T& trk) + uint32_t isTrackSelected(const T& trk, uint32_t evMask, bool count = true) { - histos.fill(HIST("hTrkCount"), 0.5); + const double inc = count ? 1. : 0.; + auto countAll = [&](int s) { + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(evMask, v)) { + trkCounts[v][s] += inc; + } + } + }; + countAll(kTrkAll); if (trk.sign() == 0) { - return false; + return 0; } - histos.fill(HIST("hTrkCount"), 1.5); + countAll(kTrkSign); if (!trk.has_collision()) { - return false; + return 0; } - histos.fill(HIST("hTrkCount"), 2.5); + countAll(kTrkHasColl); if (!trk.isPVContributor()) { - return false; - } - histos.fill(HIST("hTrkCount"), 3.5); - if (!(trk.itsNCls() > cfgITScluster)) { - return false; - } - histos.fill(HIST("hTrkCount"), 4.5); - if (!(trk.tpcNClsFound() >= cfgTPCcluster)) { - return false; - } - histos.fill(HIST("hTrkCount"), 5.5); - if (!(trk.tpcNClsCrossedRows() >= cfgTPCnCrossedRows)) { - return false; + return 0; } - histos.fill(HIST("hTrkCount"), 6.5); + countAll(kTrkPVContrib); if (trk.pt() < cfgCutPtLower || trk.pt() > cfgCutPtUpper || std::abs(trk.eta()) > cfgCutEta) { - return false; + return 0; } - histos.fill(HIST("hTrkCount"), 7.5); - if (!trk.isGlobalTrack()) { - return false; + countAll(kTrkPtEta); + if (!trk.isGlobalTrack() || trk.tpcNClsCrossedRows() < cfgTPCnCrossedRows) { + return 0; } - histos.fill(HIST("hTrkCount"), 8.5); + countAll(kTrkGlobal); - if (cfgPtDepDCAxy) { - float maxDcaXY = cfgDcaXyP0 + cfgDcaXyP1 / std::pow(trk.pt(), cfgDcaXyP2); - if (std::abs(trk.dcaXY()) > maxDcaXY) { - return false; + const float maxDcaXY = KSystDcaXyScale * (cfgSyst.cfgDcaXyP0 + cfgSyst.cfgDcaXyP1 / std::pow(trk.pt(), cfgSyst.cfgDcaXyP2.value)); + uint32_t mask = 0; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(evMask, v)) { + continue; } - histos.fill(HIST("hTrkCount"), 9.5); - } else { - if (std::abs(trk.dcaXY()) > cfgCutTracKDcaMaxXY) { - return false; + if (!(trk.itsNCls() > (v == kSystNITS ? KSystITScluster : cfgITScluster.value))) { + continue; } - histos.fill(HIST("hTrkCount"), 9.5); - } - if (cfgPtDepDCAz) { - float maxDcaZ = cfgDcaZP0 + cfgDcaZP1 / std::pow(trk.pt(), cfgDcaZP2); - if (std::abs(trk.dcaZ()) > maxDcaZ) { - return false; + trkCounts[v][kTrkITS] += inc; + if (!(trk.tpcNClsFound() >= (v == kSystNTPC ? KSystTPCcluster : cfgTPCcluster.value))) { + continue; } - histos.fill(HIST("hTrkCount"), 10.5); - } else { - if (std::abs(trk.dcaZ()) > cfgCutTracKDcaMaxZ) { - return false; + trkCounts[v][kTrkTPC] += inc; + if (!(std::abs(trk.dcaZ()) < (v == kSystDCAz ? KSystDcaMaxZ : cfgCutTracKDcaMaxZ.value))) { + continue; + } + trkCounts[v][kTrkDCAz] += inc; + // the cut of the track-selection variations + if (v == kSystChi2TPC && !(trk.tpcChi2NCl() < KSystChi2TPC)) { + continue; + } + if (v == kSystDCAxy && !(std::abs(trk.dcaXY()) < maxDcaXY)) { + continue; + } + trkCounts[v][kTrkVariationCut] += inc; + mask |= (1u << v); + } + return mask; + } + + // Adds the track cut flow of the event to hTrkCountSyst and hTrkCount. + void flushTrackCounts() + { + for (int v = 0; v < kNSyst; ++v) { + for (int s = 0; s < kNTrkCut; ++s) { + if (trkCounts[v][s] > 0.) { + hists.trkCount->Fill(s, v, trkCounts[v][s]); + if (hists.trkCount1D[v]) { + hists.trkCount1D[v]->Fill(s, trkCounts[v][s]); + } + trkCounts[v][s] = 0.; + } } - histos.fill(HIST("hTrkCount"), 10.5); } - return true; } + // Charged primary in the observable acceptance (event vertex cuts are applied to the reco collision). template bool isParticleSelected(const T& particle) { @@ -414,122 +938,138 @@ struct RadialFlowDecorr { if (particle.pt() < cfgCutPtLower || particle.pt() > cfgCutPtUpper || std::abs(particle.eta()) > cfgCutEta) { return false; } - if (std::abs(particle.vz()) > cfgVtxZCut) { - return false; - } return true; } - float getCentrality(const auto& col) const + // Origin of a reco MC track (ETrackOrigin) with respect to the MC collision of coll. + template + int trackOrigin(const T& track, const C& coll) const + { + if (!track.has_mcParticle()) { + return kOriginNoLabel; + } + if (track.mcMask() & (1 << KMcMaskFake)) { + return kOriginFakeFlag; + } + if (cfgCorr.cfgMcItsTpcMismatchAsFake.value && (track.mcMask() & (1 << KMcMaskItsTpcMismatch))) { + return kOriginItsTpcMismatch; + } + const auto& particle = track.mcParticle(); + if (particle.mcCollisionId() != coll.mcCollisionId()) { + return kOriginOtherColl; + } + return particle.isPhysicalPrimary() ? kOriginPrimary : kOriginSecondary; + } + + float getCentrality(const auto& col, int choice) const { - if (cfgCentralityChoice.value == kCentFT0C) { + if (choice == kCentFT0C) { return col.centFT0C(); } - if (cfgCentralityChoice.value == kCentFT0M) { + if (choice == kCentFT0M) { return col.centFT0M(); } - if (cfgCentralityChoice.value == kCentFDDM) { + if (choice == kCentFDDM) { return col.centFDDM(); } - if (cfgCentralityChoice.value == kCentFV0A) { + if (choice == kCentFV0A) { return col.centFV0A(); } return KinvalidCentrality; } - // Inclusive efficiency/fake lookup (no species dependence). - float getEfficiency(float mult, float pt, float eta, - int effidx, bool useEff) const + // Observable eta bins containing eta: the full-range bin 0 and one narrow bin. + int etaBinsOf(float eta, std::array& bins) const { - if (!useEff) { - return (effidx == 0) ? 1.0f : 0.0f; + int n = 0; + for (int ieta = 0; ieta < nEta && n < 2; ++ieta) { + if (eta > etaLw[ieta] && eta <= etaUp[ieta]) { + bins[n++] = ieta; + } } + return n; + } - const float invalid = - std::numeric_limits::quiet_NaN(); - - TH3F* h = (effidx == 0) ? state.hEff : state.hFake; - - if (!h || !std::isfinite(mult) || - !std::isfinite(pt) || !std::isfinite(eta)) { + // =========================================================================== + // Corrections + // =========================================================================== + // Inclusive efficiency/fake lookup (no species dependence). + static float getEfficiency(const TH3* h, float mult, float pt, float eta, bool isEff) + { + const float invalid = std::numeric_limits::quiet_NaN(); + if (!h || !std::isfinite(mult) || !std::isfinite(pt) || !std::isfinite(eta)) { return invalid; } - int ibx = h->GetXaxis()->FindFixBin(mult); - // Reject Nch underflow, clamp overflow. if (ibx < 1) { return invalid; } - ibx = std::min(ibx, h->GetNbinsX()); - int iby = h->GetYaxis()->FindFixBin(pt); int ibz = h->GetZaxis()->FindFixBin(eta); - // Do not extrapolate in pT or eta. - if (iby < 1 || iby > h->GetNbinsY() || - ibz < 1 || ibz > h->GetNbinsZ()) { + if (iby < 1 || iby > h->GetNbinsY() || ibz < 1 || ibz > h->GetNbinsZ()) { return invalid; } - const double val = h->GetBinContent(ibx, iby, ibz); const double err = h->GetBinError(ibx, iby, ibz); - // A bin without a positive error is not a valid measurement. - if (!std::isfinite(val) || - !std::isfinite(err) || err <= 0.0) { + if (!std::isfinite(val) || !std::isfinite(err) || err <= 0.0) { return invalid; } - // Physical-domain checks. - if (val < 0.0 || val > 1.0 || - (effidx == 0 && val == 0.0)) { + if (val < 0.0 || val > 1.0 || (isEff && val == 0.0)) { return invalid; } - return static_cast(val); } - float efficiencyVariationFactor(float pt) const + // Nominal efficiency and fake rate of the map of variation v (no variation factor). + bool getValidEffFake(int v, int occBin, float mult, float pt, float eta, float& eff, float& fake) { - if (cfgSystType.value != kSystEff && cfgSystType.value != kSystEffPtDep) { - return 1.f; + if (!cfgEff) { + eff = 1.f; + fake = 0.f; + return true; + } + const auto& in = inputs[v]; + if (occBin >= static_cast(in.hEff.size())) { + return false; } - // Empirical shape: full variation at cfgPtMin, zero at cfgPtMax. - const bool ptDep = cfgEffVarPtDep.value || cfgSystType.value == kSystEffPtDep; - const float shape = ptDep ? (cfgPtMax.value - pt) / (cfgPtMax.value - cfgPtMin.value) : 1.f; - return 1.f + 0.01f * cfgEffVarPercent.value * shape; + auto valid = [&]() { return std::isfinite(eff) && std::isfinite(fake) && eff > KFloatEpsilon && eff <= 1.f && fake >= 0.f && fake < 1.f; }; + eff = getEfficiency(in.hEff[occBin], mult, pt, eta, true); + fake = getEfficiency(in.hFake[occBin], mult, pt, eta, false); + if (valid()) { + return true; + } + // empty or statistically invalid cell of this occupancy bin: the occupancy-integrated map, + // then the map integrated over occupancy and N_ch + eff = getEfficiency(in.hEffAllOcc, mult, pt, eta, true); + fake = getEfficiency(in.hFakeAllOcc, mult, pt, eta, false); + if (valid()) { + return true; + } + eff = getEfficiency(in.hEffPtEta, mult, pt, eta, true); + fake = getEfficiency(in.hFakePtEta, mult, pt, eta, false); + return valid(); } - bool getValidEffFake(float mult, float pt, float eta, bool useEff, - float& eff, float& fake) const + float efficiencyVariationFactor(int v, float pt) { - eff = getEfficiency(mult, pt, eta, 0, useEff); - fake = getEfficiency(mult, pt, eta, 1, useEff); - - // Validate the nominal corrections before applying any variation. - if (!std::isfinite(eff) || !std::isfinite(fake) || - eff <= KFloatEpsilon || eff > 1.f || - fake < 0.f || fake >= 1.f) { - return false; + if (v == kSystEff) { + return 1.f + 0.01f * KEffVarPercent; } - - // Apply the variation once, in DATA mean/fluctuation jobs. - if (useEff && (cfgRunDataMean.value || cfgRunDataFluc.value)) { - eff *= efficiencyVariationFactor(pt); + if (v == kSystEffPtDep) { + // Linear in pT: full variation at cfgPtMin, none at KEffVarPtZero. + const float shape = std::clamp((KEffVarPtZero - pt) / (KEffVarPtZero - cfgPtMin), 0.f, 1.f); + return 1.f + 0.01f * KEffVarPercent * shape; } - - // The varied efficiency is an effective correction denominator. - return std::isfinite(eff) && eff > KFloatEpsilon; + return 1.f; } - float getFlatteningWeight(float vz, float chg, float pt, float eta, float phi, bool useFlat) const + static float getFlatteningWeight(const THnSparseF* h, float vz, float chg, float pt, float eta, float phi) { - if (!useFlat) { - return 1.0; - } - THnSparseF* h = state.hFlatWeight; if (!h) { return 0.0; } @@ -542,137 +1082,57 @@ struct RadialFlowDecorr { return h->GetBinContent(bins.data()); } - TH3F* rebinNchMap(const TH3F* h, int sysConfig, - const char* newName) const + // Weight w = w_flat (1 - fake) / eff of the track for every variation in mask; a map shared + // by several variations is looked up once. Returns the variations with a usable weight and + // the Base efficiency, fake rate and flattening weight for the QA profiles. + uint32_t getTrackWeights(uint32_t mask, float mult, int occBin, float vz, float sign, float pt, float eta, float phi, bool useFlat, + std::array& weights, float& effBase, float& fakeBase, float& flatBase) { - if (!h) { - return nullptr; - } - - // PbPb: 100 tracks; NeNe, OO, pp: 10 tracks - const int trackStep = (sysConfig == 1) ? 100 : 10; - - const int nx = h->GetNbinsX(); - const int ny = h->GetNbinsY(); - const int nz = h->GetNbinsZ(); - - const double originalWidth = h->GetXaxis()->GetBinWidth(1); - - const int groupSize = - static_cast(std::lround(trackStep / originalWidth)); - - // Ensure the input binning allows exact 10/100-track groups - if (groupSize < 1 || - std::abs(groupSize * originalWidth - trackStep) > KFloatEpsilon) { - LOGF(fatal, "Nch bin width is incompatible with %d-track rebinning", - trackStep); - return nullptr; - } - - for (int ix = 1; ix <= nx; ++ix) { - if (std::abs(h->GetXaxis()->GetBinWidth(ix) - - originalWidth) > KFloatEpsilon) { - LOGF(fatal, "Input Nch axis must have uniform bin widths"); - return nullptr; + std::array effDone{}; + std::array effS{}, fakeS{}, flatS{}; + std::array flatDone{}; + uint32_t weightMask = 0; + effBase = std::numeric_limits::quiet_NaN(); + fakeBase = std::numeric_limits::quiet_NaN(); + flatBase = 1.f; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v)) { + continue; } - } - - // Construct the new Nch bin edges - std::vector xEdges; - xEdges.push_back(h->GetXaxis()->GetBinLowEdge(1)); - - for (int ix = 1; ix <= nx; ix += groupSize) { - const int last = std::min(ix + groupSize - 1, nx); - xEdges.push_back(h->GetXaxis()->GetBinUpEdge(last)); - } - - // Preserve the original pT and eta axes exactly - auto copyEdges = [](const TAxis* axis) { - std::vector edges; - - for (int i = 1; i <= axis->GetNbins(); ++i) { - edges.push_back(axis->GetBinLowEdge(i)); + const int es = effSource(v); + if (effDone[es] == 0) { + effDone[es] = getValidEffFake(es, occBin, mult, pt, eta, effS[es], fakeS[es]) ? 1 : -1; } - - edges.push_back(axis->GetBinUpEdge(axis->GetNbins())); - return edges; - }; - - const auto yEdges = copyEdges(h->GetYaxis()); - const auto zEdges = copyEdges(h->GetZaxis()); - - const int nNewX = static_cast(xEdges.size()) - 1; - - auto* rebinned = new TH3F( - newName, h->GetTitle(), - nNewX, xEdges.data(), - ny, yEdges.data(), - nz, zEdges.data()); - - rebinned->SetDirectory(nullptr); - rebinned->Sumw2(); - - // Rebin independently for each (pT, eta) slice - for (int iy = 1; iy <= ny; ++iy) { - for (int iz = 1; iz <= nz; ++iz) { - - int lastValid = 0; - double lastValue = 0.; - double lastError = 0.; - - for (int ib = 1; ib <= nNewX; ++ib) { - - const int first = (ib - 1) * groupSize + 1; - const int last = std::min(first + groupSize - 1, nx); - - double sumW = 0.; - double sumVW = 0.; - - for (int ix = first; ix <= last; ++ix) { - - const double v = h->GetBinContent(ix, iy, iz); - const double e = h->GetBinError(ix, iy, iz); - - // Inverse-variance weighting requires a positive error - if (!std::isfinite(v) || - !std::isfinite(e) || e <= 0.) { - continue; - } - - const double weight = 1. / (e * e); - - sumW += weight; - sumVW += v * weight; - } - - if (sumW > 0.) { - - const double avg = sumVW / sumW; - const double err = std::sqrt(1. / sumW); - - rebinned->SetBinContent(ib, iy, iz, avg); - rebinned->SetBinError(ib, iy, iz, err); - - lastValid = ib; - lastValue = avg; - lastError = err; - } - } - - // Extend the final valid value into trailing empty bins. - // This also allows safe overflow clamping at readout. - for (int ib = lastValid + 1; ib <= nNewX; ++ib) { - if (lastValid == 0) { - break; - } - - rebinned->SetBinContent(ib, iy, iz, lastValue); - rebinned->SetBinError(ib, iy, iz, lastError); + if (effDone[es] < 0) { + continue; + } + const float eff = effS[es] * efficiencyVariationFactor(v, pt); + if (!(eff > KFloatEpsilon)) { + continue; + } + float flat = 1.f; + const int fs = flatSource(v); + if (useFlat && cfgFlat && fs >= 0) { + if (!flatDone[fs]) { + flatS[fs] = getFlatteningWeight(inputs[fs].hFlat, vz, sign, pt, eta, phi); + flatDone[fs] = true; } + flat = flatS[fs]; + } + const float weight = flat * (1.0f - fakeS[es]) / eff; + if (!std::isfinite(weight) || weight <= 0.f) { + continue; + } + weights[v] = weight; + weightMask |= (1u << v); + trkCounts[v][kTrkWeight] += 1.; + if (v == kSystBase) { + effBase = eff; + fakeBase = fakeS[es]; + flatBase = flat; } } - - return rebinned; + return weightMask; } // Per-event two- and three-particle pT correlators (standard method) from the @@ -721,14 +1181,373 @@ struct RadialFlowDecorr { } // =========================================================================== - // Table joins + // Forward detectors // =========================================================================== - using GeneralCollisions = soa::Join; - - Filter collisionFilter = nabs(aod::collision::posZ) < cfgVtxZCut; - using AodCollisionsSel = soa::Filtered; + // Eta sub-bin of detector det containing eta, -1 outside its edges. + int fwdSubBin(int det, float eta) const + { + const auto& e = fwdEdges[det]; + if (e.size() < 2 || eta < e.front() || eta >= e.back()) { + return -1; + } + const int i = static_cast(std::upper_bound(e.begin(), e.end(), eta) - e.begin()) - 1; + return fwdOffset[det] + i; + } + + void addFwd(int det, float eta, float ampl, FwdSignal& fwd) + { + fwd.ampl[det] += ampl; + const int sub = fwdSubBin(det, eta); + if (sub >= 0) { + fwd.ampl[sub] += ampl; + } + if (hists.fwdEta) { + hists.fwdEta->Fill(det, eta, ampl); + if (sub >= 0) { + hists.fwdEta->Fill(sub, eta, ampl); + } + } + } + + void markFwdValid(int det, FwdSignal& fwd) const + { + fwd.valid[det] = true; + for (int i = 0; i + 1 < static_cast(fwdEdges[det].size()); ++i) { + fwd.valid[fwdOffset[det] + i] = true; + } + } + + float channelEta(const std::array& p, float vz) + { + const float r = std::hypot(p[0], p[1]); + const float dz = p[2] - (cfgFwdOpt.cfgFwdVzCorr ? vz : 0.f); + return r > 0.f ? std::asinh(dz / r) : std::numeric_limits::quiet_NaN(); + } + + void fillChannelQA(int ch, float raw, float corr, float eta) + { + if (hists.fwdChannel) { + hists.fwdChannel->Fill(ch, kChAmpRaw, raw); + hists.fwdChannel->Fill(ch, kChAmpCorr, corr); + hists.fwdChannel->Fill(ch, kChEta, eta); + } + } + + // Channel positions with the FT0/FV0 alignment (as in PWGCF/flowDecorrelation) and the + // gain equalisation, loaded at each new run. + template + void loadFwdCalib(const B& bc, bool applyGain) + { + if (bc.runNumber() == fwdCalib.run) { + return; + } + fwdCalib.run = bc.runNumber(); + const uint64_t ts = bc.timestamp(); + + auto* alFT0 = ccdb->getForTimeStamp>("FT0/Calib/Align", ts); + auto* alFV0 = ccdb->getForTimeStamp>("FV0/Calib/Align", ts); + if (!alFT0 || alFT0->size() < 2 || !alFV0 || alFV0->size() < 2) { + LOGF(fatal, "Could not load FT0/FV0 alignment (FT0/Calib/Align, FV0/Calib/Align) for timestamp %llu", ts); + return; + } + + // offsetFT0[0]: FT0A, offsetFT0[1]: FT0C + fwdCalib.ft0Det.calculateChannelCenter(); + for (int ch = 0; ch < KNChFT0; ++ch) { + const int side = ch < KNChFT0A ? 0 : 1; + const auto c = fwdCalib.ft0Det.getChannelCenter(ch); + float z = c.Z() + (*alFT0)[side].getZ(); + if (ch >= KNChFT0A) { + z = -z; + } + fwdCalib.posFT0[ch] = {static_cast(c.X() + (*alFT0)[side].getX()), static_cast(c.Y() + (*alFT0)[side].getY()), z}; + } + // offsetFV0[0]: left half, offsetFV0[1]: right half + static constexpr std::array KCellsInLeft = {0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19, 24, 25, 26, 27, 32, 40, 33, 41, 34, 42, 35, 43}; + for (int ch = 0; ch < KNChFV0; ++ch) { + const bool left = std::find(KCellsInLeft.begin(), KCellsInLeft.end(), ch) != KCellsInLeft.end(); + const auto& off = (*alFV0)[left ? 0 : 1]; + const auto c = fwdCalib.fv0Det->getReadoutCenter(ch); + fwdCalib.posFV0[ch] = {static_cast(c.x + off.getX()), static_cast(c.y + off.getY()), static_cast(c.z + off.getZ())}; + } + + fwdCalib.gainFT0.fill(1.f); + fwdCalib.gainFV0.fill(1.f); + if (applyGain && cfgFwdOpt.cfgFwdGainEq) { + auto* gFT0 = getOptional>(cfgFwdOpt.cfgGainEqPath.value + "/FT0", ts); + auto* gFV0 = getOptional>(cfgFwdOpt.cfgGainEqPath.value + "/FV0", ts); + if (gFT0 && gFT0->size() >= static_cast(KNChFT0)) { + std::copy_n(gFT0->begin(), KNChFT0, fwdCalib.gainFT0.begin()); + } else { + LOGF(warning, "No FT0 gain equalisation at %s/FT0 for run %d: unit gains", cfgFwdOpt.cfgGainEqPath.value.c_str(), fwdCalib.run); + } + if (gFV0 && gFV0->size() >= static_cast(KNChFV0)) { + std::copy_n(gFV0->begin(), KNChFV0, fwdCalib.gainFV0.begin()); + } else { + LOGF(warning, "No FV0 gain equalisation at %s/FV0 for run %d: unit gains", cfgFwdOpt.cfgGainEqPath.value.c_str(), fwdCalib.run); + } + } + LOGF(info, "Forward-detector calibration loaded for run %d", fwdCalib.run); + } + + // Gain-equalised amplitudes of FT0A, FT0C and FV0A, totals and eta sub-bins. + template + void computeFwdReco(const C& col, float vz, FwdSignal& fwd, bool qa) + { + fwd = {}; + auto corr = [](float a, float g) { return g > 0.f ? a / g : a; }; + if (col.has_foundFT0()) { + const auto& ft0 = col.foundFT0(); + markFwdValid(kFT0A, fwd); + markFwdValid(kFT0C, fwd); + for (std::size_t i = 0; i < ft0.channelA().size(); ++i) { + const int id = ft0.channelA()[i]; + if (id < 0 || id >= KNChFT0A) { + continue; + } + const float a = corr(ft0.amplitudeA()[i], fwdCalib.gainFT0[id]); + const float eta = channelEta(fwdCalib.posFT0[id], vz); + addFwd(kFT0A, eta, a, fwd); + if (qa) { + fillChannelQA(id, ft0.amplitudeA()[i], a, eta); + } + } + for (std::size_t i = 0; i < ft0.channelC().size(); ++i) { + const int id = ft0.channelC()[i] + KNChFT0A; + if (id < KNChFT0A || id >= KNChFT0) { + continue; + } + const float a = corr(ft0.amplitudeC()[i], fwdCalib.gainFT0[id]); + const float eta = channelEta(fwdCalib.posFT0[id], vz); + addFwd(kFT0C, eta, a, fwd); + if (qa) { + fillChannelQA(id, ft0.amplitudeC()[i], a, eta); + } + } + } + if (col.has_foundFV0()) { + const auto& fv0 = col.foundFV0(); + markFwdValid(kFV0A, fwd); + for (std::size_t i = 0; i < fv0.channel().size(); ++i) { + const int id = fv0.channel()[i]; + if (id < 0 || id >= KNChFV0) { + continue; + } + const float a = corr(fv0.amplitude()[i], fwdCalib.gainFV0[id]); + const float eta = channelEta(fwdCalib.posFV0[id], vz); + addFwd(kFV0A, eta, a, fwd); + if (qa) { + fillChannelQA(KNChFT0 + id, fv0.amplitude()[i], a, eta); + } + } + } + } + + // Number of charged primaries in the acceptance of each forward detector (MC truth). + template + void computeFwdTruth(const P& particles, FwdSignal& fwd) + { + fwd = {}; + for (int det = 0; det < kNFwdDet; ++det) { + markFwdValid(det, fwd); + } + for (const auto& particle : particles) { + if (!particle.isPhysicalPrimary()) { + continue; + } + const float eta = particle.eta(); + if (std::abs(eta) < 1.5f) { + continue; + } + auto* pd = pdg->GetParticle(particle.pdgCode()); + if (!pd || std::abs(pd->Charge()) == 0) { + continue; + } + for (int det = 0; det < kNFwdDet; ++det) { + const int sub = fwdSubBin(det, eta); + if (sub >= 0) { + fwd.ampl[det] += 1.; + fwd.ampl[sub] += 1.; + } + } + } + } + + // =========================================================================== + // Observables + // =========================================================================== + // Bin content of p at (x, ybin[, zbin]), NaN for an empty bin. + static double profileValue(const TProfile2D* p, double x, int ybin) + { + if (!p) { + return std::numeric_limits::quiet_NaN(); + } + const int gb = p->GetBin(p->GetXaxis()->FindBin(x), ybin); + if (p->GetBinEntries(gb) <= 0.) { + return std::numeric_limits::quiet_NaN(); + } + return p->GetBinContent(gb); + } + static double profileValue(const TProfile3D* p, double x, int ybin, int zbin) + { + if (!p) { + return std::numeric_limits::quiet_NaN(); + } + const int gb = p->GetBin(p->GetXaxis()->FindBin(x), ybin, zbin); + if (p->GetBinEntries(gb) <= 0.) { + return std::numeric_limits::quiet_NaN(); + } + return p->GetBinContent(gb); + } + + void computeEventObs(const PowerSums& s, int v, int level, const FwdSignal* fwd, float nCh, EventObs& evObs) + { + const auto& input = inputs[v]; + const double nan = std::numeric_limits::quiet_NaN(); + evObs.nCh = nCh; + for (int ieta = 0; ieta < nEta; ++ieta) { + evObs.nW[ieta] = s.wk[ieta][1]; + evObs.mean[ieta] = (s.wk[ieta][1] > KFloatEpsilon) ? (s.pmwk[ieta][1][1] / s.wk[ieta][1]) : nan; + evObs.c2[ieta] = nan; + evObs.c3[ieta] = nan; + evObs.p1kBar[ieta] = nan; + // c2 (>=2 tracks), c3 (>=3 tracks) and the pT deviation need a usable reference + const double mmpt = profileValue(input.pmean[level], nCh, ieta + 1, kMeanPt + 1); + if (std::isfinite(mmpt) && mmpt != 0) { + const auto corr = calculateC2C3FromSums(s.pmwk[ieta], s.wk[ieta], mmpt); + evObs.c2[ieta] = corr.c2; + evObs.c3[ieta] = corr.c3; + if (std::isfinite(evObs.mean[ieta])) { + evObs.p1kBar[ieta] = evObs.mean[ieta] - mmpt; + } + } + evObs.dN[ieta] = evObs.nW[ieta] - profileValue(input.pmean[level], nCh, ieta + 1, kMeanN + 1); + } + evObs.fwdValid.fill(false); + if (!fwd || !input.pmeanFwd[level]) { + return; + } + for (int k = 0; k < nFwd; ++k) { + if (!fwd->valid[k]) { + continue; + } + const double m = profileValue(input.pmeanFwd[level], nCh, k + 1); + if (!std::isfinite(m)) { + continue; + } + evObs.fwd[k] = fwd->ampl[k]; + evObs.dFwd[k] = fwd->ampl[k] - m; + evObs.fwdValid[k] = true; + } + } + + // Fill the fluctuation profiles of one (variation, level) at x = N_ch, or of one bootstrap + // replica with weight w at x = centrality. + void fillFluc(const FlucHists& h, const EventObs& evObs, double w, float x) + { + auto fill = [w](const std::shared_ptr& p, double a, double b, double c, double val) { + if (p) { + p->Fill(a, b, c, val, w); + } + }; + + // multiplicity, meanpT, C2, C3 vs eta bin + for (int ieta = 0; ieta < nEta; ++ieta) { + fill(h.obs, x, ieta, kObsN, evObs.nW[ieta]); + if (std::isfinite(evObs.mean[ieta])) { + fill(h.obs, x, ieta, kObsMeanPt, evObs.mean[ieta]); + } + if (std::isfinite(evObs.c2[ieta])) { + fill(h.obs, x, ieta, kObsC2, evObs.c2[ieta]); + } + if (std::isfinite(evObs.c3[ieta])) { + fill(h.obs, x, ieta, kObsC3, evObs.c3[ieta]); + } + } + + // mirror-pair subevent (C2Sub, C3Sub) vs eta bin + for (int ietaA = 1; ietaA <= (nEta - 1) / 2; ++ietaA) { + int ietaC = nEta - ietaA; + float c2Sub = evObs.p1kBar[ietaA] * evObs.p1kBar[ietaC]; + // Mirror pairs (A, C = -A): both orderings are filled, each at the bin of its pair, + // so bins A < 0 hold AAB and bins C > 0 hold ABB of the same mirror pair. For a + // symmetric collision system the two agree in expectation: eta -> -eta maps + // AAB(a, -a) onto AAB(-a, a) = ABB(a, -a). Comparing bin A with bin nEta - A is + // therefore a forward/backward check, not two independent observables. + float c3SubA = evObs.c2[ietaA] * evObs.p1kBar[ietaC]; // AAB: 2 particles from A, 1 from C + float c3SubC = evObs.c2[ietaC] * evObs.p1kBar[ietaA]; // ABB: 2 particles from C, 1 from A + + if (std::isfinite(c2Sub)) { + fill(h.obs, x, ietaA, kObsC2Sub, c2Sub); + } + if (std::isfinite(c3SubA)) { + fill(h.obs, x, ietaA, kObsC3Sub, c3SubA); + } + if (std::isfinite(c3SubC)) { + fill(h.obs, x, ietaC, kObsC3Sub, c3SubC); + } + } + + // full 2D subevent maps, in (eta_A, eta_C) and in (Delta eta, Sigma eta) + for (int ietaA = 1; ietaA < nEta; ++ietaA) { + for (int ietaC = 1; ietaC < nEta; ++ietaC) { + float etaValA = (etaLw[ietaA] + etaUp[ietaA]) / 2.0f; + float etaValB = (etaLw[ietaC] + etaUp[ietaC]) / 2.0f; + const float bw = etaUp[ietaA] - etaLw[ietaA]; + const float gap = (ietaA - ietaC) * bw; + const float sum = 2.f * etaLw[1] + (ietaA + ietaC - 1) * bw; // = cA + cC on the lattice + + float c2Sub = (ietaA == ietaC) ? static_cast(evObs.c2[ietaA]) : evObs.p1kBar[ietaA] * evObs.p1kBar[ietaC]; + // C3 sub-event map, ORDERED: cell (A,C) = c2[A] p1kBar[C] = _A _C, + // two particles from A and one from C ("AAB"). The same pair of bins read the + // other way round, cell (C,A) = c2[C] p1kBar[A], is "ABB" (one from A, two from C). + // Both are filled, so the map carries AAB and ABB of every bin pair: the row at + // eta_A = eta_ref is AAB with the pair at eta_ref, the column is ABB. Unlike C2, + // this map is NOT symmetric under A <-> C, and the two triangles must not be + // averaged or treated as duplicates. Diagonal: the within-bin c3 ("AAA"). + float c3Sub2D = (ietaA == ietaC) ? static_cast(evObs.c3[ietaA]) : evObs.c2[ietaA] * evObs.p1kBar[ietaC]; + + if (std::isfinite(c2Sub)) { + fill(h.c2Sub2D, x, etaValA, etaValB, c2Sub); + fill(h.gapSum2D, x, gap, sum, c2Sub); + } + if (std::isfinite(c3Sub2D)) { + fill(h.c3Sub2D, x, etaValA, etaValB, c3Sub2D); + fill(h.c3GapSum2D, x, gap, sum, c3Sub2D); + } + } + } + + // forward amplitude a_k: , and , with + // da = a - , d[pT] = [pT] - <[pT]>, dN = N - , the means at N_ch from the mean pass + for (int k = 0; k < nFwd; ++k) { + if (!evObs.fwdValid[k]) { + continue; + } + const double da = evObs.dFwd[k]; + if (h.fwdVar) { + h.fwdVar->Fill(x, k, da * da, w); + } + for (int ieta = 0; ieta < nEta; ++ieta) { + if (std::isfinite(evObs.p1kBar[ieta])) { + fill(h.covFwdPt, x, ieta, k, evObs.p1kBar[ieta] * da); + } + if (std::isfinite(evObs.dN[ieta])) { + fill(h.covFwdN, x, ieta, k, evObs.dN[ieta] * da); + } + } + } + } + + // =========================================================================== + // Table joins + // =========================================================================== + using GeneralCollisions = soa::Join; + + Filter collisionFilter = nabs(aod::collision::posZ) < cfgEvSel.cfgVtxZCut; + using AodCollisionsSel = soa::Filtered; using UnfilteredTracks = soa::Join< aod::Tracks, aod::TracksExtra, aod::TrackSelection, aod::TracksDCA>; @@ -736,295 +1555,580 @@ struct RadialFlowDecorr { using AodTracksSel = soa::Filtered; using TCs = soa::Join; using FilteredTCs = soa::Filtered; - using BCsRun3 = soa::Join; using MyRun3MCCollisions = soa::Join< aod::Collisions, aod::EvSels, aod::Mults, aod::MultsExtra, aod::CentFT0Cs, aod::CentFT0Ms, aod::CentFDDMs, aod::CentFV0As, aod::CentNGlobals, aod::McCollisionLabels>; + // reconstructed collisions of one MC collision + using McRecoCollisions = soa::SmallGroups; - PresliceUnsorted colPerMcCollision = aod::mccollisionlabel::mcCollisionId; + Preslice tracksPerCollision = aod::track::collisionId; // =========================================================================== // Histogram declarations // =========================================================================== + void labelFwdAxis(TAxis* ax) const + { + for (int k = 0; k < nFwd && k < ax->GetNbins(); ++k) { + ax->SetBinLabel(k + 1, fwdLabels[k].c_str()); + } + } + + static void labelAxis(TAxis* ax, const std::vector& labels) + { + for (std::size_t i = 0; i < labels.size() && static_cast(i) < ax->GetNbins(); ++i) { + ax->SetBinLabel(i + 1, labels[i]); + } + } + + void labelSystAxis(TAxis* ax) const + { + for (int v = 0; v < kNSyst && v < ax->GetNbins(); ++v) { + ax->SetBinLabel(v + 1, systName[v].c_str()); + } + } + void declareCommonQA() { histos.add("hVtxZ_after_sel", ";z_{vtx} (cm)", kTH1F, {{KNbinsZvtx, KZvtxMin, KZvtxMax}}); histos.add("hVtxZ", ";z_{vtx} (cm)", kTH1F, {{KNbinsZvtx, KZvtxMin, KZvtxMax}}); histos.add("hCentrality", ";centrality (%)", kTH1F, {{centAxis1Per}}); - histos.add("Hist2D_globalTracks_PVTracks", ";N_{global};N_{PV}", kTH2F, {{nChAxis}, {nChAxis}}); - histos.add("Hist2D_cent_nch", ";N_{PV};cent (%)", kTH2F, {{nChAxis}, {centAxis1Per}}); - histos.add("Hist2D_globalTracks_cent", "cent (%);N_{global}", kTH2F, {{centAxis1Per}, {nChAxis}}); - histos.add("Hist2D_PVTracks_cent", "cent (%);N_{PV}", kTH2F, {{centAxis1Per}, {nChAxis}}); + histos.add("Hist2D_globalTracks_PVTracks", ";N_{PV};N_{ch} (global tracks)", kTH2F, {{multQAAxis}, {multQAAxis}}); + histos.add("Hist2D_PVTracks_cent", "cent (%);N_{PV}", kTH2F, {{centAxis1Per}, {multQAAxis}}); + hists.nchOccupancy = histos.add("Hist2D_Nch_occupancy", ";N_{ch} (global tracks);trackOccupancyInTimeRange", kTH2F, {{nchAxisObs}, {KNbinsOccQA, 0., KOccQAMax}}); histos.add("hP", ";p (GeV/c)", kTH1F, {{KNbinsPt, KPMin, KPMax}}); histos.add("hPt", ";p_{T} (GeV/c)", kTH1F, {{KNbinsPt, KPtMin, KPtMax}}); histos.add("hEta", ";#eta", kTH1F, {{KNbinsEtaFine, KEtaMin, KEtaMax}}); histos.add("hPhi", ";#phi", kTH1F, {{KNbinsPhi, KPhiMin, TwoPI}}); - histos.add("hEvtCount", "Number of Event;; Count", kTH1F, {{9, 0, 9}}); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(1, "all Events"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(2, "after sel8"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(3, "after VertexZ Cut"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(4, "after kNoSameBunchPileup"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(5, "after kIsGoodZvtxFT0vsPV"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(6, "after kIsGoodITSLayersAll"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(7, "after PVTracksCent Pileup Cut"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(8, "after Linear Pileup Cut (Up)"); - histos.get(HIST("hEvtCount"))->GetXaxis()->SetBinLabel(9, "after Linear Pileup Cut (Lw)"); - - histos.add("hTrkCount", "Number of Tracks;; Count", kTH1F, {{11, 0, 11}}); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(1, "all Tracks"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(2, "after sign!=0"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(3, "after has_collision"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(4, "after isPVContributor"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(5, "after itsNCls"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(6, "after tpcNClsFound"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(7, "after tpcNClsCrossedRows"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(8, "after pT,#eta selections"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(9, "after isGlobalTrack"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(10, "after dcaXY"); - histos.get(HIST("hTrkCount"))->GetXaxis()->SetBinLabel(11, "after dcaZ"); - } - - void declareMCCommonHists() - { - histos.add("h3_AllPrimary", ";N_{PV};p_{T};#eta", kTH3F, {{nChAxis2}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - histos.add("h3_RecoMatchedToPrimary", ";N_{PV};p_{T};#eta", kTH3F, {{nChAxis2}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - histos.add("h3_AllReco", ";N_{PV};p_{T};#eta", kTH3F, {{nChAxis2}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - histos.add("h3_RecoUnMatchedToPrimary_Secondary", ";N_{PV};p_{T};#eta", kTH3F, {{nChAxis2}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - histos.add("h3_RecoUnMatchedToPrimary_Fake", ";N_{PV};p_{T};#eta", kTH3F, {{nChAxis2}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); + // cut flows of every variation (y bin 1 = Base); 1D copies per variation in declareCounts() + hists.evtCount = histos.add("hEvtCountSyst", "Number of events;;variation", kTH2D, {{kNEvCut, -0.5, kNEvCut - 0.5}, {kNSyst, -0.5, kNSyst - 0.5}}); + labelAxis(hists.evtCount->GetXaxis(), evtCutLabels()); + labelSystAxis(hists.evtCount->GetYaxis()); - histos.add("ptResolution", ";p_{T}^{MC};(p_{T}^{reco}-p_{T}^{MC})/p_{T}^{MC}", kTH2F, {{KNbinsPtRes, KPtMin, KPtMax}, {100, -0.2, 0.2}}); - histos.add("etaResolution", ";#eta^{MC};#eta^{reco}-#eta^{MC}", kTH2F, {{KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}, {100, -0.02, 0.02}}); - histos.add("etaTruthReco", ";#eta^{MC};#eta^{reco}", kTH2F, {{KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}, {KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}}); - histos.add("TruthTracKVz", ";Vz^{MC};Vz^{Reco}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {KNbinsVz, KVzMin, KVzMax}}); - histos.add("vzResolution", ";Vz^{MC};(Vz^{reco}-Vz^{MC})/Vz^{MC}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {100, -0.1, 0.1}}); + hists.trkCount = histos.add("hTrkCountSyst", "Number of tracks;;variation", kTH2D, {{kNTrkCut, -0.5, kNTrkCut - 0.5}, {kNSyst, -0.5, kNSyst - 0.5}}); + labelAxis(hists.trkCount->GetXaxis(), trkCutLabels()); + labelSystAxis(hists.trkCount->GetYaxis()); } - void declareMCGetFlatHists() + static std::vector evtCutLabels() { - histos.add("MCGen/hEtaPhiReco", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("MCGen/hEtaPhiRecoEffWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("MCGen/hEtaPhiRecoWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); + return {"all", "sel8", "|z_{vtx}|", "kIsGoodZvtxFT0vsPV", "kNoSameBunchPileup", "kNoCollInTimeRangeStandard", + "ITS good layers", "n#sigma/linear pile-up", "variation cut", "cent < cfgCentMax (selected)", + "selected: kNoHighMultCollInPrevRof", "selected: occupancy < 0"}; + } + static std::vector trkCutLabels() + { + return {"all", "sign#neq0", "has_collision", "isPVContributor", "p_{T}, #eta", "isGlobalTrack, crossed rows", + "itsNCls", "tpcNClsFound", "DCAz", "variation cut (selected)", "valid weight (used)"}; } - void declareMCMeanHists() + // hEvtCount and hTrkCount of each variation of this pass (Base at the top level, the others + // in their folder), so that every variation's output carries its own cut flow. + void declareCounts(uint32_t mask) { - histos.add("Eff_cent", ";cent", kTProfile, {centAxis1Per}); - histos.add("Eff_Ntrk", ";N_{PV}", kTProfile, {nChAxis2}); - histos.add("Eff_pT", ";p_{T}", kTProfile, {{KNbinsPtRes, KPtMin, KPtMax}}); - histos.add("Eff_eta", ";#eta", kTProfile, {{KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - histos.add("Fake_cent", ";cent", kTProfile, {centAxis1Per}); - histos.add("Fake_Ntrk", ";N_{PV}", kTProfile, {nChAxis2}); - histos.add("Fake_pT", ";p_{T}", kTProfile, {{KNbinsPtRes, KPtMin, KPtMax}}); - histos.add("Fake_eta", ";#eta", kTProfile, {{KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - histos.add("wgt_cent", ";cent", kTProfile, {centAxis1Per}); - histos.add("wgt_Ntrk", ";N_{PV}", kTProfile, {nChAxis2}); - histos.add("wgt_pT", ";p_{T}", kTProfile, {{KNbinsPtRes, KPtMin, KPtMax}}); - histos.add("wgt_eta", ";#eta", kTProfile, {{KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); - - histos.add("MCGen/Prof_Cent_Nchrec", ";cent;#LT N#GT", kTProfile, {centAxis1Per}); - histos.add("MCGen/Prof_Mult_Nchrec", ";mult;#LT N#GT", kTProfile, {nChAxis}); - histos.add("MCGen/Prof_Cent_MeanpT", ";cent;#LT p_{T}#GT", kTProfile, {centAxis1Per}); - histos.add("MCGen/Prof_Mult_MeanpT", ";mult;#LT p_{T}#GT", kTProfile, {nChAxis}); - - histos.add("pmeanTru_nch_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmeanReco_nch_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmeanRecoEffcorr_nch_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmeanMultTru_nch_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmeanMultReco_nch_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmeanMultRecoEffcorr_nch_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - - histos.add("MCGen/hEtaPhiReco", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("MCGen/hEtaPhiRecoEffWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("MCGen/hEtaPhiRecoWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - - histos.add("Prof2D_MeanpTSub_Tru", ";cent;etaA;etaC", kTProfile3D, {{centAxis1Per}, {etaBinAxis}, {etaBinAxis}}); - histos.add("Prof2D_MeanpTSub_Reco", ";cent;etaA;etaC", kTProfile3D, {{centAxis1Per}, {etaBinAxis}, {etaBinAxis}}); - histos.add("Prof2D_MeanpTSub_RecoEffCorr", ";cent;etaA;etaC", kTProfile3D, {{centAxis1Per}, {etaBinAxis}, {etaBinAxis}}); + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v)) { + continue; + } + auto& r = reg(v); + hists.evtCount1D[v] = r.add((systPrefix(v) + "hEvtCount").c_str(), ("Number of events, " + systName[v]).c_str(), kTH1D, {{kNEvCut, -0.5, kNEvCut - 0.5}}); + labelAxis(hists.evtCount1D[v]->GetXaxis(), evtCutLabels()); + hists.trkCount1D[v] = r.add((systPrefix(v) + "hTrkCount").c_str(), ("Number of tracks, " + systName[v]).c_str(), kTH1D, {{kNTrkCut, -0.5, kNTrkCut - 0.5}}); + labelAxis(hists.trkCount1D[v]->GetXaxis(), trkCutLabels()); + } } - void declareMCFlucHists() + void declareCutQA() { - histos.add("MCGen/Prof_Cent_NEta_Nchrec", ";cent;eta", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("MCGen/Prof_Mult_NEta_Nchrec", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("MCGen/Prof_Cent_NEta_MeanpT", ";cent;eta", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("MCGen/Prof_Mult_NEta_MeanpT", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); + const AxisSpec ptAxis{100, 0., 10., "p_{T} (GeV/c)"}; + for (int st = 0; st < kNQAStage; ++st) { + const std::string d = st == kQABefore ? "CutQA/before/" : "CutQA/after/"; + auto h1 = [&](const char* name, const char* title, const AxisSpec& ax) { return histos.add((d + name).c_str(), title, kTH1F, {ax}); }; + auto h2 = [&](const char* name, const char* title, const AxisSpec& ax, const AxisSpec& ay) { return histos.add((d + name).c_str(), title, kTH2F, {ax, ay}); }; + hists.qaEvt1D[kQAVtxZ][st] = h1("hVtxZ", ";z_{vtx} (cm)", {200, -20., 20.}); + hists.qaEvt1D[kQACent][st] = h1("hCent", ";centrality (%)", {centAxis1Per}); + hists.qaEvt1D[kQAOccupancy][st] = h1("hOccupancy", ";trackOccupancyInTimeRange", {KNbinsOccQA, 0., KOccQAMax}); + hists.qaEvt2D[kQANchNPV][st] = h2("hNch_NPV", ";N_{PV};N_{ch} (global tracks)", multQAAxis, multQAAxis); + hists.qaEvt2D[kQANchCent][st] = h2("hNch_cent", ";centrality (%);N_{ch} (global tracks)", centAxis1Per, multQAAxis); + hists.qaTrk1D[kQAPt][st] = h1("hTrkPt", "", ptAxis); + hists.qaTrk1D[kQAEta][st] = h1("hTrkEta", ";#eta", {60, -1.5, 1.5}); + hists.qaTrk1D[kQAItsNCls][st] = h1("hTrkItsNCls", ";ITS clusters", {8, -0.5, 7.5}); + hists.qaTrk1D[kQAItsChi2][st] = h1("hTrkItsChi2NCl", ";ITS #chi^{2}/cluster", {80, 0., 40.}); + hists.qaTrk1D[kQATpcNClsFound][st] = h1("hTrkTpcNClsFound", ";TPC found clusters", {161, -0.5, 160.5}); + hists.qaTrk1D[kQATpcCrossedRows][st] = h1("hTrkTpcCrossedRows", ";TPC crossed rows", {161, -0.5, 160.5}); + hists.qaTrk1D[kQATpcCrossedRowsOverFindable][st] = h1("hTrkTpcCrossedRowsOverFindable", ";TPC crossed rows / findable clusters", {75, 0., 1.5}); + hists.qaTrk1D[kQATpcChi2][st] = h1("hTrkTpcChi2NCl", ";TPC #chi^{2}/cluster", {100, 0., 10.}); + hists.qaTrk1D[kQAPVContributor][st] = h1("hTrkPVContributor", ";isPVContributor", {2, -0.5, 1.5}); + hists.qaTrk1D[kQAGlobalTrack][st] = h1("hTrkGlobalTrack", ";isGlobalTrack", {2, -0.5, 1.5}); + hists.qaTrk2D[kQADcaXYPt][st] = h2("hTrkDcaXY_pt", ";p_{T} (GeV/c);DCA_{xy} (cm)", ptAxis, {400, -1., 1.}); + hists.qaTrk2D[kQADcaZPt][st] = h2("hTrkDcaZ_pt", ";p_{T} (GeV/c);DCA_{z} (cm)", ptAxis, {300, -3., 3.}); + } + } - histos.add("MCGen/Prof_MeanpT_Cent_etabin", ";cent;eta", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("MCGen/Prof_C2_Cent_etabin", ";cent;eta", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("MCGen/Prof_C2Sub_Cent_etabin", ";cent;eta", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); + template + void fillCutQAEvent(int st, const C& col, int nCh, float cent) + { + hists.qaEvt1D[kQAVtxZ][st]->Fill(col.posZ()); + hists.qaEvt1D[kQACent][st]->Fill(cent); + hists.qaEvt1D[kQAOccupancy][st]->Fill(col.trackOccupancyInTimeRange()); + hists.qaEvt2D[kQANchNPV][st]->Fill(col.multNTracksPV(), nCh); + hists.qaEvt2D[kQANchCent][st]->Fill(cent, nCh); + } - histos.add("MCGen/Prof_MeanpT_Mult_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("MCGen/Prof_C2_Mult_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("MCGen/Prof_C2Sub_Mult_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); + template + void fillCutQATrack(int st, const T& trk) + { + hists.qaTrk1D[kQAPt][st]->Fill(trk.pt()); + hists.qaTrk1D[kQAEta][st]->Fill(trk.eta()); + hists.qaTrk1D[kQAItsNCls][st]->Fill(trk.itsNCls()); + hists.qaTrk1D[kQAItsChi2][st]->Fill(trk.itsChi2NCl()); + hists.qaTrk1D[kQATpcNClsFound][st]->Fill(trk.tpcNClsFound()); + hists.qaTrk1D[kQATpcCrossedRows][st]->Fill(trk.tpcNClsCrossedRows()); + hists.qaTrk1D[kQATpcCrossedRowsOverFindable][st]->Fill(trk.tpcCrossedRowsOverFindableCls()); + hists.qaTrk1D[kQATpcChi2][st]->Fill(trk.tpcChi2NCl()); + hists.qaTrk1D[kQAPVContributor][st]->Fill(trk.isPVContributor()); + hists.qaTrk1D[kQAGlobalTrack][st]->Fill(trk.isGlobalTrack()); + hists.qaTrk2D[kQADcaXYPt][st]->Fill(trk.pt(), trk.dcaXY()); + hists.qaTrk2D[kQADcaZPt][st]->Fill(trk.pt(), trk.dcaZ()); + } - histos.add("MCGen/Prof_C3_Cent_etabin", ";cent;eta", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("MCGen/Prof_C3_Mult_etabin", ";mult;eta", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("MCGen/Prof_C3Sub_Cent_etabin", ";cent;#eta-bin of the pair (single in the mirror bin)", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("MCGen/Prof_C3Sub_Mult_etabin", ";mult;#eta-bin of the pair (single in the mirror bin)", kTProfile2D, {{nChAxis}, {etaBinAxis}}); + // (centrality, N_ch) of the selected events of every variation: its event count and the + // centrality of each N_ch bin with the estimator of the variation. + void declareNchCent(uint32_t mask) + { + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v) || hists.nchCent[v]) { + continue; + } + hists.nchCent[v] = reg(v).add((systPrefix(v) + "Hist2D_globalTracks_cent").c_str(), "cent (%);N_{ch} (global tracks)", kTH2F, {{centAxis1Per}, {nchAxisOf(v)}}); + } + } - histos.add("MCGen/hEtaPhiReco", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("MCGen/hEtaPhiRecoEffWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("MCGen/hEtaPhiRecoWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); + void declareFwdQA() + { + if (hists.fwdChannel) { + return; + } + hists.fwdChannel = histos.add("FwdQA/Prof_Channel_qa", ";channel (FT0A 0-95, FT0C 96-207, FV0A 208-255);quantity", kTProfile2D, {{KNChFT0 + KNChFV0, -0.5, KNChFT0 + KNChFV0 - 0.5}, {kNChQA, -0.5, kNChQA - 0.5}}); + labelAxis(hists.fwdChannel->GetYaxis(), {"amplitude", "gain-equalised amplitude", "#eta (aligned, vs z_{vtx})"}); + hists.fwdEta = histos.add("FwdQA/Prof_Eta_fwdbin", ";forward bin;amplitude-weighted #LT#eta#GT", kTProfile, {fwdAxis}); + labelFwdAxis(hists.fwdEta->GetXaxis()); + } - histos.add("MCGen/Prof_C2Sub2D_Cent_etaA_etaC", ";cent;etaA;etaC", kTProfile3D, {{centAxis1Per}, {etaAxis}, {etaAxis}}); - histos.add("MCGen/Prof_GapSum2D", ";cent;gap;sum", kTProfile3D, {{centAxis1Per}, {gapAxis}, {sumAxis}}); - histos.add("MCGen/Prof_C3Sub2D_Cent_etaA_etaC", ";cent;#eta_{A} (pair);#eta_{C} (single)", kTProfile3D, {{centAxis1Per}, {etaAxis}, {etaAxis}}); - histos.add("MCGen/Prof_C3GapSum2D", ";cent;#eta_{pair} #minus #eta_{single};#eta_{pair} + #eta_{single}", kTProfile3D, {{centAxis1Per}, {gapAxis}, {sumAxis}}); + void declareEff(uint32_t mask) + { + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v)) { + continue; + } + hists.eff[v].maps = reg(v).add((systPrefix(v) + "hEffMaps").c_str(), ";N_{ch};p_{T};#eta;occupancy;category", kTHnF, + {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {occAxisEff}, {kNEffCat, -0.5, kNEffCat - 0.5}}, true); + labelAxis(hists.eff[v].maps->GetAxis(4), {"AllPrimary", "RecoMatchedToPrimary", "AllReco", "RecoSecondary", "RecoOtherCollision", "RecoFake"}); + } } - void declareDataGetFlatHists() + void declareMCQA() { - histos.add("hEtaPhiReco", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hEtaPhiRecoEffWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hEtaPhiRecoWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hnTrkPVZDC", ";N_{PV};ZDC_{A+C}", kTH2F, {{nChAxis2}, {200, 0, 3000}}); - histos.add("hNchZDC", ";N_{trk};ZDC_{A+C}", kTH2F, {{nChAxis2}, {200, 0, 30000}}); + histos.add("ptResolution", ";p_{T}^{MC};(p_{T}^{reco}-p_{T}^{MC})/p_{T}^{MC}", kTH2F, {{KNbinsPtRes, KPtMin, KPtMax}, {100, -0.2, 0.2}}); + histos.add("etaResolution", ";#eta^{MC};#eta^{reco}-#eta^{MC}", kTH2F, {{KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}, {100, -0.02, 0.02}}); + histos.add("etaTruthReco", ";#eta^{MC};#eta^{reco}", kTH2F, {{KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}, {KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}}); + histos.add("TruthTracKVz", ";Vz^{MC};Vz^{Reco}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {KNbinsVz, KVzMin, KVzMax}}); + histos.add("vzResolution", ";Vz^{MC};(Vz^{reco}-Vz^{MC})/Vz^{MC}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {100, -0.1, 0.1}}); + hists.mcTrackOrigin = histos.add("MCQA/Hist3D_Nch_pT_origin", ";N_{ch};p_{T} (GeV/c);origin", kTH3F, {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {kNOrigin, -0.5, kNOrigin - 0.5}}); + labelAxis(hists.mcTrackOrigin->GetZaxis(), {"primary", "secondary", "other MC collision", "no label", "fake flag", "ITS-TPC mismatch"}); } - void declareDataMeanHists() + // vertex QA of all MC passes + void declareMCVtxQA() + { + if (hists.mcVtx) { + return; + } + hists.mcVtx = histos.add("MCQA/Hist2D_dVz_type", ";z_{vtx}^{reco} - z_{vtx}^{MC} (cm);reco collision", kTH2F, {{200, -0.5, 0.5}, {kNVtxQA, -0.5, kNVtxQA - 0.5}}); + labelAxis(hists.mcVtx->GetYaxis(), {"single", "split: used", "split: skipped"}); + } + + // (vz, sign, pT, eta, phi) maps, unweighted, efficiency-weighted and with all weights; + // dir = "" (DATA) or "MCReco/". + void declareFlat(uint32_t mask, const std::string& dir) { + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v) || hists.flat[v].maps) { + continue; + } + hists.flat[v].maps = reg(v).add((systPrefix(v) + dir + "hFlatMaps").c_str(), ";vz;sign;pt;eta;phi;weight", kTHnSparseF, + {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}, {kNFlatType, -0.5, kNFlatType - 0.5}}); + labelAxis(hists.flat[v].maps->GetAxis(5), {"raw", "(1-fake)/eff", "all weights"}); + } + } - histos.add("Prof_Cent_Nchrec", ";cent;#LT N_{PV}#GT", kTProfile, {centAxis1Per}); - histos.add("Prof_Mult_Nchrec", ";N_{PV};#LT N_{PV}#GT", kTProfile, {nChAxis}); - histos.add("Prof_Cent_MeanpT", ";cent;#LT p_{T}#GT", kTProfile, {centAxis1Per}); - histos.add("Prof_Mult_MeanpT", ";N_{PV};#LT p_{T}#GT", kTProfile, {nChAxis}); + // Mean-pass profiles; MC: in MCGen/, MCReco/, MCRecoEffCorr/. + void declareMean(uint32_t mask, bool isMC) + { + static const std::array mcDir = {"MCGen/", "MCReco/", "MCRecoEffCorr/"}; + const int nLev = isMC ? kNLevels : 1; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v)) { + continue; + } + auto& r = reg(v); + for (int l = 0; l < nLev; ++l) { + const std::string p = systPrefix(v) + (isMC ? mcDir[l] : std::string{}); + auto& h = hists.mean[v][l]; + h.mean = r.add((p + "Prof_Mean_Nch_etabin_obs").c_str(), ";N_{ch};#eta-bin;observable", kTProfile3D, {{nchAxisOf(v)}, {etaBinAxis}, {kNMeanObs, -0.5, kNMeanObs - 0.5}}); + labelAxis(h.mean->GetZaxis(), {"#LT[p_{T}]#GT", "#LT N#GT"}); + if (cfgFwdOpt.cfgFwd) { + h.fwdMean = r.add((p + "Prof_FwdAmpMean_Nch_fwdbin").c_str(), ";N_{ch};forward bin;#LT a#GT", kTProfile2D, {{nchAxisOf(v)}, {fwdAxis}}); + labelFwdAxis(h.fwdMean->GetYaxis()); + } + } + } + } - histos.add("pmean_nch_etabin", ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmeanMult_nch_etabin", ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("pmean_cent_etabin", ";Centrality (%);#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("pmeanMult_cent_etabin", ";Centrality (%);#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); + // Fluctuation-pass profiles named prefix + name + suffix, with x axis ax. + // withGapSum: also the maps in (Delta eta, Sigma eta). + FlucHists declareFluc(HistogramRegistry& r, const std::string& p, const std::string& s, const AxisSpec& ax, bool withGapSum) + { + FlucHists h; + auto n = [&](const char* name) { return p + name + s; }; + h.obs = r.add(n("Prof_Obs_Nch_etabin_obs").c_str(), ";N_{ch};#eta-bin;observable", kTProfile3D, {{ax}, {etaBinAxis}, {kNFlucObs, -0.5, kNFlucObs - 0.5}}); + labelAxis(h.obs->GetZaxis(), {"N", "[p_{T}]", "c_{2}", "c_{3}", "C_{2}^{sub} (mirror)", "C_{3}^{sub} (mirror)"}); + h.c2Sub2D = r.add(n("Prof_C2Sub2D_Nch_etaA_etaC").c_str(), ";N_{ch};#eta_{A};#eta_{C}", kTProfile3D, {{ax}, {etaAxis}, {etaAxis}}); + h.c3Sub2D = r.add(n("Prof_C3Sub2D_Nch_etaA_etaC").c_str(), ";N_{ch};#eta_{A} (pair);#eta_{C} (single)", kTProfile3D, {{ax}, {etaAxis}, {etaAxis}}); + if (withGapSum) { + h.gapSum2D = r.add(n("Prof_GapSum2D_Nch_gap_sum").c_str(), ";N_{ch};#eta_{A} #minus #eta_{C} (Gap);#eta_{A} + #eta_{C} (Sum)", kTProfile3D, {{ax}, {gapAxis}, {sumAxis}}); + h.c3GapSum2D = r.add(n("Prof_C3GapSum2D_Nch_gap_sum").c_str(), ";N_{ch};#eta_{pair} #minus #eta_{single};#eta_{pair} + #eta_{single}", kTProfile3D, {{ax}, {gapAxis}, {sumAxis}}); + } + if (cfgFwdOpt.cfgFwd) { + h.fwdVar = r.add(n("Prof_FwdAmpVar_Nch_fwdbin").c_str(), ";N_{ch};forward bin;#LT #delta a^{2}#GT", kTProfile2D, {{ax}, {fwdAxis}}); + h.covFwdPt = r.add(n("Prof_CovFwdAmpMeanpT_Nch_etabin_fwdbin").c_str(), ";N_{ch};#eta-bin;forward bin;#LT #delta[p_{T}] #delta a#GT", kTProfile3D, {{ax}, {etaBinAxis}, {fwdAxis}}); + h.covFwdN = r.add(n("Prof_CovFwdAmpNch_Nch_etabin_fwdbin").c_str(), ";N_{ch};#eta-bin;forward bin;#LT #delta N #delta a#GT", kTProfile3D, {{ax}, {etaBinAxis}, {fwdAxis}}); + labelFwdAxis(h.fwdVar->GetYaxis()); + labelFwdAxis(h.covFwdPt->GetZaxis()); + labelFwdAxis(h.covFwdN->GetZaxis()); + } + return h; + } - histos.add("hEtaPhiReco", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hEtaPhiRecoEffWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hEtaPhiRecoWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); + // Gap/sum maps for every DATA variation; in MC only for Base, to limit the memory. + void declareFlucAll(uint32_t mask, bool isMC) + { + static const std::array mcDir = {"MCGen/", "MCReco/", "MCRecoEffCorr/"}; + const int nLev = isMC ? kNLevels : 1; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(mask, v)) { + continue; + } + for (int l = 0; l < nLev; ++l) { + hists.fluc[v][l] = declareFluc(reg(v), systPrefix(v) + (isMC ? mcDir[l] : std::string{}), "", nchAxisOf(v), !isMC || v == kSystBase); + } + } + } - histos.add("Prof2D_MeanpTSub", ";cent;#eta_{A} bin;#eta_{C} bin", kTProfile3D, {{centAxis1Per}, {etaBinAxis}, {etaBinAxis}}); + // Poisson-bootstrap replicas of all fluctuation profiles (Base, DataFluc only). Their x axis + // is the Base centrality in 5% classes, the binning in which the errors are evaluated. + void declareBootstrapHists() + { + for (int s = 0; s < nBoot; ++s) { + hists.boot[s] = declareFluc(histosBoot, "Bootstrap/", Form("_sample%d", s), centAxisBoot, true); + } + } - histos.add("pEffWeight_pt_eta_cent", ";p_{T} (GeV/c);#eta;cent;#LT eff#GT", kTProfile3D, {{KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {centAxis1Per}}); - histos.add("pFakeWeight_pt_eta_cent", ";p_{T} (GeV/c);#eta;cent;#LT fake#GT", kTProfile3D, {{KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {centAxis1Per}}); - histos.add("pFlatWeight_pt_eta_cent", ";p_{T} (GeV/c);#eta;cent;#LT w_{#phi}#GT", kTProfile3D, {{KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {centAxis1Per}}); + // Histograms of each pass, booked after the inputs so that switched-off variations get none. + void declareMCGetEffHists() + { + declareNchCent(maskEff); + declareEff(maskEff); + declareMCQA(); } - void declareDataFlucHists() + void declareMCGetFlatHists() + { + declareNchCent(maskFlat); + declareFlat(maskFlat, "MCReco/"); + } + + void declareMCMeanHists() { - histos.add("Prof_MeanpT_Cent_etabin", ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("Prof_MeanpT_Mult_etabin", ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("Prof_C2_Cent_etabin", ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("Prof_C2_Mult_etabin", ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("Prof_C2Sub_Cent_etabin", ";Centrality;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("Prof_C2Sub_Mult_etabin", ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); + declareNchCent(maskAll); + declareFlat(1u, "MCReco/"); // Base map with all weights, as QA + if (cfgFwdOpt.cfgFwd) { + declareFwdQA(); + } + declareMean(maskAll, true); + histos.add("Prof_Weights_pT_type", ";p_{T};weight", kTProfile2D, {{KNbinsPtRes, KPtMin, KPtMax}, {3, -0.5, 2.5}}); + labelAxis(histos.get(HIST("Prof_Weights_pT_type"))->GetYaxis(), {"eff", "fake", "w"}); + } - histos.add("Prof_C3_Cent_etabin", ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("Prof_C3_Mult_etabin", ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - histos.add("Prof_C3Sub_Cent_etabin", ";Centrality;#eta-bin of the pair (single in the mirror bin)", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - histos.add("Prof_C3Sub_Mult_etabin", ";N_{PV};#eta-bin of the pair (single in the mirror bin)", kTProfile2D, {{nChAxis}, {etaBinAxis}}); + void declareMCFlucHists() + { + declareNchCent(maskAll); + declareFlat(1u, "MCReco/"); + if (cfgFwdOpt.cfgFwd) { + declareFwdQA(); + } + declareFlucAll(maskAll, true); + } - histos.add("hEtaPhiReco", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hEtaPhiRecoEffWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); - histos.add("hEtaPhiRecoWtd", ";vz;sign;pt;eta;phi", kTHnSparseF, {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}}); + void declareDataGetFlatHists() + { + declareNchCent(maskFlat); + declareFlat(maskFlat, ""); + } - histos.add("Prof_C2Sub2D_Cent_etaA_etaC", ";cent;#eta_{A};#eta_{C}", kTProfile3D, {{centAxis1Per}, {etaAxis}, {etaAxis}}); - histos.add("Prof_GapSum2D", ";cent;#eta_{A} #minus #eta_{C} (Gap);#eta_{A} + #eta_{C} (Sum)", kTProfile3D, {{centAxis1Per}, {gapAxis}, {sumAxis}}); - histos.add("Prof_C3Sub2D_Cent_etaA_etaC", ";cent;#eta_{A} (pair);#eta_{C} (single)", kTProfile3D, {{centAxis1Per}, {etaAxis}, {etaAxis}}); - histos.add("Prof_C3GapSum2D", ";cent;#eta_{pair} #minus #eta_{single};#eta_{pair} + #eta_{single}", kTProfile3D, {{centAxis1Per}, {gapAxis}, {sumAxis}}); + void declareDataMeanHists() + { + declareNchCent(maskAll); + declareFlat(1u, ""); + if (cfgFwdOpt.cfgFwd) { + declareFwdQA(); + } + declareMean(maskAll, false); + histos.add("Prof_Weights_pt_eta_type", ";p_{T} (GeV/c);#eta;weight", kTProfile3D, {{KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {3, -0.5, 2.5}}); + labelAxis(histos.get(HIST("Prof_Weights_pt_eta_type"))->GetZaxis(), {"eff", "fake", "w_{#phi}"}); } - // 30 Poisson-bootstrap replicas of every final fluctuation observable - // (base data run only). Filled by pointer to bypass the compile-time HIST() - // macro, which cannot take a runtime sample index. - void declareBootstrapHists() + void declareDataFlucHists() { - for (int s = 0; s < nBoot; ++s) { - bs.multCent[s] = histos.add(Form("Bootstrap/Prof_Mult_Cent_etabin_sample%d", s), ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - bs.multMult[s] = histos.add(Form("Bootstrap/Prof_Mult_Mult_etabin_sample%d", s), ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - bs.meanpTCent[s] = histos.add(Form("Bootstrap/Prof_MeanpT_Cent_etabin_sample%d", s), ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - bs.meanpTMult[s] = histos.add(Form("Bootstrap/Prof_MeanpT_Mult_etabin_sample%d", s), ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - bs.c2Cent[s] = histos.add(Form("Bootstrap/Prof_C2_Cent_etabin_sample%d", s), ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - bs.c2Mult[s] = histos.add(Form("Bootstrap/Prof_C2_Mult_etabin_sample%d", s), ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - bs.c2SubCent[s] = histos.add(Form("Bootstrap/Prof_C2Sub_Cent_etabin_sample%d", s), ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - bs.c2SubMult[s] = histos.add(Form("Bootstrap/Prof_C2Sub_Mult_etabin_sample%d", s), ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - bs.c3Cent[s] = histos.add(Form("Bootstrap/Prof_C3_Cent_etabin_sample%d", s), ";cent;#eta-bin", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - bs.c3Mult[s] = histos.add(Form("Bootstrap/Prof_C3_Mult_etabin_sample%d", s), ";N_{PV};#eta-bin", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - bs.c3SubCent[s] = histos.add(Form("Bootstrap/Prof_C3Sub_Cent_etabin_sample%d", s), ";cent;#eta-bin of the pair (single in the mirror bin)", kTProfile2D, {{centAxis1Per}, {etaBinAxis}}); - bs.c3SubMult[s] = histos.add(Form("Bootstrap/Prof_C3Sub_Mult_etabin_sample%d", s), ";N_{PV};#eta-bin of the pair (single in the mirror bin)", kTProfile2D, {{nChAxis}, {etaBinAxis}}); - bs.c2Sub2D[s] = histos.add(Form("Bootstrap/Prof_C2Sub2D_Cent_etaA_etaC_sample%d", s), ";cent;#eta_{A};#eta_{C}", kTProfile3D, {{centAxis1Per}, {etaAxis}, {etaAxis}}); - bs.gapSum2D[s] = histos.add(Form("Bootstrap/Prof_GapSum2D_sample%d", s), ";cent;gap;sum", kTProfile3D, {{centAxis1Per}, {gapAxis}, {sumAxis}}); - bs.c3Sub2D[s] = histos.add(Form("Bootstrap/Prof_C3Sub2D_Cent_etaA_etaC_sample%d", s), ";cent;#eta_{A} (pair);#eta_{C} (single)", kTProfile3D, {{centAxis1Per}, {etaAxis}, {etaAxis}}); - bs.c3GapSum2D[s] = histos.add(Form("Bootstrap/Prof_C3GapSum2D_sample%d", s), ";cent;#eta_{pair} #minus #eta_{single};#eta_{pair} + #eta_{single}", kTProfile3D, {{centAxis1Per}, {gapAxis}, {sumAxis}}); + declareNchCent(maskAll); + declareFlat(1u, ""); + if (cfgFwdOpt.cfgFwd) { + declareFwdQA(); + } + declareFlucAll(maskAll, false); + if (doBoot) { + declareBootstrapHists(); } } // =========================================================================== // CCDB helpers // =========================================================================== - THnSparseF* buildWeightMapFromRaw(THnSparseF* hRaw, const char* mapName) + static void copyAxis(TAxis* dst, const TAxis* src) { - if (!hRaw) { - LOGF(error, "Raw eta-phi map for '%s' is null; no flattening will be applied.", mapName); - return nullptr; + const TArrayD* xb = src->GetXbins(); + if (xb->GetSize() > 0) { + dst->Set(src->GetNbins(), xb->GetArray()); + } else { + dst->Set(src->GetNbins(), src->GetXmin(), src->GetXmax()); } - auto hWMap = dynamic_cast(hRaw->Clone(mapName)); - hWMap->SetTitle(Form("Flattening Weight Map %s (w_{#phi} = / N_{#phi})", mapName)); - hWMap->Reset(); - auto axV = hRaw->GetAxis(0); - auto axChg = hRaw->GetAxis(1); - auto axPt = hRaw->GetAxis(2); - auto axE = hRaw->GetAxis(3); - auto axP = hRaw->GetAxis(4); + } - std::array bins{}; - for (int iv = 1; iv <= axV->GetNbins(); ++iv) { - bins[0] = iv; - for (int ichg = 1; ichg <= axChg->GetNbins(); ++ichg) { - bins[1] = ichg; - for (int ipt = 1; ipt <= axPt->GetNbins(); ++ipt) { - bins[2] = ipt; - for (int ie = 1; ie <= axE->GetNbins(); ++ie) { - bins[3] = ie; + // Flattening weights w_phi = / N_phi from bin `type` of the last axis of hFlatMaps. + THnSparseF* buildWeightMapFromRaw(const THnSparse* hRaw, int type, const char* mapName) + { + if (!hRaw || hRaw->GetNdimensions() != 6) { + LOGF(error, "Raw eta-phi map for '%s' is null or not 6D; no flattening will be applied.", mapName); + return nullptr; + } + std::array nb{}; + for (int i = 0; i < 5; ++i) { + nb[i] = hRaw->GetAxis(i)->GetNbins(); + } + auto* hWMap = new THnSparseF(mapName, Form("Flattening Weight Map %s (w_{#phi} = / N_{#phi})", mapName), 5, nb.data()); + for (int i = 0; i < 5; ++i) { + copyAxis(hWMap->GetAxis(i), hRaw->GetAxis(i)); + } + + std::array bins{}; + std::array wbins{}; + bins[5] = type + 1; + for (int iv = 1; iv <= nb[0]; ++iv) { + bins[0] = wbins[0] = iv; + for (int ichg = 1; ichg <= nb[1]; ++ichg) { + bins[1] = wbins[1] = ichg; + for (int ipt = 1; ipt <= nb[2]; ++ipt) { + bins[2] = wbins[2] = ipt; + for (int ie = 1; ie <= nb[3]; ++ie) { + bins[3] = wbins[3] = ie; double sum = 0.0; - int nphi = axP->GetNbins(); + const int nphi = nb[4]; for (int ip = 1; ip <= nphi; ++ip) { bins[4] = ip; sum += hRaw->GetBinContent(bins.data()); } const double avg = (nphi > 0 ? sum / nphi : 0.0); for (int ip = 1; ip <= nphi; ++ip) { - bins[4] = ip; + bins[4] = wbins[4] = ip; const double raw = hRaw->GetBinContent(bins.data()); const double w = (avg > 0.0 && raw > 0.0) ? (avg / raw) : 1.0; - hWMap->SetBinContent(bins.data(), w); + hWMap->SetBinContent(wbins.data(), w); } } } } } - LOGF(info, "Flattening weight map '%s' built.", mapName); - return hWMap; + LOGF(info, "Flattening weight map '%s' built.", mapName); + return hWMap; + } + + template + void loadProfileFromList(TList* src, const char* name, TP*& target) + { + if (!src) { + return; + } + auto* obj = src->FindObject(name); + if (!obj) { + LOGF(error, "Profile %s missing in CCDB TList", name); + return; + } + auto* tp = dynamic_cast(obj); + if (!tp) { + LOGF(error, "%s is not the expected profile type (it is %s)", name, obj->ClassName()); + return; + } + target = dynamic_cast(tp->Clone()); + target->SetDirectory(nullptr); + LOGF(info, "Loaded %s from list", name); + } + + // Switch variation v off because an input is missing; a missing Base input is fatal. + void dropSyst(int v, const std::string& why) + { + if (v == kSystBase) { + LOGF(fatal, "Base input missing: %s", why.c_str()); + return; + } + LOGF(error, "Variation %s disabled: %s", systName[v].c_str(), why.c_str()); + const uint32_t bit = ~(1u << v); + maskAll &= bit; + maskEff &= bit; + maskFlat &= bit; + } + + // CCDB object that may be missing: nullptr instead of the manager's fatal error. Used for the + // inputs of a variation (missing: the variation is switched off, Base stops in dropSyst) and the + // gain equalisation (missing: unit gains); everything else must exist. + template + T* getOptional(const std::string& path, int64_t ts) + { + ccdb->setFatalWhenNull(false); + T* obj = ccdb->getForTimeStamp(path, ts); + ccdb->setFatalWhenNull(true); + return obj; + } + + // Efficiency and fake-rate maps of variation s, one per occupancy bin: + // eff = matched primaries / primaries, fake = (secondaries + other collision + fakes) / all reco. + bool loadEffMaps(int s, const std::string& path, int64_t now) + { + auto* lst = getOptional(path, now); + if (!lst) { + return false; + } + auto* hn = dynamic_cast(lst->FindObject("hEffMaps")); + if (!hn || hn->GetNdimensions() != 5) { + LOGF(error, "Missing efficiency/fake map hEffMaps (5D) in %s", path.c_str()); + return false; + } + const int nOcc = hn->GetAxis(3)->GetNbins(); + if (nOcc != static_cast(effOccEdges.size()) - 1) { + LOGF(error, "%s: %d occupancy bins in the efficiency map, %d in cfgEffOccupancyEdges", path.c_str(), nOcc, static_cast(effOccEdges.size()) - 1); + return false; + } + // occ < 0: all occupancy bins + auto proj = [&](int occ, int cat) { + hn->GetAxis(3)->SetRange(occ < 0 ? 0 : occ + 1, occ < 0 ? 0 : occ + 1); + hn->GetAxis(4)->SetRange(cat + 1, cat + 1); + TH3* h = hn->Projection(0, 1, 2, "E"); + h->SetDirectory(nullptr); + return h; + }; + for (int occ = -1; occ < nOcc; ++occ) { + TH3* eff = proj(occ, kEffRecoMatchedPrim); + std::unique_ptr allPrim(proj(occ, kEffAllPrim)); + TH3* fake = proj(occ, kEffRecoSec); + std::unique_ptr other(proj(occ, kEffRecoOtherColl)); + std::unique_ptr fakes(proj(occ, kEffRecoFake)); + std::unique_ptr allReco(proj(occ, kEffAllReco)); + fake->Add(other.get()); + fake->Add(fakes.get()); + if (occ < 0) { + const int nNch = eff->GetNbinsX(); + auto ratio = [&](TH3* num, TH3* den, const char* name) { + auto* r = dynamic_cast(num->RebinX(nNch, name)); + std::unique_ptr d(dynamic_cast(den->RebinX(nNch, Form("%s_den", name)))); + r->SetDirectory(nullptr); + r->Divide(d.get()); + return r; + }; + inputs[s].hEffPtEta = ratio(eff, allPrim.get(), Form("hEff_%d_ptEta", s)); + inputs[s].hFakePtEta = ratio(fake, allReco.get(), Form("hFake_%d_ptEta", s)); + } + eff->Divide(allPrim.get()); + fake->Divide(allReco.get()); + eff->SetName(Form("hEff_%d_occ%d", s, occ)); + fake->SetName(Form("hFake_%d_occ%d", s, occ)); + if (occ < 0) { + inputs[s].hEffAllOcc = eff; + inputs[s].hFakeAllOcc = fake; + } else { + inputs[s].hEff.push_back(eff); + inputs[s].hFake.push_back(fake); + } + } + hn->GetAxis(3)->SetRange(0, 0); + hn->GetAxis(4)->SetRange(0, 0); + return true; + } + + THnSparseF* loadFlatMap(int s, const std::string& path, const std::string& dir, int64_t now) + { + auto* lst = getOptional(path, now); + if (!lst) { + return nullptr; + } + const std::string hName = dir + "hFlatMaps"; + auto* hRaw = dynamic_cast(lst->FindObject(hName.c_str())); + if (!hRaw) { + LOGF(error, "Flattening map '%s' not found in %s", hName.c_str(), path.c_str()); + return nullptr; + } + return buildWeightMapFromRaw(hRaw, cfgEff ? kFlatWtd : kFlatRaw, Form("hFlatWeight_%d", s)); } - template - void loadProfileFromList(TList* src, const char* name, TP*& target) + // Mean-pass objects of variation v for the fluctuation pass. + void loadMeans(int v, const std::string& path, bool isMC, int64_t now) { - if (!src) { - return; - } - auto* obj = src->FindObject(name); - if (!obj) { - LOGF(error, "Profile %s missing in CCDB TList", name); + static const std::array mcDir = {"MCGen/", "MCReco/", "MCRecoEffCorr/"}; + auto* lst = getOptional(path, now); + if (!lst) { + dropSyst(v, "no mean-pT list at " + path); return; } - auto* tp = dynamic_cast(obj); - if (!tp) { - LOGF(error, "%s is not the expected profile type (it is %s)", name, obj->ClassName()); - return; + const int nLev = isMC ? kNLevels : 1; + for (int l = 0; l < nLev; ++l) { + const std::string d = isMC ? mcDir[l] : std::string{}; + loadProfileFromList(lst, (d + "Prof_Mean_Nch_etabin_obs").c_str(), inputs[v].pmean[l]); + if (!inputs[v].pmean[l]) { + dropSyst(v, "mean-pT profile missing in " + path); + return; + } + // The fluctuation pass reads its reference at (N_ch bin, eta bin), so the binning of the + // mean pass has to be the same. + const auto* p = inputs[v].pmean[l]; + if (p->GetYaxis()->GetNbins() != nEta + 1 || + p->GetXaxis()->GetNbins() != nchAxisOf(v).getNbins() || + std::abs(p->GetXaxis()->GetBinWidth(1) - nchBinWidthOf(v)) > KFloatEpsilon) { + LOGF(fatal, + "Mean pass %s has %d N_ch bins of width %.1f and %d eta bins; this job uses %d bins of " + "width %d and %d eta bins. Rerun the mean pass with the same binning.", + path.c_str(), p->GetXaxis()->GetNbins(), p->GetXaxis()->GetBinWidth(1), p->GetYaxis()->GetNbins(), + nchAxisOf(v).getNbins(), nchBinWidthOf(v), nEta + 1); + } + if (cfgFwdOpt.cfgFwd) { + loadProfileFromList(lst, (d + "Prof_FwdAmpMean_Nch_fwdbin").c_str(), inputs[v].pmeanFwd[l]); + if (inputs[v].pmeanFwd[l] && inputs[v].pmeanFwd[l]->GetYaxis()->GetNbins() != nFwd) { + LOGF(error, "%s: mean pass has %d forward bins, this job %d; forward covariances not filled", + path.c_str(), inputs[v].pmeanFwd[l]->GetYaxis()->GetNbins(), nFwd); + inputs[v].pmeanFwd[l] = nullptr; + } + } } - target = dynamic_cast(tp->Clone()); - target->SetDirectory(nullptr); - LOGF(info, "Loaded %s from list", name); } // =========================================================================== @@ -1033,16 +2137,37 @@ struct RadialFlowDecorr { void init(InitContext&) { TH1::SetDefaultSumw2(true); - if (cfgSystType == kSystCent && cfgCentralityChoice == kCentFT0C) { - LOGF(warning, "cfgSystType=systCent but cfgCentralityChoice is still FT0C (the base estimator)."); - } - // Nch axes by system + // Multiplicity axes: multQAAxis for the QA, nchAxisObs (nchAxisNchBin2) for the observables, + // nchAxisEff for the efficiency maps. + const int nMax = (cfgSys == kPbPb) ? cfgNch.cfgNchPbMax.value : (cfgSys == kpp ? cfgNch.cfgNchPPMax.value : cfgNch.cfgNchOMax.value); if (cfgSys == kPbPb) { - nChAxis = {cfgNchPbMax / 2, KBinOffset, cfgNchPbMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; - nChAxis2 = {cfgNchPbMax / 4, KBinOffset, cfgNchPbMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; + multQAAxis = {nMax / 2, KBinOffset, nMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; } else { - nChAxis = {cfgNchOMax, KBinOffset, cfgNchOMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; - nChAxis2 = {cfgNchOMax, KBinOffset, cfgNchOMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; + multQAAxis = {nMax, KBinOffset, nMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; + } + { + // bin k holds k*width ... (k+1)*width - 1 tracks + auto makeNchAxis = [&](int width) { + if (width < 1) { + LOGF(fatal, "N_ch bin width must be >= 1 (got %d)", width); + } + const int nb = (nMax + width - 1) / width; + return AxisSpec{nb, -KBinOffset, nb * width - KBinOffset, "N_{ch}"}; + }; + const bool pp = (cfgSys == kpp); + nchWidth = (cfgSys == kPbPb) ? cfgNch.cfgNchBinWidthPbPb.value : (pp ? cfgNch.cfgNchBinWidthPP.value : cfgNch.cfgNchBinWidthOO.value); + nchWidthNchBin2 = (cfgSys == kPbPb) ? KSystNchBinWidthPbPb : (pp ? KSystNchBinWidthPP : KSystNchBinWidthOO); + nchAxisObs = makeNchAxis(nchWidth); + nchAxisNchBin2 = makeNchAxis(nchWidthNchBin2); + nchAxisEff = makeNchAxis((cfgSys == kPbPb) ? KEffNchWidthPbPb : KEffNchWidth); + } + { + const auto& e = cfgCorr.cfgEffOccupancyEdges.value; + if (e.size() < 2 || !std::is_sorted(e.begin(), e.end())) { + LOGF(fatal, "cfgEffOccupancyEdges must be >= 2 increasing values"); + } + effOccEdges.assign(e.begin(), e.end()); + occAxisEff = AxisSpec{effOccEdges, "trackOccupancyInTimeRange"}; } // ---- run type: MC is pinned to the reference binning, DATA is configurable ---- @@ -1106,9 +2231,65 @@ struct RadialFlowDecorr { sumAxis = AxisSpec{nGS, -gsMax, gsMax, "#Sigma#eta"}; } - // bootstrap active only for the base data fluctuation pass - doBoot = cfgRunDataFluc && (cfgSystType == kSystBase) && (cfgNBootstrap > 0); + // ---- forward bins: detector totals, then the eta sub-bins of each detector ---- + { + static const std::array detName = {"FT0A", "FT0C", "FV0A"}; + fwdEdges = {cfgFwdOpt.cfgFwdEtaEdgesFT0A.value, cfgFwdOpt.cfgFwdEtaEdgesFT0C.value, cfgFwdOpt.cfgFwdEtaEdgesFV0A.value}; + fwdLabels.assign(detName.begin(), detName.end()); + nFwd = kNFwdDet; + for (int det = 0; det < kNFwdDet; ++det) { + auto& e = fwdEdges[det]; + if (e.size() < 2 || !std::is_sorted(e.begin(), e.end())) { + LOGF(fatal, "%s eta edges must be >= 2 increasing values", detName[det]); + } + fwdOffset[det] = nFwd; + for (std::size_t i = 0; i + 1 < e.size(); ++i) { + fwdLabels.push_back(Form("%s %.1f<#eta<%.1f", detName[det], e[i], e[i + 1])); + ++nFwd; + } + } + if (nFwd > KNFwdMax) { + LOGF(fatal, "%d forward bins exceed KNFwdMax=%d", nFwd, KNFwdMax); + } + fwdAxis = AxisSpec{nFwd, -0.5, nFwd - 0.5, "forward bin"}; + } + + // ---- variations ---- + const uint32_t active = (static_cast(cfgSyst.cfgSystMask.value) | 1u) & KAllSystMask; + maskAll = maskEff = maskFlat = 0; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(active, v)) { + continue; + } + maskAll |= (1u << v); + if (effSource(v) == v) { + maskEff |= (1u << v); + } + if (flatSource(v) == v) { + maskFlat |= (1u << v); + } + } + const bool isPP = (cfgSys == kpp); + itsLayerNom = cfgEvSel.cfgITSLayerCut >= 0 ? cfgEvSel.cfgITSLayerCut.value : (isPP ? kITSLayer0123 : kITSLayersAll); + if (itsLayerNom == kITSLayersAll) { + itsLayerVar = kITSLayer0123; + } else if (itsLayerNom == kITSLayer0123) { + itsLayerVar = kITSLayer3; + } else { + itsLayerVar = kITSLayersAll; + } + occupancyMax = (cfgSys == kPbPb) ? cfgSyst.cfgSystOccMaxPbPb.value : (isPP ? cfgSyst.cfgSystOccMaxPP.value : cfgSyst.cfgSystOccMaxOO.value); + pupNSigma = (cfgSys == kPbPb) ? cfgPileup.cfgPupnSigPbPb.value : (isPP ? cfgPileup.cfgPupnSigPP.value : cfgPileup.cfgPupnSigOO.value); + if (cfgEvSel.cfgCentralityChoice == KSystCentralityChoice && hasBit(maskAll, kSystCent)) { + LOGF(warning, "systCent uses the same centrality estimator as Base (%d).", cfgEvSel.cfgCentralityChoice.value); + } + + // bootstrap: Base data fluctuation pass only + doBoot = cfgRunDataFluc && (cfgNBootstrap > 0); nBoot = std::min(cfgNBootstrap, KMaxBoot); + if (cfgNBootstrap > KMaxBoot) { + LOGF(warning, "cfgNBootstrap=%d capped at %d", cfgNBootstrap.value, KMaxBoot); + } rng.SetSeed(cfgBootstrapSeed); // ---- CCDB ---- @@ -1117,6 +2298,7 @@ struct RadialFlowDecorr { ccdb->setLocalObjectValidityChecking(); int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); ccdb->setCreatedNotAfter(now); + fwdCalib.fv0Det = o2::fv0::Geometry::instance(o2::fv0::Geometry::eUninitialized); std::string sysDir; switch (cfgSys) { @@ -1135,803 +2317,677 @@ struct RadialFlowDecorr { default: LOGF(fatal, "Invalid cfgSys value: %d", cfgSys.value); } - std::string pathEff = cfgCCDBUserPath.value + "/" + sysDir + "/Job1_EffMaps"; - std::string pathMCFlat = cfgCCDBUserPath.value + "/" + sysDir + "/Job1_MCFlatMaps"; - std::string pathMCMean = cfgCCDBUserPath.value + "/" + sysDir + "/Job2_MCMean"; - std::string pathDataFlat = cfgCCDBUserPath.value + "/" + sysDir + "/Job1_DataFlatMaps"; - std::string pathDataMean = cfgCCDBUserPath.value + "/" + sysDir + "/Job2_DataMean"; + const std::string pathEff = cfgCCDBUserPath.value + "/" + sysDir + "/Job1_EffMaps"; + const std::string pathMCFlat = cfgCCDBUserPath.value + "/" + sysDir + "/Job1_MCFlatMaps"; + const std::string pathMCMean = cfgCCDBUserPath.value + "/" + sysDir + "/Job2_MCMean"; + const std::string pathDataFlat = cfgCCDBUserPath.value + "/" + sysDir + "/Job1_DataFlatMaps"; + const std::string pathDataMean = cfgCCDBUserPath.value + "/" + sysDir + "/Job2_DataMean"; - // ---- declarations ---- - declareCommonQA(); - if (cfgRunMCMean || cfgRunMCFluc || cfgRunGetEff) { - declareMCCommonHists(); + // ---- inputs of each variation; a variation without its inputs is switched off ---- + // variations used by the enabled passes + uint32_t used = 0; + if (cfgRunGetEff) { + used |= maskEff; } - if (cfgRunGetMCFlat) { - declareMCGetFlatHists(); - histos.addClone("MCGen/", "MCReco/"); - histos.addClone("MCGen/", "MCRecoEffCorr/"); - } - if (cfgRunMCMean) { - declareMCMeanHists(); - histos.addClone("MCGen/", "MCReco/"); - histos.addClone("MCGen/", "MCRecoEffCorr/"); - } - if (cfgRunMCFluc) { - declareMCFlucHists(); - histos.addClone("MCGen/", "MCReco/"); - histos.addClone("MCGen/", "MCRecoEffCorr/"); + if (cfgRunGetMCFlat || cfgRunGetDataFlat) { + used |= maskFlat; } - if (cfgRunGetDataFlat) { - declareDataGetFlatHists(); - } - if (cfgRunDataMean) { - declareDataMeanHists(); - } - if (cfgRunDataFluc) { - declareDataFlucHists(); - if (doBoot) { - declareBootstrapHists(); - } + if (cfgRunMCMean || cfgRunMCFluc || cfgRunDataMean || cfgRunDataFluc) { + used |= maskAll; } - // ---- efficiency/fake maps (inclusive) ---- - if (!cfgRunGetEff && cfgEff) { - auto* lst = ccdb->getForTimeStamp(pathEff, now); - if (!lst) { - LOGF(fatal, "Efficiency maps required but CCDB list is null at %s!", pathEff.c_str()); - return; - } - - // 1. Process Efficiency Map - auto* hNum = dynamic_cast(lst->FindObject("h3_RecoMatchedToPrimary")); - auto* hDen = dynamic_cast(lst->FindObject("h3_AllPrimary")); - if (hNum && hDen) { - state.hEff = dynamic_cast(hNum->Clone("hEff")); - state.hEff->SetDirectory(nullptr); - state.hEff->Sumw2(); - state.hEff->Divide(hDen); - - TH3F* rebinnedEff = rebinNchMap(state.hEff, cfgSys.value, "hEffRebinned"); - if (!rebinnedEff) { - LOGF(fatal, "Failed to rebin efficiency map"); - return; + // efficiency/fake maps + if (cfgEff && (cfgRunGetMCFlat || cfgRunMCMean || cfgRunMCFluc || isDataRun)) { + std::array loaded{}; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(used, v)) { + continue; } - - delete state.hEff; - state.hEff = rebinnedEff; - - } else { - LOGF(fatal, "Missing CCDB objects for efficiency (h3_RecoMatchedToPrimary / h3_AllPrimary)."); - } - - // 2. Process Fake Map - auto* hNumS = dynamic_cast(lst->FindObject("h3_RecoUnMatchedToPrimary_Secondary")); - auto* hNumF = dynamic_cast(lst->FindObject("h3_RecoUnMatchedToPrimary_Fake")); - auto* hDenF = dynamic_cast(lst->FindObject("h3_AllReco")); - if (hNumS && hNumF && hDenF) { - state.hFake = dynamic_cast(hNumS->Clone("hFake")); - state.hFake->SetDirectory(nullptr); - state.hFake->Sumw2(); - state.hFake->Add(hNumF); // Secondary + fake: add exactly once. - state.hFake->Divide(hDenF); - - TH3F* rebinnedFake = rebinNchMap(state.hFake, cfgSys.value, "hFakeRebinned"); - if (!rebinnedFake) { - LOGF(fatal, "Failed to rebin fake map"); - return; + const int s = effSource(v); + if (loaded[s] == 0) { + loaded[s] = loadEffMaps(s, pathEff + systSuffix(s), now) ? 1 : -1; } - - delete state.hFake; - state.hFake = rebinnedFake; - - } else { - LOGF(fatal, "Missing CCDB objects for fakes."); - } - } - - // ---- flattening maps (inclusive) ---- - if (!cfgRunGetEff && cfgFlat) { - if (cfgRunDataMean || cfgRunDataFluc) { - auto* lstDataFlat = ccdb->getForTimeStamp(pathDataFlat, now); - if (lstDataFlat) { - std::string hName = cfgEff ? "hEtaPhiRecoWtd" : "hEtaPhiReco"; - auto* hRaw = dynamic_cast(lstDataFlat->FindObject(hName.c_str())); - if (hRaw) { - state.hFlatWeight = buildWeightMapFromRaw(hRaw, "hFlatWeight"); - } else { - LOGF(error, "Data flattening map '%s' not found.", hName.c_str()); - } - } else { - LOGF(error, "Could not retrieve Data Flattening TList from: %s", pathDataFlat.c_str()); - } - } - if (cfgRunMCMean || cfgRunMCFluc) { - auto* lstMCFlat = ccdb->getForTimeStamp(pathMCFlat, now); - if (lstMCFlat) { - std::string hName = cfgEff ? "MCReco/hEtaPhiRecoWtd" : "MCReco/hEtaPhiReco"; - auto* hRaw = dynamic_cast(lstMCFlat->FindObject(hName.c_str())); - if (hRaw) { - state.hFlatWeight = buildWeightMapFromRaw(hRaw, "hFlatWeight"); - } else { - LOGF(warning, "MC flattening source '%s' not found.", hName.c_str()); - } - } else { - LOGF(error, "Could not retrieve MC Flattening TList from: %s", pathMCFlat.c_str()); + if (loaded[s] < 0) { + dropSyst(v, "no efficiency maps at " + pathEff + systSuffix(s)); + continue; + } + inputs[v].hEff = inputs[s].hEff; + inputs[v].hFake = inputs[s].hFake; + inputs[v].hEffAllOcc = inputs[s].hEffAllOcc; + inputs[v].hFakeAllOcc = inputs[s].hFakeAllOcc; + inputs[v].hEffPtEta = inputs[s].hEffPtEta; + inputs[v].hFakePtEta = inputs[s].hFakePtEta; + } + } + + // flattening maps + if (cfgFlat && (cfgRunMCMean || cfgRunMCFluc || cfgRunDataMean || cfgRunDataFluc)) { + const bool mc = cfgRunMCMean || cfgRunMCFluc; + const std::string& path = mc ? pathMCFlat : pathDataFlat; + const std::string dir = mc ? "MCReco/" : ""; + std::array loaded{}; + for (int v = 0; v < kNSyst; ++v) { + const int s = flatSource(v); + if (!hasBit(maskAll, v) || s < 0) { + continue; + } + if (loaded[s] == 0) { + inputs[s].hFlat = loadFlatMap(s, path + systSuffix(s), dir, now); + loaded[s] = inputs[s].hFlat ? 1 : -1; + } + if (loaded[s] < 0) { + dropSyst(v, "no flattening map at " + path + systSuffix(s)); + continue; } + inputs[v].hFlat = inputs[s].hFlat; } } - // ---- sigma-pileup limits ---- - // These were produced by the (now removed) nSigma pass. They are re-sourced - // from the flattening-map file, which also stores Hist2D_globalTracks_cent. - // Loaded only when the sigma-pileup cut is actually requested. - if (cfgApplySigPupCut) { - const bool mcSide = (cfgRunGetEff || cfgRunGetMCFlat || cfgRunMCMean || cfgRunMCFluc); - std::string limPath = mcSide ? pathMCFlat : pathDataFlat; - auto* limList = ccdb->getForTimeStamp(limPath, now); - if (limList) { - auto loadLimits = [&](const char* name, std::vector>& limits, float& xMin, float& xMax) { - auto* h2 = dynamic_cast(limList->FindObject(name)); + // ---- n-sigma pile-up bands, from the Base flattening output of the same production ---- + // The flattening and efficiency passes only apply the linear cut: their maps define the bands. + { + const bool meanOrFluc = cfgRunMCMean || cfgRunMCFluc || cfgRunDataMean || cfgRunDataFluc; + const bool wantCent = cfgPileup.cfgApplySigPupCut && meanOrFluc; + const bool wantPV = cfgPileup.cfgApplySigPupCutPV && meanOrFluc; + if ((wantCent || wantPV) && (cfgRunGetEff || cfgRunGetMCFlat || cfgRunGetDataFlat)) { + LOGF(fatal, "The n-sigma pile-up bands come from the flattening pass: run it in a separate job before the mean/fluctuation passes."); + } + if (wantCent || wantPV) { + const std::string& bandPath = isMcRun ? pathMCFlat : pathDataFlat; + auto* bandList = ccdb->getForTimeStamp(bandPath, now); + if (!bandList) { + LOGF(fatal, "n-sigma pile-up bands requested but no flattening output at %s", bandPath.c_str()); + } + auto makeBand = [&](const char* name, NSigmaBand& band) { + auto* h2 = dynamic_cast(bandList->FindObject(name)); if (!h2) { + LOGF(fatal, "n-sigma pile-up band: %s missing in %s", name, bandPath.c_str()); return; } - std::unique_ptr prof(h2->ProfileX("ptmp", 1, -1, "S")); - int nBins = prof->GetNbinsX(); - xMin = prof->GetXaxis()->GetXmin(); - xMax = prof->GetXaxis()->GetXmax(); - limits.assign(nBins + 2, {-99999.f, 999999.f}); + std::unique_ptr prof(h2->ProfileX(Form("%s_band", name), 1, -1, "S")); + const int nBins = prof->GetNbinsX(); + band.xMin = prof->GetXaxis()->GetXmin(); + band.xMax = prof->GetXaxis()->GetXmax(); + band.meanRms.assign(nBins + 2, {0.f, -1.f}); for (int i = 1; i <= nBins; ++i) { - float mean = prof->GetBinContent(i); - float rms = prof->GetBinError(i); - limits[i] = {mean - cfgPupnSig * rms, mean + cfgPupnSig * rms}; + // a sparse bin (high-N_PV tail, peripheral edge) gets no band: its RMS is not measured + if (prof->GetBinEntries(i) >= KMinBandEntries) { + band.meanRms[i] = {static_cast(prof->GetBinContent(i)), static_cast(prof->GetBinError(i))}; + } } + band.applied = true; + LOGF(info, "n-sigma pile-up band (%.1f sigma, PileupNSigma %.1f) from %s in %s", pupNSigma, + pupNSigma - KSystPupnSigStep, name, bandPath.c_str()); }; - loadLimits("Hist2D_globalTracks_cent", state.mLimitsNchCent, state.mMinXNchCent, state.mMaxXNchCent); - } else { - LOGF(warning, "sigma-pileup limits source list missing at %s; sigma-pileup cut effectively disabled.", limPath.c_str()); - } - } - - // ---- MC mean profiles for MC fluc ---- - if (cfgRunMCFluc) { - LOGF(info, "Loading MC Mean profiles from CCDB path: %s", pathMCMean.c_str()); - auto* lstMCMean = ccdb->getForTimeStamp(pathMCMean, now); - if (lstMCMean) { - loadProfileFromList(lstMCMean, "pmeanTru_nch_etabin", state.pmeanTruNchEtabinStep2); - loadProfileFromList(lstMCMean, "pmeanReco_nch_etabin", state.pmeanRecoNchEtabinStep2); - loadProfileFromList(lstMCMean, "pmeanRecoEffcorr_nch_etabin", state.pmeanRecoEffcorrNchEtabinStep2); - // MC is pinned to 0.2-wide bins; guard against a stale MC-mean object built - // at a different width (its y-axis would then not have nEta+1 bins). - if (state.pmeanTruNchEtabinStep2 && - state.pmeanTruNchEtabinStep2->GetYaxis()->GetNbins() != nEta + 1) { - LOGF(fatal, - "MC Mean eta binning mismatch: loaded profile has %d y-bins, this job " - "expects nEta+1=%d. Rebuild the MC-mean pass at the current binning.", - state.pmeanTruNchEtabinStep2->GetYaxis()->GetNbins(), nEta + 1); + if (wantCent) { + makeBand("Hist2D_globalTracks_cent", pileupBands[kBandCent]); + } + if (wantPV) { + makeBand("Hist2D_globalTracks_PVTracks", pileupBands[kBandPV]); } - } else { - LOGF(error, "Could not retrieve TList for MC Mean from: %s", pathMCMean.c_str()); } } - // ---- Data mean profiles for data fluc (one per systematic variation) ---- - if (cfgRunDataFluc) { - int st = std::clamp(cfgSystType, 0, kNSystType - 1); - std::string meanPath = pathDataMean + systSuffix[st]; - LOGF(info, "Loading Data Mean profiles for systematic '%s' from: %s", - (st == kSystBase ? "Base" : systSuffix[st].c_str()), meanPath.c_str()); - auto* lstDataMean = ccdb->getForTimeStamp(meanPath, now); - if (lstDataMean) { - loadProfileFromList(lstDataMean, "pmean_nch_etabin", state.pmeanNchEtabinStep2); - // The DataMean and DataFluc passes must share cfgEtaBinWidth, else the - // (ibx, ieta+1) read in processDataFluc maps a fluc bin index onto a - // different physical eta slice, silently. The mean profile y-axis has - // nEta+1 bins by construction (etaBinAxis). - if (state.pmeanNchEtabinStep2 && - state.pmeanNchEtabinStep2->GetYaxis()->GetNbins() != nEta + 1) { - LOGF(fatal, - "Data Mean eta binning mismatch: loaded profile has %d y-bins, this job " - "expects nEta+1=%d -> DataMean and DataFluc used different cfgEtaBinWidth.", - state.pmeanNchEtabinStep2->GetYaxis()->GetNbins(), nEta + 1); + // ---- mean profiles for the fluctuation passes (one per variation) ---- + if (cfgRunMCFluc || cfgRunDataFluc) { + const std::string& path = cfgRunMCFluc ? pathMCMean : pathDataMean; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(maskAll, v)) { + LOGF(info, "Loading mean profiles of %s from %s", systName[v].c_str(), (path + systSuffix(v)).c_str()); + loadMeans(v, path + systSuffix(v), cfgRunMCFluc, now); } - } else { - LOGF(error, "Could not retrieve TList for Data Mean from: %s", meanPath.c_str()); } } - LOGF(info, "CCDB initialization complete for RadialFlowDecorr."); - } - // =========================================================================== - // MC: build efficiency / fake maps (inclusive) - // =========================================================================== - void processGetEffHists(MyRun3MCCollisions::iterator const& mcCollision, FilteredTCs const& mcTracks, aod::McParticles const& mcParticles) - { - histos.fill(HIST("hVtxZ"), mcCollision.posZ()); - if (!mcCollision.has_mcCollision() || !isEventSelected(mcCollision)) { - return; + // ---- declarations ---- + declareCommonQA(); + if (isMcRun) { + declareMCVtxQA(); } - float cent = getCentrality(mcCollision); - if (cent > KCentMax) { - return; + declareCounts(cfgRunGetEff ? maskEff : ((cfgRunGetMCFlat || cfgRunGetDataFlat) ? maskFlat : maskAll)); + if (doprocessCutQA) { + declareCutQA(); } - float multPV = mcCollision.multNTracksPV(); - float vz = mcCollision.posZ(); - if (!isPassAddPileup(multPV, mcTracks.size(), cent)) { - return; + if (cfgRunGetEff) { + declareMCGetEffHists(); } - - histos.fill(HIST("hVtxZ_after_sel"), mcCollision.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, mcTracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), mcTracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, mcTracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, multPV); - - for (const auto& particle : mcParticles) { - if (particle.mcCollisionId() != mcCollision.mcCollisionId()) { - continue; - } - if (!isParticleSelected(particle) || !particle.isPhysicalPrimary()) { - continue; - } - histos.fill(HIST("h3_AllPrimary"), multPV, particle.pt(), particle.eta()); + if (cfgRunGetMCFlat) { + declareMCGetFlatHists(); } - - for (const auto& track : mcTracks) { - if (track.collisionId() != mcCollision.index()) { - continue; - } - if (!isTrackSelected(track)) { - continue; - } - - float pt = track.pt(), eta = track.eta(); - histos.fill(HIST("hPt"), pt); - histos.fill(HIST("hEta"), eta); - histos.fill(HIST("h3_AllReco"), multPV, pt, eta); - - if (track.has_mcParticle()) { - auto mcP = track.mcParticle(); - if (mcP.isPhysicalPrimary()) { - histos.fill(HIST("ptResolution"), mcP.pt(), (pt - mcP.pt()) / mcP.pt()); - histos.fill(HIST("etaResolution"), mcP.eta(), eta - mcP.eta()); - histos.fill(HIST("etaTruthReco"), mcP.eta(), eta); - histos.fill(HIST("vzResolution"), mcP.vz(), (vz - mcP.vz()) / mcP.vz()); - histos.fill(HIST("TruthTracKVz"), mcP.vz(), vz); - histos.fill(HIST("h3_RecoMatchedToPrimary"), multPV, mcP.pt(), mcP.eta()); - } else { - histos.fill(HIST("h3_RecoUnMatchedToPrimary_Secondary"), multPV, pt, eta); - } - } else { - histos.fill(HIST("h3_RecoUnMatchedToPrimary_Fake"), multPV, pt, eta); - } + if (cfgRunMCMean) { + declareMCMeanHists(); } - } - PROCESS_SWITCH(RadialFlowDecorr, processGetEffHists, "process MC to calculate EffWeights", cfgRunGetEff); - - // =========================================================================== - // MC: build flattening maps (inclusive) - // =========================================================================== - void processMCFlat(MyRun3MCCollisions::iterator const& mcCollision, FilteredTCs const& mcTracks) - { - histos.fill(HIST("hVtxZ"), mcCollision.posZ()); - if (!mcCollision.has_mcCollision() || !isEventSelected(mcCollision)) { - return; + if (cfgRunMCFluc) { + declareMCFlucHists(); } - float cent = getCentrality(mcCollision); - if (cent > KCentMax) { - return; + if (cfgRunGetDataFlat) { + declareDataGetFlatHists(); } - float multPV = mcCollision.multNTracksPV(); - float vz = mcCollision.posZ(); - if (!isPassAddPileup(multPV, mcTracks.size(), cent)) { - return; + if (cfgRunDataMean) { + declareDataMeanHists(); + } + if (cfgRunDataFluc) { + declareDataFlucHists(); } - histos.fill(HIST("hVtxZ_after_sel"), mcCollision.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, mcTracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), mcTracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, mcTracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, multPV); - - for (const auto& track : mcTracks) { - if (track.collisionId() != mcCollision.index()) { - continue; - } - if (!isTrackSelected(track)) { - continue; - } - - float pt = track.pt(), eta = track.eta(), phi = track.phi(); - auto sign = track.sign(); - histos.fill(HIST("hPt"), pt); - histos.fill(HIST("hEta"), eta); - histos.fill(HIST("hPhi"), phi); - - float eff = 1.f, fake = 0.f; - if (!getValidEffFake(multPV, pt, eta, cfgEff, eff, fake)) { - continue; - } - float w = (1.0f - fake) / eff; - if (std::isfinite(w) && w > 0.f) { - histos.fill(HIST("MCReco/hEtaPhiRecoEffWtd"), vz, sign, pt, eta, phi, w); - histos.fill(HIST("MCReco/hEtaPhiReco"), vz, sign, pt, eta, phi, 1.0); - histos.fill(HIST("MCReco/hEtaPhiRecoWtd"), vz, sign, pt, eta, phi, w); + std::string list; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(used & maskAll, v) || (cfgRunGetEff && hasBit(maskEff, v)) || ((cfgRunGetMCFlat || cfgRunGetDataFlat) && hasBit(maskFlat, v))) { + list += " " + systName[v]; } } + LOGF(info, "RadialFlowDecorr variations in this job:%s (ITS layer cut nominal %d, variation %d; N_ch bins of %d, NchBin2 %d; occupancy cut %d)", + list.c_str(), itsLayerNom, itsLayerVar, nchWidth, nchWidthNchBin2, occupancyMax); + LOGF(info, "CCDB initialization complete for RadialFlowDecorr."); } - PROCESS_SWITCH(RadialFlowDecorr, processMCFlat, "process MC to calculate FlatWeights", cfgRunGetMCFlat); // =========================================================================== - // MC: mean pT (truth / reco / reco-eff-corrected) + // Helpers of the process functions // =========================================================================== - void processMCMean(MyRun3MCCollisions::iterator const& mcCollision, FilteredTCs const& mcTracks, aod::McParticles const& mcParticles) + // (vz, sign, pT, eta, phi) map of variation v: raw, with all weights w and, for Base, with + // the efficiency weight wEff = (1 - fake) / eff. + void fillFlat(int v, float vz, float sign, float pt, float eta, float phi, float w, float wEff) { - std::array sumWiTruth{}, sumWiptiTruth{}; - std::array sumWiReco{}, sumWiptiReco{}; - std::array sumWiRecoEffCorr{}, sumWiptiRecoEffCorr{}; - - histos.fill(HIST("hVtxZ"), mcCollision.posZ()); - if (!mcCollision.has_mcCollision() || !isEventSelected(mcCollision)) { - return; - } - float cent = getCentrality(mcCollision); - if (cent > KCentMax) { + const auto& h = hists.flat[v].maps; + if (!h) { return; } - float multPV = mcCollision.multNTracksPV(); - float vz = mcCollision.posZ(); - if (!isPassAddPileup(multPV, mcTracks.size(), cent)) { - return; + std::array x = {vz, sign, pt, eta, phi, static_cast(kFlatRaw)}; + h->Fill(x.data(), 1.); + x[5] = kFlatWtd; + h->Fill(x.data(), w); + if (v == kSystBase) { + x[5] = kFlatEffWtd; + h->Fill(x.data(), wEff); } + } - histos.fill(HIST("hVtxZ_after_sel"), mcCollision.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, mcTracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), mcTracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, mcTracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, multPV); - - // --- truth --- - for (const auto& particle : mcParticles) { - if (particle.mcCollisionId() != mcCollision.mcCollisionId()) { - continue; - } - if (!isParticleSelected(particle) || !particle.isPhysicalPrimary()) { - continue; - } - float pt = particle.pt(), eta = particle.eta(); - if (pt <= cfgPtMin || pt > cfgPtMax) { - continue; - } - for (int ieta = 0; ieta < nEta; ++ieta) { - if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { - continue; - } - sumWiTruth[ieta]++; - sumWiptiTruth[ieta] += pt; + // The reco collision with the most PV contributors; with split vertices the others are + // skipped, so the truth of an MC collision enters once. Fills the vertex QA. + template + int64_t bestRecoCollision(const R& recoCollisions, float mcVz) + { + int64_t best = -1; + int maxContrib = -1; + int nReco = 0; + for (const auto& rc : recoCollisions) { + ++nReco; + if (rc.numContrib() > maxContrib) { + maxContrib = rc.numContrib(); + best = rc.globalIndex(); } } - - histos.fill(HIST("MCGen/Prof_Cent_Nchrec"), cent, sumWiTruth[0]); - histos.fill(HIST("MCGen/Prof_Mult_Nchrec"), multPV, sumWiTruth[0]); - if (sumWiTruth[0] > KFloatEpsilon) { - histos.fill(HIST("MCGen/Prof_Cent_MeanpT"), cent, sumWiptiTruth[0] / sumWiTruth[0]); - histos.fill(HIST("MCGen/Prof_Mult_MeanpT"), multPV, sumWiptiTruth[0] / sumWiTruth[0]); + for (const auto& rc : recoCollisions) { + const int type = (nReco == 1) ? kVtxSingle : (rc.globalIndex() == best ? kVtxSplitBest : kVtxSplitOther); + hists.mcVtx->Fill(rc.posZ() - mcVz, type); } + return best; + } - // --- reco --- - for (const auto& track : mcTracks) { - if (track.collisionId() != mcCollision.index()) { + // =========================================================================== + // MC: build efficiency / fake maps (inclusive) + // =========================================================================== + void processGetEffHists(aod::McCollisions::iterator const& mcCollision, McRecoCollisions const& recoCollisions, + FilteredTCs const& mcTracks, aod::McParticles const& mcParticles) + { + const int64_t best = bestRecoCollision(recoCollisions, mcCollision.posZ()); + for (const auto& coll : recoCollisions) { + if (coll.globalIndex() != best) { continue; } - if (!isTrackSelected(track)) { - continue; + const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); + histos.fill(HIST("hVtxZ"), coll.posZ()); + const auto ev = isEventSelected(coll, tracks.size(), maskEff); + if (!ev.mask) { + return; } - float pt = track.pt(), eta = track.eta(), phi = track.phi(); - if (pt <= cfgPtMin || pt > cfgPtMax) { - continue; + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); + const double occ = effOccValue(ev.occBin); + + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); + } + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - auto sign = track.sign(); - histos.fill(HIST("hPt"), pt); - histos.fill(HIST("hEta"), eta); - histos.fill(HIST("hPhi"), phi); - float eff = 1.f, fake = 0.f; - if (!getValidEffFake(multPV, pt, eta, cfgEff, eff, fake)) { - continue; - } - float flatW = getFlatteningWeight(vz, sign, pt, eta, phi, cfgFlat); - float w = flatW * (1.0 - fake) / eff; - if (!std::isfinite(w) || w <= 0.f || eff <= KFloatEpsilon) { - continue; - } + // one entry in the hEffMaps of every variation in mask + auto fillEffMaps = [&](uint32_t mask, double pt, double eta, int cat) { + const std::array x = {nCh, pt, eta, occ, static_cast(cat)}; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(mask, v)) { + hists.eff[v].maps->Fill(x.data()); + } + } + }; - for (int ieta = 0; ieta < nEta; ++ieta) { - if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + // --- truth: charged primaries of the MC collision --- + for (const auto& particle : mcParticles) { + if (!isParticleSelected(particle) || !particle.isPhysicalPrimary()) { continue; } - sumWiReco[ieta]++; - sumWiptiReco[ieta] += pt; - sumWiRecoEffCorr[ieta] += w; - sumWiptiRecoEffCorr[ieta] += w * pt; + fillEffMaps(ev.mask, particle.pt(), particle.eta(), kEffAllPrim); } - histos.fill(HIST("Eff_cent"), cent, eff); - histos.fill(HIST("Fake_cent"), cent, fake); - histos.fill(HIST("wgt_cent"), cent, w); - histos.fill(HIST("Eff_Ntrk"), multPV, eff); - histos.fill(HIST("Fake_Ntrk"), multPV, fake); - histos.fill(HIST("wgt_Ntrk"), multPV, w); - histos.fill(HIST("Eff_pT"), pt, eff); - histos.fill(HIST("Fake_pT"), pt, fake); - histos.fill(HIST("wgt_pT"), pt, w); - histos.fill(HIST("Eff_eta"), eta, eff); - histos.fill(HIST("Fake_eta"), eta, fake); - histos.fill(HIST("wgt_eta"), eta, w); - - histos.fill(HIST("MCReco/hEtaPhiReco"), vz, sign, pt, eta, phi); - histos.fill(HIST("MCReco/hEtaPhiRecoWtd"), vz, sign, pt, eta, phi, w); - histos.fill(HIST("MCReco/hEtaPhiRecoEffWtd"), vz, sign, pt, eta, phi, (1.0 - fake) / eff); - } - - // subevent mean-pT maps - for (int ietaA = 0; ietaA < nEta; ++ietaA) { - for (int ietaC = 0; ietaC < nEta; ++ietaC) { - float nTruAB = sumWiTruth[ietaA] + sumWiTruth[ietaC]; - float nRecoAB = sumWiReco[ietaA] + sumWiReco[ietaC]; - float nCorrAB = sumWiRecoEffCorr[ietaA] + sumWiRecoEffCorr[ietaC]; - - if (sumWiTruth[ietaA] > KFloatEpsilon && sumWiTruth[ietaC] > KFloatEpsilon) { - histos.fill(HIST("Prof2D_MeanpTSub_Tru"), cent, ietaA, ietaC, (sumWiptiTruth[ietaA] + sumWiptiTruth[ietaC]) / nTruAB); + // --- reco --- + for (const auto& track : tracks) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { + continue; } - if (sumWiReco[ietaA] > KFloatEpsilon && sumWiReco[ietaC] > KFloatEpsilon) { - histos.fill(HIST("Prof2D_MeanpTSub_Reco"), cent, ietaA, ietaC, (sumWiptiReco[ietaA] + sumWiptiReco[ietaC]) / nRecoAB); + float pt = track.pt(), eta = track.eta(); + const bool isBase = hasBit(trackMask, kSystBase); + const int origin = trackOrigin(track, coll); + if (isBase) { + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + hists.mcTrackOrigin->Fill(nCh, pt, origin); } - if (sumWiRecoEffCorr[ietaA] > KFloatEpsilon && sumWiRecoEffCorr[ietaC] > KFloatEpsilon) { - histos.fill(HIST("Prof2D_MeanpTSub_RecoEffCorr"), cent, ietaA, ietaC, (sumWiptiRecoEffCorr[ietaA] + sumWiptiRecoEffCorr[ietaC]) / nCorrAB); + fillEffMaps(trackMask, pt, eta, kEffAllReco); + if (origin == kOriginPrimary) { + const auto& mcP = track.mcParticle(); + if (isBase) { + histos.fill(HIST("ptResolution"), mcP.pt(), (pt - mcP.pt()) / mcP.pt()); + histos.fill(HIST("etaResolution"), mcP.eta(), eta - mcP.eta()); + histos.fill(HIST("etaTruthReco"), mcP.eta(), eta); + histos.fill(HIST("vzResolution"), mcP.vz(), (vz - mcP.vz()) / mcP.vz()); + histos.fill(HIST("TruthTracKVz"), mcP.vz(), vz); + } + fillEffMaps(trackMask, mcP.pt(), mcP.eta(), kEffRecoMatchedPrim); + } else if (origin == kOriginSecondary) { + fillEffMaps(trackMask, pt, eta, kEffRecoSec); + } else if (origin == kOriginOtherColl) { + fillEffMaps(trackMask, pt, eta, kEffRecoOtherColl); + } else { + fillEffMaps(trackMask, pt, eta, kEffRecoFake); } } - - if (sumWiTruth[ietaA] > KFloatEpsilon) { - histos.fill(HIST("pmeanTru_nch_etabin"), multPV, ietaA, sumWiptiTruth[ietaA] / sumWiTruth[ietaA]); - histos.fill(HIST("pmeanMultTru_nch_etabin"), multPV, ietaA, sumWiTruth[ietaA]); - } - if (sumWiReco[ietaA] > KFloatEpsilon) { - histos.fill(HIST("pmeanReco_nch_etabin"), multPV, ietaA, sumWiptiReco[ietaA] / sumWiReco[ietaA]); - histos.fill(HIST("pmeanMultReco_nch_etabin"), multPV, ietaA, sumWiReco[ietaA]); - } - if (sumWiRecoEffCorr[ietaA] > KFloatEpsilon) { - histos.fill(HIST("pmeanRecoEffcorr_nch_etabin"), multPV, ietaA, sumWiptiRecoEffCorr[ietaA] / sumWiRecoEffCorr[ietaA]); - histos.fill(HIST("pmeanMultRecoEffcorr_nch_etabin"), multPV, ietaA, sumWiRecoEffCorr[ietaA]); - } + flushTrackCounts(); } } - PROCESS_SWITCH(RadialFlowDecorr, processMCMean, "process MC to calculate mean pt", cfgRunMCMean); + PROCESS_SWITCH(RadialFlowDecorr, processGetEffHists, "process MC to calculate EffWeights", cfgRunGetEff); // =========================================================================== - // MC: fluctuations (C2, subevent) at three levels + // MC: build flattening maps (inclusive) // =========================================================================== - void processMCFluc(MyRun3MCCollisions::iterator const& mcCollision, FilteredTCs const& mcTracks, aod::McParticles const& mcParticles) + void processMCFlat(aod::McCollisions::iterator const& mcCollision, McRecoCollisions const& recoCollisions, FilteredTCs const& mcTracks) { - if (!state.pmeanTruNchEtabinStep2 || !state.pmeanRecoNchEtabinStep2 || !state.pmeanRecoEffcorrNchEtabinStep2) { - LOGF(warning, "MC fluc: mean pT map missing"); - return; - } - - std::array, KIntM>, KNEtaMax> sumPmwkTru{}; - std::array, KNEtaMax> sumWkTru{}; - std::array, KIntM>, KNEtaMax> sumPmwkReco{}; - std::array, KNEtaMax> sumWkReco{}; - std::array, KIntM>, KNEtaMax> sumPmwkRecoEffCor{}; - std::array, KNEtaMax> sumWkRecoEffCor{}; - - std::array meanTru{}, c2Tru{}, c3Tru{}; - std::array meanReco{}, c2Reco{}, c3Reco{}; - std::array meanRecoEffCor{}, c2RecoEffCor{}, c3RecoEffCor{}; - - std::array p1kBarTru{}, p1kBarReco{}, p1kBarRecoEffCor{}; - - if (!mcCollision.has_mcCollision() || !isEventSelected(mcCollision)) { - return; - } - float cent = getCentrality(mcCollision); - if (cent > KCentMax) { - return; - } - float multPV = mcCollision.multNTracksPV(); - float vz = mcCollision.posZ(); - if (!isPassAddPileup(multPV, mcTracks.size(), cent)) { - return; - } - - histos.fill(HIST("hVtxZ_after_sel"), mcCollision.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, mcTracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), mcTracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, mcTracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, multPV); - - // --- truth sums --- - for (const auto& particle : mcParticles) { - if (particle.mcCollisionId() != mcCollision.mcCollisionId()) { - continue; - } - if (!isParticleSelected(particle) || !particle.isPhysicalPrimary()) { - continue; - } - float pt = particle.pt(); - if (pt <= cfgPtMin || pt > cfgPtMax) { - continue; - } - float eta = particle.eta(); - for (int ieta = 0; ieta < nEta; ++ieta) { - if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { - continue; - } - for (int k = 0; k < KIntK; ++k) { - for (int m = 0; m < KIntM; ++m) { - sumPmwkTru[ieta][m][k] += std::pow(pt, m); - } - sumWkTru[ieta][k]++; - } - } - } - - // --- reco sums --- - for (const auto& track : mcTracks) { - if (track.collisionId() != mcCollision.index()) { + const int64_t best = bestRecoCollision(recoCollisions, mcCollision.posZ()); + for (const auto& coll : recoCollisions) { + if (coll.globalIndex() != best) { continue; } - if (!isTrackSelected(track)) { - continue; - } - float pt = track.pt(); - if (pt <= cfgPtMin || pt > cfgPtMax) { - continue; + const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); + histos.fill(HIST("hVtxZ"), coll.posZ()); + const auto ev = isEventSelected(coll, tracks.size(), maskFlat); + if (!ev.mask) { + return; } - float eta = track.eta(), phi = track.phi(); - auto sign = track.sign(); - histos.fill(HIST("hPt"), pt); - histos.fill(HIST("hEta"), eta); - histos.fill(HIST("hPhi"), phi); + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); - float eff = 1.f, fake = 0.f; - if (!getValidEffFake(multPV, pt, eta, cfgEff, eff, fake)) { - continue; + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); } - float flatW = getFlatteningWeight(vz, sign, pt, eta, phi, cfgFlat); - float w = flatW * (1.0 - fake) / eff; - if (!std::isfinite(w) || w <= 0.f || eff <= KFloatEpsilon) { - continue; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - for (int ieta = 0; ieta < nEta; ++ieta) { - if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + std::array w{}; + for (const auto& track : tracks) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { continue; } - for (int k = 0; k < KIntK; ++k) { - for (int m = 0; m < KIntM; ++m) { - sumPmwkReco[ieta][m][k] += std::pow(1.0, k) * std::pow(pt, m); - sumPmwkRecoEffCor[ieta][m][k] += std::pow(w, k) * std::pow(pt, m); + float pt = track.pt(); + if (pt <= cfgPtMin || pt > cfgPtMax) { + continue; + } + float eta = track.eta(), phi = track.phi(); + float sign = track.sign(); + if (hasBit(trackMask, kSystBase)) { + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + histos.fill(HIST("hPhi"), phi); + } + + // efficiency weights only: the flattening map is what this pass measures + float eff, fake, flatWeight; + const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, false, w, eff, fake, flatWeight); + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(weightMask, v)) { + fillFlat(v, vz, sign, pt, eta, phi, w[v], w[v]); } - sumWkReco[ieta][k] += std::pow(1.0, k); - sumWkRecoEffCor[ieta][k] += std::pow(w, k); } } - - histos.fill(HIST("MCReco/hEtaPhiReco"), vz, sign, pt, eta, phi); - histos.fill(HIST("MCReco/hEtaPhiRecoWtd"), vz, sign, pt, eta, phi, w); - histos.fill(HIST("MCReco/hEtaPhiRecoEffWtd"), vz, sign, pt, eta, phi, (1.0 - fake) / eff); + flushTrackCounts(); } + } + PROCESS_SWITCH(RadialFlowDecorr, processMCFlat, "process MC to calculate FlatWeights", cfgRunGetMCFlat); - for (int ieta = 0; ieta < nEta; ++ieta) { - const int ibx = state.pmeanTruNchEtabinStep2->GetXaxis()->FindBin(multPV); - const int iby = ieta + 1; - - float mmptTru = state.pmeanTruNchEtabinStep2->GetBinContent(ibx, iby); - float mmptReco = state.pmeanRecoNchEtabinStep2->GetBinContent(ibx, iby); - float mmptRecoEffCor = state.pmeanRecoEffcorrNchEtabinStep2->GetBinContent(ibx, iby); - - // truth - meanTru[ieta] = (sumWkTru[ieta][1] > KFloatEpsilon) ? (sumPmwkTru[ieta][1][1] / sumWkTru[ieta][1]) : std::numeric_limits::quiet_NaN(); - c2Tru[ieta] = std::numeric_limits::quiet_NaN(); - c3Tru[ieta] = std::numeric_limits::quiet_NaN(); - p1kBarTru[ieta] = std::numeric_limits::quiet_NaN(); - if (std::isfinite(mmptTru) && mmptTru != 0) { - const auto corr = calculateC2C3FromSums(sumPmwkTru[ieta], sumWkTru[ieta], mmptTru); - c2Tru[ieta] = corr.c2; - c3Tru[ieta] = corr.c3; - if (std::isfinite(meanTru[ieta])) { - p1kBarTru[ieta] = meanTru[ieta] - mmptTru; - } - } - // reco - meanReco[ieta] = (sumWkReco[ieta][1] > KFloatEpsilon) ? (sumPmwkReco[ieta][1][1] / sumWkReco[ieta][1]) : std::numeric_limits::quiet_NaN(); - c2Reco[ieta] = std::numeric_limits::quiet_NaN(); - c3Reco[ieta] = std::numeric_limits::quiet_NaN(); - p1kBarReco[ieta] = std::numeric_limits::quiet_NaN(); - if (std::isfinite(mmptReco) && mmptReco != 0) { - const auto corr = calculateC2C3FromSums(sumPmwkReco[ieta], sumWkReco[ieta], mmptReco); - c2Reco[ieta] = corr.c2; - c3Reco[ieta] = corr.c3; - if (std::isfinite(meanReco[ieta])) { - p1kBarReco[ieta] = meanReco[ieta] - mmptReco; - } - } - // reco, efficiency-corrected - meanRecoEffCor[ieta] = (sumWkRecoEffCor[ieta][1] > KFloatEpsilon) ? (sumPmwkRecoEffCor[ieta][1][1] / sumWkRecoEffCor[ieta][1]) : std::numeric_limits::quiet_NaN(); - c2RecoEffCor[ieta] = std::numeric_limits::quiet_NaN(); - c3RecoEffCor[ieta] = std::numeric_limits::quiet_NaN(); - p1kBarRecoEffCor[ieta] = std::numeric_limits::quiet_NaN(); - if (std::isfinite(mmptRecoEffCor) && mmptRecoEffCor != 0) { - const auto corr = calculateC2C3FromSums(sumPmwkRecoEffCor[ieta], sumWkRecoEffCor[ieta], mmptRecoEffCor); - c2RecoEffCor[ieta] = corr.c2; - c3RecoEffCor[ieta] = corr.c3; - if (std::isfinite(meanRecoEffCor[ieta])) { - p1kBarRecoEffCor[ieta] = meanRecoEffCor[ieta] - mmptRecoEffCor; - } - } - } - - // per-eta counts & means - for (int ieta = 0; ieta < nEta; ++ieta) { - histos.fill(HIST("MCGen/Prof_Cent_NEta_Nchrec"), cent, ieta, sumWkTru[ieta][1]); - histos.fill(HIST("MCGen/Prof_Mult_NEta_Nchrec"), multPV, ieta, sumWkTru[ieta][1]); - histos.fill(HIST("MCReco/Prof_Cent_NEta_Nchrec"), cent, ieta, sumWkReco[ieta][1]); - histos.fill(HIST("MCReco/Prof_Mult_NEta_Nchrec"), multPV, ieta, sumWkReco[ieta][1]); - histos.fill(HIST("MCRecoEffCorr/Prof_Cent_NEta_Nchrec"), cent, ieta, sumWkRecoEffCor[ieta][1]); - histos.fill(HIST("MCRecoEffCorr/Prof_Mult_NEta_Nchrec"), multPV, ieta, sumWkRecoEffCor[ieta][1]); - - if (sumWkTru[ieta][1] > KFloatEpsilon) { - histos.fill(HIST("MCGen/Prof_Cent_NEta_MeanpT"), cent, ieta, meanTru[ieta]); - histos.fill(HIST("MCGen/Prof_Mult_NEta_MeanpT"), multPV, ieta, meanTru[ieta]); - } - if (sumWkReco[ieta][1] > KFloatEpsilon) { - histos.fill(HIST("MCReco/Prof_Cent_NEta_MeanpT"), cent, ieta, meanReco[ieta]); - histos.fill(HIST("MCReco/Prof_Mult_NEta_MeanpT"), multPV, ieta, meanReco[ieta]); + // =========================================================================== + // MC: mean pT (truth / reco / reco-eff-corrected) + // =========================================================================== + void processMCMean(aod::McCollisions::iterator const& mcCollision, McRecoCollisions const& recoCollisions, + aod::BCsWithTimestamps const&, aod::FT0s const&, aod::FV0As const&, + FilteredTCs const& mcTracks, aod::McParticles const& mcParticles) + { + const int64_t best = bestRecoCollision(recoCollisions, mcCollision.posZ()); + for (const auto& coll : recoCollisions) { + if (coll.globalIndex() != best) { + continue; } - if (sumWkRecoEffCor[ieta][1] > KFloatEpsilon) { - histos.fill(HIST("MCRecoEffCorr/Prof_Cent_NEta_MeanpT"), cent, ieta, meanRecoEffCor[ieta]); - histos.fill(HIST("MCRecoEffCorr/Prof_Mult_NEta_MeanpT"), multPV, ieta, meanRecoEffCor[ieta]); + const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); + histos.fill(HIST("hVtxZ"), coll.posZ()); + const auto ev = isEventSelected(coll, tracks.size(), maskAll); + if (!ev.mask) { + return; } - } + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); - // meanpT & C2 vs eta bin - for (int ieta = 0; ieta < nEta; ++ieta) { - if (std::isfinite(meanTru[ieta])) { - histos.fill(HIST("MCGen/Prof_MeanpT_Cent_etabin"), cent, ieta, meanTru[ieta]); - histos.fill(HIST("MCGen/Prof_MeanpT_Mult_etabin"), multPV, ieta, meanTru[ieta]); - } - if (std::isfinite(c2Tru[ieta])) { - histos.fill(HIST("MCGen/Prof_C2_Cent_etabin"), cent, ieta, c2Tru[ieta]); - histos.fill(HIST("MCGen/Prof_C2_Mult_etabin"), multPV, ieta, c2Tru[ieta]); + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); } - if (std::isfinite(c3Tru[ieta])) { - histos.fill(HIST("MCGen/Prof_C3_Cent_etabin"), cent, ieta, c3Tru[ieta]); - histos.fill(HIST("MCGen/Prof_C3_Mult_etabin"), multPV, ieta, c3Tru[ieta]); - } - if (std::isfinite(meanReco[ieta])) { - histos.fill(HIST("MCReco/Prof_MeanpT_Cent_etabin"), cent, ieta, meanReco[ieta]); - histos.fill(HIST("MCReco/Prof_MeanpT_Mult_etabin"), multPV, ieta, meanReco[ieta]); - } - if (std::isfinite(c2Reco[ieta])) { - histos.fill(HIST("MCReco/Prof_C2_Cent_etabin"), cent, ieta, c2Reco[ieta]); - histos.fill(HIST("MCReco/Prof_C2_Mult_etabin"), multPV, ieta, c2Reco[ieta]); + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - if (std::isfinite(c3Reco[ieta])) { - histos.fill(HIST("MCReco/Prof_C3_Cent_etabin"), cent, ieta, c3Reco[ieta]); - histos.fill(HIST("MCReco/Prof_C3_Mult_etabin"), multPV, ieta, c3Reco[ieta]); + + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v)) { + for (auto& s : sums[v]) { + s.reset(); + } + } } - if (std::isfinite(meanRecoEffCor[ieta])) { - histos.fill(HIST("MCRecoEffCorr/Prof_MeanpT_Cent_etabin"), cent, ieta, meanRecoEffCor[ieta]); - histos.fill(HIST("MCRecoEffCorr/Prof_MeanpT_Mult_etabin"), multPV, ieta, meanRecoEffCor[ieta]); + + // --- truth: independent of the track cuts, copied to every selected variation --- + PowerSums& sumsTru = sums[kSystBase][kGen]; + if (!hasBit(ev.mask, kSystBase)) { + sumsTru.reset(); } - if (std::isfinite(c2RecoEffCor[ieta])) { - histos.fill(HIST("MCRecoEffCorr/Prof_C2_Cent_etabin"), cent, ieta, c2RecoEffCor[ieta]); - histos.fill(HIST("MCRecoEffCorr/Prof_C2_Mult_etabin"), multPV, ieta, c2RecoEffCor[ieta]); + for (const auto& particle : mcParticles) { + if (!isParticleSelected(particle) || !particle.isPhysicalPrimary()) { + continue; + } + float pt = particle.pt(), eta = particle.eta(); + if (pt <= cfgPtMin || pt > cfgPtMax) { + continue; + } + for (int ieta = 0; ieta < nEta; ++ieta) { + if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + continue; + } + sumsTru.add(ieta, pt, 1.); + } } - if (std::isfinite(c3RecoEffCor[ieta])) { - histos.fill(HIST("MCRecoEffCorr/Prof_C3_Cent_etabin"), cent, ieta, c3RecoEffCor[ieta]); - histos.fill(HIST("MCRecoEffCorr/Prof_C3_Mult_etabin"), multPV, ieta, c3RecoEffCor[ieta]); + for (int v = 1; v < kNSyst; ++v) { + if (hasBit(ev.mask, v)) { + sums[v][kGen] = sumsTru; + } } - } - // mirror-pair subevent (C2Sub, C3Sub) vs eta bin - for (int ietaA = 1; ietaA <= (nEta - 1) / 2; ++ietaA) { - int ietaC = nEta - ietaA; + // --- reco --- + std::array w{}; + for (const auto& track : tracks) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { + continue; + } + float pt = track.pt(); + if (pt <= cfgPtMin || pt > cfgPtMax) { + continue; + } + float eta = track.eta(), phi = track.phi(); + float sign = track.sign(); + if (hasBit(trackMask, kSystBase)) { + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + histos.fill(HIST("hPhi"), phi); + } - float c2SubTru = p1kBarTru[ietaA] * p1kBarTru[ietaC]; - float c2SubReco = p1kBarReco[ietaA] * p1kBarReco[ietaC]; - float c2SubRecoEffCor = p1kBarRecoEffCor[ietaA] * p1kBarRecoEffCor[ietaC]; + // reco level: every selected track, independent of the corrections + for (int ieta = 0; ieta < nEta; ++ieta) { + if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + continue; + } + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(trackMask, v)) { + sums[v][kReco].add(ieta, pt, 1.); + } + } + } - float c3SubTruA = c2Tru[ietaA] * p1kBarTru[ietaC]; // 2 from A, 1 from C - float c3SubTruC = c2Tru[ietaC] * p1kBarTru[ietaA]; // 2 from C, 1 from A - float c3SubRecoA = c2Reco[ietaA] * p1kBarReco[ietaC]; - float c3SubRecoC = c2Reco[ietaC] * p1kBarReco[ietaA]; - float c3SubRecoEffCorA = c2RecoEffCor[ietaA] * p1kBarRecoEffCor[ietaC]; - float c3SubRecoEffCorC = c2RecoEffCor[ietaC] * p1kBarRecoEffCor[ietaA]; + float eff, fake, flatWeight; + const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (!weightMask) { + continue; + } + if (hasBit(weightMask, kSystBase)) { + histos.fill(HIST("Prof_Weights_pT_type"), pt, 0, eff); + histos.fill(HIST("Prof_Weights_pT_type"), pt, 1, fake); + histos.fill(HIST("Prof_Weights_pT_type"), pt, 2, w[kSystBase]); + fillFlat(kSystBase, vz, sign, pt, eta, phi, w[kSystBase], (1.f - fake) / eff); + } - if (std::isfinite(c2SubTru)) { - histos.fill(HIST("MCGen/Prof_C2Sub_Cent_etabin"), cent, ietaA, c2SubTru); - histos.fill(HIST("MCGen/Prof_C2Sub_Mult_etabin"), multPV, ietaA, c2SubTru); - } - if (std::isfinite(c2SubReco)) { - histos.fill(HIST("MCReco/Prof_C2Sub_Cent_etabin"), cent, ietaA, c2SubReco); - histos.fill(HIST("MCReco/Prof_C2Sub_Mult_etabin"), multPV, ietaA, c2SubReco); - } - if (std::isfinite(c2SubRecoEffCor)) { - histos.fill(HIST("MCRecoEffCorr/Prof_C2Sub_Cent_etabin"), cent, ietaA, c2SubRecoEffCor); - histos.fill(HIST("MCRecoEffCorr/Prof_C2Sub_Mult_etabin"), multPV, ietaA, c2SubRecoEffCor); + // reco, efficiency-corrected + for (int ieta = 0; ieta < nEta; ++ieta) { + if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + continue; + } + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(weightMask, v)) { + sums[v][kRecoEffCorr].add(ieta, pt, w[v]); + } + } + } } - if (std::isfinite(c3SubTruA)) { - histos.fill(HIST("MCGen/Prof_C3Sub_Cent_etabin"), cent, ietaA, c3SubTruA); - histos.fill(HIST("MCGen/Prof_C3Sub_Mult_etabin"), multPV, ietaA, c3SubTruA); + flushTrackCounts(); + + // --- forward detectors: charged primaries (truth) and amplitudes (reco) --- + FwdSignal fwdTru, fwdReco; + if (cfgFwdOpt.cfgFwd) { + loadFwdCalib(coll.template bc_as(), false); + computeFwdTruth(mcParticles, fwdTru); + computeFwdReco(coll, vz, fwdReco, hasBit(ev.mask, kSystBase)); } - if (std::isfinite(c3SubTruC)) { - histos.fill(HIST("MCGen/Prof_C3Sub_Cent_etabin"), cent, ietaC, c3SubTruC); - histos.fill(HIST("MCGen/Prof_C3Sub_Mult_etabin"), multPV, ietaC, c3SubTruC); + + // --- <[pT]> and per eta bin, per forward bin --- + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(ev.mask, v)) { + continue; + } + for (int l = 0; l < kNLevels; ++l) { + const auto& s = sums[v][l]; + const auto& h = hists.mean[v][l]; + for (int ieta = 0; ieta < nEta; ++ieta) { + const double n = s.wk[ieta][1]; + h.mean->Fill(nCh, ieta, kMeanN, n); + if (n <= KFloatEpsilon) { + continue; + } + const double mpt = s.pmwk[ieta][1][1] / n; + if (std::isfinite(mpt)) { + h.mean->Fill(nCh, ieta, kMeanPt, mpt); + } + } + const FwdSignal& fwd = (l == kGen) ? fwdTru : fwdReco; + if (cfgFwdOpt.cfgFwd && h.fwdMean) { + for (int k = 0; k < nFwd; ++k) { + if (fwd.valid[k]) { + h.fwdMean->Fill(nCh, k, fwd.ampl[k]); + } + } + } + } } - if (std::isfinite(c3SubRecoA)) { - histos.fill(HIST("MCReco/Prof_C3Sub_Cent_etabin"), cent, ietaA, c3SubRecoA); - histos.fill(HIST("MCReco/Prof_C3Sub_Mult_etabin"), multPV, ietaA, c3SubRecoA); + } + } + PROCESS_SWITCH(RadialFlowDecorr, processMCMean, "process MC to calculate mean pt", cfgRunMCMean); + + // =========================================================================== + // MC: fluctuations (C2, subevent, forward covariances) at three levels + // =========================================================================== + void processMCFluc(aod::McCollisions::iterator const& mcCollision, McRecoCollisions const& recoCollisions, + aod::BCsWithTimestamps const&, aod::FT0s const&, aod::FV0As const&, + FilteredTCs const& mcTracks, aod::McParticles const& mcParticles) + { + const int64_t best = bestRecoCollision(recoCollisions, mcCollision.posZ()); + for (const auto& coll : recoCollisions) { + if (coll.globalIndex() != best) { + continue; } - if (std::isfinite(c3SubRecoC)) { - histos.fill(HIST("MCReco/Prof_C3Sub_Cent_etabin"), cent, ietaC, c3SubRecoC); - histos.fill(HIST("MCReco/Prof_C3Sub_Mult_etabin"), multPV, ietaC, c3SubRecoC); + const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); + histos.fill(HIST("hVtxZ"), coll.posZ()); + const auto ev = isEventSelected(coll, tracks.size(), maskAll); + if (!ev.mask) { + return; } - if (std::isfinite(c3SubRecoEffCorA)) { - histos.fill(HIST("MCRecoEffCorr/Prof_C3Sub_Cent_etabin"), cent, ietaA, c3SubRecoEffCorA); - histos.fill(HIST("MCRecoEffCorr/Prof_C3Sub_Mult_etabin"), multPV, ietaA, c3SubRecoEffCorA); + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); + + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); } - if (std::isfinite(c3SubRecoEffCorC)) { - histos.fill(HIST("MCRecoEffCorr/Prof_C3Sub_Cent_etabin"), cent, ietaC, c3SubRecoEffCorC); - histos.fill(HIST("MCRecoEffCorr/Prof_C3Sub_Mult_etabin"), multPV, ietaC, c3SubRecoEffCorC); + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - } - // full 2D subevent map - for (int ietaA = 1; ietaA < nEta; ++ietaA) { - for (int ietaC = 1; ietaC < nEta; ++ietaC) { - float etaValA = (etaLw[ietaA] + etaUp[ietaA]) / 2.0f; - float etaValB = (etaLw[ietaC] + etaUp[ietaC]) / 2.0f; - const float w = etaUp[ietaA] - etaLw[ietaA]; - const float gap = (ietaA - ietaC) * w; - const float sum = 2.f * etaLw[1] + (ietaA + ietaC - 1) * w; // = cA + cC on the lattice + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v)) { + for (auto& s : sums[v]) { + s.reset(); + } + } + } - float c2SubTru = (ietaA == ietaC) ? static_cast(c2Tru[ietaA]) : p1kBarTru[ietaA] * p1kBarTru[ietaC]; - float c2SubReco = (ietaA == ietaC) ? static_cast(c2Reco[ietaA]) : p1kBarReco[ietaA] * p1kBarReco[ietaC]; - float c2SubRecoEffCor = (ietaA == ietaC) ? static_cast(c2RecoEffCor[ietaA]) : p1kBarRecoEffCor[ietaA] * p1kBarRecoEffCor[ietaC]; + // --- truth sums: independent of the track cuts, copied to every selected variation --- + PowerSums& sumsTru = sums[kSystBase][kGen]; + if (!hasBit(ev.mask, kSystBase)) { + sumsTru.reset(); + } + for (const auto& particle : mcParticles) { + if (!isParticleSelected(particle) || !particle.isPhysicalPrimary()) { + continue; + } + float pt = particle.pt(), eta = particle.eta(); + if (pt <= cfgPtMin || pt > cfgPtMax) { + continue; + } + for (int ieta = 0; ieta < nEta; ++ieta) { + if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + continue; + } + sumsTru.add(ieta, pt, 1.); + } + } + for (int v = 1; v < kNSyst; ++v) { + if (hasBit(ev.mask, v)) { + sums[v][kGen] = sumsTru; + } + } - float c3Sub2DTru = (ietaA == ietaC) ? static_cast(c3Tru[ietaA]) : c2Tru[ietaA] * p1kBarTru[ietaC]; // diag: within-bin c3; off-diag: 2 from A, 1 from C - float c3Sub2DReco = (ietaA == ietaC) ? static_cast(c3Reco[ietaA]) : c2Reco[ietaA] * p1kBarReco[ietaC]; - float c3Sub2DRecoEffCor = (ietaA == ietaC) ? static_cast(c3RecoEffCor[ietaA]) : c2RecoEffCor[ietaA] * p1kBarRecoEffCor[ietaC]; + // --- reco sums --- + std::array w{}; + for (const auto& track : tracks) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { + continue; + } + float pt = track.pt(); + if (pt <= cfgPtMin || pt > cfgPtMax) { + continue; + } + float eta = track.eta(), phi = track.phi(); + float sign = track.sign(); + if (hasBit(trackMask, kSystBase)) { + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + histos.fill(HIST("hPhi"), phi); + } - if (std::isfinite(c2SubTru)) { - histos.fill(HIST("MCGen/Prof_C2Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c2SubTru); - histos.fill(HIST("MCGen/Prof_GapSum2D"), cent, gap, sum, c2SubTru); + // reco level: every selected track, independent of the corrections + for (int ieta = 0; ieta < nEta; ++ieta) { + if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + continue; + } + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(trackMask, v)) { + sums[v][kReco].add(ieta, pt, 1.); + } + } } - if (std::isfinite(c2SubReco)) { - histos.fill(HIST("MCReco/Prof_C2Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c2SubReco); - histos.fill(HIST("MCReco/Prof_GapSum2D"), cent, gap, sum, c2SubReco); + + float eff, fake, flatWeight; + const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (!weightMask) { + continue; } - if (std::isfinite(c2SubRecoEffCor)) { - histos.fill(HIST("MCRecoEffCorr/Prof_C2Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c2SubRecoEffCor); - histos.fill(HIST("MCRecoEffCorr/Prof_GapSum2D"), cent, gap, sum, c2SubRecoEffCor); + if (hasBit(weightMask, kSystBase)) { + fillFlat(kSystBase, vz, sign, pt, eta, phi, w[kSystBase], (1.f - fake) / eff); } - if (std::isfinite(c3Sub2DTru)) { - histos.fill(HIST("MCGen/Prof_C3Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c3Sub2DTru); - histos.fill(HIST("MCGen/Prof_C3GapSum2D"), cent, gap, sum, c3Sub2DTru); + // reco, efficiency-corrected + for (int ieta = 0; ieta < nEta; ++ieta) { + if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { + continue; + } + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(weightMask, v)) { + sums[v][kRecoEffCorr].add(ieta, pt, w[v]); + } + } } - if (std::isfinite(c3Sub2DReco)) { - histos.fill(HIST("MCReco/Prof_C3Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c3Sub2DReco); - histos.fill(HIST("MCReco/Prof_C3GapSum2D"), cent, gap, sum, c3Sub2DReco); + } + flushTrackCounts(); + + // --- forward detectors: charged primaries (truth) and amplitudes (reco) --- + FwdSignal fwdTru, fwdReco; + if (cfgFwdOpt.cfgFwd) { + loadFwdCalib(coll.template bc_as(), false); + computeFwdTruth(mcParticles, fwdTru); + computeFwdReco(coll, vz, fwdReco, hasBit(ev.mask, kSystBase)); + } + + // --- c2, c3, subevent correlators and forward covariances of every variation and level --- + EventObs evObs; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(ev.mask, v)) { + continue; } - if (std::isfinite(c3Sub2DRecoEffCor)) { - histos.fill(HIST("MCRecoEffCorr/Prof_C3Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c3Sub2DRecoEffCor); - histos.fill(HIST("MCRecoEffCorr/Prof_C3GapSum2D"), cent, gap, sum, c3Sub2DRecoEffCor); + for (int l = 0; l < kNLevels; ++l) { + const FwdSignal* fwd = cfgFwdOpt.cfgFwd ? (l == kGen ? &fwdTru : &fwdReco) : nullptr; + computeEventObs(sums[v][l], v, l, fwd, nCh, evObs); + fillFluc(hists.fluc[v][l], evObs, 1., nCh); } } } @@ -1941,32 +2997,34 @@ struct RadialFlowDecorr { // =========================================================================== // DATA: build flattening maps (inclusive) // =========================================================================== - void processGetDataFlat(AodCollisionsSel::iterator const& coll, BCsRun3 const& /*bcs*/, aod::Zdcs const& /*zdcsData*/, AodTracksSel const& tracks) + void processGetDataFlat(AodCollisionsSel::iterator const& coll, AodTracksSel const& tracks) { histos.fill(HIST("hVtxZ"), coll.posZ()); - if (!isEventSelected(coll)) { + const auto ev = isEventSelected(coll, tracks.size(), maskFlat); + if (!ev.mask) { return; } - float cent = getCentrality(coll); - if (cent > KCentMax) { - return; + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); + + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); } - if (!isPassAddPileup(coll.multNTracksPV(), tracks.size(), cent)) { - return; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - histos.fill(HIST("hVtxZ_after_sel"), coll.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), coll.multNTracksPV(), tracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), tracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, tracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, coll.multNTracksPV()); - - int ntrk = 0; - float vz = coll.posZ(); - + std::array w{}; for (const auto& track : tracks) { - if (!isTrackSelected(track)) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { continue; } float pt = track.pt(); @@ -1974,146 +3032,135 @@ struct RadialFlowDecorr { continue; } float eta = track.eta(), phi = track.phi(); - auto sign = track.sign(); - - histos.fill(HIST("hPt"), pt); - histos.fill(HIST("hEta"), eta); - histos.fill(HIST("hPhi"), phi); - if (eta > etaLw[0] && eta < etaUp[0]) { - ntrk++; - } - - float eff = 1.f, fake = 0.f; - if (!getValidEffFake(coll.multNTracksPV(), pt, eta, cfgEff, eff, fake)) { - continue; - } - float w = (1.0f - fake) / eff; - if (!std::isfinite(w) || w <= 0.f) { - continue; - } - - histos.fill(HIST("hEtaPhiReco"), vz, sign, pt, eta, phi); - histos.fill(HIST("hEtaPhiRecoEffWtd"), vz, sign, pt, eta, phi, (1.0f - fake) / eff); - histos.fill(HIST("hEtaPhiRecoWtd"), vz, sign, pt, eta, phi, w); - } - - if (cfgZDC) { - const auto& foundBC = coll.foundBC_as(); - if (!foundBC.has_zdc()) { - return; + float sign = track.sign(); + if (hasBit(trackMask, kSystBase)) { + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + histos.fill(HIST("hPhi"), phi); + } + + // efficiency weights only: the flattening map is what this pass measures + float eff, fake, flatWeight; + const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, false, w, eff, fake, flatWeight); + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(weightMask, v)) { + fillFlat(v, vz, sign, pt, eta, phi, w[v], w[v]); + } } - auto zdc = foundBC.zdc(); - auto zdcAmp = zdc.energyCommonZNA() + zdc.energyCommonZNC(); - histos.fill(HIST("hnTrkPVZDC"), coll.multNTracksPV(), zdcAmp); - histos.fill(HIST("hNchZDC"), ntrk, zdcAmp); } + flushTrackCounts(); } PROCESS_SWITCH(RadialFlowDecorr, processGetDataFlat, "process data to calculate Flattening maps", cfgRunGetDataFlat); // =========================================================================== // DATA: mean pT // =========================================================================== - void processDataMean(AodCollisionsSel::iterator const& coll, BCsRun3 const& /*bcs*/, aod::Zdcs const& /*zdcsData*/, AodTracksSel const& tracks) + void processDataMean(AodCollisionsSel::iterator const& coll, aod::BCsWithTimestamps const&, aod::FT0s const&, aod::FV0As const&, AodTracksSel const& tracks) { - std::array sumWi{}, sumWipti{}; - - if (!isEventSelected(coll)) { + const auto ev = isEventSelected(coll, tracks.size(), maskAll); + if (!ev.mask) { return; } - float cent = getCentrality(coll); - if (cent > KCentMax) { - return; + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); + + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); } - if (!isPassAddPileup(coll.multNTracksPV(), tracks.size(), cent)) { - return; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - histos.fill(HIST("hVtxZ_after_sel"), coll.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), coll.multNTracksPV(), tracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), tracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, tracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, coll.multNTracksPV()); - - float vz = coll.posZ(); - + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v)) { + sums[v][0].reset(); + } + } + std::array w{}; for (const auto& track : tracks) { - if (!isTrackSelected(track)) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { continue; } float p = track.p(); float pt = track.pt(); - float eta = track.eta(), phi = track.phi(); - auto sign = track.sign(); if (p < KFloatEpsilon) { continue; } if (pt <= cfgPtMin || pt > cfgPtMax) { continue; } - - histos.fill(HIST("hP"), p); - histos.fill(HIST("hPt"), pt); - histos.fill(HIST("hEta"), eta); - histos.fill(HIST("hPhi"), phi); - - float eff = 1.f, fake = 0.f; - if (!getValidEffFake(coll.multNTracksPV(), pt, eta, cfgEff, eff, fake)) { - continue; + float eta = track.eta(), phi = track.phi(); + float sign = track.sign(); + if (hasBit(trackMask, kSystBase)) { + histos.fill(HIST("hP"), p); + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + histos.fill(HIST("hPhi"), phi); } - float flatWeight = getFlatteningWeight(vz, sign, pt, eta, phi, cfgFlat); - histos.fill(HIST("pEffWeight_pt_eta_cent"), pt, eta, cent, eff); - histos.fill(HIST("pFakeWeight_pt_eta_cent"), pt, eta, cent, fake); - histos.fill(HIST("pFlatWeight_pt_eta_cent"), pt, eta, cent, flatWeight); - - if (eff <= KFloatEpsilon) { + float eff, fake, flatWeight; + const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (!weightMask) { continue; } - - float w = flatWeight * (1.0f - fake) / eff; - if (!std::isfinite(w) || w <= 0.f) { - continue; + if (hasBit(weightMask, kSystBase)) { + histos.fill(HIST("Prof_Weights_pt_eta_type"), pt, eta, 0, eff); + histos.fill(HIST("Prof_Weights_pt_eta_type"), pt, eta, 1, fake); + histos.fill(HIST("Prof_Weights_pt_eta_type"), pt, eta, 2, flatWeight); + fillFlat(kSystBase, vz, sign, pt, eta, phi, w[kSystBase], (1.f - fake) / eff); } - histos.fill(HIST("hEtaPhiReco"), vz, sign, pt, eta, phi); - histos.fill(HIST("hEtaPhiRecoEffWtd"), vz, sign, pt, eta, phi, (1.0f - fake) / eff); - histos.fill(HIST("hEtaPhiRecoWtd"), vz, sign, pt, eta, phi, w); - for (int ieta = 0; ieta < nEta; ++ieta) { if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { continue; } - sumWi[ieta] += w; - sumWipti[ieta] += w * pt; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(weightMask, v)) { + sums[v][0].add(ieta, pt, w[v]); + } + } } } + flushTrackCounts(); - if (sumWi[0] > KFloatEpsilon) { - histos.fill(HIST("Prof_Cent_Nchrec"), cent, sumWi[0]); - histos.fill(HIST("Prof_Mult_Nchrec"), coll.multNTracksPV(), sumWi[0]); - histos.fill(HIST("Prof_Cent_MeanpT"), cent, sumWipti[0] / sumWi[0]); - histos.fill(HIST("Prof_Mult_MeanpT"), coll.multNTracksPV(), sumWipti[0] / sumWi[0]); + // --- forward detectors: gain-equalised amplitudes --- + FwdSignal fwd; + if (cfgFwdOpt.cfgFwd) { + loadFwdCalib(coll.template bc_as(), true); + computeFwdReco(coll, vz, fwd, hasBit(ev.mask, kSystBase)); } - for (int ietaA = 0; ietaA < nEta; ++ietaA) { - for (int ietaC = 0; ietaC < nEta; ++ietaC) { - if ((sumWi[ietaA] < KFloatEpsilon) || (sumWi[ietaC] < KFloatEpsilon)) { + // --- <[pT]> and per eta bin, per forward bin --- + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(ev.mask, v)) { + continue; + } + const auto& s = sums[v][0]; + const auto& h = hists.mean[v][0]; + for (int ieta = 0; ieta < nEta; ++ieta) { + const double n = s.wk[ieta][1]; + h.mean->Fill(nCh, ieta, kMeanN, n); + if (n <= KFloatEpsilon) { continue; } - double wCorrAB = sumWi[ietaA] + sumWi[ietaC]; - if (wCorrAB > 0) { - float mptsub = (sumWipti[ietaA] + sumWipti[ietaC]) / wCorrAB; - histos.fill(HIST("Prof2D_MeanpTSub"), cent, ietaA, ietaC, mptsub); + const double mpt = s.pmwk[ieta][1][1] / n; + if (std::isfinite(mpt)) { + h.mean->Fill(nCh, ieta, kMeanPt, mpt); } } - if (sumWi[ietaA] > KFloatEpsilon) { - double mpt = sumWipti[ietaA] / sumWi[ietaA]; - if (std::isfinite(mpt)) { - histos.fill(HIST("pmean_nch_etabin"), coll.multNTracksPV(), ietaA, mpt); - histos.fill(HIST("pmeanMult_nch_etabin"), coll.multNTracksPV(), ietaA, sumWi[ietaA]); - histos.fill(HIST("pmean_cent_etabin"), cent, ietaA, mpt); - histos.fill(HIST("pmeanMult_cent_etabin"), cent, ietaA, sumWi[ietaA]); + if (cfgFwdOpt.cfgFwd && h.fwdMean) { + for (int k = 0; k < nFwd; ++k) { + if (fwd.valid[k]) { + h.fwdMean->Fill(nCh, k, fwd.ampl[k]); + } } } } @@ -2121,230 +3168,145 @@ struct RadialFlowDecorr { PROCESS_SWITCH(RadialFlowDecorr, processDataMean, "process data to calculate mean pT", cfgRunDataMean); // =========================================================================== - // DATA: fluctuations (C2, subevent) + Poisson bootstrap for the base run + // DATA: fluctuations (C2, subevent, forward covariances) + Poisson bootstrap for Base // =========================================================================== - void processDataFluc(AodCollisionsSel::iterator const& coll, BCsRun3 const& /*bcs*/, aod::Zdcs const& /*zdcsData*/, AodTracksSel const& tracks) + void processDataFluc(AodCollisionsSel::iterator const& coll, aod::BCsWithTimestamps const&, aod::FT0s const&, aod::FV0As const&, AodTracksSel const& tracks) { - if (!isEventSelected(coll)) { - return; - } - float cent = getCentrality(coll); - if (cent > KCentMax) { - return; - } - if (!isPassAddPileup(coll.multNTracksPV(), tracks.size(), cent)) { + const auto ev = isEventSelected(coll, tracks.size(), maskAll); + if (!ev.mask) { return; } + const float nCh = tracks.size(); + const float multPV = coll.multNTracksPV(); + const float vz = coll.posZ(); - histos.fill(HIST("hVtxZ_after_sel"), coll.posZ()); - histos.fill(HIST("hCentrality"), cent); - histos.fill(HIST("Hist2D_globalTracks_PVTracks"), coll.multNTracksPV(), tracks.size()); - histos.fill(HIST("Hist2D_cent_nch"), tracks.size(), cent); - histos.fill(HIST("Hist2D_globalTracks_cent"), cent, tracks.size()); - histos.fill(HIST("Hist2D_PVTracks_cent"), cent, coll.multNTracksPV()); - - if (!state.pmeanNchEtabinStep2) { - LOGF(warning, "Data fluc: mean pT map missing"); - return; + if (hasBit(ev.mask, kSystBase)) { + histos.fill(HIST("hVtxZ_after_sel"), vz); + histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); + histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); + histos.fill(HIST("Hist2D_PVTracks_cent"), ev.cent[kSystBase], multPV); + hists.nchOccupancy->Fill(nCh, ev.occupancy); } - if (cfgEff && (!state.hEff || !state.hFake)) { - LOGF(warning, "Data fluc: Efficiency maps requested but not present."); - return; - } - - if (cfgFlat && !state.hFlatWeight) { - LOGF(warning, "Data fluc: Flattening map requested but not present."); - return; + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v) && hists.nchCent[v]) { + hists.nchCent[v]->Fill(ev.cent[v], nCh); + } } - std::array, KIntM>, KNEtaMax> sumpmwk{}; - std::array, KNEtaMax> sumwk{}; - std::array mean{}, c2{}, c3{}, p1kBar{}; - std::array meanMult{}; - - // --- Poisson bootstrap: one weight per sample per event --- - std::array poisW{}; - if (doBoot) { - for (int s = 0; s < nBoot; ++s) { - poisW[s] = rng.Poisson(1.0); + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(ev.mask, v)) { + sums[v][0].reset(); } } - auto fillBS2D = [&](std::array, KMaxBoot>& arr, double x, double y, double val) { - if (!doBoot) { - return; - } - for (int s = 0; s < nBoot; ++s) { - arr[s]->Fill(x, y, val, poisW[s]); - } - }; - auto fillBS3D = [&](std::array, KMaxBoot>& arr, double x, double y, double z, double val) { - if (!doBoot) { - return; - } - for (int s = 0; s < nBoot; ++s) { - arr[s]->Fill(x, y, z, val, poisW[s]); - } - }; - - float vz = coll.posZ(); - + std::array w{}; for (const auto& track : tracks) { - if (!isTrackSelected(track)) { + const uint32_t trackMask = isTrackSelected(track, ev.mask); + if (!trackMask) { continue; } float p = track.p(); float pt = track.pt(); - float eta = track.eta(), phi = track.phi(); - auto sign = track.sign(); if (p < KFloatEpsilon) { continue; } if (pt <= cfgPtMin || pt > cfgPtMax) { continue; } - - float eff = 1.f, fake = 0.f; - if (!getValidEffFake(coll.multNTracksPV(), pt, eta, cfgEff, eff, fake)) { - continue; + float eta = track.eta(), phi = track.phi(); + float sign = track.sign(); + if (hasBit(trackMask, kSystBase)) { + histos.fill(HIST("hP"), p); + histos.fill(HIST("hPt"), pt); + histos.fill(HIST("hEta"), eta); + histos.fill(HIST("hPhi"), phi); } - float flatWeight = getFlatteningWeight(vz, sign, pt, eta, phi, cfgFlat); - - float w = flatWeight * (1.0f - fake) / eff; - if (!std::isfinite(w) || w <= 0.f) { + float eff, fake, flatWeight; + const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (!weightMask) { continue; } + if (hasBit(weightMask, kSystBase)) { + fillFlat(kSystBase, vz, sign, pt, eta, phi, w[kSystBase], (1.f - fake) / eff); + } for (int ieta = 0; ieta < nEta; ++ieta) { if (eta <= etaLw[ieta] || eta > etaUp[ieta]) { continue; } - for (int k = 0; k < KIntK; ++k) { - for (int m = 0; m < KIntM; ++m) { - sumpmwk[ieta][m][k] += std::pow(w, k) * std::pow(pt, m); + for (int v = 0; v < kNSyst; ++v) { + if (hasBit(weightMask, v)) { + sums[v][0].add(ieta, pt, w[v]); } - sumwk[ieta][k] += std::pow(w, k); } } } + flushTrackCounts(); - for (int ieta = 0; ieta < nEta; ++ieta) { - const int ibx = state.pmeanNchEtabinStep2->GetXaxis()->FindBin(coll.multNTracksPV()); - const int iby = ieta + 1; - - float mmpt = state.pmeanNchEtabinStep2->GetBinContent(ibx, iby); - - meanMult[ieta] = sumwk[ieta][1]; - mean[ieta] = (sumwk[ieta][1] > KFloatEpsilon) ? (sumpmwk[ieta][1][1] / sumwk[ieta][1]) : std::numeric_limits::quiet_NaN(); - - // c2 (>=2 tracks), c3 (>=3 tracks) and the pT deviation need a usable reference - c2[ieta] = std::numeric_limits::quiet_NaN(); - c3[ieta] = std::numeric_limits::quiet_NaN(); - p1kBar[ieta] = std::numeric_limits::quiet_NaN(); - if (std::isfinite(mmpt) && mmpt != 0) { - const auto corr = calculateC2C3FromSums(sumpmwk[ieta], sumwk[ieta], mmpt); - c2[ieta] = corr.c2; - c3[ieta] = corr.c3; - if (std::isfinite(mean[ieta])) { - p1kBar[ieta] = mean[ieta] - mmpt; - } - } + // --- forward detectors: gain-equalised amplitudes --- + FwdSignal fwd; + if (cfgFwdOpt.cfgFwd) { + loadFwdCalib(coll.template bc_as(), true); + computeFwdReco(coll, vz, fwd, hasBit(ev.mask, kSystBase)); } - // meanpT & C2 vs eta bin - for (int ieta = 0; ieta < nEta; ++ieta) { - if (std::isfinite(meanMult[ieta])) { - fillBS2D(bs.multCent, cent, ieta, meanMult[ieta]); - fillBS2D(bs.multMult, coll.multNTracksPV(), ieta, meanMult[ieta]); + // --- Poisson bootstrap: one weight per sample per event --- + std::array poisW{}; + if (doBoot) { + for (int s = 0; s < nBoot; ++s) { + poisW[s] = rng.Poisson(1.0); } + } - if (std::isfinite(mean[ieta])) { - histos.fill(HIST("Prof_MeanpT_Cent_etabin"), cent, ieta, mean[ieta]); - histos.fill(HIST("Prof_MeanpT_Mult_etabin"), coll.multNTracksPV(), ieta, mean[ieta]); - fillBS2D(bs.meanpTCent, cent, ieta, mean[ieta]); - fillBS2D(bs.meanpTMult, coll.multNTracksPV(), ieta, mean[ieta]); - } - if (std::isfinite(c2[ieta])) { - histos.fill(HIST("Prof_C2_Cent_etabin"), cent, ieta, c2[ieta]); - histos.fill(HIST("Prof_C2_Mult_etabin"), coll.multNTracksPV(), ieta, c2[ieta]); - fillBS2D(bs.c2Cent, cent, ieta, c2[ieta]); - fillBS2D(bs.c2Mult, coll.multNTracksPV(), ieta, c2[ieta]); + // --- c2, c3, subevent correlators and forward covariances of every variation --- + EventObs evObs; + for (int v = 0; v < kNSyst; ++v) { + if (!hasBit(ev.mask, v)) { + continue; } - if (std::isfinite(c3[ieta])) { - histos.fill(HIST("Prof_C3_Cent_etabin"), cent, ieta, c3[ieta]); - histos.fill(HIST("Prof_C3_Mult_etabin"), coll.multNTracksPV(), ieta, c3[ieta]); - fillBS2D(bs.c3Cent, cent, ieta, c3[ieta]); - fillBS2D(bs.c3Mult, coll.multNTracksPV(), ieta, c3[ieta]); + computeEventObs(sums[v][0], v, 0, cfgFwdOpt.cfgFwd ? &fwd : nullptr, nCh, evObs); + fillFluc(hists.fluc[v][0], evObs, 1., nCh); + // bootstrap replicas of Base, in the 5% centrality classes + if (v == kSystBase && doBoot) { + for (int s = 0; s < nBoot; ++s) { + if (poisW[s] > 0.) { + fillFluc(hists.boot[s], evObs, poisW[s], ev.cent[kSystBase]); + } + } } } + } + PROCESS_SWITCH(RadialFlowDecorr, processDataFluc, "process data to calculate fluc pT", cfgRunDataFluc); - // mirror-pair subevent (C2Sub, C3Sub) vs eta bin - for (int ietaA = 1; ietaA <= (nEta - 1) / 2; ++ietaA) { - int ietaC = nEta - ietaA; - float c2Sub = p1kBar[ietaA] * p1kBar[ietaC]; - // Mirror pairs (A, C = -A): both orderings are filled, each at the bin of its pair, - // so bins A < 0 hold AAB and bins C > 0 hold ABB of the same mirror pair. For a - // symmetric collision system the two agree in expectation: eta -> -eta maps - // AAB(a, -a) onto AAB(-a, a) = ABB(a, -a). Comparing bin A with bin nEta - A is - // therefore a forward/backward check, not two independent observables. - float c3SubA = c2[ietaA] * p1kBar[ietaC]; // AAB: 2 particles from A, 1 from C - float c3SubC = c2[ietaC] * p1kBar[ietaA]; // ABB: 2 particles from C, 1 from A - - if (std::isfinite(c2Sub)) { - histos.fill(HIST("Prof_C2Sub_Cent_etabin"), cent, ietaA, c2Sub); - histos.fill(HIST("Prof_C2Sub_Mult_etabin"), coll.multNTracksPV(), ietaA, c2Sub); - fillBS2D(bs.c2SubCent, cent, ietaA, c2Sub); - fillBS2D(bs.c2SubMult, coll.multNTracksPV(), ietaA, c2Sub); - } - if (std::isfinite(c3SubA)) { - histos.fill(HIST("Prof_C3Sub_Cent_etabin"), cent, ietaA, c3SubA); - histos.fill(HIST("Prof_C3Sub_Mult_etabin"), coll.multNTracksPV(), ietaA, c3SubA); - fillBS2D(bs.c3SubCent, cent, ietaA, c3SubA); - fillBS2D(bs.c3SubMult, coll.multNTracksPV(), ietaA, c3SubA); - } - if (std::isfinite(c3SubC)) { - histos.fill(HIST("Prof_C3Sub_Cent_etabin"), cent, ietaC, c3SubC); - histos.fill(HIST("Prof_C3Sub_Mult_etabin"), coll.multNTracksPV(), ietaC, c3SubC); - fillBS2D(bs.c3SubCent, cent, ietaC, c3SubC); - fillBS2D(bs.c3SubMult, coll.multNTracksPV(), ietaC, c3SubC); + // =========================================================================== + // Cut QA + // =========================================================================== + // On the unfiltered collisions and tracks (the analysis passes see only the filtered ones): + // every quantity the Base selection cuts on, before and after it. The counters are not + // filled here. N_ch is the number of tracks of the analysis track filter. + void processCutQA(GeneralCollisions::iterator const& col, UnfilteredTracks const& tracks) + { + int nCh = 0; + for (const auto& trk : tracks) { + if (trk.isGlobalTrack() && trk.pt() > KPtMin && trk.pt() < KPtMax) { + ++nCh; } } - - // full 2D subevent map - for (int ietaA = 1; ietaA < nEta; ++ietaA) { - for (int ietaC = 1; ietaC < nEta; ++ietaC) { - float etaValA = (etaLw[ietaA] + etaUp[ietaA]) / 2.0f; - float etaValB = (etaLw[ietaC] + etaUp[ietaC]) / 2.0f; - const float w = etaUp[ietaA] - etaLw[ietaA]; - const float gap = (ietaA - ietaC) * w; - const float sum = 2.f * etaLw[1] + (ietaA + ietaC - 1) * w; // = cA + cC on the lattice - - float c2Sub = (ietaA == ietaC) ? static_cast(c2[ietaA]) : p1kBar[ietaA] * p1kBar[ietaC]; - // C3 sub-event map, ORDERED: cell (A,C) = c2[A] p1kBar[C] = _A _C, - // two particles from A and one from C ("AAB"). The same pair of bins read the - // other way round, cell (C,A) = c2[C] p1kBar[A], is "ABB" (one from A, two from C). - // Both are filled, so the map carries AAB and ABB of every bin pair: the row at - // eta_A = eta_ref is AAB with the pair at eta_ref, the column is ABB. Unlike C2, - // this map is NOT symmetric under A <-> C, and the two triangles must not be - // averaged or treated as duplicates. Diagonal: the within-bin c3 ("AAA"). - float c3Sub2D = (ietaA == ietaC) ? static_cast(c3[ietaA]) : c2[ietaA] * p1kBar[ietaC]; - - if (std::isfinite(c2Sub)) { - histos.fill(HIST("Prof_C2Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c2Sub); - histos.fill(HIST("Prof_GapSum2D"), cent, gap, sum, c2Sub); - fillBS3D(bs.c2Sub2D, cent, etaValA, etaValB, c2Sub); - fillBS3D(bs.gapSum2D, cent, gap, sum, c2Sub); - } - if (std::isfinite(c3Sub2D)) { - histos.fill(HIST("Prof_C3Sub2D_Cent_etaA_etaC"), cent, etaValA, etaValB, c3Sub2D); - histos.fill(HIST("Prof_C3GapSum2D"), cent, gap, sum, c3Sub2D); - fillBS3D(bs.c3Sub2D, cent, etaValA, etaValB, c3Sub2D); - fillBS3D(bs.c3GapSum2D, cent, gap, sum, c3Sub2D); - } + const float cent = getCentrality(col, cfgEvSel.cfgCentralityChoice); + fillCutQAEvent(kQABefore, col, nCh, cent); + const auto ev = isEventSelected(col, nCh, 1u << kSystBase, false); + if (!hasBit(ev.mask, kSystBase)) { + return; + } + fillCutQAEvent(kQAAfter, col, nCh, cent); + for (const auto& trk : tracks) { + fillCutQATrack(kQABefore, trk); + if (hasBit(isTrackSelected(trk, 1u << kSystBase, false), kSystBase)) { + fillCutQATrack(kQAAfter, trk); } } } - PROCESS_SWITCH(RadialFlowDecorr, processDataFluc, "process data to calculate fluc pT", cfgRunDataFluc); + PROCESS_SWITCH(RadialFlowDecorr, processCutQA, "QA of the event and track cut quantities before and after the Base selection", true); }; WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) From 8f831fb79aa329567ec66b86ac24a0afb623b765 Mon Sep 17 00:00:00 2001 From: Somadutta Bhatta Date: Sat, 24 Oct 2026 01:12:04 +0200 Subject: [PATCH 2/3] [PWGCF] radialFlowDecorr: fix GCC enum-float arithmetic errors and cppcheck const pointer --- .../Tasks/radialFlowDecorr.cxx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx b/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx index 7478e2a9bc1..15e8381d468 100644 --- a/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx +++ b/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx @@ -495,7 +495,7 @@ struct RadialFlowDecorr { AxisSpec etaBinAxis{10, -0.5, 9.5, "#eta bin Number"}; AxisSpec gapAxis{15, -1.5, 1.5, "#Delta#eta"}; AxisSpec sumAxis{15, -1.5, 1.5, "#Sigma#eta"}; - AxisSpec fwdAxis{kNFwdDet, -0.5, kNFwdDet - 0.5, "forward bin"}; + AxisSpec fwdAxis{kNFwdDet, -0.5, static_cast(kNFwdDet) - 0.5, "forward bin"}; // --- process switches -------------------------------------------------------- Configurable cfgRunGetEff{"cfgRunGetEff", false, "Run MC pass to build efficiency/fake maps"}; @@ -1604,11 +1604,11 @@ struct RadialFlowDecorr { histos.add("hPhi", ";#phi", kTH1F, {{KNbinsPhi, KPhiMin, TwoPI}}); // cut flows of every variation (y bin 1 = Base); 1D copies per variation in declareCounts() - hists.evtCount = histos.add("hEvtCountSyst", "Number of events;;variation", kTH2D, {{kNEvCut, -0.5, kNEvCut - 0.5}, {kNSyst, -0.5, kNSyst - 0.5}}); + hists.evtCount = histos.add("hEvtCountSyst", "Number of events;;variation", kTH2D, {{kNEvCut, -0.5, static_cast(kNEvCut) - 0.5}, {kNSyst, -0.5, static_cast(kNSyst) - 0.5}}); labelAxis(hists.evtCount->GetXaxis(), evtCutLabels()); labelSystAxis(hists.evtCount->GetYaxis()); - hists.trkCount = histos.add("hTrkCountSyst", "Number of tracks;;variation", kTH2D, {{kNTrkCut, -0.5, kNTrkCut - 0.5}, {kNSyst, -0.5, kNSyst - 0.5}}); + hists.trkCount = histos.add("hTrkCountSyst", "Number of tracks;;variation", kTH2D, {{kNTrkCut, -0.5, static_cast(kNTrkCut) - 0.5}, {kNSyst, -0.5, static_cast(kNSyst) - 0.5}}); labelAxis(hists.trkCount->GetXaxis(), trkCutLabels()); labelSystAxis(hists.trkCount->GetYaxis()); } @@ -1634,9 +1634,9 @@ struct RadialFlowDecorr { continue; } auto& r = reg(v); - hists.evtCount1D[v] = r.add((systPrefix(v) + "hEvtCount").c_str(), ("Number of events, " + systName[v]).c_str(), kTH1D, {{kNEvCut, -0.5, kNEvCut - 0.5}}); + hists.evtCount1D[v] = r.add((systPrefix(v) + "hEvtCount").c_str(), ("Number of events, " + systName[v]).c_str(), kTH1D, {{kNEvCut, -0.5, static_cast(kNEvCut) - 0.5}}); labelAxis(hists.evtCount1D[v]->GetXaxis(), evtCutLabels()); - hists.trkCount1D[v] = r.add((systPrefix(v) + "hTrkCount").c_str(), ("Number of tracks, " + systName[v]).c_str(), kTH1D, {{kNTrkCut, -0.5, kNTrkCut - 0.5}}); + hists.trkCount1D[v] = r.add((systPrefix(v) + "hTrkCount").c_str(), ("Number of tracks, " + systName[v]).c_str(), kTH1D, {{kNTrkCut, -0.5, static_cast(kNTrkCut) - 0.5}}); labelAxis(hists.trkCount1D[v]->GetXaxis(), trkCutLabels()); } } @@ -1712,7 +1712,7 @@ struct RadialFlowDecorr { if (hists.fwdChannel) { return; } - hists.fwdChannel = histos.add("FwdQA/Prof_Channel_qa", ";channel (FT0A 0-95, FT0C 96-207, FV0A 208-255);quantity", kTProfile2D, {{KNChFT0 + KNChFV0, -0.5, KNChFT0 + KNChFV0 - 0.5}, {kNChQA, -0.5, kNChQA - 0.5}}); + hists.fwdChannel = histos.add("FwdQA/Prof_Channel_qa", ";channel (FT0A 0-95, FT0C 96-207, FV0A 208-255);quantity", kTProfile2D, {{KNChFT0 + KNChFV0, -0.5, KNChFT0 + KNChFV0 - 0.5}, {kNChQA, -0.5, static_cast(kNChQA) - 0.5}}); labelAxis(hists.fwdChannel->GetYaxis(), {"amplitude", "gain-equalised amplitude", "#eta (aligned, vs z_{vtx})"}); hists.fwdEta = histos.add("FwdQA/Prof_Eta_fwdbin", ";forward bin;amplitude-weighted #LT#eta#GT", kTProfile, {fwdAxis}); labelFwdAxis(hists.fwdEta->GetXaxis()); @@ -1725,7 +1725,7 @@ struct RadialFlowDecorr { continue; } hists.eff[v].maps = reg(v).add((systPrefix(v) + "hEffMaps").c_str(), ";N_{ch};p_{T};#eta;occupancy;category", kTHnF, - {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {occAxisEff}, {kNEffCat, -0.5, kNEffCat - 0.5}}, true); + {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {occAxisEff}, {kNEffCat, -0.5, static_cast(kNEffCat) - 0.5}}, true); labelAxis(hists.eff[v].maps->GetAxis(4), {"AllPrimary", "RecoMatchedToPrimary", "AllReco", "RecoSecondary", "RecoOtherCollision", "RecoFake"}); } } @@ -1737,7 +1737,7 @@ struct RadialFlowDecorr { histos.add("etaTruthReco", ";#eta^{MC};#eta^{reco}", kTH2F, {{KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}, {KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}}); histos.add("TruthTracKVz", ";Vz^{MC};Vz^{Reco}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {KNbinsVz, KVzMin, KVzMax}}); histos.add("vzResolution", ";Vz^{MC};(Vz^{reco}-Vz^{MC})/Vz^{MC}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {100, -0.1, 0.1}}); - hists.mcTrackOrigin = histos.add("MCQA/Hist3D_Nch_pT_origin", ";N_{ch};p_{T} (GeV/c);origin", kTH3F, {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {kNOrigin, -0.5, kNOrigin - 0.5}}); + hists.mcTrackOrigin = histos.add("MCQA/Hist3D_Nch_pT_origin", ";N_{ch};p_{T} (GeV/c);origin", kTH3F, {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {kNOrigin, -0.5, static_cast(kNOrigin) - 0.5}}); labelAxis(hists.mcTrackOrigin->GetZaxis(), {"primary", "secondary", "other MC collision", "no label", "fake flag", "ITS-TPC mismatch"}); } @@ -1747,7 +1747,7 @@ struct RadialFlowDecorr { if (hists.mcVtx) { return; } - hists.mcVtx = histos.add("MCQA/Hist2D_dVz_type", ";z_{vtx}^{reco} - z_{vtx}^{MC} (cm);reco collision", kTH2F, {{200, -0.5, 0.5}, {kNVtxQA, -0.5, kNVtxQA - 0.5}}); + hists.mcVtx = histos.add("MCQA/Hist2D_dVz_type", ";z_{vtx}^{reco} - z_{vtx}^{MC} (cm);reco collision", kTH2F, {{200, -0.5, 0.5}, {kNVtxQA, -0.5, static_cast(kNVtxQA) - 0.5}}); labelAxis(hists.mcVtx->GetYaxis(), {"single", "split: used", "split: skipped"}); } @@ -1760,7 +1760,7 @@ struct RadialFlowDecorr { continue; } hists.flat[v].maps = reg(v).add((systPrefix(v) + dir + "hFlatMaps").c_str(), ";vz;sign;pt;eta;phi;weight", kTHnSparseF, - {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}, {kNFlatType, -0.5, kNFlatType - 0.5}}); + {{vzAxis}, {chgAxis}, {pTAxis}, {etaFlatAxis}, {phiAxis}, {kNFlatType, -0.5, static_cast(kNFlatType) - 0.5}}); labelAxis(hists.flat[v].maps->GetAxis(5), {"raw", "(1-fake)/eff", "all weights"}); } } @@ -1778,7 +1778,7 @@ struct RadialFlowDecorr { for (int l = 0; l < nLev; ++l) { const std::string p = systPrefix(v) + (isMC ? mcDir[l] : std::string{}); auto& h = hists.mean[v][l]; - h.mean = r.add((p + "Prof_Mean_Nch_etabin_obs").c_str(), ";N_{ch};#eta-bin;observable", kTProfile3D, {{nchAxisOf(v)}, {etaBinAxis}, {kNMeanObs, -0.5, kNMeanObs - 0.5}}); + h.mean = r.add((p + "Prof_Mean_Nch_etabin_obs").c_str(), ";N_{ch};#eta-bin;observable", kTProfile3D, {{nchAxisOf(v)}, {etaBinAxis}, {kNMeanObs, -0.5, static_cast(kNMeanObs) - 0.5}}); labelAxis(h.mean->GetZaxis(), {"#LT[p_{T}]#GT", "#LT N#GT"}); if (cfgFwdOpt.cfgFwd) { h.fwdMean = r.add((p + "Prof_FwdAmpMean_Nch_fwdbin").c_str(), ";N_{ch};forward bin;#LT a#GT", kTProfile2D, {{nchAxisOf(v)}, {fwdAxis}}); @@ -1794,7 +1794,7 @@ struct RadialFlowDecorr { { FlucHists h; auto n = [&](const char* name) { return p + name + s; }; - h.obs = r.add(n("Prof_Obs_Nch_etabin_obs").c_str(), ";N_{ch};#eta-bin;observable", kTProfile3D, {{ax}, {etaBinAxis}, {kNFlucObs, -0.5, kNFlucObs - 0.5}}); + h.obs = r.add(n("Prof_Obs_Nch_etabin_obs").c_str(), ";N_{ch};#eta-bin;observable", kTProfile3D, {{ax}, {etaBinAxis}, {kNFlucObs, -0.5, static_cast(kNFlucObs) - 0.5}}); labelAxis(h.obs->GetZaxis(), {"N", "[p_{T}]", "c_{2}", "c_{3}", "C_{2}^{sub} (mirror)", "C_{3}^{sub} (mirror)"}); h.c2Sub2D = r.add(n("Prof_C2Sub2D_Nch_etaA_etaC").c_str(), ";N_{ch};#eta_{A};#eta_{C}", kTProfile3D, {{ax}, {etaAxis}, {etaAxis}}); h.c3Sub2D = r.add(n("Prof_C3Sub2D_Nch_etaA_etaC").c_str(), ";N_{ch};#eta_{A} (pair);#eta_{C} (single)", kTProfile3D, {{ax}, {etaAxis}, {etaAxis}}); @@ -2083,7 +2083,7 @@ struct RadialFlowDecorr { return nullptr; } const std::string hName = dir + "hFlatMaps"; - auto* hRaw = dynamic_cast(lst->FindObject(hName.c_str())); + const auto* hRaw = dynamic_cast(lst->FindObject(hName.c_str())); if (!hRaw) { LOGF(error, "Flattening map '%s' not found in %s", hName.c_str(), path.c_str()); return nullptr; From 066d539c311fc20f3b4a93ef28c84bcf310182be Mon Sep 17 00:00:00 2001 From: Somadutta Bhatta Date: Sat, 24 Oct 2026 10:17:42 +0200 Subject: [PATCH 3/3] [PWGCF] radialFlowDecorr: TH3 efficiency maps in centrality, no mcMask, PWGCF-standard track/event cuts, clang-tidy fixes --- .../Tasks/radialFlowDecorr.cxx | 544 ++++++++---------- 1 file changed, 240 insertions(+), 304 deletions(-) diff --git a/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx b/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx index 15e8381d468..de6ea4b8ca7 100644 --- a/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx +++ b/PWGCF/EbyEFluctuations/Tasks/radialFlowDecorr.cxx @@ -50,6 +50,8 @@ #include #include +#include + #include #include #include @@ -102,15 +104,9 @@ struct RadialFlowDecorr { static constexpr float KVzMax = 40.f; static constexpr int KNbinsEtaFine = 20; static constexpr float KEtaFineMax = 1.f; - // N_ch bin width (tracks) of the efficiency maps - static constexpr int KEffNchWidthPbPb = 100; - static constexpr int KEffNchWidth = 10; // occupancy QA axis static constexpr int KNbinsOccQA = 150; static constexpr float KOccQAMax = 15000.f; - // mcMask bits of McTrackLabels (bit ON = mismatch) - static constexpr int KMcMaskItsTpcMismatch = 13; - static constexpr int KMcMaskFake = 15; // Forward detectors: FT0 channels 0-95 are A side, 96-207 C side; FV0-A has 48 cells. static constexpr int KNChFT0A = 96; @@ -165,22 +161,23 @@ struct RadialFlowDecorr { kObsC3Sub, // c2_A _C at A (AAB) and c2_C _A at C (ABB), mirror pair kNFlucObs }; - enum EEffCat { // hEffMaps + enum EEffCat { // efficiency maps h3_* (centrality, pT, eta) kEffAllPrim = 0, // charged primaries of the MC collision kEffRecoMatchedPrim, // reco tracks of primaries of the same MC collision, at the MC pT and eta kEffAllReco, kEffRecoSec, // secondaries of the same MC collision kEffRecoOtherColl, // particles of another MC collision (pile-up, wrong association) - kEffRecoFake, // no label, fake flag or ITS-TPC mismatch (mcMask) + kEffRecoFake, // reco tracks without MC label kNEffCat }; + inline static const std::array effMapName = { + "h3_AllPrimary", "h3_RecoMatchedToPrimary", "h3_AllReco", "h3_RecoUnMatchedToPrimary_Secondary", + "h3_RecoUnMatchedToPrimary_OtherCollision", "h3_RecoUnMatchedToPrimary_Fake"}; enum ETrackOrigin { // MCQA/Hist3D_Nch_pT_origin kOriginPrimary = 0, kOriginSecondary, kOriginOtherColl, kOriginNoLabel, - kOriginFakeFlag, - kOriginItsTpcMismatch, kNOrigin }; enum EVtxQA { // MCQA/Hist2D_dVz_type @@ -207,6 +204,9 @@ struct RadialFlowDecorr { kEvSel8, kEvVtxZ, kEvGoodZvtx, + kEvTFBorder, + kEvITSROFBorder, + kEvVertexITSTPC, kEvSameBunch, kEvTimeRange, kEvITSLayer, @@ -227,6 +227,7 @@ struct RadialFlowDecorr { kTrkGlobal, kTrkITS, kTrkTPC, + kTrkTPCChi2, kTrkDCAz, kTrkVariationCut, kTrkWeight, // valid efficiency (and flattening) weight: used @@ -272,7 +273,7 @@ struct RadialFlowDecorr { // Systematic variations, all evaluated in every job. Base is the nominal measurement. // The cut values are the K* constants below; only the occupancy limits are configurable. // Cent : FT0M instead of the nominal estimator (cfgCentralityChoice, FT0C) - // Chi2TPC : additionally chi2/TPC cluster < 3 + // Chi2TPC : chi2 per TPC cluster < 2 instead of cfgCutTpcChi2NCl // DCAxy : |DCAxy| < 5/7 x (globalTrack pT-dependent DCAxy cut), i.e. 5 instead of 7 sigma // DCAz : |DCAz| < 1 cm instead of cfgCutTracKDcaMaxZ // DCAxy and DCAz are null checks: with the nominal isPVContributor requirement even @@ -281,8 +282,8 @@ struct RadialFlowDecorr { // EffpTDep : efficiency scaled by -10% at cfgPtMin, linearly to 0 at 10 GeV/c // Flat : no acceptance (flattening) weights // ITSLayer : ITS good-layer requirement one step looser (All -> 0123, 0123 -> Layer3) - // NITS : itsNCls > 3 instead of cfgITScluster - // NTPC : tpcNClsFound >= 90 instead of cfgTPCcluster + // NITS : itsNCls >= 4 instead of cfgITScluster + // NTPC : tpcNClsFound >= 80 instead of cfgTPCcluster // Pileup : additionally kNoCollInRofStandard // Vz : |z_vtx| < 8 cm instead of cfgVtxZCut // NchBin2 : Base selection and inputs, observables (and its own mean pass) in N_ch bins @@ -314,18 +315,18 @@ struct RadialFlowDecorr { "Base", "systCent", "systChi2TPC", "systDCAxy", "systDCAz", "systEff", "systEffpTDep", "systFlat", "systITSLayer", "systNITS", "systNTPC", "systPileup", "systVz", "systNchBin2", "systOccupancy", "systPileupNSigma"}; - static constexpr uint32_t KAllSystMask = (1u << kNSyst) - 1u; + static constexpr uint32_t KAllSystMask = (1 << kNSyst) - 1; static constexpr int KNSystPerRegistry = 4; // variations 1..15 are spread over 4 registries // Cut of each variation (fixed; the occupancy limits are configurables, cfgSystOccMax*). // Pileup adds kNoCollInRofStandard; ITSLayer loosens the nominal ITS good-layer requirement by one step. static constexpr int KSystCentralityChoice = 2; // Cent: FT0M - static constexpr float KSystChi2TPC = 3.f; // Chi2TPC: chi2 per TPC cluster < 3 (nominal 4) + static constexpr float KSystChi2TPC = 2.f; // Chi2TPC: chi2 per TPC cluster < 2 (nominal 2.5) static constexpr float KSystDcaXyScale = 5.f / 7.f; // DCAxy: 5 sigma (nominal 7 sigma) static constexpr float KSystDcaMaxZ = 1.f; // DCAz: |DCAz| < 1 cm (nominal 2 cm) static constexpr float KEffVarPercent = -10.f; // Eff, EffpTDep: efficiency scaled by -10% static constexpr float KEffVarPtZero = 10.f; // EffpTDep: variation falls linearly to 0 at 10 GeV/c - static constexpr int KSystITScluster = 3; // NITS: itsNCls > 3 (nominal > 5) - static constexpr int KSystTPCcluster = 90; // NTPC: tpcNClsFound >= 90 (nominal 70) + static constexpr int KSystITScluster = 4; // NITS: itsNCls >= 4 (nominal 5) + static constexpr int KSystTPCcluster = 80; // NTPC: tpcNClsFound >= 80 (nominal 70) static constexpr float KSystVtxZCut = 8.f; // Vz: |z_vtx| < 8 cm (nominal 10 cm) static constexpr int KSystNchBinWidthPbPb = 2; // NchBin2: N_ch bin width, PbPb (nominal 10) static constexpr int KSystNchBinWidthOO = 2; // NchBin2: OO and NeNe (nominal 5) @@ -365,16 +366,15 @@ struct RadialFlowDecorr { // Variations filled by each pass (set in init()): maskEff in the MC efficiency pass, // maskFlat in the flattening passes, maskAll in the mean and fluctuation passes. - uint32_t maskAll = 1u; - uint32_t maskEff = 1u; - uint32_t maskFlat = 1u; + uint32_t maskAll = 1; + uint32_t maskEff = 1; + uint32_t maskFlat = 1; int itsLayerNom = kITSLayersAll; int itsLayerVar = kITSLayer0123; - int occupancyMax = 0; // Occupancy variation cut of this system - float pupNSigma = 5.f; // width of the n-sigma pile-up bands of this system - int nchWidth = 10; // N_ch bin width of the observables of this system - int nchWidthNchBin2 = 2; // N_ch bin width of NchBin2 for this system - std::vector effOccEdges; // occupancy bins of the efficiency maps + int occupancyMax = 0; // Occupancy variation cut of this system + float pupNSigma = 5.f; // width of the n-sigma pile-up bands of this system + int nchWidth = 10; // N_ch bin width of the observables of this system + int nchWidthNchBin2 = 2; // N_ch bin width of NchBin2 for this system // Forward bins: 0-2 detector totals (FT0A, FT0C, FV0A), then the eta sub-bins of each detector. int nFwd = kNFwdDet; @@ -389,6 +389,9 @@ struct RadialFlowDecorr { Configurable cfgCentralityChoice{"cfgCentralityChoice", 1, "Which centrality estimator? 1-->FT0C, 2-->FT0M, 3-->FDDM, 4-->FV0A"}; Configurable cfgCentMax{"cfgCentMax", 99.f, "Events with centrality below this value (%) are kept"}; Configurable cfgIsGoodZvtxFT0VsPV{"cfgIsGoodZvtxFT0VsPV", true, "Good Vertexing cut"}; + Configurable cfgEvSelNoTimeFrameBorder{"cfgEvSelNoTimeFrameBorder", true, "kNoTimeFrameBorder"}; + Configurable cfgEvSelNoITSROFrameBorder{"cfgEvSelNoITSROFrameBorder", true, "kNoITSROFrameBorder"}; + Configurable cfgEvSelIsVertexITSTPC{"cfgEvSelIsVertexITSTPC", true, "kIsVertexITSTPC"}; Configurable cfgEvSelNoSameBunchPileup{"cfgEvSelNoSameBunchPileup", true, "Pileup removal: kNoSameBunchPileup"}; Configurable cfgEvSelNoCollInTimeRangeStd{"cfgEvSelNoCollInTimeRangeStd", true, "Pileup removal: kNoCollInTimeRangeStandard"}; Configurable cfgITSLayerCut{"cfgITSLayerCut", -1, "ITS good-layer cut: -1 auto (pp 0123, else All), 0 none, 1 Layer3, 2 Layer0123, 3 LayersAll"}; @@ -419,8 +422,9 @@ struct RadialFlowDecorr { Configurable cfgCutEta{"cfgCutEta", 0.8f, "absolute Eta cut"}; Configurable cfgEtaBinWidth{"cfgEtaBinWidth", 0.1f, "DATA only: width of each narrow observable eta bin (eta units); cfgCutEta must be an integer multiple so eta=0 is a bin edge. MC is pinned to 0.2."}; Configurable cfgCutTracKDcaMaxZ{"cfgCutTracKDcaMaxZ", 2.0f, "Maximum |DCAz| (cm)"}; - Configurable cfgITScluster{"cfgITScluster", 5, "Nominal: itsNCls > value"}; + Configurable cfgITScluster{"cfgITScluster", 5, "Nominal: itsNCls >= value"}; Configurable cfgTPCcluster{"cfgTPCcluster", 70, "Nominal: tpcNClsFound >= value"}; + Configurable cfgCutTpcChi2NCl{"cfgCutTpcChi2NCl", 2.5f, "Nominal: tpcChi2NCl < value"}; Configurable cfgTPCnCrossedRows{"cfgTPCnCrossedRows", 70, "Minimum Number of TPC crossed-rows"}; // Systematic variations (names stay flat in the JSON) @@ -434,12 +438,6 @@ struct RadialFlowDecorr { Configurable cfgSystOccMaxPP{"cfgSystOccMaxPP", 500, "Occupancy: maximum trackOccupancyInTimeRange, pp"}; } cfgSyst; - // Corrections - struct : ConfigurableGroup { - Configurable> cfgEffOccupancyEdges{"cfgEffOccupancyEdges", {0.f, 500.f, 1000.f, 2000.f, 4000.f, 100000.f}, "trackOccupancyInTimeRange bin edges of the efficiency maps (data and MC); occupancy < 0 goes to the first bin"}; - Configurable cfgMcItsTpcMismatchAsFake{"cfgMcItsTpcMismatchAsFake", true, "MC: tracks with the ITS-TPC mismatch bit of mcMask count as fakes"}; - } cfgCorr; - // N_ch axes. Largest N_ch (global tracks) seen in the data: 3542 (PbPb), 328 (OO), 72 (pp) struct : ConfigurableGroup { Configurable cfgNchPbMax{"cfgNchPbMax", 4000, "Max N_ch (global tracks) and N_PV of the axes, PbPb"}; @@ -473,12 +471,11 @@ struct RadialFlowDecorr { // --- axes -------------------------------------------------------------------- AxisSpec centAxis1Per{100, 0.0, 100.0, "Centrality (%)"}; - AxisSpec centAxisBoot{20, 0.0, 100.0, "Centrality (%)"}; // bootstrap replicas: final 5% classes - AxisSpec multQAAxis{1, 0., 1., "Nch", "Nch"}; // QA (N_PV correlations) - AxisSpec nchAxisObs{1, 0., 1., "N_{ch}"}; // every observable (bins of nchWidth) - AxisSpec nchAxisNchBin2{1, 0., 1., "N_{ch}"}; // NchBin2 (bins of nchWidthNchBin2) - AxisSpec nchAxisEff{1, 0., 1., "N_{ch}"}; // efficiency maps - AxisSpec occAxisEff{1, 0., 1., "occupancy"}; // efficiency maps + AxisSpec centAxisBoot{20, 0.0, 100.0, "Centrality (%)"}; // bootstrap replicas: final 5% classes + AxisSpec multQAAxis{1, 0., 1., "Nch", "Nch"}; // QA (N_PV correlations) + AxisSpec nchAxisObs{1, 0., 1., "N_{ch}"}; // every observable (bins of nchWidth) + AxisSpec nchAxisNchBin2{1, 0., 1., "N_{ch}"}; // NchBin2 (bins of nchWidthNchBin2) + AxisSpec centAxisEff{{0., 5., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100.}, "Centrality (%)"}; // efficiency maps AxisSpec vzAxis{5, -12.5, 12.5, "Vz"}; AxisSpec chgAxis{3, -1.5, 1.5}; @@ -543,13 +540,10 @@ struct RadialFlowDecorr { // --- per-variation inputs ---------------------------------------------------- struct SystInputs { - // efficiency and fake rate versus (N_ch, pT, eta), one map per occupancy bin, and integrated - // over occupancy (used where the cell of the occupancy bin is empty or not a valid efficiency) - std::vector hEff; - std::vector hFake; - TH3* hEffAllOcc = nullptr; - TH3* hFakeAllOcc = nullptr; - // integrated over occupancy and N_ch (one N_ch bin): last resort for sparse cells + // efficiency and fake rate versus (centrality, pT, eta), and integrated over centrality (one + // centrality bin: used where the cell is empty or not a valid efficiency) + TH3* hEff = nullptr; + TH3* hFake = nullptr; TH3* hEffPtEta = nullptr; TH3* hFakePtEta = nullptr; THnSparseF* hFlat = nullptr; @@ -574,7 +568,7 @@ struct RadialFlowDecorr { // --- per-variation histograms, filled through pointers (HIST() needs names known at compile time) --- struct EffHists { - std::shared_ptr maps; // (N_ch, pT, eta, occupancy, EEffCat) + std::array, kNEffCat> maps{}; // (centrality, pT, eta), one per EEffCat }; struct FlatHists { std::shared_ptr maps; // (vz, sign, pT, eta, phi, EFlatType) @@ -669,7 +663,6 @@ struct RadialFlowDecorr { uint32_t mask = 0; std::array cent{}; int occupancy = -1; - int occBin = 0; // occupancy bin of the efficiency maps }; // Per-event observables of one (variation, level). @@ -680,8 +673,6 @@ struct RadialFlowDecorr { std::array fwdValid{}; }; - static bool hasBit(uint32_t mask, int v) { return (mask >> v) & 1u; } - // =========================================================================== // Selection helpers // =========================================================================== @@ -735,15 +726,6 @@ struct RadialFlowDecorr { return true; } - // Occupancy bin of the efficiency maps; occupancy < 0 (time-frame border) goes to the first bin. - int effOccBin(int occupancy) const - { - const int n = static_cast(effOccEdges.size()) - 1; - const int i = static_cast(std::upper_bound(effOccEdges.begin(), effOccEdges.end(), static_cast(occupancy)) - effOccEdges.begin()) - 1; - return std::clamp(i, 0, n - 1); - } - double effOccValue(int occBin) const { return 0.5 * (effOccEdges[occBin] + effOccEdges[occBin + 1]); } - // One event at cut-flow stage s of variation v: hEvtCountSyst and that variation's hEvtCount. void countEvent(int s, int v) { @@ -762,7 +744,7 @@ struct RadialFlowDecorr { EventSelection ev; auto countAll = [&](int s) { for (int v = 0; v < kNSyst && count; ++v) { - if (hasBit(active, v)) { + if (TESTBIT(active, v)) { countEvent(s, v); } } @@ -780,16 +762,27 @@ struct RadialFlowDecorr { return ev; } countAll(kEvGoodZvtx); + if (cfgEvSel.cfgEvSelNoTimeFrameBorder && !col.selection_bit(o2::aod::evsel::kNoTimeFrameBorder)) { + return ev; + } + countAll(kEvTFBorder); + if (cfgEvSel.cfgEvSelNoITSROFrameBorder && !col.selection_bit(o2::aod::evsel::kNoITSROFrameBorder)) { + return ev; + } + countAll(kEvITSROFBorder); + if (cfgEvSel.cfgEvSelIsVertexITSTPC && !col.selection_bit(o2::aod::evsel::kIsVertexITSTPC)) { + return ev; + } + countAll(kEvVertexITSTPC); const float centBase = getCentrality(col, cfgEvSel.cfgCentralityChoice); const float multPV = col.multNTracksPV(); ev.occupancy = col.trackOccupancyInTimeRange(); - ev.occBin = effOccBin(ev.occupancy); const bool passAddPileup = isPassAddPileup(multPV, nCh, centBase, pupNSigma); const bool passAddPileupTight = isPassAddPileup(multPV, nCh, centBase, pupNSigma - KSystPupnSigStep); for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(active, v)) { + if (!TESTBIT(active, v)) { continue; } auto countOne = [&](int s) { @@ -822,7 +815,7 @@ struct RadialFlowDecorr { if (v == kSystVz && !(std::abs(col.posZ()) < KSystVtxZCut)) { continue; } - if (v == kSystOccupancy && !(ev.occupancy >= 0 && ev.occupancy < occupancyMax)) { + if (v == kSystOccupancy && (ev.occupancy < 0 || ev.occupancy >= occupancyMax)) { continue; } countOne(kEvVariationCut); @@ -830,7 +823,7 @@ struct RadialFlowDecorr { continue; } countOne(kEvCent); - ev.mask |= (1u << v); + SETBIT(ev.mask, v); if (col.selection_bit(o2::aod::evsel::kNoHighMultCollInPrevRof)) { countOne(kEvInfoPrevRof); } @@ -850,7 +843,7 @@ struct RadialFlowDecorr { const double inc = count ? 1. : 0.; auto countAll = [&](int s) { for (int v = 0; v < kNSyst; ++v) { - if (hasBit(evMask, v)) { + if (TESTBIT(evMask, v)) { trkCounts[v][s] += inc; } } @@ -880,10 +873,10 @@ struct RadialFlowDecorr { const float maxDcaXY = KSystDcaXyScale * (cfgSyst.cfgDcaXyP0 + cfgSyst.cfgDcaXyP1 / std::pow(trk.pt(), cfgSyst.cfgDcaXyP2.value)); uint32_t mask = 0; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(evMask, v)) { + if (!TESTBIT(evMask, v)) { continue; } - if (!(trk.itsNCls() > (v == kSystNITS ? KSystITScluster : cfgITScluster.value))) { + if (!(trk.itsNCls() >= (v == kSystNITS ? KSystITScluster : cfgITScluster.value))) { continue; } trkCounts[v][kTrkITS] += inc; @@ -891,19 +884,20 @@ struct RadialFlowDecorr { continue; } trkCounts[v][kTrkTPC] += inc; - if (!(std::abs(trk.dcaZ()) < (v == kSystDCAz ? KSystDcaMaxZ : cfgCutTracKDcaMaxZ.value))) { + if (!(trk.tpcChi2NCl() < (v == kSystChi2TPC ? KSystChi2TPC : cfgCutTpcChi2NCl.value))) { continue; } - trkCounts[v][kTrkDCAz] += inc; - // the cut of the track-selection variations - if (v == kSystChi2TPC && !(trk.tpcChi2NCl() < KSystChi2TPC)) { + trkCounts[v][kTrkTPCChi2] += inc; + if (!(std::abs(trk.dcaZ()) < (v == kSystDCAz ? KSystDcaMaxZ : cfgCutTracKDcaMaxZ.value))) { continue; } + trkCounts[v][kTrkDCAz] += inc; + // the cut of the DCAxy variation if (v == kSystDCAxy && !(std::abs(trk.dcaXY()) < maxDcaXY)) { continue; } trkCounts[v][kTrkVariationCut] += inc; - mask |= (1u << v); + SETBIT(mask, v); } return mask; } @@ -948,12 +942,6 @@ struct RadialFlowDecorr { if (!track.has_mcParticle()) { return kOriginNoLabel; } - if (track.mcMask() & (1 << KMcMaskFake)) { - return kOriginFakeFlag; - } - if (cfgCorr.cfgMcItsTpcMismatchAsFake.value && (track.mcMask() & (1 << KMcMaskItsTpcMismatch))) { - return kOriginItsTpcMismatch; - } const auto& particle = track.mcParticle(); if (particle.mcCollisionId() != coll.mcCollisionId()) { return kOriginOtherColl; @@ -994,14 +982,14 @@ struct RadialFlowDecorr { // Corrections // =========================================================================== // Inclusive efficiency/fake lookup (no species dependence). - static float getEfficiency(const TH3* h, float mult, float pt, float eta, bool isEff) + static float getEfficiency(const TH3* h, float cent, float pt, float eta, bool isEff) { const float invalid = std::numeric_limits::quiet_NaN(); - if (!h || !std::isfinite(mult) || !std::isfinite(pt) || !std::isfinite(eta)) { + if (!h || !std::isfinite(cent) || !std::isfinite(pt) || !std::isfinite(eta)) { return invalid; } - int ibx = h->GetXaxis()->FindFixBin(mult); - // Reject Nch underflow, clamp overflow. + int ibx = h->GetXaxis()->FindFixBin(cent); + // Reject centrality underflow, clamp overflow. if (ibx < 1) { return invalid; } @@ -1014,8 +1002,9 @@ struct RadialFlowDecorr { } const double val = h->GetBinContent(ibx, iby, ibz); const double err = h->GetBinError(ibx, iby, ibz); - // A bin without a positive error is not a valid measurement. - if (!std::isfinite(val) || !std::isfinite(err) || err <= 0.0) { + // A bin without a positive error is not a valid measurement, except a fake rate of 0 + // (no fake track in the bin), which has no error. + if (!std::isfinite(val) || !std::isfinite(err) || (err <= 0.0 && (isEff || val != 0.0))) { return invalid; } // Physical-domain checks. @@ -1026,7 +1015,7 @@ struct RadialFlowDecorr { } // Nominal efficiency and fake rate of the map of variation v (no variation factor). - bool getValidEffFake(int v, int occBin, float mult, float pt, float eta, float& eff, float& fake) + bool getValidEffFake(int v, float cent, float pt, float eta, float& eff, float& fake) { if (!cfgEff) { eff = 1.f; @@ -1034,24 +1023,16 @@ struct RadialFlowDecorr { return true; } const auto& in = inputs[v]; - if (occBin >= static_cast(in.hEff.size())) { - return false; - } auto valid = [&]() { return std::isfinite(eff) && std::isfinite(fake) && eff > KFloatEpsilon && eff <= 1.f && fake >= 0.f && fake < 1.f; }; - eff = getEfficiency(in.hEff[occBin], mult, pt, eta, true); - fake = getEfficiency(in.hFake[occBin], mult, pt, eta, false); + eff = getEfficiency(in.hEff, cent, pt, eta, true); + fake = getEfficiency(in.hFake, cent, pt, eta, false); if (valid()) { return true; } - // empty or statistically invalid cell of this occupancy bin: the occupancy-integrated map, - // then the map integrated over occupancy and N_ch - eff = getEfficiency(in.hEffAllOcc, mult, pt, eta, true); - fake = getEfficiency(in.hFakeAllOcc, mult, pt, eta, false); - if (valid()) { - return true; - } - eff = getEfficiency(in.hEffPtEta, mult, pt, eta, true); - fake = getEfficiency(in.hFakePtEta, mult, pt, eta, false); + // empty or statistically invalid cell, or no valid centrality: the map integrated over centrality + const float centAll = in.hEffPtEta ? in.hEffPtEta->GetXaxis()->GetBinCenter(1) : 0.f; + eff = getEfficiency(in.hEffPtEta, centAll, pt, eta, true); + fake = getEfficiency(in.hFakePtEta, centAll, pt, eta, false); return valid(); } @@ -1085,7 +1066,7 @@ struct RadialFlowDecorr { // Weight w = w_flat (1 - fake) / eff of the track for every variation in mask; a map shared // by several variations is looked up once. Returns the variations with a usable weight and // the Base efficiency, fake rate and flattening weight for the QA profiles. - uint32_t getTrackWeights(uint32_t mask, float mult, int occBin, float vz, float sign, float pt, float eta, float phi, bool useFlat, + uint32_t getTrackWeights(uint32_t mask, float cent, float vz, float sign, float pt, float eta, float phi, bool useFlat, std::array& weights, float& effBase, float& fakeBase, float& flatBase) { std::array effDone{}; @@ -1096,12 +1077,12 @@ struct RadialFlowDecorr { fakeBase = std::numeric_limits::quiet_NaN(); flatBase = 1.f; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v)) { + if (!TESTBIT(mask, v)) { continue; } const int es = effSource(v); if (effDone[es] == 0) { - effDone[es] = getValidEffFake(es, occBin, mult, pt, eta, effS[es], fakeS[es]) ? 1 : -1; + effDone[es] = getValidEffFake(es, cent, pt, eta, effS[es], fakeS[es]) ? 1 : -1; } if (effDone[es] < 0) { continue; @@ -1124,7 +1105,7 @@ struct RadialFlowDecorr { continue; } weights[v] = weight; - weightMask |= (1u << v); + SETBIT(weightMask, v); trkCounts[v][kTrkWeight] += 1.; if (v == kSystBase) { effBase = eff; @@ -1274,8 +1255,8 @@ struct RadialFlowDecorr { fwdCalib.gainFT0.fill(1.f); fwdCalib.gainFV0.fill(1.f); if (applyGain && cfgFwdOpt.cfgFwdGainEq) { - auto* gFT0 = getOptional>(cfgFwdOpt.cfgGainEqPath.value + "/FT0", ts); - auto* gFV0 = getOptional>(cfgFwdOpt.cfgGainEqPath.value + "/FV0", ts); + auto* gFT0 = ccdb->getForTimeStamp>(cfgFwdOpt.cfgGainEqPath.value + "/FT0", ts); + auto* gFV0 = ccdb->getForTimeStamp>(cfgFwdOpt.cfgGainEqPath.value + "/FV0", ts); if (gFT0 && gFT0->size() >= static_cast(KNChFT0)) { std::copy_n(gFT0->begin(), KNChFT0, fwdCalib.gainFT0.begin()); } else { @@ -1615,14 +1596,15 @@ struct RadialFlowDecorr { static std::vector evtCutLabels() { - return {"all", "sel8", "|z_{vtx}|", "kIsGoodZvtxFT0vsPV", "kNoSameBunchPileup", "kNoCollInTimeRangeStandard", + return {"all", "sel8", "|z_{vtx}|", "kIsGoodZvtxFT0vsPV", "kNoTimeFrameBorder", "kNoITSROFrameBorder", "kIsVertexITSTPC", + "kNoSameBunchPileup", "kNoCollInTimeRangeStandard", "ITS good layers", "n#sigma/linear pile-up", "variation cut", "cent < cfgCentMax (selected)", "selected: kNoHighMultCollInPrevRof", "selected: occupancy < 0"}; } static std::vector trkCutLabels() { return {"all", "sign#neq0", "has_collision", "isPVContributor", "p_{T}, #eta", "isGlobalTrack, crossed rows", - "itsNCls", "tpcNClsFound", "DCAz", "variation cut (selected)", "valid weight (used)"}; + "itsNCls", "tpcNClsFound", "tpcChi2NCl", "DCAz", "variation cut (selected)", "valid weight (used)"}; } // hEvtCount and hTrkCount of each variation of this pass (Base at the top level, the others @@ -1630,7 +1612,7 @@ struct RadialFlowDecorr { void declareCounts(uint32_t mask) { for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v)) { + if (!TESTBIT(mask, v)) { continue; } auto& r = reg(v); @@ -1700,7 +1682,7 @@ struct RadialFlowDecorr { void declareNchCent(uint32_t mask) { for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v) || hists.nchCent[v]) { + if (!TESTBIT(mask, v) || hists.nchCent[v]) { continue; } hists.nchCent[v] = reg(v).add((systPrefix(v) + "Hist2D_globalTracks_cent").c_str(), "cent (%);N_{ch} (global tracks)", kTH2F, {{centAxis1Per}, {nchAxisOf(v)}}); @@ -1721,12 +1703,13 @@ struct RadialFlowDecorr { void declareEff(uint32_t mask) { for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v)) { + if (!TESTBIT(mask, v)) { continue; } - hists.eff[v].maps = reg(v).add((systPrefix(v) + "hEffMaps").c_str(), ";N_{ch};p_{T};#eta;occupancy;category", kTHnF, - {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}, {occAxisEff}, {kNEffCat, -0.5, static_cast(kNEffCat) - 0.5}}, true); - labelAxis(hists.eff[v].maps->GetAxis(4), {"AllPrimary", "RecoMatchedToPrimary", "AllReco", "RecoSecondary", "RecoOtherCollision", "RecoFake"}); + for (int c = 0; c < kNEffCat; ++c) { + hists.eff[v].maps[c] = reg(v).add((systPrefix(v) + effMapName[c]).c_str(), ";centrality (%);p_{T} (GeV/c);#eta", kTH3F, + {{centAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {KNbinsEtaFine, -KEtaFineMax, KEtaFineMax}}); + } } } @@ -1737,8 +1720,8 @@ struct RadialFlowDecorr { histos.add("etaTruthReco", ";#eta^{MC};#eta^{reco}", kTH2F, {{KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}, {KNbinsEtaRes, -KEtaFineMax, KEtaFineMax}}); histos.add("TruthTracKVz", ";Vz^{MC};Vz^{Reco}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {KNbinsVz, KVzMin, KVzMax}}); histos.add("vzResolution", ";Vz^{MC};(Vz^{reco}-Vz^{MC})/Vz^{MC}", kTH2F, {{KNbinsVz, KVzMin, KVzMax}, {100, -0.1, 0.1}}); - hists.mcTrackOrigin = histos.add("MCQA/Hist3D_Nch_pT_origin", ";N_{ch};p_{T} (GeV/c);origin", kTH3F, {{nchAxisEff}, {KNbinsPtRes, KPtMin, KPtMax}, {kNOrigin, -0.5, static_cast(kNOrigin) - 0.5}}); - labelAxis(hists.mcTrackOrigin->GetZaxis(), {"primary", "secondary", "other MC collision", "no label", "fake flag", "ITS-TPC mismatch"}); + hists.mcTrackOrigin = histos.add("MCQA/Hist3D_Nch_pT_origin", ";N_{ch};p_{T} (GeV/c);origin", kTH3F, {{nchAxisObs}, {KNbinsPtRes, KPtMin, KPtMax}, {kNOrigin, -0.5, static_cast(kNOrigin) - 0.5}}); + labelAxis(hists.mcTrackOrigin->GetZaxis(), {"primary", "secondary", "other MC collision", "no label"}); } // vertex QA of all MC passes @@ -1756,7 +1739,7 @@ struct RadialFlowDecorr { void declareFlat(uint32_t mask, const std::string& dir) { for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v) || hists.flat[v].maps) { + if (!TESTBIT(mask, v) || hists.flat[v].maps) { continue; } hists.flat[v].maps = reg(v).add((systPrefix(v) + dir + "hFlatMaps").c_str(), ";vz;sign;pt;eta;phi;weight", kTHnSparseF, @@ -1771,7 +1754,7 @@ struct RadialFlowDecorr { static const std::array mcDir = {"MCGen/", "MCReco/", "MCRecoEffCorr/"}; const int nLev = isMC ? kNLevels : 1; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v)) { + if (!TESTBIT(mask, v)) { continue; } auto& r = reg(v); @@ -1819,7 +1802,7 @@ struct RadialFlowDecorr { static const std::array mcDir = {"MCGen/", "MCReco/", "MCRecoEffCorr/"}; const int nLev = isMC ? kNLevels : 1; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(mask, v)) { + if (!TESTBIT(mask, v)) { continue; } for (int l = 0; l < nLev; ++l) { @@ -1854,7 +1837,7 @@ struct RadialFlowDecorr { void declareMCMeanHists() { declareNchCent(maskAll); - declareFlat(1u, "MCReco/"); // Base map with all weights, as QA + declareFlat(1, "MCReco/"); // Base map with all weights, as QA if (cfgFwdOpt.cfgFwd) { declareFwdQA(); } @@ -1866,7 +1849,7 @@ struct RadialFlowDecorr { void declareMCFlucHists() { declareNchCent(maskAll); - declareFlat(1u, "MCReco/"); + declareFlat(1, "MCReco/"); if (cfgFwdOpt.cfgFwd) { declareFwdQA(); } @@ -1882,7 +1865,7 @@ struct RadialFlowDecorr { void declareDataMeanHists() { declareNchCent(maskAll); - declareFlat(1u, ""); + declareFlat(1, ""); if (cfgFwdOpt.cfgFwd) { declareFwdQA(); } @@ -1894,7 +1877,7 @@ struct RadialFlowDecorr { void declareDataFlucHists() { declareNchCent(maskAll); - declareFlat(1u, ""); + declareFlat(1, ""); if (cfgFwdOpt.cfgFwd) { declareFwdQA(); } @@ -1994,91 +1977,54 @@ struct RadialFlowDecorr { return; } LOGF(error, "Variation %s disabled: %s", systName[v].c_str(), why.c_str()); - const uint32_t bit = ~(1u << v); - maskAll &= bit; - maskEff &= bit; - maskFlat &= bit; + CLRBIT(maskAll, v); + CLRBIT(maskEff, v); + CLRBIT(maskFlat, v); } - // CCDB object that may be missing: nullptr instead of the manager's fatal error. Used for the - // inputs of a variation (missing: the variation is switched off, Base stops in dropSyst) and the - // gain equalisation (missing: unit gains); everything else must exist. - template - T* getOptional(const std::string& path, int64_t ts) - { - ccdb->setFatalWhenNull(false); - T* obj = ccdb->getForTimeStamp(path, ts); - ccdb->setFatalWhenNull(true); - return obj; - } - - // Efficiency and fake-rate maps of variation s, one per occupancy bin: - // eff = matched primaries / primaries, fake = (secondaries + other collision + fakes) / all reco. + // Efficiency and fake-rate maps of variation s versus (centrality, pT, eta): + // eff = matched primaries / primaries, fake = (secondaries + other collision + no label) / all reco. bool loadEffMaps(int s, const std::string& path, int64_t now) { - auto* lst = getOptional(path, now); + auto* lst = ccdb->getForTimeStamp(path, now); if (!lst) { return false; } - auto* hn = dynamic_cast(lst->FindObject("hEffMaps")); - if (!hn || hn->GetNdimensions() != 5) { - LOGF(error, "Missing efficiency/fake map hEffMaps (5D) in %s", path.c_str()); - return false; - } - const int nOcc = hn->GetAxis(3)->GetNbins(); - if (nOcc != static_cast(effOccEdges.size()) - 1) { - LOGF(error, "%s: %d occupancy bins in the efficiency map, %d in cfgEffOccupancyEdges", path.c_str(), nOcc, static_cast(effOccEdges.size()) - 1); - return false; - } - // occ < 0: all occupancy bins - auto proj = [&](int occ, int cat) { - hn->GetAxis(3)->SetRange(occ < 0 ? 0 : occ + 1, occ < 0 ? 0 : occ + 1); - hn->GetAxis(4)->SetRange(cat + 1, cat + 1); - TH3* h = hn->Projection(0, 1, 2, "E"); - h->SetDirectory(nullptr); - return h; - }; - for (int occ = -1; occ < nOcc; ++occ) { - TH3* eff = proj(occ, kEffRecoMatchedPrim); - std::unique_ptr allPrim(proj(occ, kEffAllPrim)); - TH3* fake = proj(occ, kEffRecoSec); - std::unique_ptr other(proj(occ, kEffRecoOtherColl)); - std::unique_ptr fakes(proj(occ, kEffRecoFake)); - std::unique_ptr allReco(proj(occ, kEffAllReco)); - fake->Add(other.get()); - fake->Add(fakes.get()); - if (occ < 0) { - const int nNch = eff->GetNbinsX(); - auto ratio = [&](TH3* num, TH3* den, const char* name) { - auto* r = dynamic_cast(num->RebinX(nNch, name)); - std::unique_ptr d(dynamic_cast(den->RebinX(nNch, Form("%s_den", name)))); - r->SetDirectory(nullptr); - r->Divide(d.get()); - return r; - }; - inputs[s].hEffPtEta = ratio(eff, allPrim.get(), Form("hEff_%d_ptEta", s)); - inputs[s].hFakePtEta = ratio(fake, allReco.get(), Form("hFake_%d_ptEta", s)); - } - eff->Divide(allPrim.get()); - fake->Divide(allReco.get()); - eff->SetName(Form("hEff_%d_occ%d", s, occ)); - fake->SetName(Form("hFake_%d_occ%d", s, occ)); - if (occ < 0) { - inputs[s].hEffAllOcc = eff; - inputs[s].hFakeAllOcc = fake; - } else { - inputs[s].hEff.push_back(eff); - inputs[s].hFake.push_back(fake); + std::array h{}; + for (int c = 0; c < kNEffCat; ++c) { + h[c] = dynamic_cast(lst->FindObject(effMapName[c].c_str())); + if (!h[c]) { + LOGF(error, "Missing efficiency map %s in %s", effMapName[c].c_str(), path.c_str()); + return false; } } - hn->GetAxis(3)->SetRange(0, 0); - hn->GetAxis(4)->SetRange(0, 0); + auto* eff = dynamic_cast(h[kEffRecoMatchedPrim]->Clone(Form("hEff_%d", s))); + auto* fake = dynamic_cast(h[kEffRecoSec]->Clone(Form("hFake_%d", s))); + eff->SetDirectory(nullptr); + fake->SetDirectory(nullptr); + fake->Add(h[kEffRecoOtherColl]); + fake->Add(h[kEffRecoFake]); + // integrated over centrality (one centrality bin), for empty or invalid cells + const int nCent = eff->GetNbinsX(); + auto ratio = [&](TH3* num, TH3* den, const char* name) { + auto* r = num->RebinX(nCent, name); + std::unique_ptr d(den->RebinX(nCent, Form("%s_den", name))); + r->SetDirectory(nullptr); + r->Divide(d.get()); + return r; + }; + inputs[s].hEffPtEta = ratio(eff, h[kEffAllPrim], Form("hEff_%d_ptEta", s)); + inputs[s].hFakePtEta = ratio(fake, h[kEffAllReco], Form("hFake_%d_ptEta", s)); + eff->Divide(h[kEffAllPrim]); + fake->Divide(h[kEffAllReco]); + inputs[s].hEff = eff; + inputs[s].hFake = fake; return true; } THnSparseF* loadFlatMap(int s, const std::string& path, const std::string& dir, int64_t now) { - auto* lst = getOptional(path, now); + auto* lst = ccdb->getForTimeStamp(path, now); if (!lst) { return nullptr; } @@ -2095,7 +2041,7 @@ struct RadialFlowDecorr { void loadMeans(int v, const std::string& path, bool isMC, int64_t now) { static const std::array mcDir = {"MCGen/", "MCReco/", "MCRecoEffCorr/"}; - auto* lst = getOptional(path, now); + auto* lst = ccdb->getForTimeStamp(path, now); if (!lst) { dropSyst(v, "no mean-pT list at " + path); return; @@ -2137,8 +2083,7 @@ struct RadialFlowDecorr { void init(InitContext&) { TH1::SetDefaultSumw2(true); - // Multiplicity axes: multQAAxis for the QA, nchAxisObs (nchAxisNchBin2) for the observables, - // nchAxisEff for the efficiency maps. + // Multiplicity axes: multQAAxis for the QA, nchAxisObs (nchAxisNchBin2) for the observables. const int nMax = (cfgSys == kPbPb) ? cfgNch.cfgNchPbMax.value : (cfgSys == kpp ? cfgNch.cfgNchPPMax.value : cfgNch.cfgNchOMax.value); if (cfgSys == kPbPb) { multQAAxis = {nMax / 2, KBinOffset, nMax + KBinOffset, "Nch", "PV-contributor track multiplicity"}; @@ -2159,15 +2104,6 @@ struct RadialFlowDecorr { nchWidthNchBin2 = (cfgSys == kPbPb) ? KSystNchBinWidthPbPb : (pp ? KSystNchBinWidthPP : KSystNchBinWidthOO); nchAxisObs = makeNchAxis(nchWidth); nchAxisNchBin2 = makeNchAxis(nchWidthNchBin2); - nchAxisEff = makeNchAxis((cfgSys == kPbPb) ? KEffNchWidthPbPb : KEffNchWidth); - } - { - const auto& e = cfgCorr.cfgEffOccupancyEdges.value; - if (e.size() < 2 || !std::is_sorted(e.begin(), e.end())) { - LOGF(fatal, "cfgEffOccupancyEdges must be >= 2 increasing values"); - } - effOccEdges.assign(e.begin(), e.end()); - occAxisEff = AxisSpec{effOccEdges, "trackOccupancyInTimeRange"}; } // ---- run type: MC is pinned to the reference binning, DATA is configurable ---- @@ -2255,18 +2191,18 @@ struct RadialFlowDecorr { } // ---- variations ---- - const uint32_t active = (static_cast(cfgSyst.cfgSystMask.value) | 1u) & KAllSystMask; + const uint32_t active = (static_cast(cfgSyst.cfgSystMask.value) | 1) & KAllSystMask; maskAll = maskEff = maskFlat = 0; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(active, v)) { + if (!TESTBIT(active, v)) { continue; } - maskAll |= (1u << v); + SETBIT(maskAll, v); if (effSource(v) == v) { - maskEff |= (1u << v); + SETBIT(maskEff, v); } if (flatSource(v) == v) { - maskFlat |= (1u << v); + SETBIT(maskFlat, v); } } const bool isPP = (cfgSys == kpp); @@ -2280,7 +2216,7 @@ struct RadialFlowDecorr { } occupancyMax = (cfgSys == kPbPb) ? cfgSyst.cfgSystOccMaxPbPb.value : (isPP ? cfgSyst.cfgSystOccMaxPP.value : cfgSyst.cfgSystOccMaxOO.value); pupNSigma = (cfgSys == kPbPb) ? cfgPileup.cfgPupnSigPbPb.value : (isPP ? cfgPileup.cfgPupnSigPP.value : cfgPileup.cfgPupnSigOO.value); - if (cfgEvSel.cfgCentralityChoice == KSystCentralityChoice && hasBit(maskAll, kSystCent)) { + if (cfgEvSel.cfgCentralityChoice == KSystCentralityChoice && TESTBIT(maskAll, kSystCent)) { LOGF(warning, "systCent uses the same centrality estimator as Base (%d).", cfgEvSel.cfgCentralityChoice.value); } @@ -2298,6 +2234,9 @@ struct RadialFlowDecorr { ccdb->setLocalObjectValidityChecking(); int64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); ccdb->setCreatedNotAfter(now); + // a missing object returns nullptr: every lookup is checked (a missing variation input switches + // the variation off, a missing Base input or alignment is fatal, a missing gain gives unit gains) + ccdb->setFatalWhenNull(false); fwdCalib.fv0Det = o2::fv0::Geometry::instance(o2::fv0::Geometry::eUninitialized); std::string sysDir; @@ -2340,7 +2279,7 @@ struct RadialFlowDecorr { if (cfgEff && (cfgRunGetMCFlat || cfgRunMCMean || cfgRunMCFluc || isDataRun)) { std::array loaded{}; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(used, v)) { + if (!TESTBIT(used, v)) { continue; } const int s = effSource(v); @@ -2353,8 +2292,6 @@ struct RadialFlowDecorr { } inputs[v].hEff = inputs[s].hEff; inputs[v].hFake = inputs[s].hFake; - inputs[v].hEffAllOcc = inputs[s].hEffAllOcc; - inputs[v].hFakeAllOcc = inputs[s].hFakeAllOcc; inputs[v].hEffPtEta = inputs[s].hEffPtEta; inputs[v].hFakePtEta = inputs[s].hFakePtEta; } @@ -2368,7 +2305,7 @@ struct RadialFlowDecorr { std::array loaded{}; for (int v = 0; v < kNSyst; ++v) { const int s = flatSource(v); - if (!hasBit(maskAll, v) || s < 0) { + if (!TESTBIT(maskAll, v) || s < 0) { continue; } if (loaded[s] == 0) { @@ -2432,7 +2369,7 @@ struct RadialFlowDecorr { if (cfgRunMCFluc || cfgRunDataFluc) { const std::string& path = cfgRunMCFluc ? pathMCMean : pathDataMean; for (int v = 0; v < kNSyst; ++v) { - if (hasBit(maskAll, v)) { + if (TESTBIT(maskAll, v)) { LOGF(info, "Loading mean profiles of %s from %s", systName[v].c_str(), (path + systSuffix(v)).c_str()); loadMeans(v, path + systSuffix(v), cfgRunMCFluc, now); } @@ -2472,7 +2409,7 @@ struct RadialFlowDecorr { std::string list; for (int v = 0; v < kNSyst; ++v) { - if (hasBit(used & maskAll, v) || (cfgRunGetEff && hasBit(maskEff, v)) || ((cfgRunGetMCFlat || cfgRunGetDataFlat) && hasBit(maskFlat, v))) { + if (TESTBIT(used & maskAll, v) || (cfgRunGetEff && TESTBIT(maskEff, v)) || ((cfgRunGetMCFlat || cfgRunGetDataFlat) && TESTBIT(maskFlat, v))) { list += " " + systName[v]; } } @@ -2538,15 +2475,15 @@ struct RadialFlowDecorr { const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); histos.fill(HIST("hVtxZ"), coll.posZ()); const auto ev = isEventSelected(coll, tracks.size(), maskEff); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - const double occ = effOccValue(ev.occBin); + const float cent = ev.cent[kSystBase]; - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -2554,17 +2491,16 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } - // one entry in the hEffMaps of every variation in mask - auto fillEffMaps = [&](uint32_t mask, double pt, double eta, int cat) { - const std::array x = {nCh, pt, eta, occ, static_cast(cat)}; + // one entry in the efficiency map of category cat of every variation in mask + auto fillEffMaps = [&](uint32_t mask, float pt, float eta, int cat) { for (int v = 0; v < kNSyst; ++v) { - if (hasBit(mask, v)) { - hists.eff[v].maps->Fill(x.data()); + if (TESTBIT(mask, v)) { + hists.eff[v].maps[cat]->Fill(cent, pt, eta); } } }; @@ -2580,11 +2516,11 @@ struct RadialFlowDecorr { // --- reco --- for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float pt = track.pt(), eta = track.eta(); - const bool isBase = hasBit(trackMask, kSystBase); + const bool isBase = TESTBIT(trackMask, kSystBase); const int origin = trackOrigin(track, coll); if (isBase) { histos.fill(HIST("hPt"), pt); @@ -2628,14 +2564,14 @@ struct RadialFlowDecorr { const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); histos.fill(HIST("hVtxZ"), coll.posZ()); const auto ev = isEventSelected(coll, tracks.size(), maskFlat); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -2643,7 +2579,7 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } @@ -2651,7 +2587,7 @@ struct RadialFlowDecorr { std::array w{}; for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float pt = track.pt(); @@ -2660,17 +2596,17 @@ struct RadialFlowDecorr { } float eta = track.eta(), phi = track.phi(); float sign = track.sign(); - if (hasBit(trackMask, kSystBase)) { + if (TESTBIT(trackMask, kSystBase)) { histos.fill(HIST("hPt"), pt); histos.fill(HIST("hEta"), eta); histos.fill(HIST("hPhi"), phi); } // efficiency weights only: the flattening map is what this pass measures - float eff, fake, flatWeight; - const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, false, w, eff, fake, flatWeight); + float eff = 1.f, fake = 0.f, flatWeight = 1.f; + const uint32_t weightMask = getTrackWeights(trackMask, ev.cent[kSystBase], vz, sign, pt, eta, phi, false, w, eff, fake, flatWeight); for (int v = 0; v < kNSyst; ++v) { - if (hasBit(weightMask, v)) { + if (TESTBIT(weightMask, v)) { fillFlat(v, vz, sign, pt, eta, phi, w[v], w[v]); } } @@ -2695,14 +2631,14 @@ struct RadialFlowDecorr { const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); histos.fill(HIST("hVtxZ"), coll.posZ()); const auto ev = isEventSelected(coll, tracks.size(), maskAll); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -2710,13 +2646,13 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v)) { + if (TESTBIT(ev.mask, v)) { for (auto& s : sums[v]) { s.reset(); } @@ -2725,7 +2661,7 @@ struct RadialFlowDecorr { // --- truth: independent of the track cuts, copied to every selected variation --- PowerSums& sumsTru = sums[kSystBase][kGen]; - if (!hasBit(ev.mask, kSystBase)) { + if (!TESTBIT(ev.mask, kSystBase)) { sumsTru.reset(); } for (const auto& particle : mcParticles) { @@ -2744,7 +2680,7 @@ struct RadialFlowDecorr { } } for (int v = 1; v < kNSyst; ++v) { - if (hasBit(ev.mask, v)) { + if (TESTBIT(ev.mask, v)) { sums[v][kGen] = sumsTru; } } @@ -2753,7 +2689,7 @@ struct RadialFlowDecorr { std::array w{}; for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float pt = track.pt(); @@ -2762,7 +2698,7 @@ struct RadialFlowDecorr { } float eta = track.eta(), phi = track.phi(); float sign = track.sign(); - if (hasBit(trackMask, kSystBase)) { + if (TESTBIT(trackMask, kSystBase)) { histos.fill(HIST("hPt"), pt); histos.fill(HIST("hEta"), eta); histos.fill(HIST("hPhi"), phi); @@ -2774,18 +2710,18 @@ struct RadialFlowDecorr { continue; } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(trackMask, v)) { + if (TESTBIT(trackMask, v)) { sums[v][kReco].add(ieta, pt, 1.); } } } - float eff, fake, flatWeight; - const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); - if (!weightMask) { + float eff = 1.f, fake = 0.f, flatWeight = 1.f; + const uint32_t weightMask = getTrackWeights(trackMask, ev.cent[kSystBase], vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (weightMask == 0) { continue; } - if (hasBit(weightMask, kSystBase)) { + if (TESTBIT(weightMask, kSystBase)) { histos.fill(HIST("Prof_Weights_pT_type"), pt, 0, eff); histos.fill(HIST("Prof_Weights_pT_type"), pt, 1, fake); histos.fill(HIST("Prof_Weights_pT_type"), pt, 2, w[kSystBase]); @@ -2798,7 +2734,7 @@ struct RadialFlowDecorr { continue; } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(weightMask, v)) { + if (TESTBIT(weightMask, v)) { sums[v][kRecoEffCorr].add(ieta, pt, w[v]); } } @@ -2811,12 +2747,12 @@ struct RadialFlowDecorr { if (cfgFwdOpt.cfgFwd) { loadFwdCalib(coll.template bc_as(), false); computeFwdTruth(mcParticles, fwdTru); - computeFwdReco(coll, vz, fwdReco, hasBit(ev.mask, kSystBase)); + computeFwdReco(coll, vz, fwdReco, TESTBIT(ev.mask, kSystBase)); } // --- <[pT]> and per eta bin, per forward bin --- for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(ev.mask, v)) { + if (!TESTBIT(ev.mask, v)) { continue; } for (int l = 0; l < kNLevels; ++l) { @@ -2862,14 +2798,14 @@ struct RadialFlowDecorr { const auto tracks = mcTracks.sliceBy(tracksPerCollision, coll.globalIndex()); histos.fill(HIST("hVtxZ"), coll.posZ()); const auto ev = isEventSelected(coll, tracks.size(), maskAll); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -2877,13 +2813,13 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v)) { + if (TESTBIT(ev.mask, v)) { for (auto& s : sums[v]) { s.reset(); } @@ -2892,7 +2828,7 @@ struct RadialFlowDecorr { // --- truth sums: independent of the track cuts, copied to every selected variation --- PowerSums& sumsTru = sums[kSystBase][kGen]; - if (!hasBit(ev.mask, kSystBase)) { + if (!TESTBIT(ev.mask, kSystBase)) { sumsTru.reset(); } for (const auto& particle : mcParticles) { @@ -2911,7 +2847,7 @@ struct RadialFlowDecorr { } } for (int v = 1; v < kNSyst; ++v) { - if (hasBit(ev.mask, v)) { + if (TESTBIT(ev.mask, v)) { sums[v][kGen] = sumsTru; } } @@ -2920,7 +2856,7 @@ struct RadialFlowDecorr { std::array w{}; for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float pt = track.pt(); @@ -2929,7 +2865,7 @@ struct RadialFlowDecorr { } float eta = track.eta(), phi = track.phi(); float sign = track.sign(); - if (hasBit(trackMask, kSystBase)) { + if (TESTBIT(trackMask, kSystBase)) { histos.fill(HIST("hPt"), pt); histos.fill(HIST("hEta"), eta); histos.fill(HIST("hPhi"), phi); @@ -2941,18 +2877,18 @@ struct RadialFlowDecorr { continue; } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(trackMask, v)) { + if (TESTBIT(trackMask, v)) { sums[v][kReco].add(ieta, pt, 1.); } } } - float eff, fake, flatWeight; - const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); - if (!weightMask) { + float eff = 1.f, fake = 0.f, flatWeight = 1.f; + const uint32_t weightMask = getTrackWeights(trackMask, ev.cent[kSystBase], vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (weightMask == 0) { continue; } - if (hasBit(weightMask, kSystBase)) { + if (TESTBIT(weightMask, kSystBase)) { fillFlat(kSystBase, vz, sign, pt, eta, phi, w[kSystBase], (1.f - fake) / eff); } @@ -2962,7 +2898,7 @@ struct RadialFlowDecorr { continue; } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(weightMask, v)) { + if (TESTBIT(weightMask, v)) { sums[v][kRecoEffCorr].add(ieta, pt, w[v]); } } @@ -2975,13 +2911,13 @@ struct RadialFlowDecorr { if (cfgFwdOpt.cfgFwd) { loadFwdCalib(coll.template bc_as(), false); computeFwdTruth(mcParticles, fwdTru); - computeFwdReco(coll, vz, fwdReco, hasBit(ev.mask, kSystBase)); + computeFwdReco(coll, vz, fwdReco, TESTBIT(ev.mask, kSystBase)); } // --- c2, c3, subevent correlators and forward covariances of every variation and level --- EventObs evObs; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(ev.mask, v)) { + if (!TESTBIT(ev.mask, v)) { continue; } for (int l = 0; l < kNLevels; ++l) { @@ -3001,14 +2937,14 @@ struct RadialFlowDecorr { { histos.fill(HIST("hVtxZ"), coll.posZ()); const auto ev = isEventSelected(coll, tracks.size(), maskFlat); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -3016,7 +2952,7 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } @@ -3024,7 +2960,7 @@ struct RadialFlowDecorr { std::array w{}; for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float pt = track.pt(); @@ -3033,17 +2969,17 @@ struct RadialFlowDecorr { } float eta = track.eta(), phi = track.phi(); float sign = track.sign(); - if (hasBit(trackMask, kSystBase)) { + if (TESTBIT(trackMask, kSystBase)) { histos.fill(HIST("hPt"), pt); histos.fill(HIST("hEta"), eta); histos.fill(HIST("hPhi"), phi); } // efficiency weights only: the flattening map is what this pass measures - float eff, fake, flatWeight; - const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, false, w, eff, fake, flatWeight); + float eff = 1.f, fake = 0.f, flatWeight = 1.f; + const uint32_t weightMask = getTrackWeights(trackMask, ev.cent[kSystBase], vz, sign, pt, eta, phi, false, w, eff, fake, flatWeight); for (int v = 0; v < kNSyst; ++v) { - if (hasBit(weightMask, v)) { + if (TESTBIT(weightMask, v)) { fillFlat(v, vz, sign, pt, eta, phi, w[v], w[v]); } } @@ -3058,14 +2994,14 @@ struct RadialFlowDecorr { void processDataMean(AodCollisionsSel::iterator const& coll, aod::BCsWithTimestamps const&, aod::FT0s const&, aod::FV0As const&, AodTracksSel const& tracks) { const auto ev = isEventSelected(coll, tracks.size(), maskAll); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -3073,20 +3009,20 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v)) { + if (TESTBIT(ev.mask, v)) { sums[v][0].reset(); } } std::array w{}; for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float p = track.p(); @@ -3099,19 +3035,19 @@ struct RadialFlowDecorr { } float eta = track.eta(), phi = track.phi(); float sign = track.sign(); - if (hasBit(trackMask, kSystBase)) { + if (TESTBIT(trackMask, kSystBase)) { histos.fill(HIST("hP"), p); histos.fill(HIST("hPt"), pt); histos.fill(HIST("hEta"), eta); histos.fill(HIST("hPhi"), phi); } - float eff, fake, flatWeight; - const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); - if (!weightMask) { + float eff = 1.f, fake = 0.f, flatWeight = 1.f; + const uint32_t weightMask = getTrackWeights(trackMask, ev.cent[kSystBase], vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (weightMask == 0) { continue; } - if (hasBit(weightMask, kSystBase)) { + if (TESTBIT(weightMask, kSystBase)) { histos.fill(HIST("Prof_Weights_pt_eta_type"), pt, eta, 0, eff); histos.fill(HIST("Prof_Weights_pt_eta_type"), pt, eta, 1, fake); histos.fill(HIST("Prof_Weights_pt_eta_type"), pt, eta, 2, flatWeight); @@ -3123,7 +3059,7 @@ struct RadialFlowDecorr { continue; } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(weightMask, v)) { + if (TESTBIT(weightMask, v)) { sums[v][0].add(ieta, pt, w[v]); } } @@ -3135,12 +3071,12 @@ struct RadialFlowDecorr { FwdSignal fwd; if (cfgFwdOpt.cfgFwd) { loadFwdCalib(coll.template bc_as(), true); - computeFwdReco(coll, vz, fwd, hasBit(ev.mask, kSystBase)); + computeFwdReco(coll, vz, fwd, TESTBIT(ev.mask, kSystBase)); } // --- <[pT]> and per eta bin, per forward bin --- for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(ev.mask, v)) { + if (!TESTBIT(ev.mask, v)) { continue; } const auto& s = sums[v][0]; @@ -3173,14 +3109,14 @@ struct RadialFlowDecorr { void processDataFluc(AodCollisionsSel::iterator const& coll, aod::BCsWithTimestamps const&, aod::FT0s const&, aod::FV0As const&, AodTracksSel const& tracks) { const auto ev = isEventSelected(coll, tracks.size(), maskAll); - if (!ev.mask) { + if (ev.mask == 0) { return; } const float nCh = tracks.size(); const float multPV = coll.multNTracksPV(); const float vz = coll.posZ(); - if (hasBit(ev.mask, kSystBase)) { + if (TESTBIT(ev.mask, kSystBase)) { histos.fill(HIST("hVtxZ_after_sel"), vz); histos.fill(HIST("hCentrality"), ev.cent[kSystBase]); histos.fill(HIST("Hist2D_globalTracks_PVTracks"), multPV, nCh); @@ -3188,20 +3124,20 @@ struct RadialFlowDecorr { hists.nchOccupancy->Fill(nCh, ev.occupancy); } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v) && hists.nchCent[v]) { + if (TESTBIT(ev.mask, v) && hists.nchCent[v]) { hists.nchCent[v]->Fill(ev.cent[v], nCh); } } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(ev.mask, v)) { + if (TESTBIT(ev.mask, v)) { sums[v][0].reset(); } } std::array w{}; for (const auto& track : tracks) { const uint32_t trackMask = isTrackSelected(track, ev.mask); - if (!trackMask) { + if (trackMask == 0) { continue; } float p = track.p(); @@ -3214,19 +3150,19 @@ struct RadialFlowDecorr { } float eta = track.eta(), phi = track.phi(); float sign = track.sign(); - if (hasBit(trackMask, kSystBase)) { + if (TESTBIT(trackMask, kSystBase)) { histos.fill(HIST("hP"), p); histos.fill(HIST("hPt"), pt); histos.fill(HIST("hEta"), eta); histos.fill(HIST("hPhi"), phi); } - float eff, fake, flatWeight; - const uint32_t weightMask = getTrackWeights(trackMask, nCh, ev.occBin, vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); - if (!weightMask) { + float eff = 1.f, fake = 0.f, flatWeight = 1.f; + const uint32_t weightMask = getTrackWeights(trackMask, ev.cent[kSystBase], vz, sign, pt, eta, phi, true, w, eff, fake, flatWeight); + if (weightMask == 0) { continue; } - if (hasBit(weightMask, kSystBase)) { + if (TESTBIT(weightMask, kSystBase)) { fillFlat(kSystBase, vz, sign, pt, eta, phi, w[kSystBase], (1.f - fake) / eff); } @@ -3235,7 +3171,7 @@ struct RadialFlowDecorr { continue; } for (int v = 0; v < kNSyst; ++v) { - if (hasBit(weightMask, v)) { + if (TESTBIT(weightMask, v)) { sums[v][0].add(ieta, pt, w[v]); } } @@ -3247,7 +3183,7 @@ struct RadialFlowDecorr { FwdSignal fwd; if (cfgFwdOpt.cfgFwd) { loadFwdCalib(coll.template bc_as(), true); - computeFwdReco(coll, vz, fwd, hasBit(ev.mask, kSystBase)); + computeFwdReco(coll, vz, fwd, TESTBIT(ev.mask, kSystBase)); } // --- Poisson bootstrap: one weight per sample per event --- @@ -3261,7 +3197,7 @@ struct RadialFlowDecorr { // --- c2, c3, subevent correlators and forward covariances of every variation --- EventObs evObs; for (int v = 0; v < kNSyst; ++v) { - if (!hasBit(ev.mask, v)) { + if (!TESTBIT(ev.mask, v)) { continue; } computeEventObs(sums[v][0], v, 0, cfgFwdOpt.cfgFwd ? &fwd : nullptr, nCh, evObs); @@ -3294,14 +3230,14 @@ struct RadialFlowDecorr { } const float cent = getCentrality(col, cfgEvSel.cfgCentralityChoice); fillCutQAEvent(kQABefore, col, nCh, cent); - const auto ev = isEventSelected(col, nCh, 1u << kSystBase, false); - if (!hasBit(ev.mask, kSystBase)) { + const auto ev = isEventSelected(col, nCh, 1 << kSystBase, false); + if (!TESTBIT(ev.mask, kSystBase)) { return; } fillCutQAEvent(kQAAfter, col, nCh, cent); for (const auto& trk : tracks) { fillCutQATrack(kQABefore, trk); - if (hasBit(isTrackSelected(trk, 1u << kSystBase, false), kSystBase)) { + if (TESTBIT(isTrackSelected(trk, 1 << kSystBase, false), kSystBase)) { fillCutQATrack(kQAAfter, trk); } }