diff --git a/PWGMM/UE/Tasks/flattenicityTask.cxx b/PWGMM/UE/Tasks/flattenicityTask.cxx index e6a720c7d7b..d4507cd76b0 100644 --- a/PWGMM/UE/Tasks/flattenicityTask.cxx +++ b/PWGMM/UE/Tasks/flattenicityTask.cxx @@ -10,7 +10,10 @@ // or submit itself to any jurisdiction. /// \file flattenicityTask.cxx -/// \brief Flattenicity analysis task for UE studies +/// \brief Flattenicity analysis task for UE studies, with two flattenicity +/// definitions: particle-based (charged particles/tracks mapped into +/// FT0 cells) and FT0-detector-amplitude-based (real FT0 channel +/// signals), for Hyperloop /// \author Eisha Rani /// \since August 2026 @@ -29,8 +32,6 @@ #include #include #include -#include -#include using namespace o2; using namespace o2::framework; @@ -58,19 +59,6 @@ struct FlattenicityTask { static constexpr int NPhysicalPrimaryBit = 0x4; - // ============================================ - // Multiplicity class boundaries - // ============================================ - static constexpr int NchBound10 = 5; - static constexpr int NchBound20 = 8; - static constexpr int NchBound30 = 11; - static constexpr int NchBound40 = 14; - static constexpr int NchBound50 = 17; - static constexpr int NchBound60 = 20; - static constexpr int NchBound70 = 24; - static constexpr int NchBound80 = 28; - static constexpr int NchBound90 = 33; - // ============================================ // Histogram Definitions // ============================================ @@ -84,32 +72,44 @@ struct FlattenicityTask { {"hNch_INEL", "Nch distribution (INEL>0);N_{ch};Entries", {HistType::kTH1F, {{100, -0.5, 99.5}}}}, {"hNch_FT0", "Nch distribution (INEL>0 & FT0);N_{ch};Entries", {HistType::kTH1F, {{100, -0.5, 99.5}}}}, - // Flattenicity - {"hFlattenicity", "Flattenicity distribution;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_vs_Nch", "Flattenicity vs Nch;N_{ch};1-#rho", {HistType::kTH2F, {{50, -0.5, 99.5}, {50, 0.0, 1.0}}}}, + // Definition 1: flattenicity from charged particles/tracks mapped into FT0 cells + {"hFlattenicityParticles", "Flattenicity from charged particles in FT0 acceptance;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_vs_Nch", "Flattenicity (particles) vs Nch;N_{ch};1-#rho", {HistType::kTH2F, {{50, -0.5, 99.5}, {50, 0.0, 1.0}}}}, - // FT0 cell occupancy + // Definition 2: flattenicity from FT0 detector amplitudes only + {"hFlattenicityFT0", "Flattenicity from FT0 detector amplitudes;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + + // FT0 cell occupancy (from particle-level mapping) {"hCellOccupancy", "FT0 cell occupancy;Cell ID;Entries", {HistType::kTH1F, {{NCell, 0, NCell}}}}, {"hCellOccupancyFT0A", "FT0-A cell occupancy;Cell ID;Entries", {HistType::kTH1F, {{NchA, 0, NchA}}}}, {"hCellOccupancyFT0C", "FT0-C cell occupancy;Cell ID;Entries", {HistType::kTH1F, {{NchC, 0, NchC}}}}, - // Multiplicity classes - {"hFlattenicity_0_10", "Flattenicity 0-10%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_10_20", "Flattenicity 10-20%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_20_30", "Flattenicity 20-30%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_30_40", "Flattenicity 30-40%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_40_50", "Flattenicity 40-50%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_50_60", "Flattenicity 50-60%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_60_70", "Flattenicity 60-70%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_70_80", "Flattenicity 70-80%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_80_90", "Flattenicity 80-90%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, - {"hFlattenicity_90_100", "Flattenicity 90-100%;1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + // Percentile classes (I-VIII: 0-1,1-5,5-10,10-20,20-30,30-40,40-50,50-100%) + // -- particle-based definition + {"hFlattenicityParticles_0_1", "Flattenicity (particles) class I (0-1%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_1_5", "Flattenicity (particles) class II (1-5%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_5_10", "Flattenicity (particles) class III (5-10%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_10_20", "Flattenicity (particles) class IV (10-20%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_20_30", "Flattenicity (particles) class V (20-30%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_30_40", "Flattenicity (particles) class VI (30-40%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_40_50", "Flattenicity (particles) class VII (40-50%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityParticles_50_100", "Flattenicity (particles) class VIII (50-100%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + + // -- FT0-amplitude-based definition + {"hFlattenicityFT0_0_1", "Flattenicity (FT0) class I (0-1%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_1_5", "Flattenicity (FT0) class II (1-5%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_5_10", "Flattenicity (FT0) class III (5-10%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_10_20", "Flattenicity (FT0) class IV (10-20%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_20_30", "Flattenicity (FT0) class V (20-30%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_30_40", "Flattenicity (FT0) class VI (30-40%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_40_50", "Flattenicity (FT0) class VII (40-50%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, + {"hFlattenicityFT0_50_100", "Flattenicity (FT0) class VIII (50-100%);1-#rho;Entries", {HistType::kTH1F, {{50, 0.0, 1.0}}}}, }}; // ============================================ // Configurables // ============================================ - Configurable cfgPtMin{"cfgPtMin", 0.1, "Minimum pT for tracks"}; + Configurable cfgPtMin{"cfgPtMin", 0.0, "Minimum pT for tracks/particles (GeV/c)"}; Configurable cfgEtaMax{"cfgEtaMax", 0.8, "Maximum |eta| for tracks"}; Configurable cfgVzMax{"cfgVzMax", 10.0, "Maximum |vz| for collisions"}; Configurable cfgNCrossedRowsTPC{"cfgNCrossedRowsTPC", 70, "Minimum TPC crossed rows"}; @@ -118,6 +118,32 @@ struct FlattenicityTask { Configurable cfgDCAZ{"cfgDCAZ", 0.1, "Maximum DCA z"}; Configurable cfgRequireGoldenChi2{"cfgRequireGoldenChi2", true, "Require golden chi2"}; + // Percentile-class boundaries (in 1-rho), one full set of 7 edges per + // definition (classes I-VIII: 0-1,1-5,5-10,10-20,20-30,30-40,40-50,50-100%). + // Kept as individually named Configurables for Hyperloop -- these are NOT + // computed on the fly; they must be set from an actual percentile + // calibration of the corresponding 1-rho distribution (particle-based and + // FT0-amplitude-based distributions are different quantities and will not + // share the same boundary values). The defaults below are placeholders. + + // -- particle-based definition boundaries + Configurable cfgParticlesCut0To1{"cfgParticlesCut0To1", 0.90, "Particles 1-rho lower edge for 0-1%"}; + Configurable cfgParticlesCut1To5{"cfgParticlesCut1To5", 0.80, "Particles 1-rho lower edge for 1-5%"}; + Configurable cfgParticlesCut5To10{"cfgParticlesCut5To10", 0.75, "Particles 1-rho lower edge for 5-10%"}; + Configurable cfgParticlesCut10To20{"cfgParticlesCut10To20", 0.65, "Particles 1-rho lower edge for 10-20%"}; + Configurable cfgParticlesCut20To30{"cfgParticlesCut20To30", 0.55, "Particles 1-rho lower edge for 20-30%"}; + Configurable cfgParticlesCut30To40{"cfgParticlesCut30To40", 0.45, "Particles 1-rho lower edge for 30-40%"}; + Configurable cfgParticlesCut40To50{"cfgParticlesCut40To50", 0.35, "Particles 1-rho lower edge for 40-50%"}; + + // -- FT0-amplitude-based definition boundaries + Configurable cfgFT0Cut0To1{"cfgFT0Cut0To1", 0.904, "FT0 1-rho lower edge for 0-1%"}; + Configurable cfgFT0Cut1To5{"cfgFT0Cut1To5", 0.888, "FT0 1-rho lower edge for 1-5%"}; + Configurable cfgFT0Cut5To10{"cfgFT0Cut5To10", 0.840, "FT0 1-rho lower edge for 5-10%"}; + Configurable cfgFT0Cut10To20{"cfgFT0Cut10To20", 0.780, "FT0 1-rho lower edge for 10-20%"}; + Configurable cfgFT0Cut20To30{"cfgFT0Cut20To30", 0.720, "FT0 1-rho lower edge for 20-30%"}; + Configurable cfgFT0Cut30To40{"cfgFT0Cut30To40", 0.660, "FT0 1-rho lower edge for 30-40%"}; + Configurable cfgFT0Cut40To50{"cfgFT0Cut40To50", 0.600, "FT0 1-rho lower edge for 40-50%"}; + // ============================================ // Particle charge function // ============================================ @@ -137,69 +163,119 @@ struct FlattenicityTask { } // ============================================ - // Flattenicity calculation + // Generic ALICE flattenicity formula, shared by both definitions // ============================================ - float computeFlattenicity(const std::array& counts) + float computeRhoFromCells(const std::array& counts) { - float total = 0.0; - for (int i = 0; i < NCell; i++) { + float total = 0.0f; + for (int i = 0; i < NCell; ++i) { total += counts[i]; } - - if (total <= 0) { - return -1.0; + if (total <= 0.0f) { + return -1.0f; } float mean = total / NCell; - if (mean <= 0) { - return -1.0; + if (mean <= 0.0f) { + return -1.0f; } - float sumSq = 0.0; - for (int i = 0; i < NCell; i++) { + float sumSq = 0.0f; + for (int i = 0; i < NCell; ++i) { sumSq += (counts[i] - mean) * (counts[i] - mean); } - float rho = std::sqrt(sumSq) / (NCell * mean); - return rho; + return std::sqrt(sumSq) / (NCell * mean); + } + + // Definition 1: charged-particle/track flattenicity in FT0 acceptance. + float computeFlattenicityParticles(const std::array& particleCounts) + { + float rho = computeRhoFromCells(particleCounts); + return (rho >= 0.0f) ? (1.0f - rho) : -1.0f; + } + + // Definition 2: FT0-detector-only flattenicity from amplitudes. + float computeFlattenicityFT0(const std::array& ft0Counts) + { + float rho = computeRhoFromCells(ft0Counts); + return (rho >= 0.0f) ? (1.0f - rho) : -1.0f; } // ============================================ - // Assign particle to FT0 cell + // Assign particle/track to FT0 cell // ============================================ int assignToFT0Cell(float eta, float phi, bool& isFT0A) { - // Check if in FT0 acceptance bool inFT0A = (eta > FT0AEtaMin && eta < FT0AEtaMax); bool inFT0C = (eta > FT0CEtaMin && eta < FT0CEtaMax); - if (!inFT0A && !inFT0C) { return -1; } isFT0A = inFT0A; - // Phi bin int phiBin = static_cast(std::floor(phi / (o2::constants::math::TwoPI / NPhiSectors))); phiBin = std::max(0, std::min(phiBin, NPhiSectors - 1)); - int cellId = -1; - if (inFT0A) { // FT0-A: cells 0-95 float etaWidth = (FT0AEtaMax - FT0AEtaMin) / NEtaA; int etaBin = static_cast(std::floor((eta - FT0AEtaMin) / etaWidth)); etaBin = std::max(0, std::min(etaBin, NEtaA - 1)); - cellId = etaBin * NPhiSectors + phiBin; - } else if (inFT0C) { - // FT0-C: cells 96-207 - float etaWidth = (FT0CEtaMax - FT0CEtaMin) / NEtaC; - int etaBin = static_cast(std::floor((eta - FT0CEtaMin) / etaWidth)); - etaBin = std::max(0, std::min(etaBin, NEtaC - 1)); - cellId = NchA + etaBin * NPhiSectors + phiBin; + return etaBin * NPhiSectors + phiBin; + } + + // FT0-C: cells 96-207 + float etaWidth = (FT0CEtaMax - FT0CEtaMin) / NEtaC; + int etaBin = static_cast(std::floor((eta - FT0CEtaMin) / etaWidth)); + etaBin = std::max(0, std::min(etaBin, NEtaC - 1)); + return NchA + etaBin * NPhiSectors + phiBin; + } + + // ============================================ + // Percentile-class filling helpers + // ============================================ + void fillParticlesPercentileHistograms(float flat) + { + if (flat >= cfgParticlesCut0To1) { + histos.fill(HIST("hFlattenicityParticles_0_1"), flat); + } else if (flat >= cfgParticlesCut1To5) { + histos.fill(HIST("hFlattenicityParticles_1_5"), flat); + } else if (flat >= cfgParticlesCut5To10) { + histos.fill(HIST("hFlattenicityParticles_5_10"), flat); + } else if (flat >= cfgParticlesCut10To20) { + histos.fill(HIST("hFlattenicityParticles_10_20"), flat); + } else if (flat >= cfgParticlesCut20To30) { + histos.fill(HIST("hFlattenicityParticles_20_30"), flat); + } else if (flat >= cfgParticlesCut30To40) { + histos.fill(HIST("hFlattenicityParticles_30_40"), flat); + } else if (flat >= cfgParticlesCut40To50) { + histos.fill(HIST("hFlattenicityParticles_40_50"), flat); + } else { + histos.fill(HIST("hFlattenicityParticles_50_100"), flat); } + } - return cellId; + void fillFT0PercentileHistograms(float flat) + { + if (flat >= cfgFT0Cut0To1) { + histos.fill(HIST("hFlattenicityFT0_0_1"), flat); + } else if (flat >= cfgFT0Cut1To5) { + histos.fill(HIST("hFlattenicityFT0_1_5"), flat); + } else if (flat >= cfgFT0Cut5To10) { + histos.fill(HIST("hFlattenicityFT0_5_10"), flat); + } else if (flat >= cfgFT0Cut10To20) { + histos.fill(HIST("hFlattenicityFT0_10_20"), flat); + } else if (flat >= cfgFT0Cut20To30) { + histos.fill(HIST("hFlattenicityFT0_20_30"), flat); + } else if (flat >= cfgFT0Cut30To40) { + histos.fill(HIST("hFlattenicityFT0_30_40"), flat); + } else if (flat >= cfgFT0Cut40To50) { + histos.fill(HIST("hFlattenicityFT0_40_50"), flat); + } else { + histos.fill(HIST("hFlattenicityFT0_50_100"), flat); + } } // ============================================ @@ -208,8 +284,8 @@ struct FlattenicityTask { template bool isSelectedTrack(const T& track) { - // pT selection - if (track.pt() < cfgPtMin) { + // pT > 0 + if (track.pt() <= 0.0f || track.pt() < cfgPtMin) { return false; } @@ -248,13 +324,14 @@ struct FlattenicityTask { // ============================================ // Process MC collisions + // (only the particle-based definition is available here: there is no + // FT0 detector object at MC-truth level in this process signature) // ============================================ void processMC( aod::McCollision const& /* mcCollision */, aod::McParticles const& mcParticles) { - // Initialize counters - std::array truthCounts{}; + std::array particleCounts{}; int nchINEL = 0; int nchFT0 = 0; @@ -274,8 +351,8 @@ struct FlattenicityTask { continue; } - // pT > 0.1 - if (particle.pt() < cfgPtMin) { + // pT > 0 + if (particle.pt() <= 0.0f || particle.pt() < cfgPtMin) { continue; } @@ -305,7 +382,7 @@ struct FlattenicityTask { int cellId = assignToFT0Cell(particle.eta(), particle.phi(), isFT0A); if (cellId >= 0 && cellId < NCell) { - truthCounts[cellId] += 1.0; + particleCounts[cellId] += 1.0f; histos.fill(HIST("hCellOccupancy"), cellId); if (isFT0A) { histos.fill(HIST("hCellOccupancyFT0A"), cellId); @@ -330,35 +407,11 @@ struct FlattenicityTask { histos.fill(HIST("hEvents"), 2); // INEL>0 & FT0 histos.fill(HIST("hNch_FT0"), nchFT0); - // Compute flattenicity - float rho = computeFlattenicity(truthCounts); - if (rho > 0) { - float flattenicity = 1.0 - rho; - histos.fill(HIST("hFlattenicity"), flattenicity); - histos.fill(HIST("hFlattenicity_vs_Nch"), nchFT0, flattenicity); - - // Multiplicity classes (based on Nch) - if (nchFT0 < NchBound10) { - histos.fill(HIST("hFlattenicity_0_10"), flattenicity); - } else if (nchFT0 < NchBound20) { - histos.fill(HIST("hFlattenicity_10_20"), flattenicity); - } else if (nchFT0 < NchBound30) { - histos.fill(HIST("hFlattenicity_20_30"), flattenicity); - } else if (nchFT0 < NchBound40) { - histos.fill(HIST("hFlattenicity_30_40"), flattenicity); - } else if (nchFT0 < NchBound50) { - histos.fill(HIST("hFlattenicity_40_50"), flattenicity); - } else if (nchFT0 < NchBound60) { - histos.fill(HIST("hFlattenicity_50_60"), flattenicity); - } else if (nchFT0 < NchBound70) { - histos.fill(HIST("hFlattenicity_60_70"), flattenicity); - } else if (nchFT0 < NchBound80) { - histos.fill(HIST("hFlattenicity_70_80"), flattenicity); - } else if (nchFT0 < NchBound90) { - histos.fill(HIST("hFlattenicity_80_90"), flattenicity); - } else { - histos.fill(HIST("hFlattenicity_90_100"), flattenicity); - } + float flatParticles = computeFlattenicityParticles(particleCounts); + if (flatParticles >= 0.0f) { + histos.fill(HIST("hFlattenicityParticles"), flatParticles); + histos.fill(HIST("hFlattenicityParticles_vs_Nch"), nchFT0, flatParticles); + fillParticlesPercentileHistograms(flatParticles); } } } @@ -367,6 +420,8 @@ struct FlattenicityTask { // ============================================ // Process data collisions + // (both definitions are available here: particle-based from selected + // reconstructed tracks, and FT0-amplitude-based from the real FT0 signal) // ============================================ void processData( aod::Collision const& collision, @@ -392,40 +447,40 @@ struct FlattenicityTask { return; } - // Track selection and counting - std::array recoCounts{}; - + // Definition 1: selected charged tracks mapped into FT0 cells + std::array recoParticleCounts{}; for (const auto& track : tracks) { if (!isSelectedTrack(track)) { continue; } - - // Assign to FT0 cell using track extrapolation bool isFT0A = false; int cellId = assignToFT0Cell(track.eta(), track.phi(), isFT0A); if (cellId >= 0 && cellId < NCell) { - recoCounts[cellId] += 1.0; + recoParticleCounts[cellId] += 1.0f; } } - histos.fill(HIST("hEvents"), 3); // Data events - - // Compute flattenicity from FT0 amplitudes + // Definition 2: FT0 detector amplitudes only std::array ft0Counts{}; - - // FT0-A channels (0-95) - for (int i = 0; i < NchA; i++) { + for (int i = 0; i < NchA; ++i) { ft0Counts[i] = ft0.channelA()[i]; } - - // FT0-C channels (96-207) - for (int i = 0; i < NchC; i++) { + for (int i = 0; i < NchC; ++i) { ft0Counts[NchA + i] = ft0.channelC()[i]; } - float rho = computeFlattenicity(ft0Counts); - if (rho > 0) { - histos.fill(HIST("hFlattenicity"), 1.0 - rho); + histos.fill(HIST("hEvents"), 3); // Data events + + float flatParticles = computeFlattenicityParticles(recoParticleCounts); + if (flatParticles >= 0.0f) { + histos.fill(HIST("hFlattenicityParticles"), flatParticles); + fillParticlesPercentileHistograms(flatParticles); + } + + float flatFT0 = computeFlattenicityFT0(ft0Counts); + if (flatFT0 >= 0.0f) { + histos.fill(HIST("hFlattenicityFT0"), flatFT0); + fillFT0PercentileHistograms(flatFT0); } }