File indexing completed on 2025-07-12 07:55:38
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 }