Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:07:54

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include "Acts/Seeding/SeedFinderGbts.hpp"
0010 
0011 #include "Acts/Seeding/GbtsTrackingFilter.hpp"
0012 #include "Acts/Seeding/SeedFinderGbtsConfig.hpp"
0013 
0014 #include <algorithm>
0015 #include <cmath>
0016 #include <fstream>
0017 #include <memory>
0018 #include <numbers>
0019 #include <numeric>
0020 #include <utility>
0021 #include <vector>
0022 
0023 namespace Acts::Experimental {
0024 
0025 SeedFinderGbts::SeedFinderGbts(
0026     SeedFinderGbtsConfig config, std::unique_ptr<GbtsGeometry> gbtsGeo,
0027     const std::vector<TrigInDetSiLayer>* layerGeometry,
0028     std::unique_ptr<const Acts::Logger> logger)
0029     : m_config(std::move(config)),
0030       m_geo(std::move(gbtsGeo)),
0031       m_layerGeometry(layerGeometry),
0032       m_logger(std::move(logger)) {
0033   m_config.phiSliceWidth = 2 * std::numbers::pi / m_config.nMaxPhiSlice;
0034 
0035   m_mlLut = parseGbtsMLLookupTable(m_config.lutInputFile);
0036 }
0037 
0038 SeedContainer2 SeedFinderGbts::createSeeds(
0039     const RoiDescriptor& roi,
0040     const SPContainerComponentsType& SpContainerComponents,
0041     int max_layers) const {
0042   std::unique_ptr<GbtsDataStorage> storage =
0043       std::make_unique<GbtsDataStorage>(m_geo, m_config, m_mlLut);
0044 
0045   SeedContainer2 SeedContainer;
0046   std::vector<std::vector<GbtsNode>> node_storage =
0047       createNodes(SpContainerComponents, max_layers);
0048   unsigned int nPixelLoaded = 0;
0049   unsigned int nStripLoaded = 0;
0050 
0051   for (std::size_t l = 0; l < node_storage.size(); l++) {
0052     const std::vector<GbtsNode>& nodes = node_storage[l];
0053 
0054     if (nodes.empty()) {
0055       continue;
0056     }
0057 
0058     bool is_pixel = true;
0059     if (is_pixel) {  // placeholder for now until strip hits are added in
0060 
0061       nPixelLoaded += storage->loadPixelGraphNodes(l, nodes, m_config.useML);
0062 
0063     } else {
0064       nStripLoaded += storage->loadStripGraphNodes(l, nodes);
0065     }
0066   }
0067   ACTS_DEBUG("Loaded " << nPixelLoaded << " pixel spacepoints and "
0068                        << nStripLoaded << " strip spacepoints");
0069 
0070   storage->sortByPhi();
0071 
0072   storage->initializeNodes(m_config.useML);
0073 
0074   storage->generatePhiIndexing(1.5f * m_config.phiSliceWidth);
0075 
0076   std::vector<GbtsEdge> edgeStorage;
0077 
0078   std::pair<int, int> graphStats = buildTheGraph(roi, storage, edgeStorage);
0079 
0080   ACTS_DEBUG("Created graph with " << graphStats.first << " edges and "
0081                                    << graphStats.second << " edge links");
0082 
0083   if (graphStats.first == 0 || graphStats.second == 0) {
0084     ACTS_WARNING("Missing edges or edge connections");
0085   }
0086 
0087   int maxLevel = runCCA(graphStats.first, edgeStorage);
0088 
0089   ACTS_DEBUG("Reached Level " << maxLevel << " after GNN iterations");
0090 
0091   // std::vector<std::tuple<float, int, std::vector<unsigned int>>>
0092   // vSeedCandidates;
0093   std::vector<seedProperties> vSeedCandidates;
0094   extractSeedsFromTheGraph(maxLevel, graphStats.first,
0095                            std::get<0>(SpContainerComponents).size(),
0096                            edgeStorage, vSeedCandidates);
0097 
0098   if (vSeedCandidates.empty()) {
0099     ACTS_WARNING("No Seed Candidates");
0100   }
0101 
0102   for (const auto& seed : vSeedCandidates) {
0103     if (seed.isClone != 0) {
0104       continue;
0105     }
0106 
0107     // add to seed container:
0108     std::vector<SpacePointIndex2> Sp_Indexes{};
0109     Sp_Indexes.reserve(seed.spacepoints.size());
0110 
0111     for (const auto& sp_idx : seed.spacepoints) {
0112       Sp_Indexes.emplace_back(sp_idx);
0113     }
0114 
0115     auto newSeed = SeedContainer.createSeed();
0116     newSeed.assignSpacePointIndices(Sp_Indexes);
0117     newSeed.quality() = seed.seedQuality;
0118   }
0119 
0120   ACTS_DEBUG("GBTS created " << SeedContainer.size() << " seeds");
0121 
0122   return SeedContainer;
0123 }
0124 
0125 GbtsMLLookupTable SeedFinderGbts::parseGbtsMLLookupTable(
0126     const std::string& lutInputFile) {
0127   GbtsMLLookupTable mlLUT{};
0128   if (m_config.useML) {
0129     if (lutInputFile.empty()) {
0130       throw std::runtime_error("Cannot find ML predictor LUT file");
0131     } else {
0132       mlLUT.reserve(100);
0133       std::ifstream ifs(std::string(lutInputFile).c_str());
0134 
0135       if (!ifs.is_open()) {
0136         throw std::runtime_error("Failed to open LUT file");
0137       }
0138 
0139       float cl_width{}, min1{}, max1{}, min2{}, max2{};
0140 
0141       while (ifs >> cl_width >> min1 >> max1 >> min2 >> max2) {
0142         std::array<float, 5> lut_line = {cl_width, min1, max1, min2, max2};
0143         mlLUT.emplace_back(lut_line);
0144       }
0145       if (!ifs.eof()) {
0146         // ended if parse error present, not clean EOF
0147 
0148         throw std::runtime_error("Stopped reading LUT file due to parse error");
0149       }
0150 
0151       ifs.close();
0152     }
0153   }
0154   return mlLUT;
0155 }
0156 
0157 std::vector<std::vector<GbtsNode>> SeedFinderGbts::createNodes(
0158     const SPContainerComponentsType& container, int MaxLayers) const {
0159   std::vector<std::vector<GbtsNode>> node_storage(MaxLayers);
0160   // reserve for better efficiency
0161 
0162   for (auto& v : node_storage) {
0163     v.reserve(10000);
0164   }
0165 
0166   for (auto sp : std::get<0>(container)) {
0167     // for every sp in container,
0168     // add its variables to node_storage organised by layer
0169     std::uint16_t layer = sp.extra(std::get<1>(container));
0170 
0171     // add node to storage
0172     GbtsNode& node = node_storage[layer].emplace_back(layer);
0173 
0174     // fill the node with spacepoint variables
0175 
0176     node.x() = sp.x();
0177     node.y() = sp.y();
0178     node.z() = sp.z();
0179     node.r() = sp.r();
0180     node.phi() = sp.phi();
0181     node.sp_idx() = sp.index();
0182     node.pixelClusterWidth() = sp.extra(std::get<2>(container));
0183     node.localPositionY() = sp.extra(std::get<3>(container));
0184   }
0185 
0186   return node_storage;
0187 }
0188 
0189 std::pair<int, int> SeedFinderGbts::buildTheGraph(
0190     const RoiDescriptor& roi, const std::unique_ptr<GbtsDataStorage>& storage,
0191     std::vector<GbtsEdge>& edgeStorage) const {
0192   // phi cut for triplets
0193   const float cut_dphi_max = m_config.LRTmode ? 0.07f : 0.012f;
0194   // curv cut for triplets
0195   const float cut_dcurv_max = m_config.LRTmode ? 0.015f : 0.001f;
0196   // tau cut for doublets and triplets
0197   const float cut_tau_ratio_max =
0198       m_config.LRTmode ? 0.015f : static_cast<float>(m_config.tau_ratio_cut);
0199   const float min_z0 = m_config.LRTmode ? -600.0 : roi.zedMinus();
0200   const float max_z0 = m_config.LRTmode ? 600.0 : roi.zedPlus();
0201   const float min_deltaPhi = m_config.LRTmode ? 0.01f : 0.001f;
0202 
0203   // used to calculate Z cut on doublets
0204   const float maxOuterRadius = m_config.LRTmode ? 1050.0 : 550.0;
0205 
0206   const float cut_zMinU = min_z0 + maxOuterRadius * roi.dzdrMinus();
0207   const float cut_zMaxU = max_z0 + maxOuterRadius * roi.dzdrPlus();
0208 
0209   // correction due to limited pT resolution
0210   float tripletPtMin = 0.8f * m_config.minPt;
0211   // to re-scale original tunings done for the 900 MeV pT cut
0212   const float pt_scale = 900.0f / m_config.minPt;
0213 
0214   float maxCurv = m_config.ptCoeff / tripletPtMin;
0215 
0216   float maxKappa_high_eta =
0217       m_config.LRTmode ? 1.0f * maxCurv : std::sqrt(0.8f) * maxCurv;
0218   float maxKappa_low_eta =
0219       m_config.LRTmode ? 1.0f * maxCurv : std::sqrt(0.6f) * maxCurv;
0220 
0221   // new settings for curvature cuts
0222   if (!m_config.useOldTunings && !m_config.LRTmode) {
0223     maxKappa_high_eta = 4.75e-4f * pt_scale;
0224     maxKappa_low_eta = 3.75e-4f * pt_scale;
0225   }
0226 
0227   const float dphi_coeff = m_config.LRTmode ? 1.0f * maxCurv : 0.68f * maxCurv;
0228 
0229   // the default sliding window along phi
0230   float deltaPhi = 0.5f * m_config.phiSliceWidth;
0231 
0232   unsigned int nConnections = 0;
0233 
0234   edgeStorage.reserve(m_config.nMaxEdges);
0235 
0236   int nEdges = 0;
0237 
0238   for (const auto& bg : m_geo->bin_groups()) {  // loop over bin groups
0239 
0240     GbtsEtaBin& B1 = storage->getEtaBin(bg.first);
0241 
0242     if (B1.empty()) {
0243       continue;
0244     }
0245 
0246     float rb1 = B1.getMinBinRadius();
0247 
0248     const unsigned int lk1 = B1.m_layerKey;
0249 
0250     for (const auto& b2_idx : bg.second) {
0251       const GbtsEtaBin& B2 = storage->getEtaBin(b2_idx);
0252 
0253       if (B2.empty()) {
0254         continue;
0255       }
0256 
0257       float rb2 = B2.getMaxBinRadius();
0258 
0259       if (m_config.useEtaBinning) {
0260         float abs_dr = std::fabs(rb2 - rb1);
0261         if (m_config.useOldTunings) {
0262           deltaPhi = min_deltaPhi + dphi_coeff * abs_dr;
0263         } else {
0264           if (abs_dr < 60.0) {
0265             deltaPhi = 0.002f + 4.33e-4f * pt_scale * abs_dr;
0266           } else {
0267             deltaPhi = 0.015f + 2.2e-4f * pt_scale * abs_dr;
0268           }
0269         }
0270       }
0271 
0272       unsigned int first_it = 0;
0273 
0274       for (unsigned int n1Idx = 0; n1Idx < B1.m_vn.size();
0275            n1Idx++) {  // loop over nodes in Layer 1
0276 
0277         std::vector<unsigned int>& v1In = B1.m_in[n1Idx];
0278 
0279         if (v1In.size() >= MAX_SEG_PER_NODE) {
0280           continue;
0281         }
0282 
0283         const std::array<float, 5>& n1pars = B1.m_params[n1Idx];
0284 
0285         float phi1 = n1pars[2];
0286         float r1 = n1pars[3];
0287         float z1 = n1pars[4];
0288 
0289         // sliding window phi1 +/- deltaPhi
0290 
0291         float minPhi = phi1 - deltaPhi;
0292         float maxPhi = phi1 + deltaPhi;
0293 
0294         for (unsigned int n2PhiIdx = first_it; n2PhiIdx < B2.m_vPhiNodes.size();
0295              n2PhiIdx++) {  // sliding window over nodes in Layer 2
0296 
0297           float phi2 = B2.m_vPhiNodes[n2PhiIdx].first;
0298 
0299           if (phi2 < minPhi) {
0300             first_it = n2PhiIdx;
0301             continue;
0302           }
0303           if (phi2 > maxPhi) {
0304             break;
0305           }
0306 
0307           unsigned int n2Idx = B2.m_vPhiNodes[n2PhiIdx].second;
0308 
0309           const std::vector<unsigned int>& v2In = B2.m_in[n2Idx];
0310 
0311           if (v2In.size() >= MAX_SEG_PER_NODE) {
0312             continue;
0313           }
0314 
0315           const std::array<float, 5>& n2pars = B2.m_params[n2Idx];
0316 
0317           float r2 = n2pars[3];
0318 
0319           float dr = r2 - r1;
0320 
0321           if (dr < m_config.minDeltaRadius) {
0322             continue;
0323           }
0324 
0325           float z2 = n2pars[4];
0326 
0327           float dz = z2 - z1;
0328           float tau = dz / dr;
0329           float ftau = std::fabs(tau);
0330           if (ftau > 36.0) {
0331             continue;
0332           }
0333 
0334           if (ftau < n1pars[0]) {
0335             continue;
0336           }
0337           if (ftau > n1pars[1]) {
0338             continue;
0339           }
0340 
0341           if (ftau < n2pars[0]) {
0342             continue;
0343           }
0344           if (ftau > n2pars[1]) {
0345             continue;
0346           }
0347 
0348           if (m_config.doubletFilterRZ) {
0349             float z0 = z1 - r1 * tau;
0350 
0351             if (z0 < min_z0 || z0 > max_z0) {
0352               continue;
0353             }
0354 
0355             float zouter = z0 + maxOuterRadius * tau;
0356 
0357             if (zouter < cut_zMinU || zouter > cut_zMaxU) {
0358               continue;
0359             }
0360           }
0361 
0362           float curv = (phi2 - phi1) / dr;
0363           float abs_curv = std::abs(curv);
0364 
0365           if (ftau < 4.0) {  // eta = 2.1
0366             if (abs_curv > maxKappa_low_eta) {
0367               continue;
0368             }
0369           } else {
0370             if (abs_curv > maxKappa_high_eta) {
0371               continue;
0372             }
0373           }
0374 
0375           float exp_eta = std::sqrt(1.f + tau * tau) - tau;
0376 
0377           if (m_config.matchBeforeCreate &&
0378               (lk1 == 80000 || lk1 == 81000)) {  // match edge candidate against
0379                                                  // edges incoming to n2
0380 
0381             bool isGood = v2In.size() <=
0382                           2;  // we must have enough incoming edges to decide
0383 
0384             if (!isGood) {
0385               float uat_1 = 1.0f / exp_eta;
0386 
0387               for (const auto& n2_in_idx : v2In) {
0388                 float tau2 = edgeStorage.at(n2_in_idx).m_p[0];
0389                 float tau_ratio = tau2 * uat_1 - 1.0f;
0390 
0391                 if (std::abs(tau_ratio) >
0392                     m_config.tau_ratio_precut) {  // bad match
0393                   continue;
0394                 }
0395                 isGood = true;  // good match found
0396                 break;
0397               }
0398             }
0399 
0400             if (!isGood) {  // no match found, skip creating [n1 <- n2] edge
0401               continue;
0402             }
0403           }
0404 
0405           float dPhi2 = curv * r2;
0406           float dPhi1 = curv * r1;
0407 
0408           if (nEdges < m_config.nMaxEdges) {
0409             edgeStorage.emplace_back(B1.m_vn[n1Idx], B2.m_vn[n2Idx], exp_eta,
0410                                      curv, phi1 + dPhi1);
0411 
0412             if (v1In.size() < MAX_SEG_PER_NODE) {
0413               v1In.push_back(nEdges);
0414             }
0415 
0416             int outEdgeIdx = nEdges;
0417 
0418             float uat_2 = 1.f / exp_eta;
0419             float Phi2 = phi2 + dPhi2;
0420             float curv2 = curv;
0421 
0422             for (const auto& inEdgeIdx :
0423                  v2In) {  // looking for neighbours of the new edge
0424 
0425               GbtsEdge* pS = &(edgeStorage.at(inEdgeIdx));
0426 
0427               if (pS->m_nNei >= N_SEG_CONNS) {
0428                 continue;
0429               }
0430 
0431               float tau_ratio = pS->m_p[0] * uat_2 - 1.0f;
0432 
0433               if (std::abs(tau_ratio) > cut_tau_ratio_max) {  // bad match
0434                 continue;
0435               }
0436 
0437               float dPhi = Phi2 - pS->m_p[2];
0438 
0439               if (dPhi < -std::numbers::pi) {
0440                 dPhi += 2 * std::numbers::pi;
0441               } else if (dPhi > std::numbers::pi) {
0442                 dPhi -= 2 * std::numbers::pi;
0443               }
0444 
0445               if (std::abs(dPhi) > cut_dphi_max) {
0446                 continue;
0447               }
0448 
0449               float dcurv = curv2 - pS->m_p[1];
0450 
0451               if (dcurv < -cut_dcurv_max || dcurv > cut_dcurv_max) {
0452                 continue;
0453               }
0454 
0455               pS->m_vNei[pS->m_nNei++] = outEdgeIdx;
0456 
0457               nConnections++;
0458             }
0459             nEdges++;
0460           }
0461         }  // loop over n2 (outer) nodes
0462       }  // loop over n1 (inner) nodes
0463     }  // loop over bins in Layer 2
0464   }  // loop over bin groups
0465 
0466   if (nEdges >= m_config.nMaxEdges) {
0467     ACTS_WARNING(
0468         "Maximum number of graph edges exceeded - possible efficiency loss "
0469         << nEdges);
0470   }
0471   return std::make_pair(nEdges, nConnections);
0472 }
0473 
0474 int SeedFinderGbts::runCCA(int nEdges,
0475                            std::vector<GbtsEdge>& edgeStorage) const {
0476   constexpr int maxIter = 15;
0477 
0478   int maxLevel = 0;
0479 
0480   int iter = 0;
0481 
0482   std::vector<GbtsEdge*> v_old;
0483 
0484   for (int edgeIndex = 0; edgeIndex < nEdges; edgeIndex++) {
0485     GbtsEdge* pS = &(edgeStorage[edgeIndex]);
0486     if (pS->m_nNei == 0) {
0487       continue;
0488     }
0489 
0490     v_old.push_back(pS);  // TO-DO: increment level for segments as they already
0491                           // have at least one neighbour
0492   }
0493 
0494   std::vector<GbtsEdge*> v_new;
0495   v_new.reserve(v_old.size());
0496 
0497   for (; iter < maxIter; iter++) {
0498     // generate proposals
0499 
0500     v_new.clear();
0501 
0502     for (auto pS : v_old) {
0503       int next_level = pS->m_level;
0504 
0505       for (int nIdx = 0; nIdx < pS->m_nNei; nIdx++) {
0506         unsigned int nextEdgeIdx = pS->m_vNei[nIdx];
0507 
0508         GbtsEdge* pN = &(edgeStorage[nextEdgeIdx]);
0509 
0510         if (pS->m_level == pN->m_level) {
0511           next_level = pS->m_level + 1;
0512           v_new.push_back(pS);
0513           break;
0514         }
0515       }
0516 
0517       pS->m_next = next_level;  // proposal
0518     }
0519 
0520     // update
0521 
0522     int nChanges = 0;
0523 
0524     for (auto pS : v_new) {
0525       if (pS->m_next != pS->m_level) {
0526         nChanges++;
0527         pS->m_level = pS->m_next;
0528         if (maxLevel < pS->m_level) {
0529           maxLevel = pS->m_level;
0530         }
0531       }
0532     }
0533 
0534     if (nChanges == 0) {
0535       break;
0536     }
0537 
0538     v_old.swap(v_new);
0539     v_new.clear();
0540   }
0541 
0542   return maxLevel;
0543 }
0544 
0545 void SeedFinderGbts::extractSeedsFromTheGraph(
0546     int maxLevel, int nEdges, int nHits, std::vector<GbtsEdge>& edgeStorage,
0547     std::vector<seedProperties>& vSeedCandidates) const {
0548   vSeedCandidates.clear();
0549 
0550   int minLevel = 3;  // a triplet + 2 confirmation
0551 
0552   if (m_config.LRTmode) {
0553     minLevel = 2;  // a triplet + 1 confirmation
0554   }
0555 
0556   if (maxLevel < minLevel) {
0557     return;
0558   }
0559 
0560   std::vector<GbtsEdge*> vSeeds;
0561 
0562   vSeeds.reserve(nEdges / 2);
0563 
0564   for (int edgeIndex = 0; edgeIndex < nEdges; edgeIndex++) {
0565     GbtsEdge* pS = &(edgeStorage.at(edgeIndex));
0566 
0567     if (pS->m_level < minLevel) {
0568       continue;
0569     }
0570 
0571     vSeeds.push_back(pS);
0572   }
0573 
0574   if (vSeeds.empty()) {
0575     return;
0576   }
0577 
0578   std::sort(vSeeds.begin(), vSeeds.end(), GbtsEdge::CompareLevel());
0579 
0580   // backtracking
0581 
0582   vSeedCandidates.reserve(vSeeds.size());
0583 
0584   GbtsTrackingFilter tFilter(*m_layerGeometry, edgeStorage, m_config);
0585 
0586   for (auto pS : vSeeds) {
0587     if (pS->m_level == -1) {
0588       continue;
0589     }
0590 
0591     GbtsEdgeState rs(false);
0592 
0593     tFilter.followTrack(*pS, rs);
0594 
0595     if (!rs.m_initialized) {
0596       continue;
0597     }
0598 
0599     if (static_cast<int>(rs.m_vs.size()) < minLevel) {
0600       continue;
0601     }
0602 
0603     float seed_eta = std::abs(-std::log(pS->m_p[0]));
0604 
0605     std::vector<const GbtsNode*> vN;
0606 
0607     for (std::vector<GbtsEdge*>::reverse_iterator sIt = rs.m_vs.rbegin();
0608          sIt != rs.m_vs.rend(); ++sIt) {
0609       if (seed_eta > m_config.edge_mask_min_eta) {
0610         (*sIt)->m_level = -1;  // mark as collected
0611       }
0612 
0613       if (sIt == rs.m_vs.rbegin()) {
0614         vN.push_back((*sIt)->m_n1);
0615       }
0616 
0617       vN.push_back((*sIt)->m_n2);
0618     }
0619 
0620     if (vN.size() < 3) {
0621       continue;
0622     }
0623 
0624     std::vector<unsigned int> vSpIdx;
0625 
0626     vSpIdx.resize(vN.size());
0627 
0628     for (unsigned int k = 0; k < vN.size(); k++) {
0629       vSpIdx[k] = vN[k]->sp_idx();
0630     }
0631 
0632     vSeedCandidates.emplace_back(-rs.m_J / vN.size(), 0, std::move(vSpIdx));
0633   }
0634 
0635   // clone removal code goes below ...
0636 
0637   std::sort(vSeedCandidates.begin(), vSeedCandidates.end());
0638 
0639   std::vector<int> vTrackIds(vSeedCandidates.size());
0640 
0641   // fills the vector from 1 to N
0642 
0643   std::iota(vTrackIds.begin(), vTrackIds.end(), 1);
0644 
0645   std::vector<int> H2T(nHits + 1, 0);  // hit to track associations
0646 
0647   int seedIdx = 0;
0648 
0649   for (const auto& seed : vSeedCandidates) {
0650     for (const auto& h : seed.spacepoints) {  // loop over spacepoints indices
0651 
0652       unsigned int hit_id = h + 1;
0653 
0654       int tid = H2T[hit_id];
0655       int trackId = vTrackIds[seedIdx];
0656 
0657       if (tid == 0 || tid > trackId) {  // un-used hit or used by a lesser track
0658 
0659         H2T[hit_id] = trackId;  // overwrite
0660       }
0661     }
0662 
0663     seedIdx++;
0664   }
0665 
0666   for (unsigned int trackIdx = 0; trackIdx < vSeedCandidates.size();
0667        trackIdx++) {
0668     int nTotal = vSeedCandidates[trackIdx].spacepoints.size();
0669     int nOther = 0;
0670 
0671     int trackId = vTrackIds[trackIdx];
0672 
0673     for (const auto& h : vSeedCandidates[trackIdx].spacepoints) {
0674       unsigned int hit_id = h + 1;
0675 
0676       int tid = H2T[hit_id];
0677 
0678       if (tid != trackId) {  // taken by a better candidate
0679         nOther++;
0680       }
0681     }
0682 
0683     if (nOther > m_config.hit_share_threshold * nTotal) {
0684       vSeedCandidates[trackIdx].isClone = -1;  // reject
0685     }
0686   }
0687 }
0688 
0689 }  // namespace Acts::Experimental