Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-02-22 09:55:35

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Sylvester Joosten, Chao, Chao Peng, Wouter Deconinck, Jihee Kim, Whitney Armstrong
0003 
0004 /*
0005  *  Island Clustering Algorithm for Calorimeter Blocks
0006  *  1. group all the adjacent modules
0007  *  2. split the groups between their local maxima with the energy deposit above <minClusterCenterEdep>
0008  *
0009  *  Author: Chao Peng (ANL), 09/27/2020
0010  *  References:
0011  *      https://cds.cern.ch/record/687345/files/note01_034.pdf
0012  *      https://www.jlab.org/primex/weekly_meetings/primexII/slides_2012_01_20/island_algorithm.pdf
0013  */
0014 #include <algorithm>
0015 #include <functional>
0016 #include <tuple>
0017 
0018 #include "fmt/format.h"
0019 
0020 #include "Gaudi/Property.h"
0021 #include "GaudiAlg/GaudiAlgorithm.h"
0022 #include "GaudiAlg/GaudiTool.h"
0023 #include "GaudiAlg/Transformer.h"
0024 #include "GaudiKernel/PhysicalConstants.h"
0025 #include "GaudiKernel/RndmGenerators.h"
0026 #include "GaudiKernel/ToolHandle.h"
0027 
0028 #include "DDRec/CellIDPositionConverter.h"
0029 #include "DDRec/Surface.h"
0030 #include "DDRec/SurfaceManager.h"
0031 
0032 #include "JugBase/DataHandle.h"
0033 #include "JugBase/IGeoSvc.h"
0034 
0035 // Event Model related classes
0036 #include "edm4eic/CalorimeterHitCollection.h"
0037 #include "edm4eic/ClusterCollection.h"
0038 #include "edm4eic/ProtoClusterCollection.h"
0039 #include "edm4hep/utils/vector_utils.h"
0040 #include "edm4hep/Vector3f.h"
0041 #include "edm4hep/Vector2f.h"
0042 
0043 #if defined __has_include
0044 #  if __has_include ("edm4eic/Vector3f.h")
0045 #    include "edm4eic/Vector3f.h"
0046 #  endif
0047 #  if __has_include ("edm4eic/Vector2f.h")
0048 #    include "edm4eic/Vector2f.h"
0049 #  endif
0050 #endif
0051 
0052 namespace edm4eic {
0053   class Vector2f;
0054   class Vector3f;
0055 }
0056 
0057 using namespace Gaudi::Units;
0058 
0059 namespace {
0060 
0061 using CaloHit = edm4eic::CalorimeterHit;
0062 using CaloHitCollection = edm4eic::CalorimeterHitCollection;
0063 
0064 using Vector2f = std::conditional_t<
0065   std::is_same_v<decltype(edm4eic::CalorimeterHitData::position), edm4hep::Vector3f>,
0066   edm4hep::Vector2f,
0067   edm4eic::Vector2f
0068 >;
0069 
0070 // helper functions to get distance between hits
0071 static Vector2f localDistXY(const CaloHit& h1, const CaloHit& h2) {
0072   const auto delta = h1.getLocal() - h2.getLocal();
0073   return {delta.x, delta.y};
0074 }
0075 static Vector2f localDistXZ(const CaloHit& h1, const CaloHit& h2) {
0076   const auto delta = h1.getLocal() - h2.getLocal();
0077   return {delta.x, delta.z};
0078 }
0079 static Vector2f localDistYZ(const CaloHit& h1, const CaloHit& h2) {
0080   const auto delta = h1.getLocal() - h2.getLocal();
0081   return {delta.y, delta.z};
0082 }
0083 static Vector2f dimScaledLocalDistXY(const CaloHit& h1, const CaloHit& h2) {
0084   const auto delta = h1.getLocal() - h2.getLocal();
0085   const auto dimsum = h1.getDimension() + h2.getDimension();
0086   return {2 * delta.x / dimsum.x, 2 * delta.y / dimsum.y};
0087 }
0088 static Vector2f globalDistRPhi(const CaloHit& h1, const CaloHit& h2) {
0089   using vector_type = decltype(Vector2f::a);
0090   return {
0091     static_cast<vector_type>(
0092       edm4hep::utils::magnitude(h1.getPosition()) - edm4hep::utils::magnitude(h2.getPosition())
0093     ),
0094     static_cast<vector_type>(
0095       edm4hep::utils::angleAzimuthal(h1.getPosition()) - edm4hep::utils::angleAzimuthal(h2.getPosition())
0096     )
0097   };
0098 }
0099 static Vector2f globalDistEtaPhi(const CaloHit& h1,
0100                                        const CaloHit& h2) {
0101   using vector_type = decltype(Vector2f::a);
0102   return {
0103     static_cast<vector_type>(
0104       edm4hep::utils::eta(h1.getPosition()) - edm4hep::utils::eta(h2.getPosition())
0105     ),
0106     static_cast<vector_type>(
0107       edm4hep::utils::angleAzimuthal(h1.getPosition()) - edm4hep::utils::angleAzimuthal(h2.getPosition())
0108     )
0109   };
0110 }
0111 // name: {method, units}
0112 static std::map<std::string,
0113                 std::tuple<std::function<Vector2f(const CaloHit&, const CaloHit&)>, std::vector<double>>>
0114     distMethods{
0115         {"localDistXY", {localDistXY, {mm, mm}}},        {"localDistXZ", {localDistXZ, {mm, mm}}},
0116         {"localDistYZ", {localDistYZ, {mm, mm}}},        {"dimScaledLocalDistXY", {dimScaledLocalDistXY, {1., 1.}}},
0117         {"globalDistRPhi", {globalDistRPhi, {mm, rad}}}, {"globalDistEtaPhi", {globalDistEtaPhi, {1., rad}}},
0118     };
0119 
0120 } // namespace
0121 namespace Jug::Reco {
0122 
0123 /**
0124  *  Island Clustering Algorithm for Calorimeter Blocks.
0125  *
0126  *  1. group all the adjacent modules
0127  *  2. split the groups between their local maxima with the energy deposit above <minClusterCenterEdep>
0128  *
0129  *  References:
0130  *      https://cds.cern.ch/record/687345/files/note01_034.pdf
0131  *      https://www.jlab.org/primex/weekly_meetings/primexII/slides_2012_01_20/island_algorithm.pdf
0132  *
0133  * \ingroup reco
0134  */
0135 class CalorimeterIslandCluster : public GaudiAlgorithm {
0136 private:
0137   Gaudi::Property<bool> m_splitCluster{this, "splitCluster", true};
0138   Gaudi::Property<double> m_minClusterHitEdep{this, "minClusterHitEdep", 0.};
0139   Gaudi::Property<double> m_minClusterCenterEdep{this, "minClusterCenterEdep", 50.0 * MeV};
0140   DataHandle<CaloHitCollection> m_inputHitCollection{"inputHitCollection", Gaudi::DataHandle::Reader, this};
0141   DataHandle<edm4eic::ProtoClusterCollection> m_outputProtoCollection{"outputProtoClusterCollection",
0142                                                                   Gaudi::DataHandle::Writer, this};
0143 
0144   // neighbour checking distances
0145   Gaudi::Property<double> m_sectorDist{this, "sectorDist", 5.0 * cm};
0146   Gaudi::Property<std::vector<double>> u_localDistXY{this, "localDistXY", {}};
0147   Gaudi::Property<std::vector<double>> u_localDistXZ{this, "localDistXZ", {}};
0148   Gaudi::Property<std::vector<double>> u_localDistYZ{this, "localDistYZ", {}};
0149   Gaudi::Property<std::vector<double>> u_globalDistRPhi{this, "globalDistRPhi", {}};
0150   Gaudi::Property<std::vector<double>> u_globalDistEtaPhi{this, "globalDistEtaPhi", {}};
0151   Gaudi::Property<std::vector<double>> u_dimScaledLocalDistXY{this, "dimScaledLocalDistXY", {1.8, 1.8}};
0152   // neighbor checking function
0153   std::function<Vector2f(const CaloHit&, const CaloHit&)> hitsDist;
0154 
0155   // unitless counterparts of the input parameters
0156   double minClusterHitEdep{0}, minClusterCenterEdep{0}, sectorDist{0};
0157   std::array<double, 2> neighbourDist = {0., 0.};
0158 
0159 public:
0160   CalorimeterIslandCluster(const std::string& name, ISvcLocator* svcLoc) : GaudiAlgorithm(name, svcLoc) {
0161     declareProperty("inputHitCollection", m_inputHitCollection, "");
0162     declareProperty("outputProtoClusterCollection", m_outputProtoCollection, "");
0163   }
0164 
0165   StatusCode initialize() override {
0166     if (GaudiAlgorithm::initialize().isFailure()) {
0167       return StatusCode::FAILURE;
0168     }
0169 
0170     // unitless conversion, keep consistency with juggler internal units (GeV, mm, ns, rad)
0171     minClusterHitEdep    = m_minClusterHitEdep.value() / GeV;
0172     minClusterCenterEdep = m_minClusterCenterEdep.value() / GeV;
0173     sectorDist           = m_sectorDist.value() / mm;
0174 
0175     // set coordinate system
0176     auto set_dist_method = [this](const Gaudi::Property<std::vector<double>>& uprop) {
0177       if (uprop.size() == 0) {
0178         return false;
0179       }
0180       auto& [method, units] = distMethods[uprop.name()];
0181       if (uprop.size() != units.size()) {
0182         info() << units.size() << endmsg;
0183         warning() << fmt::format("Expect {} values from {}, received {}: ({}), ignored it.", units.size(), uprop.name(),
0184                                  uprop.size(), fmt::join(uprop.value(), ", "))
0185                   << endmsg;
0186         return false;
0187       } else {
0188         for (size_t i = 0; i < units.size(); ++i) {
0189           neighbourDist[i] = uprop.value()[i] / units[i];
0190         }
0191         hitsDist = method;
0192         info() << fmt::format("Clustering uses {} with distances <= [{}]", uprop.name(), fmt::join(neighbourDist, ","))
0193                << endmsg;
0194       }
0195       return true;
0196     };
0197 
0198     std::vector<Gaudi::Property<std::vector<double>>> uprops{
0199         u_localDistXY,
0200         u_localDistXZ,
0201         u_localDistYZ,
0202         u_globalDistRPhi,
0203         u_globalDistEtaPhi,
0204         // default one should be the last one
0205         u_dimScaledLocalDistXY,
0206     };
0207 
0208     bool method_found = false;
0209     for (auto& uprop : uprops) {
0210       if (set_dist_method(uprop)) {
0211         method_found = true;
0212         break;
0213       }
0214     }
0215     if (not method_found) {
0216       error() << "Cannot determine the clustering coordinates" << endmsg;
0217       return StatusCode::FAILURE;
0218     }
0219 
0220     return StatusCode::SUCCESS;
0221   }
0222 
0223   StatusCode execute() override {
0224     // input collections
0225     const auto& hits = *(m_inputHitCollection.get());
0226     // Create output collections
0227     auto& proto = *(m_outputProtoCollection.createAndPut());
0228 
0229     // group neighboring hits
0230     std::vector<std::vector<std::pair<uint32_t, CaloHit>>> groups;
0231 
0232     std::vector<bool> visits(hits.size(), false);
0233     for (size_t i = 0; i < hits.size(); ++i) {
0234       if (msgLevel(MSG::DEBUG)) {
0235         const auto& hit = hits[i];
0236         debug() << fmt::format("hit {:d}: energy = {:.4f} MeV, local = ({:.4f}, {:.4f}) mm, "
0237                                "global=({:.4f}, {:.4f}, {:.4f}) mm",
0238                                i, hit.getEnergy() * 1000., hit.getLocal().x, hit.getLocal().y, hit.getPosition().x,
0239                                hit.getPosition().y, hit.getPosition().z)
0240                 << endmsg;
0241       }
0242       // already in a group
0243       if (visits[i]) {
0244         continue;
0245       }
0246       groups.emplace_back();
0247       // create a new group, and group all the neighboring hits
0248       dfs_group(groups.back(), i, hits, visits);
0249     }
0250 
0251     for (auto& group : groups) {
0252       if (group.empty()) {
0253         continue;
0254       }
0255       auto maxima = find_maxima(group, !m_splitCluster.value());
0256       split_group(group, maxima, proto);
0257       if (msgLevel(MSG::DEBUG)) {
0258         debug() << "hits in a group: " << group.size() << ", "
0259                 << "local maxima: " << maxima.size() << endmsg;
0260       }
0261     }
0262 
0263     return StatusCode::SUCCESS;
0264   }
0265 
0266 private:
0267   // helper function to group hits
0268   inline bool is_neighbour(const CaloHit& h1, const CaloHit& h2) const {
0269     // in the same sector
0270     if (h1.getSector() == h2.getSector()) {
0271       auto dist = hitsDist(h1, h2);
0272       return (dist.a <= neighbourDist[0]) && (dist.b <= neighbourDist[1]);
0273       // different sector, local coordinates do not work, using global coordinates
0274     } else {
0275       // sector may have rotation (barrel), so z is included
0276       return (edm4hep::utils::magnitude(h1.getPosition() - h2.getPosition()) <= sectorDist);
0277     }
0278   }
0279 
0280   // grouping function with Depth-First Search
0281   void dfs_group(std::vector<std::pair<uint32_t, CaloHit>>& group, int idx,
0282                  const CaloHitCollection& hits, std::vector<bool>& visits) const {
0283     // not a qualified hit to particpate clustering, stop here
0284     if (hits[idx].getEnergy() < minClusterHitEdep) {
0285       visits[idx] = true;
0286       return;
0287     }
0288 
0289     group.emplace_back(idx, hits[idx]);
0290     visits[idx] = true;
0291     for (size_t i = 0; i < hits.size(); ++i) {
0292       if (visits[i] || !is_neighbour(hits[idx], hits[i])) {
0293         continue;
0294       }
0295       dfs_group(group, i, hits, visits);
0296     }
0297   }
0298 
0299   // find local maxima that above a certain threshold
0300   std::vector<CaloHit>
0301   find_maxima(const std::vector<std::pair<uint32_t, CaloHit>>& group,
0302               bool global = false) const {
0303     std::vector<CaloHit> maxima;
0304     if (group.empty()) {
0305       return maxima;
0306     }
0307 
0308     if (global) {
0309       int mpos = 0;
0310       for (size_t i = 0; i < group.size(); ++i) {
0311         if (group[mpos].second.getEnergy() < group[i].second.getEnergy()) {
0312           mpos = i;
0313         }
0314       }
0315       if (group[mpos].second.getEnergy() >= minClusterCenterEdep) {
0316         maxima.push_back(group[mpos].second);
0317       }
0318       return maxima;
0319     }
0320 
0321     for (const auto& [idx, hit] : group) {
0322       // not a qualified center
0323       if (hit.getEnergy() < minClusterCenterEdep) {
0324         continue;
0325       }
0326 
0327       bool maximum = true;
0328       for (const auto& [idx2, hit2] : group) {
0329         if (hit == hit2) {
0330           continue;
0331         }
0332 
0333         if (is_neighbour(hit, hit2) && hit2.getEnergy() > hit.getEnergy()) {
0334           maximum = false;
0335           break;
0336         }
0337       }
0338 
0339       if (maximum) {
0340         maxima.push_back(hit);
0341       }
0342     }
0343 
0344     return maxima;
0345   }
0346 
0347   // helper function
0348   inline static void vec_normalize(std::vector<double>& vals) {
0349     double total = 0.;
0350     for (auto& val : vals) {
0351       total += val;
0352     }
0353     for (auto& val : vals) {
0354       val /= total;
0355     }
0356   }
0357 
0358   // split a group of hits according to the local maxima
0359   void split_group(std::vector<std::pair<uint32_t, CaloHit>>& group, const std::vector<CaloHit>& maxima,
0360                    edm4eic::ProtoClusterCollection& proto) const {
0361     // special cases
0362     if (maxima.empty()) {
0363       if (msgLevel(MSG::VERBOSE)) {
0364         verbose() << "No maxima found, not building any clusters" << endmsg;
0365       }
0366       return;
0367     } else if (maxima.size() == 1) {
0368       edm4eic::MutableProtoCluster pcl;
0369       for (auto& [idx, hit] : group) {
0370         pcl.addToHits(hit);
0371         pcl.addToWeights(1.);
0372       }
0373       proto.push_back(pcl);
0374       if (msgLevel(MSG::VERBOSE)) {
0375         verbose() << "A single maximum found, added one ProtoCluster" << endmsg;
0376       }
0377       return;
0378     }
0379 
0380     // split between maxima
0381     // TODO, here we can implement iterations with profile, or even ML for better splits
0382     std::vector<double> weights(maxima.size(), 1.);
0383     std::vector<edm4eic::MutableProtoCluster> pcls;
0384     for (size_t k = 0; k < maxima.size(); ++k) {
0385       pcls.emplace_back();
0386     }
0387 
0388     size_t i = 0;
0389     for (const auto& [idx, hit] : group) {
0390       size_t j = 0;
0391       // calculate weights for local maxima
0392       for (const auto& chit : maxima) {
0393         double dist_ref = chit.getDimension().x;
0394         double energy   = chit.getEnergy();
0395         double dist     = edm4hep::utils::magnitude(hitsDist(chit, hit));
0396         weights[j]      = std::exp(-dist / dist_ref) * energy;
0397         j += 1;
0398       }
0399 
0400       // normalize weights
0401       vec_normalize(weights);
0402 
0403       // ignore small weights
0404       for (auto& w : weights) {
0405         if (w < 0.02) {
0406           w = 0;
0407         }
0408       }
0409       vec_normalize(weights);
0410 
0411       // split energy between local maxima
0412       for (size_t k = 0; k < maxima.size(); ++k) {
0413         double weight = weights[k];
0414         if (weight <= 1e-6) {
0415           continue;
0416         }
0417         pcls[k].addToHits(hit);
0418         pcls[k].addToWeights(weight);
0419       }
0420       i += 1;
0421     }
0422     for (auto& pcl : pcls) {
0423       proto.push_back(pcl);
0424     }
0425     if (msgLevel(MSG::VERBOSE)) {
0426       verbose() << "Multiple (" << maxima.size() << ") maxima found, added a ProtoClusters for each maximum" << endmsg;
0427     }
0428   }
0429 };
0430 
0431 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
0432 DECLARE_COMPONENT(CalorimeterIslandCluster)
0433 
0434 } // namespace Jug::Reco