File indexing completed on 2026-09-22 08:05:50
0001
0002
0003
0004 #include <edm4eic/Track.h>
0005 #include <edm4eic/TrackPoint.h>
0006 #include <edm4hep/Vector3f.h>
0007 #include <edm4hep/utils/vector_utils.h>
0008 #include <podio/ObjectID.h>
0009 #include <podio/RelationRange.h>
0010 #include <podio/detail/Link.h>
0011 #include <podio/detail/LinkCollectionImpl.h>
0012 #include <cmath>
0013 #include <cstddef>
0014 #include <memory>
0015 #include <set>
0016 #include <tuple>
0017 #include <utility>
0018
0019 #include "TrackClusterMergeSplitter.h"
0020 #include "algorithms/calorimetry/TrackClusterMergeSplitterConfig.h"
0021
0022 namespace eicrecon {
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040 void TrackClusterMergeSplitter::process(const TrackClusterMergeSplitter::Input& input,
0041 const TrackClusterMergeSplitter::Output& output) const {
0042
0043
0044 const auto [in_matches, in_clusters, in_projections] = input;
0045 auto [out_protos, out_links] = output;
0046
0047
0048 if (in_clusters->empty()) {
0049 debug("No clusters in input collection.");
0050 return;
0051 }
0052
0053
0054 if (in_matches->empty()) {
0055 debug("No matched tracks in collection.");
0056 return;
0057 }
0058
0059
0060
0061
0062 std::map<edm4eic::Cluster, segment_vector, CompareObjectID<edm4eic::Cluster>> mapProjToSplit;
0063 for (const auto& match : *in_matches) {
0064 for (const auto& project : *in_projections) {
0065
0066
0067 if (match.getTrack() != project.getTrack()) {
0068 continue;
0069 }
0070 mapProjToSplit[match.getCluster()].push_back(project);
0071 }
0072 }
0073
0074
0075 const float drAdd2 = m_cfg.drAdd * m_cfg.drAdd;
0076 trace("Squared radius of merging window: radius = {}, radius^2 = {}", m_cfg.drAdd, drAdd2);
0077
0078
0079
0080
0081 std::set<edm4eic::Cluster> setUsedClust;
0082 std::map<edm4eic::Cluster, cluster_vector, CompareObjectID<edm4eic::Cluster>> mapClustToMerge;
0083 for (auto& [clust_seed, vecMatchProj] : mapProjToSplit) {
0084
0085
0086
0087
0088 std::optional<edm4eic::TrackPoint> project_seed;
0089 for (auto point : vecMatchProj.front().getPoints()) {
0090 if (point.surface == m_cfg.surfaceToUse) {
0091 project_seed = point;
0092 }
0093 }
0094 if (!project_seed) {
0095 continue;
0096 }
0097
0098
0099 if (setUsedClust.contains(clust_seed)) {
0100 continue;
0101 }
0102
0103
0104 const float eClustSeed = clust_seed.getEnergy();
0105 const float eProjSeed = m_cfg.avgEP * edm4hep::utils::magnitude(project_seed.value().momentum);
0106
0107
0108
0109
0110 const float sigSeed = (eClustSeed - eProjSeed) / m_cfg.sigEP;
0111 trace("Seed energy = {}, expected energy = {}, significance = {}", eClustSeed, eProjSeed,
0112 sigSeed);
0113
0114
0115
0116
0117
0118 if (sigSeed > m_cfg.minSigCut) {
0119 continue;
0120 }
0121
0122
0123 const float etaSeed = edm4hep::utils::eta(clust_seed.getPosition());
0124 const float phiSeed = edm4hep::utils::angleAzimuthal(clust_seed.getPosition());
0125
0126
0127 float eClustSum = eClustSeed;
0128 for (auto cluster : *in_clusters) {
0129
0130
0131 if (setUsedClust.contains(cluster)) {
0132 continue;
0133 }
0134
0135
0136 if (clust_seed == cluster) {
0137 continue;
0138 }
0139
0140
0141 const float etaClust = edm4hep::utils::eta(cluster.getPosition());
0142 const float phiClust = edm4hep::utils::angleAzimuthal(cluster.getPosition());
0143
0144
0145 const float dEtaToSeed = etaSeed - etaClust;
0146 const float dPhiToSeed = std::remainder(phiSeed - phiClust, 2. * M_PI);
0147 const float drToSeed2 = (dEtaToSeed * dEtaToSeed) + (dPhiToSeed * dPhiToSeed);
0148 trace("Distances from cluster to seed: dEta = {}, dPhi = {}, dr^2 = {}", dEtaToSeed,
0149 dPhiToSeed, drToSeed2);
0150
0151
0152
0153
0154 if (drToSeed2 > drAdd2) {
0155 continue;
0156 }
0157 mapClustToMerge[clust_seed].push_back(cluster);
0158 setUsedClust.insert(cluster);
0159 eClustSum += cluster.getEnergy();
0160
0161
0162
0163
0164 if (mapProjToSplit.contains(cluster)) {
0165 vecMatchProj.insert(vecMatchProj.end(), mapProjToSplit[cluster].begin(),
0166 mapProjToSplit[cluster].end());
0167 }
0168
0169 const float sigSum = (eClustSum - eProjSeed) / m_cfg.sigEP;
0170 trace("{} clusters to merge: current sum = {}, current significance = {}, {} track(s) "
0171 "pointing to merged cluster",
0172 mapClustToMerge[clust_seed].size(), eClustSum, sigSum, vecMatchProj.size());
0173 }
0174
0175
0176 if (mapClustToMerge.count(clust_seed) > 0) {
0177 setUsedClust.insert(clust_seed);
0178 }
0179
0180 }
0181
0182
0183
0184
0185
0186 for (auto& [clust_seed, vecClustToMerge] : mapClustToMerge) {
0187
0188
0189 protocluster_vector new_protos;
0190 for ([[maybe_unused]] const auto& project : mapProjToSplit[clust_seed]) {
0191 new_protos.push_back(out_protos->create());
0192 }
0193
0194 vecClustToMerge.push_back(clust_seed);
0195 merge_and_split_clusters(vecClustToMerge, mapProjToSplit[clust_seed], new_protos);
0196
0197
0198 for (std::size_t iProj = 0; const auto& project : mapProjToSplit[clust_seed]) {
0199 auto link = out_links->create();
0200 link.setTo(new_protos[iProj]);
0201 link.setFrom(project.getTrack());
0202 link.setWeight(1.0);
0203 trace("Matched output cluster {} to track {}", new_protos[iProj].getObjectID().index,
0204 project.getTrack().getObjectID().index);
0205 ++iProj;
0206 }
0207 }
0208
0209
0210
0211
0212 for (const auto& cluster : *in_clusters) {
0213
0214
0215 if (setUsedClust.contains(cluster)) {
0216 continue;
0217 }
0218
0219
0220 edm4eic::MutableProtoCluster proto = out_protos->create();
0221 add_cluster_to_proto(cluster, proto);
0222 trace("Copied input cluster {} onto output cluster {}", cluster.getObjectID().index,
0223 proto.getObjectID().index);
0224
0225 }
0226
0227 }
0228
0229
0230
0231
0232
0233 void TrackClusterMergeSplitter::merge_and_split_clusters(const cluster_vector& to_merge,
0234 const segment_vector& to_split,
0235 protocluster_vector& new_protos) const {
0236
0237
0238
0239
0240 if (to_split.size() == 1) {
0241 for (const auto& old_clust : to_merge) {
0242 add_cluster_to_proto(old_clust, new_protos.front());
0243 }
0244 return;
0245 }
0246 trace("Splitting merged cluster across {} tracks", to_split.size());
0247
0248
0249 std::vector<hit_to_weight_map> weights(to_split.size());
0250 for (const auto& old_clust : to_merge) {
0251 for (const auto& hit : old_clust.getHits()) {
0252
0253
0254 double wTotal = 0.;
0255 for (std::size_t iProj = 0; const auto& projToSplit : to_split) {
0256
0257
0258 std::optional<edm4eic::TrackPoint> proj;
0259 for (auto point : projToSplit.getPoints()) {
0260 if (point.surface == m_cfg.surfaceToUse) {
0261 proj = point;
0262 }
0263 }
0264 if (!proj) {
0265 continue;
0266 }
0267
0268
0269 const float etaProj = edm4hep::utils::eta(proj.value().position);
0270 const float phiProj = edm4hep::utils::angleAzimuthal(proj.value().position);
0271
0272
0273 const float etaHit = edm4hep::utils::eta(hit.getPosition());
0274 const float phiHit = edm4hep::utils::angleAzimuthal(hit.getPosition());
0275
0276
0277 const float mom = edm4hep::utils::magnitude(proj.value().momentum);
0278 const float dist =
0279 std::hypot(etaHit - etaProj, std::remainder(phiHit - phiProj, 2. * M_PI));
0280
0281
0282 const float weight = std::exp(-1. * dist / m_cfg.transverseEnergyProfileScale) * mom;
0283
0284
0285 weights[iProj][hit] = weight;
0286 wTotal += weight;
0287 ++iProj;
0288 }
0289
0290
0291 for (std::size_t iProj = 0; iProj < to_split.size(); ++iProj) {
0292 weights[iProj][hit] /= wTotal;
0293 }
0294 }
0295
0296
0297 for (std::size_t iProj = 0; iProj < to_split.size(); ++iProj) {
0298 add_cluster_to_proto(old_clust, new_protos[iProj], weights[iProj]);
0299 }
0300 }
0301
0302 }
0303
0304
0305
0306
0307 void TrackClusterMergeSplitter::add_cluster_to_proto(
0308 const edm4eic::Cluster& clust, edm4eic::MutableProtoCluster& proto,
0309 std::optional<hit_to_weight_map> split_weights) {
0310
0311
0312 for (const auto& hit : clust.getHits()) {
0313
0314
0315 double weight = 1.0;
0316 if (split_weights.has_value()) {
0317 weight = split_weights.value()[hit];
0318 }
0319
0320
0321 proto.addToHits(hit);
0322 proto.addToWeights(weight);
0323 }
0324
0325 }
0326
0327 }