Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-03-28 07:45:42

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/Seeding2/GraphBasedTrackSeeder.hpp"
0010 
0011 #include "Acts/Seeding2/GbtsTrackingFilter.hpp"
0012 #include "Acts/Utilities/MathHelpers.hpp"
0013 
0014 #include <algorithm>
0015 #include <cmath>
0016 #include <cstdint>
0017 #include <fstream>
0018 #include <memory>
0019 #include <numbers>
0020 #include <utility>
0021 #include <vector>
0022 
0023 namespace Acts::Experimental {
0024 
0025 GraphBasedTrackSeeder::DerivedConfig::DerivedConfig(const Config& config)
0026     : Config(config) {
0027   phiSliceWidth = 2 * std::numbers::pi_v<float> / config.nMaxPhiSlice;
0028 }
0029 
0030 GraphBasedTrackSeeder::GraphBasedTrackSeeder(
0031     const DerivedConfig& config, std::shared_ptr<GbtsGeometry> geometry,
0032     std::unique_ptr<const Acts::Logger> logger)
0033     : m_cfg(config),
0034       m_geometry(std::move(geometry)),
0035       m_logger(std::move(logger)) {
0036   m_mlLut = parseGbtsMlLookupTable(m_cfg.lutInputFile);
0037 }
0038 
0039 SeedContainer2 GraphBasedTrackSeeder::createSeeds(
0040     const SpacePointContainer2& spacePoints, const GbtsRoiDescriptor& roi,
0041     const std::uint32_t maxLayers, const GbtsTrackingFilter& filter) const {
0042   GbtsNodeStorage nodeStorage(m_geometry, m_mlLut);
0043 
0044   SeedContainer2 SeedContainer;
0045   std::vector<std::vector<GbtsNode>> nodesPerLayer =
0046       createNodes(spacePoints, maxLayers);
0047   std::uint32_t nPixelLoaded = 0;
0048   std::uint32_t nStripLoaded = 0;
0049 
0050   for (std::uint16_t l = 0; l < nodesPerLayer.size(); l++) {
0051     const std::vector<GbtsNode>& nodes = nodesPerLayer[l];
0052 
0053     if (nodes.empty()) {
0054       continue;
0055     }
0056 
0057     const bool isPixel = true;
0058     // placeholder for now until strip hits are added in
0059     if (isPixel) {
0060       nPixelLoaded += nodeStorage.loadPixelGraphNodes(
0061           l, nodes, m_cfg.useMl, m_cfg.maxEndcapClusterWidth);
0062     } else {
0063       nStripLoaded += nodeStorage.loadStripGraphNodes(l, nodes);
0064     }
0065   }
0066   ACTS_DEBUG("Loaded " << nPixelLoaded << " pixel space points and "
0067                        << nStripLoaded << " strip space points");
0068 
0069   nodeStorage.sortByPhi();
0070 
0071   nodeStorage.initializeNodes(m_cfg.useMl);
0072 
0073   nodeStorage.generatePhiIndexing(1.5f * m_cfg.phiSliceWidth);
0074 
0075   std::vector<GbtsEdge> edgeStorage;
0076 
0077   std::pair<std::int32_t, std::int32_t> graphStats =
0078       buildTheGraph(roi, nodeStorage, 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   std::uint32_t maxLevel = runCCA(graphStats.first, edgeStorage);
0088 
0089   ACTS_DEBUG("Reached Level " << maxLevel << " after GNN iterations");
0090 
0091   std::vector<SeedProperties> vSeedCandidates;
0092   extractSeedsFromTheGraph(maxLevel, graphStats.first, spacePoints.size(),
0093                            edgeStorage, vSeedCandidates, filter);
0094 
0095   if (vSeedCandidates.empty()) {
0096     ACTS_WARNING("No Seed Candidates");
0097   }
0098 
0099   for (const auto& seed : vSeedCandidates) {
0100     if (seed.isClone != 0) {
0101       continue;
0102     }
0103 
0104     // add to seed container:
0105     std::vector<SpacePointIndex2> Sp_Indexes{};
0106     Sp_Indexes.reserve(seed.spacePoints.size());
0107 
0108     for (const auto& sp_idx : seed.spacePoints) {
0109       Sp_Indexes.emplace_back(sp_idx);
0110     }
0111 
0112     auto newSeed = SeedContainer.createSeed();
0113     newSeed.assignSpacePointIndices(Sp_Indexes);
0114     newSeed.quality() = seed.seedQuality;
0115   }
0116 
0117   ACTS_DEBUG("GBTS created " << SeedContainer.size() << " seeds");
0118 
0119   return SeedContainer;
0120 }
0121 
0122 GbtsMlLookupTable GraphBasedTrackSeeder::parseGbtsMlLookupTable(
0123     const std::string& lutInputFile) {
0124   if (!m_cfg.useMl) {
0125     return {};
0126   }
0127   if (lutInputFile.empty()) {
0128     throw std::runtime_error("Cannot find ML predictor LUT file");
0129   }
0130 
0131   std::ifstream ifs(std::string(lutInputFile).c_str());
0132   if (!ifs.is_open()) {
0133     throw std::runtime_error("Failed to open LUT file");
0134   }
0135 
0136   GbtsMlLookupTable mlLut;
0137   mlLut.reserve(100);
0138 
0139   float clWidth{};
0140   float min1{};
0141   float max1{};
0142   float min2{};
0143   float max2{};
0144   while (ifs >> clWidth >> min1 >> max1 >> min2 >> max2) {
0145     mlLut.emplace_back(std::array<float, 5>{clWidth, min1, max1, min2, max2});
0146   }
0147 
0148   if (!ifs.eof()) {
0149     // ended if parse error present, not clean EOF
0150     throw std::runtime_error("Stopped reading LUT file due to parse error");
0151   }
0152 
0153   return mlLut;
0154 }
0155 
0156 std::vector<std::vector<GbtsNode>> GraphBasedTrackSeeder::createNodes(
0157     const SpacePointContainer2& spacePoints,
0158     const std::uint32_t maxLayers) const {
0159   auto layerColumn = spacePoints.column<std::uint32_t>("layerId");
0160   auto clusterWidthColumn = spacePoints.column<float>("clusterWidth");
0161   auto localPositionColumn = spacePoints.column<float>("localPositionY");
0162 
0163   std::vector<std::vector<GbtsNode>> nodesPerLayer(maxLayers);
0164   // reserve for better efficiency
0165   for (auto& v : nodesPerLayer) {
0166     v.reserve(10000);
0167   }
0168 
0169   for (const auto& sp : spacePoints) {
0170     // for every sp in container,
0171     // add its variables to nodeStorage organised by layer
0172     const std::uint16_t layer = sp.extra(layerColumn);
0173 
0174     // add node to storage
0175     GbtsNode& node = nodesPerLayer[layer].emplace_back(layer);
0176 
0177     // fill the node with space point variables
0178 
0179     node.x = sp.x();
0180     node.y = sp.y();
0181     node.z = sp.z();
0182     node.r = sp.r();
0183     node.phi = sp.phi();
0184     node.idx = sp.index();
0185     node.pcw = sp.extra(clusterWidthColumn);
0186     node.locPosY = sp.extra(localPositionColumn);
0187   }
0188 
0189   return nodesPerLayer;
0190 }
0191 
0192 std::pair<std::int32_t, std::int32_t> GraphBasedTrackSeeder::buildTheGraph(
0193     const GbtsRoiDescriptor& roi, GbtsNodeStorage& nodeStorage,
0194     std::vector<GbtsEdge>& edgeStorage) const {
0195   // phi cut for triplets
0196   const float cutDPhiMax = m_cfg.lrtMode ? 0.07f : 0.012f;
0197   // curv cut for triplets
0198   const float cutDCurvMax = m_cfg.lrtMode ? 0.015f : 0.001f;
0199   // tau cut for doublets and triplets
0200   const float cutTauRatioMax = m_cfg.lrtMode ? 0.015f : m_cfg.tauRatioCut;
0201   const float minZ0 = m_cfg.lrtMode ? -600.0f : static_cast<float>(roi.zMin());
0202   const float maxZ0 = m_cfg.lrtMode ? 600.0f : static_cast<float>(roi.zMax());
0203   const float minDeltaPhi = m_cfg.lrtMode ? 0.01f : 0.001f;
0204 
0205   // used to calculate Z cut on doublets
0206   const float maxOuterRadius = m_cfg.lrtMode ? 1050.0f : 550.0f;
0207 
0208   const float cutZMinU =
0209       minZ0 + maxOuterRadius * static_cast<float>(roi.dzdrMin());
0210   const float cutZMaxU =
0211       maxZ0 + maxOuterRadius * static_cast<float>(roi.dzdrMax());
0212 
0213   // correction due to limited pT resolution
0214   const float tripletPtMin = 0.8f * m_cfg.minPt;
0215 
0216   // to re-scale original tunings done for the 900 MeV pT cut
0217   const float ptScale = (0.9f * UnitConstants::GeV) / m_cfg.minPt;
0218 
0219   const float maxCurv = m_cfg.ptCoeff / tripletPtMin;
0220 
0221   float maxKappaHighEta =
0222       m_cfg.lrtMode ? 1.0f * maxCurv : std::sqrt(0.8f) * maxCurv;
0223   float maxKappaLowEta =
0224       m_cfg.lrtMode ? 1.0f * maxCurv : std::sqrt(0.6f) * maxCurv;
0225 
0226   // new settings for curvature cuts
0227   if (!m_cfg.useOldTunings && !m_cfg.lrtMode) {
0228     maxKappaHighEta = 4.75e-4f * ptScale;
0229     maxKappaLowEta = 3.75e-4f * ptScale;
0230   }
0231 
0232   const float dPhiCoeff = m_cfg.lrtMode ? 1.0f * maxCurv : 0.68f * maxCurv;
0233 
0234   // the default sliding window along phi
0235   const float deltaPhi0 = 0.5f * m_cfg.phiSliceWidth;
0236 
0237   std::uint32_t nConnections = 0;
0238 
0239   edgeStorage.reserve(m_cfg.nMaxEdges);
0240 
0241   std::uint32_t nEdges = 0;
0242 
0243   // scale factor to get indexes of binned beamspot
0244   // assuming 16-bit z0 bitmask
0245 
0246   const std::uint32_t zBins = 16;
0247   const float z0HistoCoeff = zBins / (maxZ0 - minZ0 + 1e-6);
0248 
0249   // loop over bin groups
0250   for (const auto& bg : m_geometry->binGroups()) {
0251     GbtsEtaBin& B1 = nodeStorage.getEtaBin(bg.first);
0252 
0253     if (B1.empty()) {
0254       continue;
0255     }
0256 
0257     const float rb1 = B1.minRadius;
0258 
0259     const std::uint32_t layerId1 = B1.layerId;
0260 
0261     // prepare a sliding window for each bin2 in the group
0262 
0263     std::vector<SlidingWindow> phiSlidingWindow;
0264 
0265     // initialization using default ctor
0266     phiSlidingWindow.resize(bg.second.size());
0267 
0268     std::uint32_t winIdx = 0;
0269 
0270     // loop over n2 eta-bins in L2 layers
0271     for (const auto& b2Idx : bg.second) {
0272       const GbtsEtaBin& B2 = nodeStorage.getEtaBin(b2Idx);
0273 
0274       if (B2.empty()) {
0275         ++winIdx;
0276         continue;
0277       }
0278 
0279       const float rb2 = B2.maxRadius;
0280 
0281       float deltaPhi = deltaPhi0;  // the default
0282 
0283       // override the default window width
0284       if (m_cfg.useEtaBinning) {
0285         const float absDr = std::fabs(rb2 - rb1);
0286         if (m_cfg.useOldTunings) {
0287           deltaPhi = minDeltaPhi + dPhiCoeff * absDr;
0288         } else {
0289           if (absDr < 60.0) {
0290             deltaPhi = 0.002f + 4.33e-4f * ptScale * absDr;
0291           } else {
0292             deltaPhi = 0.015f + 2.2e-4f * ptScale * absDr;
0293           }
0294         }
0295       }
0296 
0297       phiSlidingWindow[winIdx].bin = &B2;
0298       phiSlidingWindow[winIdx].hasNodes = true;
0299       phiSlidingWindow[winIdx].deltaPhi = deltaPhi;
0300       ++winIdx;
0301     }
0302 
0303     // in GBTSv3 the outer loop goes over n1 nodes in the Layer 1 bin
0304     for (std::uint32_t n1Idx = 0; n1Idx < B1.vn.size(); ++n1Idx) {
0305       // initialization using the top watermark of the edge storage
0306       B1.vFirstEdge[n1Idx] = nEdges;
0307 
0308       // the counter for the incoming graph edges created for n1
0309       std::uint16_t numCreatedEdges = 0;
0310 
0311       bool isConnected = false;
0312 
0313       std::array<std::uint8_t, 16> z0Histo = {};
0314 
0315       const std::array<float, 5>& n1pars = B1.params[n1Idx];
0316 
0317       const float phi1 = n1pars[2];
0318       const float r1 = n1pars[3];
0319       const float z1 = n1pars[4];
0320 
0321       // the intermediate loop over sliding windows
0322       for (auto& slw : phiSlidingWindow) {
0323         if (!slw.hasNodes) {
0324           continue;
0325         }
0326 
0327         const GbtsEtaBin& B2 = *slw.bin;
0328 
0329         const float deltaPhi = slw.deltaPhi;
0330 
0331         // sliding window phi1 +/- deltaPhi
0332 
0333         const float minPhi = phi1 - deltaPhi;
0334         const float maxPhi = phi1 + deltaPhi;
0335 
0336         // the inner loop over n2 nodes using sliding window
0337         for (std::uint32_t n2PhiIdx = slw.firstIt;
0338              n2PhiIdx < B2.vPhiNodes.size(); ++n2PhiIdx) {
0339           const float phi2 = B2.vPhiNodes[n2PhiIdx].first;
0340 
0341           if (phi2 < minPhi) {
0342             // update the window position
0343             slw.firstIt = n2PhiIdx;
0344             continue;
0345           }
0346           if (phi2 > maxPhi) {
0347             // break and go to the next window
0348             break;
0349           }
0350 
0351           const std::uint32_t n2Idx = B2.vPhiNodes[n2PhiIdx].second;
0352 
0353           const std::uint16_t nodeInfo = B2.vIsConnected[n2Idx];
0354 
0355           // skip isolated nodes as their incoming edges lead to nowhere
0356           if ((layerId1 == 80000) && (nodeInfo == 0)) {
0357             continue;
0358           }
0359 
0360           const std::uint32_t n2FirstEdge = B2.vFirstEdge[n2Idx];
0361           const std::uint16_t n2NumEdges = B2.vNumEdges[n2Idx];
0362           const std::uint32_t n2LastEdge = n2FirstEdge + n2NumEdges;
0363 
0364           const std::array<float, 5>& n2pars = B2.params[n2Idx];
0365 
0366           const float r2 = n2pars[3];
0367 
0368           const float dr = r2 - r1;
0369 
0370           if (dr < m_cfg.minDeltaRadius) {
0371             continue;
0372           }
0373 
0374           const float z2 = n2pars[4];
0375 
0376           const float dz = z2 - z1;
0377           const float tau = dz / dr;
0378           const float ftau = std::fabs(tau);
0379           if (ftau > 36.0f) {
0380             continue;
0381           }
0382 
0383           if (ftau < n1pars[0]) {
0384             continue;
0385           }
0386           if (ftau > n1pars[1]) {
0387             continue;
0388           }
0389 
0390           if (ftau < n2pars[0]) {
0391             continue;
0392           }
0393           if (ftau > n2pars[1]) {
0394             continue;
0395           }
0396 
0397           const float z0 = z1 - r1 * tau;
0398 
0399           if (layerId1 == 80000) {  // check against non-empty z0 histogram
0400             if (!checkZ0BitMask(nodeInfo, z0, minZ0, z0HistoCoeff)) {
0401               continue;
0402             }
0403           }
0404 
0405           if (m_cfg.doubletFilterRZ) {
0406             if (z0 < minZ0 || z0 > maxZ0) {
0407               continue;
0408             }
0409 
0410             const float zOuter = z0 + maxOuterRadius * tau;
0411 
0412             if (zOuter < cutZMinU || zOuter > cutZMaxU) {
0413               continue;
0414             }
0415           }
0416 
0417           const float curv = (phi2 - phi1) / dr;
0418           const float absCurv = std::abs(curv);
0419 
0420           if (ftau < 4.0f) {  // eta = 2.1
0421             if (absCurv > maxKappaLowEta) {
0422               continue;
0423             }
0424           } else {
0425             if (absCurv > maxKappaHighEta) {
0426               continue;
0427             }
0428           }
0429 
0430           const float expEta = fastHypot(1, tau) - tau;
0431 
0432           // match edge candidate against edges incoming to n2
0433           if (m_cfg.matchBeforeCreate &&
0434               (layerId1 == 80000 || layerId1 == 81000)) {
0435             // we must have enough incoming edges to decide
0436             bool isGood = n2NumEdges <= 2;
0437 
0438             if (!isGood) {
0439               const float uat1 = 1.0f / expEta;
0440 
0441               for (std::uint32_t n2InIdx = n2FirstEdge; n2InIdx < n2LastEdge;
0442                    ++n2InIdx) {
0443                 const float tau2 = edgeStorage.at(n2InIdx).p[0];
0444                 const float tauRatio = tau2 * uat1 - 1.0f;
0445 
0446                 if (std::abs(tauRatio) > m_cfg.tauRatioPrecut) {  // bad match
0447                   continue;
0448                 }
0449                 isGood = true;  // good match found
0450                 break;
0451               }
0452             }
0453 
0454             if (!isGood) {  // no match found, skip creating [n1 <- n2] edge
0455               continue;
0456             }
0457           }
0458 
0459           const float dPhi2 = curv * r2;
0460           const float dPhi1 = curv * r1;
0461 
0462           if (nEdges < m_cfg.nMaxEdges) {
0463             edgeStorage.emplace_back(B1.vn[n1Idx], B2.vn[n2Idx], expEta, curv,
0464                                      phi1 + dPhi1);
0465 
0466             ++numCreatedEdges;
0467 
0468             const std::uint32_t outEdgeIdx = nEdges;
0469 
0470             const float uat2 = 1.f / expEta;
0471             const float phi2u = phi2 + dPhi2;
0472             const float curv2 = curv;
0473 
0474             // looking for neighbours of the new edge
0475             for (std::uint32_t inEdgeIdx = n2FirstEdge; inEdgeIdx < n2LastEdge;
0476                  ++inEdgeIdx) {
0477               GbtsEdge* pS = &(edgeStorage.at(inEdgeIdx));
0478 
0479               if (pS->nNei >= gbtsNumSegConns) {
0480                 continue;
0481               }
0482 
0483               const float tauRatio = pS->p[0] * uat2 - 1.0f;
0484 
0485               if (std::abs(tauRatio) > cutTauRatioMax) {  // bad match
0486                 continue;
0487               }
0488 
0489               float dPhi = phi2u - pS->p[2];
0490 
0491               if (dPhi < -std::numbers::pi_v<float>) {
0492                 dPhi += 2 * std::numbers::pi_v<float>;
0493               } else if (dPhi > std::numbers::pi_v<float>) {
0494                 dPhi -= 2 * std::numbers::pi_v<float>;
0495               }
0496 
0497               if (std::abs(dPhi) > cutDPhiMax) {
0498                 continue;
0499               }
0500 
0501               const float dcurv = curv2 - pS->p[1];
0502 
0503               if (dcurv < -cutDCurvMax || dcurv > cutDCurvMax) {
0504                 continue;
0505               }
0506 
0507               pS->vNei[pS->nNei] = outEdgeIdx;
0508               ++pS->nNei;
0509 
0510               isConnected = true;  // there is at least one good match
0511 
0512               // edge confirmed - update z0 histogram
0513 
0514               const std::uint32_t z0BinIndex =
0515                   static_cast<std::uint32_t>(z0HistoCoeff * (z0 - minZ0));
0516 
0517               ++z0Histo[z0BinIndex];
0518 
0519               nConnections++;
0520             }
0521             nEdges++;
0522           }
0523         }  // loop over n2 (outer) nodes inside a sliding window on n2 bin
0524       }  // loop over sliding windows associated with n2 bins
0525 
0526       // updating the n1 node attributes
0527 
0528       B1.vNumEdges[n1Idx] = numCreatedEdges;
0529       if (isConnected) {
0530         std::uint16_t z0BitMask = 0x0;
0531 
0532         for (std::uint32_t bIdx = 0; bIdx < 16; ++bIdx) {
0533           if (z0Histo[bIdx] == 0) {
0534             continue;
0535           }
0536 
0537           z0BitMask |= (1 << bIdx);
0538         }
0539 
0540         // non-zero mask indicates that there is at least one connected edge
0541         B1.vIsConnected[n1Idx] = z0BitMask;
0542       }
0543 
0544     }  // loop over n1 (inner) nodes
0545   }  // loop over bin groups: a single n1 bin and multiple n2 bins
0546 
0547   if (nEdges >= m_cfg.nMaxEdges) {
0548     ACTS_WARNING(
0549         "Maximum number of graph edges exceeded - possible efficiency loss "
0550         << nEdges);
0551   }
0552   return std::make_pair(nEdges, nConnections);
0553 }
0554 
0555 std::int32_t GraphBasedTrackSeeder::runCCA(
0556     const std::uint32_t nEdges, std::vector<GbtsEdge>& edgeStorage) const {
0557   constexpr std::uint32_t maxIter = 15;
0558 
0559   std::int32_t maxLevel = 0;
0560 
0561   std::uint32_t iter = 0;
0562 
0563   std::vector<GbtsEdge*> v_old;
0564 
0565   for (std::uint32_t edgeIndex = 0; edgeIndex < nEdges; ++edgeIndex) {
0566     GbtsEdge* pS = &(edgeStorage[edgeIndex]);
0567     if (pS->nNei == 0) {
0568       continue;
0569     }
0570 
0571     // TODO: increment level for segments as they already have at least one
0572     // neighbour
0573     v_old.push_back(pS);
0574   }
0575 
0576   std::vector<GbtsEdge*> v_new;
0577   v_new.reserve(v_old.size());
0578 
0579   // generate proposals
0580   for (; iter < maxIter; iter++) {
0581     v_new.clear();
0582 
0583     for (GbtsEdge* pS : v_old) {
0584       std::int32_t nextLevel = pS->level;
0585 
0586       for (std::uint32_t nIdx = 0; nIdx < pS->nNei; ++nIdx) {
0587         const std::uint32_t nextEdgeIdx = pS->vNei[nIdx];
0588 
0589         GbtsEdge* pN = &(edgeStorage[nextEdgeIdx]);
0590 
0591         if (pS->level == pN->level) {
0592           nextLevel = pS->level + 1;
0593           v_new.push_back(pS);
0594           break;
0595         }
0596       }
0597 
0598       // proposal
0599       pS->next = static_cast<std::int8_t>(nextLevel);
0600     }
0601 
0602     // update
0603 
0604     std::uint32_t nChanges = 0;
0605 
0606     for (auto pS : v_new) {
0607       if (pS->next != pS->level) {
0608         nChanges++;
0609         pS->level = pS->next;
0610         if (maxLevel < pS->level) {
0611           maxLevel = pS->level;
0612         }
0613       }
0614     }
0615 
0616     if (nChanges == 0) {
0617       break;
0618     }
0619 
0620     v_old.swap(v_new);
0621     v_new.clear();
0622   }
0623 
0624   return maxLevel;
0625 }
0626 
0627 void GraphBasedTrackSeeder::extractSeedsFromTheGraph(
0628     std::uint32_t maxLevel, std::uint32_t nEdges, std::int32_t nHits,
0629     std::vector<GbtsEdge>& edgeStorage,
0630     std::vector<SeedProperties>& vSeedCandidates,
0631     const GbtsTrackingFilter& filter) const {
0632   vSeedCandidates.clear();
0633 
0634   // a triplet + 2 confirmation
0635   std::uint32_t minLevel = 3;
0636 
0637   if (m_cfg.lrtMode) {
0638     // a triplet + 1 confirmation
0639     minLevel = 2;
0640   }
0641 
0642   if (maxLevel < minLevel) {
0643     return;
0644   }
0645 
0646   std::vector<GbtsEdge*> vSeeds;
0647 
0648   vSeeds.reserve(nEdges / 2);
0649 
0650   for (std::uint32_t edgeIndex = 0; edgeIndex < nEdges; ++edgeIndex) {
0651     GbtsEdge* pS = &(edgeStorage.at(edgeIndex));
0652 
0653     if (pS->level < static_cast<std::int32_t>(minLevel)) {
0654       continue;
0655     }
0656 
0657     vSeeds.push_back(pS);
0658   }
0659 
0660   if (vSeeds.empty()) {
0661     return;
0662   }
0663 
0664   std::ranges::sort(vSeeds, std::ranges::greater{},
0665                     [](const GbtsEdge* e) { return e->level; });
0666 
0667   // backtracking
0668 
0669   vSeedCandidates.reserve(vSeeds.size());
0670 
0671   GbtsTrackingFilter::State filterState{};
0672 
0673   for (GbtsEdge* pS : vSeeds) {
0674     if (pS->level == -1) {
0675       continue;
0676     }
0677 
0678     GbtsEdgeState rs = filter.followTrack(filterState, edgeStorage, *pS);
0679 
0680     if (!rs.initialized) {
0681       continue;
0682     }
0683 
0684     if (minLevel > static_cast<std::uint32_t>(rs.vs.size())) {
0685       continue;
0686     }
0687 
0688     const float seedEta = std::abs(-std::log(pS->p[0]));
0689 
0690     std::vector<const GbtsNode*> vN;
0691 
0692     for (auto sIt = rs.vs.rbegin(); sIt != rs.vs.rend(); ++sIt) {
0693       if (seedEta > m_cfg.edgeMaskMinEta) {
0694         // mark as collected
0695         (*sIt)->level = -1;
0696       }
0697 
0698       if (sIt == rs.vs.rbegin()) {
0699         vN.push_back((*sIt)->n1);
0700       }
0701 
0702       vN.push_back((*sIt)->n2);
0703     }
0704 
0705     if (vN.size() < 3) {
0706       continue;
0707     }
0708 
0709     std::vector<std::uint32_t> vSpIdx;
0710     vSpIdx.resize(vN.size());
0711 
0712     for (std::uint32_t k = 0; k < vN.size(); ++k) {
0713       vSpIdx[k] = vN[k]->idx;
0714     }
0715 
0716     vSeedCandidates.emplace_back(-rs.j / vN.size(), 0, std::move(vSpIdx));
0717   }
0718 
0719   // clone removal code goes below ...
0720 
0721   std::ranges::sort(vSeedCandidates,
0722                     [](const SeedProperties& s1, const SeedProperties& s2) {
0723                       return s1 < s2;
0724                     });
0725 
0726   std::vector<std::uint32_t> h2t(nHits + 1, 0);  // hit to track associations
0727 
0728   std::uint32_t trackId = 0;
0729 
0730   for (const auto& seed : vSeedCandidates) {
0731     ++trackId;
0732 
0733     // loop over space points indices
0734     for (const auto& h : seed.spacePoints) {
0735       const std::uint32_t hitId = h + 1;
0736 
0737       const std::uint32_t tid = h2t[hitId];
0738 
0739       // unused hit or used by a lesser track
0740       if (tid == 0 || tid > trackId) {
0741         // overwrite
0742         h2t[hitId] = trackId;
0743       }
0744     }
0745   }
0746 
0747   for (std::uint32_t trackIdx = 0; trackIdx < vSeedCandidates.size();
0748        ++trackIdx) {
0749     const std::uint32_t nTotal = vSeedCandidates[trackIdx].spacePoints.size();
0750     std::uint32_t nOther = 0;
0751 
0752     trackId = trackIdx + 1;
0753 
0754     for (const auto& h : vSeedCandidates[trackIdx].spacePoints) {
0755       const std::uint32_t hitId = h + 1;
0756 
0757       const std::uint32_t tid = h2t[hitId];
0758 
0759       // taken by a better candidate
0760       if (tid != trackId) {
0761         nOther++;
0762       }
0763     }
0764 
0765     if (nOther > m_cfg.hitShareThreshold * nTotal) {
0766       // reject
0767       vSeedCandidates[trackIdx].isClone = -1;
0768     }
0769   }
0770 }
0771 
0772 bool GraphBasedTrackSeeder::checkZ0BitMask(const std::uint16_t z0BitMask,
0773                                            const float z0, const float minZ0,
0774                                            const float z0HistoCoeff) const {
0775   if (z0BitMask == 0) {
0776     return true;
0777   }
0778 
0779   const float dz = z0 - minZ0;
0780   const std::int32_t z0BinIndex = static_cast<std::int32_t>(z0HistoCoeff * dz);
0781 
0782   if (((z0BitMask >> z0BinIndex) & 1) != 0) {
0783     return true;
0784   }
0785 
0786   // check adjacent bins as well
0787 
0788   const float z0Resolution = 2.5;
0789 
0790   const float dzm = dz - z0Resolution;
0791 
0792   std::int32_t nextBin = static_cast<std::int32_t>(z0HistoCoeff * dzm);
0793 
0794   if (nextBin >= 0 && nextBin != z0BinIndex) {
0795     if (((z0BitMask >> nextBin) & 1) != 0) {
0796       return true;
0797     }
0798   }
0799 
0800   const float dzp = dz + z0Resolution;
0801 
0802   nextBin = static_cast<std::int32_t>(z0HistoCoeff * dzp);
0803 
0804   if (nextBin < 16 && nextBin != z0BinIndex) {
0805     if (((z0BitMask >> nextBin) & 1) != 0) {
0806       return true;
0807     }
0808   }
0809 
0810   return false;
0811 }
0812 
0813 }  // namespace Acts::Experimental