From 15a0ff0e44c07be0ab4570e7b6ae6121d66486bb Mon Sep 17 00:00:00 2001 From: Florian Jonas Date: Tue, 6 Jan 2026 16:31:40 +0100 Subject: [PATCH 1/4] [EMCAL] implementation of number of local maxima variable --- .../base/include/EMCALBase/ClusterFactory.h | 4 ++ .../EMCAL/base/include/EMCALBase/Geometry.h | 10 ++++ Detectors/EMCAL/base/src/ClusterFactory.cxx | 56 +++++++++++++++++++ Detectors/EMCAL/base/src/Geometry.cxx | 24 ++++++++ 4 files changed, 94 insertions(+) diff --git a/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h b/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h index a7e81d38838a3..0c3438042ca77 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h +++ b/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h @@ -401,6 +401,10 @@ class ClusterFactory /// in cell units void evalElipsAxis(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const; + /// + /// Calculate the number of local maxima in the cluster + void evalNExMax(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const; + /// /// Time is set to the time of the digit with the maximum energy void evalTime(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const; diff --git a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h index b4621d4b6e434..87ff092f5abcd 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h +++ b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h @@ -429,6 +429,16 @@ class Geometry /// \return Position (0 - phi, 1 - eta) of the cell inside teh supermodule std::tuple GetCellPhiEtaIndexInSModule(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; + + /// \brief Get topological row and column of cell in SM (same as for clusteriser with artifical gaps) + /// \param supermoduleID super module number + /// \param moduleID module number + /// \param phiInModule index in phi direction in module + /// \param etaInModule index in phi direction in module + /// \return tuple with (row, column) of the cell, which is global numbering scheme + std::tuple GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; + + /// \brief Adapt cell indices in supermodule to online indexing /// \param supermoduleID super module number of the channel/cell /// \param iphi row/phi cell index, modified for DCal diff --git a/Detectors/EMCAL/base/src/ClusterFactory.cxx b/Detectors/EMCAL/base/src/ClusterFactory.cxx index 342f54fd94591..1bfceb0d6a82d 100644 --- a/Detectors/EMCAL/base/src/ClusterFactory.cxx +++ b/Detectors/EMCAL/base/src/ClusterFactory.cxx @@ -120,6 +120,9 @@ o2::emcal::AnalysisCluster ClusterFactory::buildCluster(int clusterIn evalElipsAxis(inputsIndices, clusterAnalysis); evalDispersion(inputsIndices, clusterAnalysis); + // evaluate number of local maxima + evalNExMax(inputsIndices, clusterAnalysis); + evalCoreEnergy(inputsIndices, clusterAnalysis); evalTime(inputsIndices, clusterAnalysis); @@ -489,6 +492,59 @@ void ClusterFactory::evalCoreEnergy(gsl::span inputsIndice clusterAnalysis.setCoreEnergy(coreEnergy); } +/// +/// Calculate the number of local maxima in the cluster +//____________________________________________________________________________ +template +void ClusterFactory::evalNExMax(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const +{ + // Pre-compute cell indices and energies for all cells in cluster to avoid multiple expensive geometry lookups + struct CellInfo { + int row; + int column; + double energy; + }; + + std::vector cellInfos; + cellInfos.reserve(inputsIndices.size()); + + for (auto iInput : inputsIndices) { + auto [nSupMod, nModule, nIphi, nIeta] = mGeomPtr->GetCellIndex(mInputsContainer[iInput].getTower()); + + // get a nice topological indexing that is done in exactly the same way as used by the clusterizer + // this way we can handle the shared cluster cases correctly + auto [row, column] = mGeomPtr->GetTopologicalRowColumn(nSupMod, nModule, nIphi, nIeta); + cellInfos.push_back({row, column, mInputsContainer[iInput].getEnergy()}); + } + + // Now find local maxima using pre-computed data + int nExMax = 0; + for (size_t i = 0; i < cellInfos.size(); i++) { + // this cell is assumed to be local maximum unless we find a higher energy cell in the neighborhood + bool isExMax = true; + + // loop over all other cells in cluster + for (size_t j = 0; j < cellInfos.size(); j++) { + if (i == j) continue; + + // adjacent cell is any cell with adjacent phi or eta index + if (std::abs(cellInfos[i].row - cellInfos[j].row) <= 1 && + std::abs(cellInfos[i].column - cellInfos[j].column) <= 1) { + + // if there is a cell with higher energy than the current cell, it is not a local maximum + if (cellInfos[j].energy > cellInfos[i].energy) { + isExMax = false; + break; + } + } + } + if (isExMax) { + nExMax++; + } + } + clusterAnalysis.setNExMax(nExMax); +} + /// /// Calculates the axis of the shower ellipsoid in eta and phi /// in cell units diff --git a/Detectors/EMCAL/base/src/Geometry.cxx b/Detectors/EMCAL/base/src/Geometry.cxx index c194f570e47d1..c42598a61cffa 100644 --- a/Detectors/EMCAL/base/src/Geometry.cxx +++ b/Detectors/EMCAL/base/src/Geometry.cxx @@ -1103,6 +1103,30 @@ std::tuple Geometry::GetCellPhiEtaIndexInSModule(int supermoduleID, in return std::make_tuple(phiInSupermodule, etaInSupermodule); } +std::tuple Geometry::GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const +{ + auto [iphi, ieta] = GetCellPhiEtaIndexInSModule(supermoduleID, moduleID, phiInModule, etaInModule); + int row = iphi; + int column = ieta; + + // Add shifts wrt. supermodule and type of calorimeter + // NOTE: + // * Rows (phi) are arranged that one space is left empty between supermodules in phi + // This is due to the physical gap that forbids clustering + // * For DCAL, there is an additional empty column between two supermodules in eta + // Again, this is to account for the gap in DCAL + + row += supermoduleID / 2 * (24 + 1); + // In DCAL, leave a gap between two SMs with same phi + if (!IsDCALSM(supermoduleID)) { // EMCAL + column += supermoduleID % 2 * 48; + } else { + column += supermoduleID % 2 * (48 + 1); + } + + return std::make_tuple(row, column); +} + std::tuple Geometry::ShiftOnlineToOfflineCellIndexes(Int_t supermoduleID, Int_t iphi, Int_t ieta) const { if (supermoduleID == 13 || supermoduleID == 15 || supermoduleID == 17) { From 3291363d5d49f52a95b577c3e35b97cf41e01d95 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Tue, 6 Jan 2026 15:43:53 +0000 Subject: [PATCH 2/4] Please consider the following formatting changes --- .../EMCAL/base/include/EMCALBase/Geometry.h | 4 +--- Detectors/EMCAL/base/src/ClusterFactory.cxx | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h index 87ff092f5abcd..a0adbc39b43ba 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h +++ b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h @@ -429,15 +429,13 @@ class Geometry /// \return Position (0 - phi, 1 - eta) of the cell inside teh supermodule std::tuple GetCellPhiEtaIndexInSModule(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; - /// \brief Get topological row and column of cell in SM (same as for clusteriser with artifical gaps) /// \param supermoduleID super module number /// \param moduleID module number /// \param phiInModule index in phi direction in module /// \param etaInModule index in phi direction in module /// \return tuple with (row, column) of the cell, which is global numbering scheme - std::tuple GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; - + std::tuple GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; /// \brief Adapt cell indices in supermodule to online indexing /// \param supermoduleID super module number of the channel/cell diff --git a/Detectors/EMCAL/base/src/ClusterFactory.cxx b/Detectors/EMCAL/base/src/ClusterFactory.cxx index 1bfceb0d6a82d..9c0a3d48efa0a 100644 --- a/Detectors/EMCAL/base/src/ClusterFactory.cxx +++ b/Detectors/EMCAL/base/src/ClusterFactory.cxx @@ -504,33 +504,34 @@ void ClusterFactory::evalNExMax(gsl::span inputsIndices, A int column; double energy; }; - + std::vector cellInfos; cellInfos.reserve(inputsIndices.size()); - + for (auto iInput : inputsIndices) { auto [nSupMod, nModule, nIphi, nIeta] = mGeomPtr->GetCellIndex(mInputsContainer[iInput].getTower()); - + // get a nice topological indexing that is done in exactly the same way as used by the clusterizer // this way we can handle the shared cluster cases correctly auto [row, column] = mGeomPtr->GetTopologicalRowColumn(nSupMod, nModule, nIphi, nIeta); cellInfos.push_back({row, column, mInputsContainer[iInput].getEnergy()}); } - + // Now find local maxima using pre-computed data int nExMax = 0; for (size_t i = 0; i < cellInfos.size(); i++) { // this cell is assumed to be local maximum unless we find a higher energy cell in the neighborhood bool isExMax = true; - + // loop over all other cells in cluster for (size_t j = 0; j < cellInfos.size(); j++) { - if (i == j) continue; - + if (i == j) + continue; + // adjacent cell is any cell with adjacent phi or eta index - if (std::abs(cellInfos[i].row - cellInfos[j].row) <= 1 && + if (std::abs(cellInfos[i].row - cellInfos[j].row) <= 1 && std::abs(cellInfos[i].column - cellInfos[j].column) <= 1) { - + // if there is a cell with higher energy than the current cell, it is not a local maximum if (cellInfos[j].energy > cellInfos[i].energy) { isExMax = false; From 3230ac980ab2334c0f8ee9758f88fbef98862aeb Mon Sep 17 00:00:00 2001 From: Florian Jonas Date: Wed, 7 Jan 2026 10:01:33 +0100 Subject: [PATCH 3/4] further optimizations of EMCAL evalNExMax --- .../EMCAL/base/include/EMCALBase/Geometry.h | 2 +- Detectors/EMCAL/base/src/ClusterFactory.cxx | 33 ++++++++++--------- Detectors/EMCAL/base/src/Geometry.cxx | 4 +-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h index a0adbc39b43ba..d07f42689bf7a 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h +++ b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h @@ -435,7 +435,7 @@ class Geometry /// \param phiInModule index in phi direction in module /// \param etaInModule index in phi direction in module /// \return tuple with (row, column) of the cell, which is global numbering scheme - std::tuple GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; + std::tuple GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; /// \brief Adapt cell indices in supermodule to online indexing /// \param supermoduleID super module number of the channel/cell diff --git a/Detectors/EMCAL/base/src/ClusterFactory.cxx b/Detectors/EMCAL/base/src/ClusterFactory.cxx index 9c0a3d48efa0a..54c231009f906 100644 --- a/Detectors/EMCAL/base/src/ClusterFactory.cxx +++ b/Detectors/EMCAL/base/src/ClusterFactory.cxx @@ -499,41 +499,44 @@ template void ClusterFactory::evalNExMax(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const { // Pre-compute cell indices and energies for all cells in cluster to avoid multiple expensive geometry lookups - struct CellInfo { - int row; - int column; - double energy; - }; - - std::vector cellInfos; - cellInfos.reserve(inputsIndices.size()); + const size_t n = inputsIndices.size(); + std::vector rows; + std::vector columns; + std::vector energies; + + rows.reserve(n); + columns.reserve(n); + energies.reserve(n); for (auto iInput : inputsIndices) { auto [nSupMod, nModule, nIphi, nIeta] = mGeomPtr->GetCellIndex(mInputsContainer[iInput].getTower()); // get a nice topological indexing that is done in exactly the same way as used by the clusterizer // this way we can handle the shared cluster cases correctly - auto [row, column] = mGeomPtr->GetTopologicalRowColumn(nSupMod, nModule, nIphi, nIeta); - cellInfos.push_back({row, column, mInputsContainer[iInput].getEnergy()}); + const auto [row, column] = mGeomPtr->GetTopologicalRowColumn(nSupMod, nModule, nIphi, nIeta); + + rows.push_back(row); + columns.push_back(column); + energies.push_back(mInputsContainer[iInput].getEnergy()); } // Now find local maxima using pre-computed data int nExMax = 0; - for (size_t i = 0; i < cellInfos.size(); i++) { + for (size_t i = 0; i < n; i++) { // this cell is assumed to be local maximum unless we find a higher energy cell in the neighborhood bool isExMax = true; // loop over all other cells in cluster - for (size_t j = 0; j < cellInfos.size(); j++) { + for (size_t j = 0; j < n; j++) { if (i == j) continue; // adjacent cell is any cell with adjacent phi or eta index - if (std::abs(cellInfos[i].row - cellInfos[j].row) <= 1 && - std::abs(cellInfos[i].column - cellInfos[j].column) <= 1) { + if (std::abs(rows[i] - rows[j]) <= 1 && + std::abs(columns[i] - columns[j]) <= 1) { // if there is a cell with higher energy than the current cell, it is not a local maximum - if (cellInfos[j].energy > cellInfos[i].energy) { + if (energies[j] > energies[i]) { isExMax = false; break; } diff --git a/Detectors/EMCAL/base/src/Geometry.cxx b/Detectors/EMCAL/base/src/Geometry.cxx index c42598a61cffa..3707e22f2da57 100644 --- a/Detectors/EMCAL/base/src/Geometry.cxx +++ b/Detectors/EMCAL/base/src/Geometry.cxx @@ -1103,7 +1103,7 @@ std::tuple Geometry::GetCellPhiEtaIndexInSModule(int supermoduleID, in return std::make_tuple(phiInSupermodule, etaInSupermodule); } -std::tuple Geometry::GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const +std::tuple Geometry::GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const { auto [iphi, ieta] = GetCellPhiEtaIndexInSModule(supermoduleID, moduleID, phiInModule, etaInModule); int row = iphi; @@ -1124,7 +1124,7 @@ std::tuple Geometry::GetTopologicalRowColumn(int supermoduleID, int mo column += supermoduleID % 2 * (48 + 1); } - return std::make_tuple(row, column); + return std::make_tuple(static_cast(row), static_cast(column)); } std::tuple Geometry::ShiftOnlineToOfflineCellIndexes(Int_t supermoduleID, Int_t iphi, Int_t ieta) const From a2c4bf6ab41c345bde5beee7f9121cd9ff86aa60 Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Wed, 7 Jan 2026 09:05:34 +0000 Subject: [PATCH 4/4] Please consider the following formatting changes --- Detectors/EMCAL/base/src/ClusterFactory.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Detectors/EMCAL/base/src/ClusterFactory.cxx b/Detectors/EMCAL/base/src/ClusterFactory.cxx index 54c231009f906..970f7979ef86d 100644 --- a/Detectors/EMCAL/base/src/ClusterFactory.cxx +++ b/Detectors/EMCAL/base/src/ClusterFactory.cxx @@ -503,7 +503,7 @@ void ClusterFactory::evalNExMax(gsl::span inputsIndices, A std::vector rows; std::vector columns; std::vector energies; - + rows.reserve(n); columns.reserve(n); energies.reserve(n); @@ -514,7 +514,7 @@ void ClusterFactory::evalNExMax(gsl::span inputsIndices, A // get a nice topological indexing that is done in exactly the same way as used by the clusterizer // this way we can handle the shared cluster cases correctly const auto [row, column] = mGeomPtr->GetTopologicalRowColumn(nSupMod, nModule, nIphi, nIeta); - + rows.push_back(row); columns.push_back(column); energies.push_back(mInputsContainer[iInput].getEnergy()); @@ -522,7 +522,7 @@ void ClusterFactory::evalNExMax(gsl::span inputsIndices, A // Now find local maxima using pre-computed data int nExMax = 0; - for (size_t i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { // this cell is assumed to be local maximum unless we find a higher energy cell in the neighborhood bool isExMax = true;