Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-26 08:18:59

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/GraphBasedTrackSeeder.hpp"
0010 
0011 #include "Acts/Seeding/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::Options::Options(float bFieldInZ_)
0031     : bFieldInZ(bFieldInZ_) {
0032   ptCoeff = 0.5f * bFieldInZ * Acts::UnitConstants::m;
0033 }
0034 
0035 GraphBasedTrackSeeder::GraphBasedTrackSeeder(
0036     const DerivedConfig& config, std::shared_ptr<GbtsGeometry> geometry,
0037     std::unique_ptr<const Acts::Logger> logger)
0038     : m_cfg(config),
0039       m_geometry(std::move(geometry)),
0040       m_logger(std::move(logger)) {
0041   m_mlLut = parseGbtsMlLookupTable(m_cfg.lutInputFile);
0042 }
0043 
0044 void GraphBasedTrackSeeder::createSeeds(const SpacePointContainer& spacePoints,
0045                                         const GbtsRoiDescriptor& roi,
0046                                         const std::vector<bool>& isPixelLayer,
0047                                         const std::uint32_t maxLayers,
0048                                         const GbtsTrackingFilter& filter,
0049                                         const Options& options,
0050                                         SeedContainer& outputSeeds) const {
0051   const std::vector<std::vector<GbtsNode>> nodesPerLayer =
0052       createNodes(spacePoints, maxLayers);
0053 
0054   createSeeds(nodesPerLayer, isPixelLayer, roi, filter, options, outputSeeds);
0055 }
0056 
0057 void GraphBasedTrackSeeder::createSeeds(
0058     const std::vector<std::vector<GbtsNode>>& nodesPerLayer,
0059     const std::vector<bool>& isPixelLayer, const GbtsRoiDescriptor& roi,
0060     const GbtsTrackingFilter& filter, const Options& options,
0061     SeedContainer& outputSeeds) const {
0062   GbtsNodeStorage nodeStorage(m_geometry, m_mlLut);
0063 
0064   std::uint32_t nPixelLoaded = 0;
0065   std::uint32_t nStripLoaded = 0;
0066 
0067   std::uint32_t nHits = 0;
0068 
0069   for (std::uint16_t l = 0; l < nodesPerLayer.size(); l++) {
0070     const std::vector<GbtsNode>& nodes = nodesPerLayer[l];
0071     nHits += nodes.size();
0072 
0073     if (nodes.empty()) {
0074       continue;
0075     }
0076 
0077     // load nodes based on if they are in pixel or strip layers.
0078     const bool isPixel = isPixelLayer[l];
0079 
0080     if (isPixel) {
0081       nPixelLoaded += nodeStorage.loadPixelGraphNodes(
0082           l, nodes, m_cfg.useMl, m_cfg.maxEndcapClusterWidth);
0083     } else {
0084       nStripLoaded += nodeStorage.loadStripGraphNodes(l, nodes);
0085     }
0086   }
0087   ACTS_DEBUG("Loaded " << nPixelLoaded << " pixel space points and "
0088                        << nStripLoaded << " strip space points");
0089 
0090   nodeStorage.sortByPhi();
0091 
0092   nodeStorage.initializeNodes(m_cfg.useMl);
0093 
0094   nodeStorage.generatePhiIndexing(1.5f * m_cfg.phiSliceWidth);
0095 
0096   std::vector<GbtsEdge> edgeStorage;
0097 
0098   std::pair<std::int32_t, std::int32_t> graphStats =
0099       buildTheGraph(roi, nodeStorage, edgeStorage, options);
0100 
0101   ACTS_DEBUG("Created graph with " << graphStats.first << " edges and "
0102                                    << graphStats.second << " edge links");
0103 
0104   if (graphStats.first == 0 || graphStats.second == 0) {
0105     ACTS_WARNING("Missing edges or edge connections");
0106   }
0107 
0108   std::uint32_t maxLevel = runCCA(graphStats.first, edgeStorage);
0109 
0110   ACTS_DEBUG("Reached Level " << maxLevel << " after GNN iterations");
0111 
0112   std::vector<OutputSeedProperties> vOutputSeeds;
0113   extractSeedsFromTheGraph(maxLevel, graphStats.first, nHits, edgeStorage,
0114                            vOutputSeeds, filter);
0115 
0116   ACTS_DEBUG("GBTS created " << vOutputSeeds.size() << " seeds");
0117   if (vOutputSeeds.empty()) {
0118     ACTS_WARNING("No Seed Candidates");
0119   }
0120 
0121   // add to output seed container
0122   for (const auto& seed : vOutputSeeds) {
0123     auto newSeed = outputSeeds.createSeed();
0124     newSeed.assignSpacePointIndices(seed.spacePoints);
0125     newSeed.quality() = seed.seedQuality;
0126   }
0127 }
0128 
0129 GbtsMlLookupTable GraphBasedTrackSeeder::parseGbtsMlLookupTable(
0130     const std::string& lutInputFile) {
0131   if (!m_cfg.useMl) {
0132     return {};
0133   }
0134   if (lutInputFile.empty()) {
0135     throw std::runtime_error("Cannot find ML predictor LUT file");
0136   }
0137 
0138   std::ifstream ifs(std::string(lutInputFile).c_str());
0139   if (!ifs.is_open()) {
0140     throw std::runtime_error("Failed to open LUT file");
0141   }
0142 
0143   GbtsMlLookupTable mlLut;
0144   mlLut.reserve(100);
0145 
0146   float clWidth{};
0147   float min1{};
0148   float max1{};
0149   float min2{};
0150   float max2{};
0151   while (ifs >> clWidth >> min1 >> max1 >> min2 >> max2) {
0152     mlLut.emplace_back(std::array<float, 5>{clWidth, min1, max1, min2, max2});
0153   }
0154 
0155   if (!ifs.eof()) {
0156     // ended if parse error present, not clean EOF
0157     throw std::runtime_error("Stopped reading LUT file due to parse error");
0158   }
0159 
0160   return mlLut;
0161 }
0162 
0163 std::vector<std::vector<GbtsNode>> GraphBasedTrackSeeder::createNodes(
0164     const SpacePointContainer& spacePoints,
0165     const std::uint32_t maxLayers) const {
0166   auto layerColumn = spacePoints.column<std::uint32_t>("layerId");
0167   auto clusterWidthColumn = spacePoints.column<float>("clusterWidth");
0168   auto localPositionColumn = spacePoints.column<float>("localPositionY");
0169 
0170   std::vector<std::vector<GbtsNode>> nodesPerLayer(maxLayers);
0171   // reserve for better efficiency
0172   for (auto& v : nodesPerLayer) {
0173     v.reserve(10000);
0174   }
0175 
0176   // assumes case where all layers are pixel
0177   std::vector<bool> pixelLayers{};
0178   pixelLayers.reserve(maxLayers);
0179 
0180   for (const auto& sp : spacePoints) {
0181     // for every sp in container,
0182     // add its variables to nodeStorage organised by layer
0183     const std::uint16_t layer = sp.extra(layerColumn);
0184 
0185     // add node to storage
0186     GbtsNode& node = nodesPerLayer[layer].emplace_back(layer);
0187 
0188     // fill the node with space point variables
0189 
0190     node.x = sp.x();
0191     node.y = sp.y();
0192     node.z = sp.z();
0193     node.r = sp.r();
0194     node.phi = sp.phi();
0195     node.idx = sp.index();
0196     node.pcw = sp.extra(clusterWidthColumn);
0197     node.locPosY = sp.extra(localPositionColumn);
0198   }
0199 
0200   return nodesPerLayer;
0201 }
0202 
0203 std::pair<std::int32_t, std::int32_t> GraphBasedTrackSeeder::buildTheGraph(
0204     const GbtsRoiDescriptor& roi, GbtsNodeStorage& nodeStorage,
0205     std::vector<GbtsEdge>& edgeStorage, const Options& options) const {
0206   // used to calculate Z cut on doublets
0207   const float cutZMinU =
0208       m_cfg.minZ0 + m_cfg.maxOuterRadius * static_cast<float>(roi.dzdrMin());
0209   const float cutZMaxU =
0210       m_cfg.maxZ0 + m_cfg.maxOuterRadius * static_cast<float>(roi.dzdrMax());
0211 
0212   // correction due to limited pT resolution
0213   const float tripletPtMin = 0.8f * m_cfg.minPt;
0214 
0215   // to re-scale original tunings done for the 900 MeV pT cut
0216   const float ptScale = 0.9f / m_cfg.minPt;
0217 
0218   const float maxCurv = options.ptCoeff / tripletPtMin;
0219 
0220   float maxKappaHighEta =
0221       m_cfg.lrtMode ? 1.0f * maxCurv : std::sqrt(0.8f) * maxCurv;
0222   float maxKappaLowEta =
0223       m_cfg.lrtMode ? 1.0f * maxCurv : std::sqrt(0.6f) * maxCurv;
0224 
0225   // new settings for curvature cuts
0226   if (!m_cfg.useOldTunings && !m_cfg.lrtMode) {
0227     maxKappaHighEta = 4.75e-4f * ptScale;
0228     maxKappaLowEta = 3.75e-4f * ptScale;
0229   }
0230 
0231   const float dPhiCoeff = m_cfg.lrtMode ? 1.0f * maxCurv : 0.68f * maxCurv;
0232 
0233   // the default sliding window along phi
0234   const float deltaPhi0 = 0.5f * m_cfg.phiSliceWidth;
0235 
0236   std::uint32_t nConnections = 0;
0237 
0238   edgeStorage.reserve(m_cfg.nMaxEdges);
0239 
0240   std::uint32_t nEdges = 0;
0241 
0242   // scale factor to get indexes of binned beamspot
0243   // assuming 16-bit z0 bitmask
0244 
0245   const std::uint32_t zBins = 16;
0246   const float z0HistoCoeff = zBins / (m_cfg.maxZ0 - m_cfg.minZ0 + 1e-6);
0247 
0248   // loop over bin groups
0249   for (const auto& bg : m_geometry->binGroups()) {
0250     GbtsEtaBin& B1 = nodeStorage.getEtaBin(bg.first);
0251 
0252     if (B1.empty()) {
0253       continue;
0254     }
0255 
0256     const float rb1 = B1.minRadius;
0257 
0258     const std::uint32_t layerId1 = B1.layerId;
0259 
0260     const bool isBarrel1 = (layerId1 / 10000) == 8;
0261 
0262     // prepare a sliding window for each bin2 in the group
0263 
0264     std::vector<SlidingWindow> phiSlidingWindow;
0265 
0266     // initialization using default ctor
0267     phiSlidingWindow.resize(bg.second.size());
0268 
0269     std::uint32_t winIdx = 0;
0270 
0271     // loop over n2 eta-bins in L2 layers
0272     for (const auto& b2Idx : bg.second) {
0273       const GbtsEtaBin& B2 = nodeStorage.getEtaBin(b2Idx);
0274 
0275       if (B2.empty()) {
0276         ++winIdx;
0277         continue;
0278       }
0279 
0280       const float rb2 = B2.maxRadius;
0281 
0282       float deltaPhi = deltaPhi0;  // the default
0283 
0284       // override the default window width
0285       if (m_cfg.useEtaBinning) {
0286         const float absDr = std::fabs(rb2 - rb1);
0287         if (m_cfg.useOldTunings) {
0288           deltaPhi = m_cfg.minDeltaPhi + dPhiCoeff * absDr;
0289         } else {
0290           if (absDr < 60.0) {
0291             deltaPhi = 0.002f + 4.33e-4f * ptScale * absDr;
0292           } else {
0293             deltaPhi = 0.015f + 2.2e-4f * ptScale * absDr;
0294           }
0295         }
0296       }
0297 
0298       phiSlidingWindow[winIdx].bin = &B2;
0299       phiSlidingWindow[winIdx].hasNodes = true;
0300       phiSlidingWindow[winIdx].deltaPhi = deltaPhi;
0301       ++winIdx;
0302     }
0303 
0304     // in GBTSv3 the outer loop goes over n1 nodes in the Layer 1 bin
0305     for (std::uint32_t n1Idx = 0; n1Idx < B1.vn.size(); ++n1Idx) {
0306       // initialization using the top watermark of the edge storage
0307       B1.vFirstEdge[n1Idx] = nEdges;
0308 
0309       // the counter for the incoming graph edges created for n1
0310       std::uint16_t numCreatedEdges = 0;
0311 
0312       bool isConnected = false;
0313 
0314       std::array<std::uint8_t, 16> z0Histo = {};
0315 
0316       const std::array<float, 5>& n1pars = B1.params[n1Idx];
0317 
0318       const float phi1 = n1pars[2];
0319       const float r1 = n1pars[3];
0320       const float z1 = n1pars[4];
0321 
0322       // the intermediate loop over sliding windows
0323       for (auto& slw : phiSlidingWindow) {
0324         if (!slw.hasNodes) {
0325           continue;
0326         }
0327 
0328         const GbtsEtaBin& B2 = *slw.bin;
0329 
0330         const std::uint32_t lk2 = B2.layerId;
0331 
0332         const bool isBarrel2 = (lk2 / 10000) == 8;
0333 
0334         const float deltaPhi = slw.deltaPhi;
0335 
0336         // sliding window phi1 +/- deltaPhi
0337 
0338         const float minPhi = phi1 - deltaPhi;
0339         const float maxPhi = phi1 + deltaPhi;
0340 
0341         // the inner loop over n2 nodes using sliding window
0342         for (std::uint32_t n2PhiIdx = slw.firstIt;
0343              n2PhiIdx < B2.vPhiNodes.size(); ++n2PhiIdx) {
0344           const float phi2 = B2.vPhiNodes[n2PhiIdx].first;
0345 
0346           if (phi2 < minPhi) {
0347             // update the window position
0348             slw.firstIt = n2PhiIdx;
0349             continue;
0350           }
0351           if (phi2 > maxPhi) {
0352             // break and go to the next window
0353             break;
0354           }
0355 
0356           const std::uint32_t n2Idx = B2.vPhiNodes[n2PhiIdx].second;
0357 
0358           const std::uint16_t nodeInfo = B2.vIsConnected[n2Idx];
0359 
0360           // skip isolated nodes as their incoming edges lead to nowhere
0361           if ((layerId1 == 80000) && (nodeInfo == 0)) {
0362             continue;
0363           }
0364 
0365           const std::uint32_t n2FirstEdge = B2.vFirstEdge[n2Idx];
0366           const std::uint16_t n2NumEdges = B2.vNumEdges[n2Idx];
0367           const std::uint32_t n2LastEdge = n2FirstEdge + n2NumEdges;
0368 
0369           const std::array<float, 5>& n2pars = B2.params[n2Idx];
0370 
0371           const float r2 = n2pars[3];
0372 
0373           const float dr = r2 - r1;
0374 
0375           if (dr < m_cfg.minDeltaRadius) {
0376             continue;
0377           }
0378 
0379           const float z2 = n2pars[4];
0380 
0381           const float dz = z2 - z1;
0382           const float tau = dz / dr;
0383           const float ftau = std::fabs(tau);
0384           if (ftau > 36.0f) {
0385             continue;
0386           }
0387 
0388           if (ftau < n1pars[0]) {
0389             continue;
0390           }
0391           if (ftau > n1pars[1]) {
0392             continue;
0393           }
0394 
0395           if (ftau < n2pars[0]) {
0396             continue;
0397           }
0398           if (ftau > n2pars[1]) {
0399             continue;
0400           }
0401 
0402           const float z0 = z1 - r1 * tau;
0403 
0404           if (layerId1 == 80000) {  // check against non-empty z0 histogram
0405             if (!checkZ0BitMask(nodeInfo, z0, m_cfg.minZ0, z0HistoCoeff)) {
0406               continue;
0407             }
0408           }
0409 
0410           if (m_cfg.doubletFilterRZ) {
0411             if (z0 < m_cfg.minZ0 || z0 > m_cfg.maxZ0) {
0412               continue;
0413             }
0414 
0415             const float zOuter = z0 + m_cfg.maxOuterRadius * tau;
0416 
0417             if (zOuter < cutZMinU || zOuter > cutZMaxU) {
0418               continue;
0419             }
0420           }
0421 
0422           const float curv = (phi2 - phi1) / dr;
0423           const float absCurv = std::abs(curv);
0424 
0425           if (ftau < 4.0f) {  // eta = 2.1
0426             if (absCurv > maxKappaLowEta) {
0427               continue;
0428             }
0429           } else {
0430             if (absCurv > maxKappaHighEta) {
0431               continue;
0432             }
0433           }
0434 
0435           const float expEta = fastHypot(1, tau) - tau;
0436 
0437           // match edge candidate against edges incoming to n2
0438           if (m_cfg.matchBeforeCreate &&
0439               (layerId1 == 80000 || layerId1 == 81000)) {
0440             // we must have enough incoming edges to decide
0441             bool isGood = n2NumEdges <= 2;
0442 
0443             if (!isGood) {
0444               const float uat1 = 1.0f / expEta;
0445 
0446               for (std::uint32_t n2InIdx = n2FirstEdge; n2InIdx < n2LastEdge;
0447                    ++n2InIdx) {
0448                 const float tau2 = edgeStorage.at(n2InIdx).p[0];
0449                 const float tauRatio = tau2 * uat1 - 1.0f;
0450 
0451                 if (std::abs(tauRatio) > m_cfg.tauRatioPrecut) {  // bad match
0452                   continue;
0453                 }
0454                 isGood = true;  // good match found
0455                 break;
0456               }
0457             }
0458 
0459             if (!isGood) {  // no match found, skip creating [n1 <- n2] edge
0460               continue;
0461             }
0462           }
0463 
0464           const float dPhi2 = curv * r2;
0465           const float dPhi1 = curv * r1;
0466 
0467           if (nEdges < m_cfg.nMaxEdges) {
0468             edgeStorage.emplace_back(B1.vn[n1Idx], B2.vn[n2Idx], expEta, curv,
0469                                      phi1 + dPhi1);
0470 
0471             ++numCreatedEdges;
0472 
0473             const std::uint32_t outEdgeIdx = nEdges;
0474 
0475             const float uat2 = 1.f / expEta;
0476             const float phi2u = phi2 + dPhi2;
0477             const float curv2 = curv;
0478 
0479             // looking for neighbours of the new edge
0480             for (std::uint32_t inEdgeIdx = n2FirstEdge; inEdgeIdx < n2LastEdge;
0481                  ++inEdgeIdx) {
0482               GbtsEdge* pS = &(edgeStorage.at(inEdgeIdx));
0483 
0484               if (pS->nNei >= gbtsNumSegConns) {
0485                 continue;
0486               }
0487 
0488               const std::uint32_t lk3 =
0489                   m_geometry->layerIdByIndex(pS->n2->layer);
0490 
0491               const bool isBarrel3 = (lk3 / 10000) == 8;
0492 
0493               const float absTauRatio = std::abs(pS->p[0] * uat2 - 1.0f);
0494               float addTauRatioCorr = 0;
0495 
0496               if (m_cfg.useAdaptiveCuts) {
0497                 if (isBarrel1 && isBarrel2 && isBarrel3) {
0498                   const bool noGap =
0499                       ((lk3 - lk2) == 1000) && ((lk2 - layerId1) == 1000);
0500 
0501                   // assume more scattering due to the layer in between
0502                   if (!noGap) {
0503                     addTauRatioCorr = m_cfg.tauRatioCorr;
0504                   }
0505                 } else {
0506                   bool mixedTriplet = isBarrel1 && isBarrel2 && !isBarrel3;
0507                   if (mixedTriplet) {
0508                     addTauRatioCorr = m_cfg.tauRatioCorr;
0509                   }
0510                 }
0511               }
0512               // bad match
0513               if (absTauRatio > m_cfg.tauRatioCut + addTauRatioCorr) {
0514                 continue;
0515               }
0516 
0517               float dPhi = phi2u - pS->p[2];
0518 
0519               if (dPhi < -std::numbers::pi_v<float>) {
0520                 dPhi += 2 * std::numbers::pi_v<float>;
0521               } else if (dPhi > std::numbers::pi_v<float>) {
0522                 dPhi -= 2 * std::numbers::pi_v<float>;
0523               }
0524 
0525               if (std::abs(dPhi) > m_cfg.cutDPhiMax) {
0526                 continue;
0527               }
0528 
0529               const float dcurv = curv2 - pS->p[1];
0530 
0531               if (dcurv < -m_cfg.cutDCurvMax || dcurv > m_cfg.cutDCurvMax) {
0532                 continue;
0533               }
0534 
0535               // final check: cuts on pT and d0
0536               if (m_cfg.validateTriplets) {
0537                 // Pixel barrel
0538                 if (isBarrel1 && isBarrel2 && isBarrel3) {
0539                   const std::array<const GbtsNode*, 3> candidateTriplet = {
0540                       B1.vn[n1Idx], B2.vn[n2Idx], pS->n2};
0541 
0542                   if (!validateTriplet(candidateTriplet, tripletPtMin,
0543                                        absTauRatio, m_cfg.tauRatioCut,
0544                                        options)) {
0545                     continue;
0546                   }
0547                 }
0548               }
0549 
0550               pS->vNei[pS->nNei] = outEdgeIdx;
0551               ++pS->nNei;
0552 
0553               isConnected = true;  // there is at least one good match
0554 
0555               // edge confirmed - update z0 histogram
0556 
0557               const std::uint32_t z0BinIndex =
0558                   static_cast<std::uint32_t>(z0HistoCoeff * (z0 - m_cfg.minZ0));
0559 
0560               ++z0Histo[z0BinIndex];
0561 
0562               nConnections++;
0563             }
0564             nEdges++;
0565           }
0566         }  // loop over n2 (outer) nodes inside a sliding window on n2 bin
0567       }  // loop over sliding windows associated with n2 bins
0568 
0569       // updating the n1 node attributes
0570 
0571       B1.vNumEdges[n1Idx] = numCreatedEdges;
0572       if (isConnected) {
0573         std::uint16_t z0BitMask = 0x0;
0574 
0575         for (std::uint32_t bIdx = 0; bIdx < 16; ++bIdx) {
0576           if (z0Histo[bIdx] == 0) {
0577             continue;
0578           }
0579 
0580           z0BitMask |= (1 << bIdx);
0581         }
0582 
0583         // non-zero mask indicates that there is at least one connected edge
0584         B1.vIsConnected[n1Idx] = z0BitMask;
0585       }
0586 
0587     }  // loop over n1 (inner) nodes
0588   }  // loop over bin groups: a single n1 bin and multiple n2 bins
0589 
0590   if (nEdges >= m_cfg.nMaxEdges) {
0591     ACTS_WARNING(
0592         "Maximum number of graph edges exceeded - possible efficiency loss "
0593         << nEdges);
0594   }
0595   return std::make_pair(nEdges, nConnections);
0596 }
0597 
0598 std::int32_t GraphBasedTrackSeeder::runCCA(
0599     const std::uint32_t nEdges, std::vector<GbtsEdge>& edgeStorage) const {
0600   constexpr std::uint32_t maxIter = 15;
0601 
0602   std::int32_t maxLevel = 0;
0603 
0604   std::uint32_t iter = 0;
0605 
0606   std::vector<GbtsEdge*> vOld;
0607 
0608   for (std::uint32_t edgeIndex = 0; edgeIndex < nEdges; ++edgeIndex) {
0609     GbtsEdge* pS = &(edgeStorage[edgeIndex]);
0610     if (pS->nNei == 0) {
0611       continue;
0612     }
0613 
0614     // TODO: increment level for segments as they already have at least one
0615     // neighbour
0616     vOld.push_back(pS);
0617   }
0618 
0619   std::vector<GbtsEdge*> vNew;
0620   vNew.reserve(vOld.size());
0621 
0622   // generate proposals
0623   for (; iter < maxIter; iter++) {
0624     vNew.clear();
0625 
0626     for (GbtsEdge* pS : vOld) {
0627       std::int32_t nextLevel = pS->level;
0628 
0629       for (std::uint32_t nIdx = 0; nIdx < pS->nNei; ++nIdx) {
0630         const std::uint32_t nextEdgeIdx = pS->vNei[nIdx];
0631 
0632         GbtsEdge* pN = &(edgeStorage[nextEdgeIdx]);
0633 
0634         if (pS->level == pN->level) {
0635           nextLevel = pS->level + 1;
0636           vNew.push_back(pS);
0637           break;
0638         }
0639       }
0640 
0641       // proposal
0642       pS->next = static_cast<std::int8_t>(nextLevel);
0643     }
0644 
0645     // update
0646 
0647     std::uint32_t nChanges = 0;
0648 
0649     for (auto pS : vNew) {
0650       if (pS->next != pS->level) {
0651         nChanges++;
0652         pS->level = pS->next;
0653         if (maxLevel < pS->level) {
0654           maxLevel = pS->level;
0655         }
0656       }
0657     }
0658 
0659     if (nChanges == 0) {
0660       break;
0661     }
0662 
0663     vOld.swap(vNew);
0664     vNew.clear();
0665   }
0666 
0667   return maxLevel;
0668 }
0669 
0670 void GraphBasedTrackSeeder::extractSeedsFromTheGraph(
0671     std::uint32_t maxLevel, std::uint32_t nEdges, std::int32_t nHits,
0672     std::vector<GbtsEdge>& edgeStorage,
0673     std::vector<OutputSeedProperties>& vOutputSeeds,
0674     const GbtsTrackingFilter& filter) const {
0675   // a triplet + 1 confirmation
0676   std::uint8_t minLevel = 3;
0677 
0678   if (m_cfg.lrtMode) {
0679     // a triplet + no confirmation
0680     minLevel = 2;
0681   }
0682 
0683   if (maxLevel < minLevel) {
0684     return;
0685   }
0686 
0687   std::vector<GbtsEdge*> vChainHeads;
0688 
0689   vChainHeads.reserve(nEdges / 2);
0690 
0691   for (std::uint32_t edgeIndex = 0; edgeIndex < nEdges; ++edgeIndex) {
0692     GbtsEdge* pS = &(edgeStorage.at(edgeIndex));
0693 
0694     if (m_cfg.lrtMode || !m_cfg.addTriplets) {
0695       if (pS->level < minLevel) {
0696         continue;
0697       }
0698     } else {  // eta-dependent cut
0699       const float edgeAbsEta = std::abs(-std::log(pS->p[0]));
0700 
0701       if (edgeAbsEta > m_cfg.maxAbsEtaAddTripelts) {
0702         if (pS->level < minLevel) {
0703           continue;
0704         }
0705       } else {
0706         if (pS->level < minLevel - 1) {
0707           continue;
0708         }
0709       }
0710     }
0711 
0712     vChainHeads.push_back(pS);
0713   }
0714 
0715   if (vChainHeads.empty()) {
0716     return;
0717   }
0718 
0719   std::ranges::sort(vChainHeads, std::ranges::greater{},
0720                     [](const GbtsEdge* e) { return e->level; });
0721 
0722   // backtracking
0723 
0724   std::vector<SeedCandidateProperties> vSeedCandidates;
0725 
0726   vSeedCandidates.reserve(vChainHeads.size());
0727 
0728   std::vector<std::pair<float, std::uint32_t>> vArgSort;
0729 
0730   vArgSort.reserve(vChainHeads.size());
0731 
0732   std::uint32_t seedCounter = 0;
0733 
0734   GbtsTrackingFilter::State filterState{};
0735 
0736   for (GbtsEdge* pS : vChainHeads) {
0737     if (pS->level == -1) {
0738       continue;
0739     }
0740 
0741     GbtsEdgeState rs = filter.followTrack(filterState, edgeStorage, *pS);
0742 
0743     if (!rs.initialized) {
0744       continue;
0745     }
0746 
0747     const float seedAbsEta = std::abs(-std::log(pS->p[0]));
0748 
0749     const std::uint32_t chainLength = static_cast<std::uint32_t>(rs.vs.size());
0750 
0751     if (m_cfg.lrtMode || !m_cfg.addTriplets) {
0752       if (chainLength < minLevel) {
0753         continue;
0754       }
0755     } else {
0756       if (seedAbsEta > m_cfg.maxAbsEtaAddTripelts) {
0757         if (chainLength < minLevel) {
0758           continue;
0759         }
0760       } else {
0761         if (chainLength < static_cast<std::uint32_t>(minLevel) - 1u) {
0762           continue;
0763         }
0764       }
0765     }
0766 
0767     std::vector<const GbtsNode*> vN;
0768 
0769     for (auto sIt = rs.vs.rbegin(); sIt != rs.vs.rend(); ++sIt) {
0770       if (seedAbsEta > m_cfg.edgeMaskMinEta) {
0771         // mark as collected
0772         (*sIt)->level = -1;
0773       }
0774 
0775       if (sIt == rs.vs.rbegin()) {
0776         vN.push_back((*sIt)->n1);
0777       }
0778 
0779       vN.push_back((*sIt)->n2);
0780     }
0781 
0782     // a triplet is accepted if it makes it up to this point
0783     if (vN.size() < 3) {
0784       continue;
0785     }
0786 
0787     const std::uint32_t origSeedSize = vN.size();
0788 
0789     const float origSeedQuality = -rs.j / origSeedSize;
0790 
0791     std::uint32_t seedSplitFlag = (seedAbsEta < m_cfg.maxSeedSplitEta) &&
0792                                           (origSeedSize > 3) &&
0793                                           (origSeedSize <= 5)
0794                                       ? 1
0795                                       : 0;
0796 
0797     // split the seed by dropping spacepoints
0798     if (seedSplitFlag != 0) {
0799       // 2. "drop-outs" and the original seed candidate
0800       std::array<std::array<const GbtsNode*, 3>, 3> triplets{};
0801 
0802       // triplet parameter estimate
0803       std::array<float, 3> invRads{};
0804 
0805       triplets[0] = {vN[0], vN[origSeedSize / 2], vN[origSeedSize - 1]};
0806 
0807       // all but the first one
0808       const std::vector<const GbtsNode*> dropOut1(vN.begin() + 1, vN.end());
0809 
0810       triplets[1] = {dropOut1[0], dropOut1[(origSeedSize - 1) / 2],
0811                      dropOut1[origSeedSize - 2]};
0812 
0813       std::vector<const GbtsNode*> dopOut2;
0814 
0815       dopOut2.reserve(origSeedSize - 1);
0816 
0817       for (std::uint32_t k = 0; k < origSeedSize; k++) {
0818         if (k == origSeedSize / 2) {
0819           continue;  // drop the middle SP in the original seed
0820         }
0821 
0822         dopOut2.emplace_back(vN[k]);
0823       }
0824 
0825       triplets[2] = {dopOut2[0], dopOut2[(origSeedSize - 1) / 2],
0826                      dopOut2[origSeedSize - 2]};
0827 
0828       for (std::uint32_t k = 0; k < invRads.size(); k++) {
0829         invRads[k] = estimateCurvature(triplets[k]);
0830       }
0831 
0832       const std::array<float, 3> diffs = {std::abs(invRads[1] - invRads[0]),
0833                                           std::abs(invRads[2] - invRads[0]),
0834                                           std::abs(invRads[2] - invRads[1])};
0835 
0836       const bool confirmed = diffs[0] < m_cfg.maxInvRadDiff &&
0837                              diffs[1] < m_cfg.maxInvRadDiff &&
0838                              diffs[2] < m_cfg.maxInvRadDiff;
0839 
0840       if (confirmed) {
0841         seedSplitFlag = 0;  // reset the flag
0842       }
0843     }
0844 
0845     vSeedCandidates.emplace_back(origSeedQuality, 0, vN, seedSplitFlag);
0846 
0847     vArgSort.emplace_back(origSeedQuality, seedCounter);
0848 
0849     ++seedCounter;
0850   }
0851 
0852   // clone removal code goes below ...
0853 
0854   std::ranges::sort(vArgSort);
0855 
0856   std::vector<std::uint32_t> h2t(nHits + 1, 0);  // hit to track associations
0857 
0858   std::uint32_t trackId = 0;
0859 
0860   for (const auto& args : vArgSort) {
0861     const auto& seed = vSeedCandidates[args.second];
0862     ++trackId;
0863 
0864     // loop over space points indices
0865     for (const auto& h : seed.spacePoints) {
0866       const std::uint32_t hitId = h->idx + 1;
0867 
0868       const std::uint32_t tid = h2t[hitId];
0869 
0870       // unused hit or used by a lesser track
0871       if (tid == 0 || tid > trackId) {
0872         // overwrite
0873         h2t[hitId] = trackId;
0874       }
0875     }
0876   }
0877 
0878   std::uint32_t trackIdx = 0;
0879 
0880   for (const auto& args : vArgSort) {
0881     const auto& seed = vSeedCandidates[args.second].spacePoints;
0882 
0883     const std::uint32_t nTotal = seed.size();
0884 
0885     std::uint32_t nOther = 0;
0886 
0887     trackId = trackIdx + 1;
0888 
0889     ++trackIdx;
0890 
0891     for (const auto& h : seed) {
0892       const std::uint32_t hitId = h->idx + 1;
0893 
0894       const std::uint32_t tid = h2t[hitId];
0895 
0896       // taken by a better candidate
0897       if (tid != trackId) {
0898         nOther++;
0899       }
0900     }
0901 
0902     if (nOther > m_cfg.hitShareThreshold * nTotal) {
0903       // reject
0904       vSeedCandidates[args.second].isClone = -1;  // reject
0905     }
0906   }
0907   vOutputSeeds.reserve(vSeedCandidates.size());
0908 
0909   // drop the clones and split seeds if need be
0910 
0911   for (const auto& args : vArgSort) {
0912     const auto& seed = vSeedCandidates[args.second];
0913 
0914     if (seed.isClone != 0) {
0915       continue;  // identified as a clone of a better candidate
0916     }
0917 
0918     const auto& vN = seed.spacePoints;
0919 
0920     if (seed.forSeedSplitting == 0) {
0921       // add seed to output
0922 
0923       std::vector<std::uint32_t> vSpIdx;
0924 
0925       vSpIdx.resize(vN.size());
0926 
0927       for (std::uint32_t k = 0; k < vSpIdx.size(); k++) {
0928         vSpIdx[k] = vN[k]->idx;
0929       }
0930 
0931       vOutputSeeds.emplace_back(seed.seedQuality, vSpIdx);
0932 
0933       continue;
0934     }
0935 
0936     // seed split into "drop-out" seeds
0937 
0938     const std::uint32_t seedSize = vN.size();
0939 
0940     const std::array<std::size_t, 2> indices2drop = {
0941         0, seedSize / 2ul};  // the first and the middle
0942 
0943     for (const auto& skipIdx : indices2drop) {
0944       std::vector<std::uint32_t> newSeed;
0945 
0946       newSeed.reserve(seedSize - 1);
0947 
0948       for (std::uint32_t k = 0; k < seedSize; k++) {
0949         if (k == skipIdx) {
0950           continue;
0951         }
0952 
0953         newSeed.emplace_back(vN[k]->idx);
0954       }
0955 
0956       vOutputSeeds.emplace_back(seed.seedQuality, newSeed);
0957     }
0958   }
0959 }
0960 
0961 bool GraphBasedTrackSeeder::checkZ0BitMask(const std::uint16_t z0BitMask,
0962                                            const float z0, const float minZ0,
0963                                            const float z0HistoCoeff) const {
0964   if (z0BitMask == 0) {
0965     return true;
0966   }
0967 
0968   const float dz = z0 - minZ0;
0969   const std::int32_t z0BinIndex = static_cast<std::int32_t>(z0HistoCoeff * dz);
0970 
0971   if (((z0BitMask >> z0BinIndex) & 1) != 0) {
0972     return true;
0973   }
0974 
0975   // check adjacent bins as well
0976 
0977   const float z0Resolution = 2.5;
0978 
0979   const float dzm = dz - z0Resolution;
0980 
0981   std::int32_t nextBin = static_cast<std::int32_t>(z0HistoCoeff * dzm);
0982 
0983   if (nextBin >= 0 && nextBin != z0BinIndex) {
0984     if (((z0BitMask >> nextBin) & 1) != 0) {
0985       return true;
0986     }
0987   }
0988 
0989   const float dzp = dz + z0Resolution;
0990 
0991   nextBin = static_cast<std::int32_t>(z0HistoCoeff * dzp);
0992 
0993   if (nextBin < 16 && nextBin != z0BinIndex) {
0994     if (((z0BitMask >> nextBin) & 1) != 0) {
0995       return true;
0996     }
0997   }
0998 
0999   return false;
1000 }
1001 
1002 float GraphBasedTrackSeeder::estimateCurvature(
1003     const std::array<const GbtsNode*, 3>& nodes) const {
1004   // conformal mapping with the center at the last spacepoint
1005 
1006   std::array<float, 2> u{};
1007   std::array<float, 2> v{};
1008 
1009   const float x0 = nodes[2]->x;
1010   const float y0 = nodes[2]->y;
1011 
1012   const float r0 = nodes[2]->r;
1013 
1014   const float cosA = x0 / r0;
1015 
1016   const float sinA = y0 / r0;
1017 
1018   for (std::uint32_t k = 0; k < 2; k++) {
1019     const float dx = nodes[k]->x - x0;
1020 
1021     const float dy = nodes[k]->y - y0;
1022 
1023     const float r2Inv = 1.0 / (dx * dx + dy * dy);
1024 
1025     const float xn = dx * cosA + dy * sinA;
1026 
1027     const float yn = -dx * sinA + dy * cosA;
1028 
1029     u[k] = xn * r2Inv;
1030     v[k] = yn * r2Inv;
1031   }
1032 
1033   const float du = u[0] - u[1];
1034 
1035   if (du == 0.0) {
1036     return 0.0;
1037   }
1038 
1039   const float A = (v[0] - v[1]) / du;
1040 
1041   const float B = v[1] - A * u[1];
1042 
1043   // curavture in units of 1/mm
1044   return B / std::sqrt(1 + A * A);
1045 }
1046 
1047 bool GraphBasedTrackSeeder::validateTriplet(
1048     const std::array<const GbtsNode*, 3> candidateTriplet,
1049     const float tripletMinPt, const float tauRatio, const float tauRatioCut,
1050     const Options& options) const {
1051   // conformal mapping with the center at the middle spacepoint
1052 
1053   std::array<float, 2> u{};
1054   std::array<float, 2> v{};
1055 
1056   const float x0 = candidateTriplet[1]->x;
1057   const float y0 = candidateTriplet[1]->y;
1058 
1059   const float r0 = candidateTriplet[1]->r;
1060 
1061   const float cosA = x0 / r0;
1062 
1063   const float sinA = y0 / r0;
1064 
1065   for (std::uint32_t k = 0; k < 2; k++) {
1066     const std::uint32_t spIdx = (k == 1) ? 2 : k;
1067 
1068     const float dx = candidateTriplet[spIdx]->x - x0;
1069 
1070     const float dy = candidateTriplet[spIdx]->y - y0;
1071 
1072     const float r2Inv = 1.0f / (dx * dx + dy * dy);
1073 
1074     const float xn = dx * cosA + dy * sinA;
1075 
1076     const float yn = -dx * sinA + dy * cosA;
1077 
1078     u[k] = xn * r2Inv;
1079     v[k] = yn * r2Inv;
1080   }
1081 
1082   const float du = u[0] - u[1];
1083 
1084   if (du == 0.0) {
1085     return false;
1086   }
1087 
1088   const float A = (v[0] - v[1]) / du;
1089 
1090   const float B = v[1] - A * u[1];
1091 
1092   const float d0 = r0 * (B * r0 - A);
1093 
1094   if (std::abs(d0) > m_cfg.d0Max) {
1095     return false;
1096   }
1097 
1098   if (B != 0.0) {  // straight-line track is OK
1099 
1100     const float R = std::sqrt(1 + A * A) / B;
1101 
1102     // 1T magnetic field used
1103     const float pT = std::abs(options.bFieldInZ * R / 2);
1104 
1105     if (pT < tripletMinPt) {
1106       return false;
1107     }
1108 
1109     if (pT > 5 * tripletMinPt) {  // relatively high-pT track
1110 
1111       if (tauRatio > 0.9f * tauRatioCut) {
1112         return false;
1113       }
1114     }
1115   }
1116 
1117   return true;
1118 }
1119 
1120 }  // namespace Acts::Experimental