Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-06-08 07:53:21

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Chao Peng, Sylvester Joosten, Whitney Armstrong, Wouter Deconinck
0003 
0004 /*
0005  *  Topological Cell Clustering Algorithm for Imaging Calorimetry
0006  *  1. group all the adjacent pixels
0007  *
0008  *  Author: Chao Peng (ANL), 06/02/2021
0009  *  Original reference: https://arxiv.org/pdf/1603.02934.pdf
0010  *
0011  *  Modifications:
0012  *
0013  *  Wouter Deconinck (Manitoba), 08/24/2024
0014  *  - converted hit storage model from std::vector to std::set sorted on layer
0015  *    where only hits remaining to be assigned to a group are in the set
0016  *  - erase hits that are too low in energy to be part of a cluster
0017  *  - converted group storage model from std::set to std::list to allow adding
0018  *    hits while keeping iterators valid
0019  *
0020  */
0021 
0022 #include "algorithms/calorimetry/ImagingTopoCluster.h"
0023 
0024 #include <Evaluator/DD4hepUnits.h>
0025 #include <edm4hep/Vector3f.h>
0026 #include <edm4hep/utils/vector_utils.h>
0027 #include <fmt/core.h>
0028 #include <cmath>
0029 #include <cstdlib>
0030 #include <gsl/pointers>
0031 #include <vector>
0032 
0033 #include "algorithms/calorimetry/ImagingTopoClusterConfig.h"
0034 
0035 namespace eicrecon {
0036 
0037 void ImagingTopoCluster::init() {
0038   // unitless conversion
0039   // sanity checks
0040   if (m_cfg.localDistXY.size() != 2) {
0041     error("Expected 2 values (x_dist, y_dist) for localDistXY");
0042     return;
0043   }
0044   if (m_cfg.layerDistEtaPhi.size() != 2) {
0045     error("Expected 2 values (eta_dist, phi_dist) for layerDistEtaPhi");
0046     return;
0047   }
0048   if (m_cfg.minClusterCenterEdep < m_cfg.minClusterHitEdep) {
0049     error("minClusterCenterEdep must be greater than or equal to minClusterHitEdep");
0050     return;
0051   }
0052 
0053   // using juggler internal units (GeV, dd4hep::mm, dd4hep::ns, dd4hep::rad)
0054   localDistXY[0]       = m_cfg.localDistXY[0] / dd4hep::mm;
0055   localDistXY[1]       = m_cfg.localDistXY[1] / dd4hep::mm;
0056   layerDistXY[0]       = m_cfg.layerDistXY[0] / dd4hep::mm;
0057   layerDistXY[1]       = m_cfg.layerDistXY[1] / dd4hep::mm;
0058   layerDistEtaPhi[0]   = m_cfg.layerDistEtaPhi[0];
0059   layerDistEtaPhi[1]   = m_cfg.layerDistEtaPhi[1] / dd4hep::rad;
0060   sectorDist           = m_cfg.sectorDist / dd4hep::mm;
0061   minClusterHitEdep    = m_cfg.minClusterHitEdep / dd4hep::GeV;
0062   minClusterCenterEdep = m_cfg.minClusterCenterEdep / dd4hep::GeV;
0063   minClusterEdep       = m_cfg.minClusterEdep / dd4hep::GeV;
0064 
0065   // summarize the clustering parameters
0066   info("Local clustering (same sector and same layer): "
0067        "Local [x, y] distance between hits <= [{:.4f} mm, {:.4f} mm].",
0068        localDistXY[0], localDistXY[1]);
0069   switch (m_cfg.layerMode) {
0070   case ImagingTopoClusterConfig::ELayerMode::etaphi:
0071     info("Neighbour layers clustering (same sector and layer id within +- {:d}: "
0072          "Global [eta, phi] distance between hits <= [{:.4f}, {:.4f} rad].",
0073          m_cfg.neighbourLayersRange, layerDistEtaPhi[0], layerDistEtaPhi[1]);
0074     break;
0075   case ImagingTopoClusterConfig::ELayerMode::xy:
0076     info("Neighbour layers clustering (same sector and layer id within +- {:d}: "
0077          "Local [x, y] distance between hits <= [{:.4f} mm, {:.4f} mm].",
0078          m_cfg.neighbourLayersRange, layerDistXY[0], layerDistXY[1]);
0079     break;
0080   default:
0081     error("Unknown layer mode.");
0082   }
0083   info("Neighbour sectors clustering (different sector): "
0084        "Global distance between hits <= {:.4f} mm.",
0085        sectorDist);
0086 }
0087 
0088 void ImagingTopoCluster::process(const Input& input, const Output& output) const {
0089 
0090   const auto [hits] = input;
0091   auto [proto]      = output;
0092 
0093   // Sort hit indices (podio collections do not support std::sort)
0094   auto compare = [&hits](const auto& a, const auto& b) {
0095     // if !(a < b) and !(b < a), then a and b are equivalent
0096     // and only one of them will be allowed in a set
0097     if ((*hits)[a].getLayer() == (*hits)[b].getLayer()) {
0098       return (*hits)[a].getObjectID().index < (*hits)[b].getObjectID().index;
0099     }
0100     return (*hits)[a].getLayer() < (*hits)[b].getLayer();
0101   };
0102   // indices contains the remaining hit indices that have not
0103   // been assigned to a group yet
0104   std::set<std::size_t, decltype(compare)> indices(compare);
0105   // set does not have a size yet, so cannot fill with iota
0106   for (std::size_t i = 0; i < hits->size(); ++i) {
0107     indices.insert(i);
0108   }
0109   // ensure no hits were dropped due to equivalency in set
0110   if (hits->size() != indices.size()) {
0111     error("equivalent hits were dropped: #hits {:d}, #indices {:d}", hits->size(), indices.size());
0112   }
0113 
0114   // Group neighbouring hits
0115   std::vector<std::list<std::size_t>> groups;
0116   // because indices changes, the loop over indices requires some care:
0117   // - we must use iterators instead of range-for
0118   // - erase returns an incremented iterator and therefore acts as idx++
0119   // - when the set becomes empty on erase, idx is invalid and idx++ will be too
0120   // (also applies to loop in bfs_group below)
0121   for (auto idx = indices.begin(); idx != indices.end();
0122        indices.empty() ? idx = indices.end() : idx) {
0123 
0124     debug("hit {:d}: local position = ({}, {}, {}), global position = ({}, {}, {}), energy = {}",
0125           *idx, (*hits)[*idx].getLocal().x, (*hits)[*idx].getLocal().y,
0126           (*hits)[*idx].getPosition().z, (*hits)[*idx].getPosition().x,
0127           (*hits)[*idx].getPosition().y, (*hits)[*idx].getPosition().z, (*hits)[*idx].getEnergy());
0128 
0129     // not energetic enough for cluster center, but could still be cluster hit
0130     if ((*hits)[*idx].getEnergy() < minClusterCenterEdep) {
0131       idx++;
0132       continue;
0133     }
0134 
0135     // create a new group, and group all the neighbouring hits
0136     groups.emplace_back(std::list{*idx});
0137     bfs_group(*hits, indices, groups.back(), *idx);
0138 
0139     // wait with erasing until after bfs_group to ensure iterator is not invalidated in bfs_group
0140     idx = indices.erase(idx); // takes role of idx++
0141   }
0142   debug("found {} potential clusters (groups of hits)", groups.size());
0143   for (std::size_t i = 0; i < groups.size(); ++i) {
0144     debug("group {}: {} hits", i, groups[i].size());
0145   }
0146 
0147   // form clusters
0148   for (const auto& group : groups) {
0149     if (group.size() < m_cfg.minClusterNhits) {
0150       continue;
0151     }
0152     double energy = 0.;
0153     for (std::size_t idx : group) {
0154       energy += (*hits)[idx].getEnergy();
0155     }
0156     if (energy < minClusterEdep) {
0157       continue;
0158     }
0159     auto pcl = proto->create();
0160     for (std::size_t idx : group) {
0161       pcl.addToHits((*hits)[idx]);
0162       pcl.addToWeights(1);
0163     }
0164   }
0165 }
0166 
0167 // helper function to group hits
0168 bool ImagingTopoCluster::is_neighbour(const edm4eic::CalorimeterHit& h1,
0169                                       const edm4eic::CalorimeterHit& h2) const {
0170   // different sectors, simple distance check
0171   if (h1.getSector() != h2.getSector()) {
0172     return std::hypot((h1.getPosition().x - h2.getPosition().x),
0173                       (h1.getPosition().y - h2.getPosition().y),
0174                       (h1.getPosition().z - h2.getPosition().z)) <= sectorDist;
0175   }
0176 
0177   // layer check
0178   int ldiff = std::abs(h1.getLayer() - h2.getLayer());
0179   // same layer, check local positions
0180   if (ldiff == 0) {
0181     return (std::abs(h1.getLocal().x - h2.getLocal().x) <= localDistXY[0]) &&
0182            (std::abs(h1.getLocal().y - h2.getLocal().y) <= localDistXY[1]);
0183   } else if (ldiff <= m_cfg.neighbourLayersRange) {
0184     switch (m_cfg.layerMode) {
0185     case eicrecon::ImagingTopoClusterConfig::ELayerMode::etaphi:
0186       return (std::abs(edm4hep::utils::eta(h1.getPosition()) -
0187                        edm4hep::utils::eta(h2.getPosition())) <= layerDistEtaPhi[0]) &&
0188              (std::abs(edm4hep::utils::angleAzimuthal(h1.getPosition()) -
0189                        edm4hep::utils::angleAzimuthal(h2.getPosition())) <= layerDistEtaPhi[1]);
0190     case eicrecon::ImagingTopoClusterConfig::ELayerMode::xy:
0191       return (std::abs(h1.getPosition().x - h2.getPosition().x) <= layerDistXY[0]) &&
0192              (std::abs(h1.getPosition().y - h2.getPosition().y) <= layerDistXY[1]);
0193     }
0194   }
0195   // not in adjacent layers
0196   return false;
0197 }
0198 
0199 } // namespace eicrecon