File indexing completed on 2025-10-26 07:58:44
0001 
0002 
0003 
0004 #include <DD4hep/Detector.h>
0005 #include <edm4eic/CalorimeterHit.h>
0006 #include <edm4hep/Vector3f.h>
0007 #include <edm4hep/utils/vector_utils.h>
0008 #include <fmt/core.h>
0009 #include <podio/ObjectID.h>
0010 #include <podio/RelationRange.h>
0011 #include <cmath>
0012 #include <cstddef>
0013 #include <gsl/pointers>
0014 
0015 
0016 #include "TrackClusterMergeSplitter.h"
0017 #include "algorithms/calorimetry/TrackClusterMergeSplitterConfig.h"
0018 
0019 namespace eicrecon {
0020 
0021 
0022 
0023 
0024 void TrackClusterMergeSplitter::init() {
0025 
0026   
0027   m_idCalo = m_geo.detector()->constant<int>(m_cfg.idCalo);
0028   debug("Collecting projections to detector with system id {}", m_idCalo);
0029 
0030 } 
0031 
0032 
0033 
0034 
0035 
0036 
0037 
0038 
0039 
0040 
0041 
0042 
0043 
0044 
0045 
0046 
0047 
0048 
0049 
0050 
0051 
0052 
0053 
0054 
0055 
0056 
0057 
0058 void TrackClusterMergeSplitter::process(const TrackClusterMergeSplitter::Input& input,
0059                                         const TrackClusterMergeSplitter::Output& output) const {
0060 
0061   
0062   const auto [in_protoclusters, in_projections] = input;
0063   auto [out_protoclusters]                      = output;
0064 
0065   
0066   if (in_protoclusters->empty()) {
0067     debug("No proto-clusters in input collection.");
0068     return;
0069   }
0070 
0071   
0072   
0073   
0074   VecProj vecProject;
0075   get_projections(in_projections, vecProject);
0076 
0077   
0078   
0079   
0080   MapToVecProj mapProjToSplit;
0081   if (vecProject.empty()) {
0082     debug("No projections to match clusters to.");
0083     return;
0084   }
0085   match_clusters_to_tracks(in_protoclusters, vecProject, mapProjToSplit);
0086 
0087   
0088   
0089   
0090   SetClust setUsedClust;
0091   MapToVecClust mapClustToMerge;
0092   for (auto& [clustSeed, vecMatchProj] : mapProjToSplit) {
0093 
0094     
0095     
0096     auto projSeed = vecMatchProj.front();
0097 
0098     
0099     if (setUsedClust.contains(clustSeed)) {
0100       continue;
0101     }
0102 
0103     
0104     mapClustToMerge[clustSeed].push_back(clustSeed);
0105     setUsedClust.insert(clustSeed);
0106 
0107     
0108     const float eClustSeed = get_cluster_energy(clustSeed);
0109     const float eProjSeed  = m_cfg.avgEP * edm4hep::utils::magnitude(projSeed.momentum);
0110 
0111     
0112     
0113     
0114     const float sigSeed = (eClustSeed - eProjSeed) / m_cfg.sigEP;
0115     trace("Seed energy = {}, expected energy = {}, significance = {}", eClustSeed, eProjSeed,
0116           sigSeed);
0117 
0118     
0119     
0120     
0121     
0122     if (sigSeed > m_cfg.minSigCut) {
0123       continue;
0124     }
0125 
0126     
0127     const auto posSeed  = get_cluster_position(clustSeed);
0128     const float etaSeed = edm4hep::utils::eta(posSeed);
0129     const float phiSeed = edm4hep::utils::angleAzimuthal(posSeed);
0130 
0131     
0132     float eClustSum = eClustSeed;
0133     float sigSum    = sigSeed;
0134     for (auto in_cluster : *in_protoclusters) {
0135 
0136       
0137       if (setUsedClust.contains(in_cluster)) {
0138         continue;
0139       }
0140 
0141       
0142       const auto posClust  = get_cluster_position(in_cluster);
0143       const float etaClust = edm4hep::utils::eta(posClust);
0144       const float phiClust = edm4hep::utils::angleAzimuthal(posClust);
0145 
0146       
0147       const float drToSeed =
0148           std::hypot(etaSeed - etaClust, std::remainder(phiSeed - phiClust, 2. * M_PI));
0149 
0150       
0151       
0152       
0153       if (drToSeed > m_cfg.drAdd) {
0154         continue;
0155       }
0156       mapClustToMerge[clustSeed].push_back(in_cluster);
0157       setUsedClust.insert(in_cluster);
0158 
0159       
0160       
0161       
0162       if (mapProjToSplit.contains(in_cluster)) {
0163         vecMatchProj.insert(vecMatchProj.end(), mapProjToSplit[in_cluster].begin(),
0164                             mapProjToSplit[in_cluster].end());
0165       }
0166 
0167       
0168       eClustSum += get_cluster_energy(in_cluster);
0169       sigSum = (eClustSum - eProjSeed) / m_cfg.sigEP;
0170       trace("{} clusters to merge: current sum = {}, current significance = {}, {} track(s) "
0171             "pointing to merged cluster",
0172             mapClustToMerge[clustSeed].size(), eClustSum, sigSum, vecMatchProj.size());
0173     } 
0174   } 
0175 
0176   
0177   
0178   
0179   
0180   for (auto& [clustSeed, vecClustToMerge] : mapClustToMerge) {
0181     merge_and_split_clusters(vecClustToMerge, mapProjToSplit[clustSeed], out_protoclusters);
0182   } 
0183 
0184   
0185   
0186   
0187   for (auto in_cluster : *in_protoclusters) {
0188 
0189     
0190     if (setUsedClust.contains(in_cluster)) {
0191       continue;
0192     }
0193 
0194     
0195     edm4eic::MutableProtoCluster out_cluster = in_cluster.clone();
0196     out_protoclusters->push_back(out_cluster);
0197     trace("Copied input cluster {} onto output cluster {}", in_cluster.getObjectID().index,
0198           out_cluster.getObjectID().index);
0199 
0200   } 
0201 
0202 } 
0203 
0204 
0205 
0206 
0207 void TrackClusterMergeSplitter::get_projections(const edm4eic::TrackSegmentCollection* projections,
0208                                                 VecProj& relevant_projects) const {
0209 
0210   
0211   if (projections->empty()) {
0212     debug("No projections in input collection.");
0213     return;
0214   }
0215 
0216   
0217   for (auto project : *projections) {
0218     for (auto point : project.getPoints()) {
0219       if ((point.system == m_idCalo) && (point.surface == 1)) {
0220         relevant_projects.push_back(point);
0221       }
0222     } 
0223   } 
0224   debug("Collected relevant projections: {} to process", relevant_projects.size());
0225 
0226 } 
0227 
0228 
0229 
0230 
0231 
0232 
0233 void TrackClusterMergeSplitter::match_clusters_to_tracks(
0234     const edm4eic::ProtoClusterCollection* clusters, const VecProj& projections,
0235     MapToVecProj& matches) const {
0236 
0237   
0238   for (auto project : projections) {
0239 
0240     
0241     
0242     const float etaProj = edm4hep::utils::eta(project.position);
0243     const float phiProj = edm4hep::utils::angleAzimuthal(project.position);
0244 
0245     
0246     edm4eic::ProtoCluster match;
0247 
0248     
0249     bool foundMatch = false;
0250     float dMatch    = m_cfg.drAdd;
0251     for (auto cluster : *clusters) {
0252 
0253       
0254       const auto posClust  = get_cluster_position(cluster);
0255       const float etaClust = edm4hep::utils::eta(posClust);
0256       const float phiClust = edm4hep::utils::angleAzimuthal(posClust);
0257 
0258       
0259       const float dist =
0260           std::hypot(etaProj - etaClust, std::remainder(phiProj - phiClust, 2. * M_PI));
0261 
0262       
0263       if (dist <= dMatch) {
0264         foundMatch = true;
0265         dMatch     = dist;
0266         match      = cluster;
0267       }
0268     } 
0269 
0270     
0271     if (foundMatch) {
0272       matches[match].push_back(project);
0273       trace("Matched cluster to track projection: eta-phi distance = {}", dMatch);
0274     }
0275   } 
0276   debug("Finished matching clusters to track projections: {} matches", matches.size());
0277 
0278 } 
0279 
0280 
0281 
0282 
0283 
0284 
0285 
0286 
0287 void TrackClusterMergeSplitter::merge_and_split_clusters(
0288     const VecClust& to_merge, const VecProj& to_split,
0289     edm4eic::ProtoClusterCollection* out_protoclusters) const {
0290 
0291   
0292   if (to_split.size() == 1) {
0293     edm4eic::MutableProtoCluster new_clust = out_protoclusters->create();
0294     for (const auto& old_clust : to_merge) {
0295       for (const auto& hit : old_clust.getHits()) {
0296         new_clust.addToHits(hit);
0297         new_clust.addToWeights(1.);
0298       }
0299       trace("Merged input cluster {} into output cluster {}", old_clust.getObjectID().index,
0300             new_clust.getObjectID().index);
0301     }
0302     return;
0303   }
0304 
0305   
0306   std::vector<edm4eic::MutableProtoCluster> new_clusters;
0307   for (const auto& proj [[maybe_unused]] : to_split) {
0308     new_clusters.push_back(out_protoclusters->create());
0309   }
0310   trace("Splitting merged cluster across {} tracks", to_split.size());
0311 
0312   
0313   std::vector<float> weights(to_split.size(), 1.);
0314   for (const auto& old_clust : to_merge) {
0315     for (const auto& hit : old_clust.getHits()) {
0316 
0317       
0318       for (std::size_t iProj = 0; const auto& proj : to_split) {
0319 
0320         
0321         const float etaProj = edm4hep::utils::eta(proj.position);
0322         const float phiProj = edm4hep::utils::angleAzimuthal(proj.position);
0323 
0324         
0325         const float etaHit = edm4hep::utils::eta(hit.getPosition());
0326         const float phiHit = edm4hep::utils::angleAzimuthal(hit.getPosition());
0327 
0328         
0329         const float mom = edm4hep::utils::magnitude(proj.momentum);
0330         const float dist =
0331             std::hypot(etaHit - etaProj, std::remainder(phiHit - phiProj, 2. * M_PI));
0332 
0333         
0334         weights[iProj] = std::exp(-1. * dist / m_cfg.transverseEnergyProfileScale) * mom;
0335         ++iProj;
0336       }
0337 
0338       
0339       float wTotal = 0.;
0340       for (const float weight : weights) {
0341         wTotal += weight;
0342       }
0343       for (float& weight : weights) {
0344         weight /= wTotal;
0345       }
0346 
0347       
0348       for (std::size_t iProj = 0; auto& new_clust : new_clusters) {
0349         new_clust.addToHits(hit);
0350         new_clust.addToWeights(weights[iProj]);
0351       }
0352 
0353     } 
0354   } 
0355 
0356 } 
0357 
0358 
0359 
0360 
0361 float TrackClusterMergeSplitter::get_cluster_energy(const edm4eic::ProtoCluster& clust) const {
0362 
0363   float eClust = 0.;
0364   for (auto hit : clust.getHits()) {
0365     eClust += hit.getEnergy();
0366   }
0367   return eClust / m_cfg.sampFrac;
0368 
0369 } 
0370 
0371 
0372 
0373 
0374 edm4hep::Vector3f
0375 TrackClusterMergeSplitter::get_cluster_position(const edm4eic::ProtoCluster& clust) const {
0376 
0377   
0378   const float eClust = get_cluster_energy(clust) * m_cfg.sampFrac;
0379 
0380   
0381   float wTotal = 0.;
0382   edm4hep::Vector3f position(0., 0., 0.);
0383   for (auto hit : clust.getHits()) {
0384 
0385     
0386     float weight = hit.getEnergy() / eClust;
0387     wTotal += weight;
0388 
0389     
0390     position = position + (hit.getPosition() * weight);
0391   }
0392 
0393   float norm = 1.;
0394   if (wTotal == 0.) {
0395     warning("Total weight of 0 in position calculation!");
0396   } else {
0397     norm = wTotal;
0398   }
0399   return position / norm;
0400 
0401 } 
0402 
0403 }