Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-09-27 07:02:57

0001 // Copyright (C) 2022, 2023 Chao Peng, Wouter Deconinck, Sylvester Joosten, Thomas Britton
0002 // SPDX-License-Identifier: LGPL-3.0-or-later
0003 
0004 #pragma once
0005 
0006 #include <DD4hep/Detector.h>
0007 #include <DD4hep/IDDescriptor.h>
0008 #include <algorithms/algorithm.h>
0009 #include <algorithms/geo.h>
0010 #include <edm4eic/CalorimeterHitCollection.h>
0011 #include <edm4eic/ProtoClusterCollection.h>
0012 #include <edm4hep/Vector2f.h>
0013 #include <edm4hep/utils/vector_utils.h>
0014 #include <fmt/core.h>
0015 #include <array>
0016 #include <cmath>
0017 #include <cstddef>
0018 #include <functional>
0019 #include <gsl/pointers>
0020 #include <set>
0021 #include <string>
0022 #include <string_view>
0023 #include <vector>
0024 
0025 #include "CalorimeterIslandClusterConfig.h"
0026 #include "algorithms/interfaces/WithPodConfig.h"
0027 
0028 namespace eicrecon {
0029 
0030   using CaloHit = edm4eic::CalorimeterHit;
0031 
0032   using CalorimeterIslandClusterAlgorithm = algorithms::Algorithm<
0033     algorithms::Input<
0034       edm4eic::CalorimeterHitCollection
0035     >,
0036     algorithms::Output<
0037       edm4eic::ProtoClusterCollection
0038     >
0039   >;
0040 
0041   class CalorimeterIslandCluster
0042   : public CalorimeterIslandClusterAlgorithm,
0043     public WithPodConfig<CalorimeterIslandClusterConfig> {
0044 
0045   public:
0046     CalorimeterIslandCluster(std::string_view name)
0047       : CalorimeterIslandClusterAlgorithm{name,
0048                             {"inputProtoClusterCollection"},
0049                             {"outputClusterCollection"},
0050                             "Island clustering."} {}
0051 
0052     void init() final;
0053     void process(const Input&, const Output&) const final;
0054 
0055   private:
0056     const dd4hep::Detector* m_detector{algorithms::GeoSvc::instance().detector()};
0057 
0058   public:
0059 
0060     // neighbor checking function
0061     std::function<edm4hep::Vector2f(const CaloHit&, const CaloHit&)> hitsDist;
0062 
0063     std::function<edm4hep::Vector2f(const CaloHit &h1, const CaloHit &h2)> transverseEnergyProfileMetric;
0064     double u_transverseEnergyProfileScale;
0065     double transverseEnergyProfileScaleUnits;
0066 
0067     // helper function to group hits
0068     std::function<bool(const CaloHit &h1, const CaloHit &h2)> is_neighbour;
0069 
0070     // helper function to define hit maximum
0071     std::function<bool(const CaloHit &maximum, const CaloHit &other)> is_maximum_neighbourhood;
0072 
0073     // unitless counterparts of the input parameters
0074     std::array<double, 2> neighbourDist;
0075 
0076     // Pointer to the geometry service
0077     dd4hep::IDDescriptor m_idSpec;
0078 
0079   private:
0080 
0081     // grouping function with Breadth-First Search
0082     void bfs_group(const edm4eic::CalorimeterHitCollection &hits, std::set<std::size_t> &group, std::size_t idx, std::vector<bool> &visits) const {
0083       visits[idx] = true;
0084 
0085       // not a qualified hit to participate clustering, stop here
0086       if (hits[idx].getEnergy() < m_cfg.minClusterHitEdep) {
0087         return;
0088       }
0089 
0090       group.insert(idx);
0091       size_t prev_size = 0;
0092 
0093       while (prev_size != group.size()) {
0094         prev_size = group.size();
0095         for (std::size_t idx1 : group) {
0096           // check neighbours
0097           for (std::size_t idx2 = 0; idx2 < hits.size(); ++idx2) {
0098             // not a qualified hit to participate clustering, skip
0099             if (hits[idx2].getEnergy() < m_cfg.minClusterHitEdep) {
0100               continue;
0101             }
0102             if ((!visits[idx2])
0103                 && is_neighbour(hits[idx1], hits[idx2])) {
0104               group.insert(idx2);
0105               visits[idx2] = true;
0106             }
0107           }
0108         }
0109       }
0110     }
0111 
0112     // find local maxima that above a certain threshold
0113   std::vector<std::size_t> find_maxima(const edm4eic::CalorimeterHitCollection &hits, const std::set<std::size_t> &group, bool global = false) const {
0114     std::vector<std::size_t> maxima;
0115     if (group.empty()) {
0116       return maxima;
0117     }
0118 
0119     if (global) {
0120       std::size_t mpos = *group.begin();
0121       for (auto idx : group) {
0122         if (hits[mpos].getEnergy() < hits[idx].getEnergy()) {
0123           mpos = idx;
0124         }
0125       }
0126       if (hits[mpos].getEnergy() >= m_cfg.minClusterCenterEdep) {
0127         maxima.push_back(mpos);
0128       }
0129       return maxima;
0130     }
0131 
0132     for (std::size_t idx1 : group) {
0133       // not a qualified center
0134       if (hits[idx1].getEnergy() < m_cfg.minClusterCenterEdep) {
0135         continue;
0136       }
0137 
0138       bool maximum = true;
0139       for (std::size_t idx2 : group) {
0140         if (idx1 == idx2) {
0141           continue;
0142         }
0143 
0144         if (is_maximum_neighbourhood(hits[idx1], hits[idx2]) && (hits[idx2].getEnergy() > hits[idx1].getEnergy())) {
0145           maximum = false;
0146           break;
0147         }
0148       }
0149 
0150       if (maximum) {
0151         maxima.push_back(idx1);
0152       }
0153     }
0154 
0155     return maxima;
0156   }
0157     // helper function
0158     inline static void vec_normalize(std::vector<double>& vals) {
0159         double total = 0.;
0160         for (auto& val : vals) {
0161             total += val;
0162         }
0163         for (auto& val : vals) {
0164         val /= total;
0165         }
0166     }
0167 
0168     // split a group of hits according to the local maxima
0169     //TODO: confirm protoclustering without protoclustercollection
0170   void split_group(const edm4eic::CalorimeterHitCollection &hits, std::set<std::size_t>& group, const std::vector<std::size_t>& maxima, edm4eic::ProtoClusterCollection *protoClusters) const {
0171     // special cases
0172     if (maxima.empty()) {
0173       debug("No maxima found, not building any clusters");
0174       return;
0175     } else if (maxima.size() == 1) {
0176       edm4eic::MutableProtoCluster pcl = protoClusters->create();
0177       for (std::size_t idx : group) {
0178         pcl.addToHits(hits[idx]);
0179         pcl.addToWeights(1.);
0180       }
0181 
0182       debug("A single maximum found, added one ProtoCluster");
0183 
0184       return;
0185     }
0186 
0187     // split between maxima
0188     // TODO, here we can implement iterations with profile, or even ML for better splits
0189     std::vector<double> weights(maxima.size(), 1.);
0190     std::vector<edm4eic::MutableProtoCluster> pcls;
0191     for (size_t k = 0; k < maxima.size(); ++k) {
0192       pcls.push_back(protoClusters->create());
0193     }
0194 
0195     for (std::size_t idx : group) {
0196       size_t j = 0;
0197       // calculate weights for local maxima
0198       for (std::size_t cidx : maxima) {
0199         double energy   = hits[cidx].getEnergy();
0200         double dist     = edm4hep::utils::magnitude(transverseEnergyProfileMetric(hits[cidx], hits[idx]));
0201         weights[j]      = std::exp(-dist * transverseEnergyProfileScaleUnits / m_cfg.transverseEnergyProfileScale) * energy;
0202         j += 1;
0203       }
0204 
0205       // normalize weights
0206       vec_normalize(weights);
0207 
0208       // ignore small weights
0209       for (auto& w : weights) {
0210         if (w < 0.02) {
0211           w = 0;
0212         }
0213       }
0214       vec_normalize(weights);
0215 
0216       // split energy between local maxima
0217       for (size_t k = 0; k < maxima.size(); ++k) {
0218         double weight = weights[k];
0219         if (weight <= 1e-6) {
0220           continue;
0221         }
0222         pcls[k].addToHits(hits[idx]);
0223         pcls[k].addToWeights(weight);
0224       }
0225     }
0226     debug("Multiple ({}) maxima found, added a ProtoClusters for each maximum", maxima.size());
0227   }
0228 };
0229 
0230 } // namespace eicrecon