File indexing completed on 2026-07-16 08:07:54
0001
0002
0003
0004
0005
0006
0007
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) {
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
0092
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
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
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
0161
0162 for (auto& v : node_storage) {
0163 v.reserve(10000);
0164 }
0165
0166 for (auto sp : std::get<0>(container)) {
0167
0168
0169 std::uint16_t layer = sp.extra(std::get<1>(container));
0170
0171
0172 GbtsNode& node = node_storage[layer].emplace_back(layer);
0173
0174
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
0193 const float cut_dphi_max = m_config.LRTmode ? 0.07f : 0.012f;
0194
0195 const float cut_dcurv_max = m_config.LRTmode ? 0.015f : 0.001f;
0196
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
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
0210 float tripletPtMin = 0.8f * m_config.minPt;
0211
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
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
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()) {
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++) {
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
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++) {
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) {
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)) {
0379
0380
0381 bool isGood = v2In.size() <=
0382 2;
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) {
0393 continue;
0394 }
0395 isGood = true;
0396 break;
0397 }
0398 }
0399
0400 if (!isGood) {
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) {
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) {
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 }
0462 }
0463 }
0464 }
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);
0491
0492 }
0493
0494 std::vector<GbtsEdge*> v_new;
0495 v_new.reserve(v_old.size());
0496
0497 for (; iter < maxIter; iter++) {
0498
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;
0518 }
0519
0520
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;
0551
0552 if (m_config.LRTmode) {
0553 minLevel = 2;
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
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;
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
0636
0637 std::sort(vSeedCandidates.begin(), vSeedCandidates.end());
0638
0639 std::vector<int> vTrackIds(vSeedCandidates.size());
0640
0641
0642
0643 std::iota(vTrackIds.begin(), vTrackIds.end(), 1);
0644
0645 std::vector<int> H2T(nHits + 1, 0);
0646
0647 int seedIdx = 0;
0648
0649 for (const auto& seed : vSeedCandidates) {
0650 for (const auto& h : seed.spacepoints) {
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) {
0658
0659 H2T[hit_id] = trackId;
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) {
0679 nOther++;
0680 }
0681 }
0682
0683 if (nOther > m_config.hit_share_threshold * nTotal) {
0684 vSeedCandidates[trackIdx].isClone = -1;
0685 }
0686 }
0687 }
0688
0689 }