File indexing completed on 2026-08-20 08:19:53
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/unit_test.hpp>
0010
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "Acts/EventData/SeedContainer.hpp"
0013 #include "Acts/EventData/SpacePointContainer.hpp"
0014 #include "Acts/Seeding/GbtsGeometry.hpp"
0015 #include "Acts/Seeding/GbtsLayerConnection.hpp"
0016 #include "Acts/Seeding/GbtsRoiDescriptor.hpp"
0017 #include "Acts/Seeding/GbtsTrackingFilter.hpp"
0018 #include "Acts/Seeding/GraphBasedTrackSeeder.hpp"
0019
0020 #include <algorithm>
0021 #include <array>
0022 #include <cmath>
0023 #include <cstdint>
0024 #include <memory>
0025 #include <numbers>
0026 #include <optional>
0027 #include <sstream>
0028 #include <string>
0029 #include <utility>
0030 #include <vector>
0031
0032
0033
0034
0035
0036 namespace Acts::Test {
0037
0038 namespace {
0039
0040 using namespace Acts::UnitLiterals;
0041 using Experimental::GbtsLayerType;
0042
0043
0044 constexpr float kBarrelHalfZ = 150.f;
0045
0046
0047 constexpr float kDiscMinR = 30.f;
0048 constexpr float kDiscMaxR = 220.f;
0049
0050
0051 constexpr float kEtaBinWidth = 0.2f;
0052
0053
0054
0055
0056
0057 struct LayerSpec {
0058 std::int32_t id{};
0059 GbtsLayerType type{};
0060
0061 float refCoord{};
0062
0063 float minBound{};
0064 float maxBound{};
0065 };
0066
0067 constexpr LayerSpec barrelLayer(std::int32_t id, float radius) {
0068 return {id, GbtsLayerType::Barrel, radius, -kBarrelHalfZ, kBarrelHalfZ};
0069 }
0070
0071 constexpr LayerSpec discLayer(std::int32_t id, float z) {
0072 return {id, GbtsLayerType::Endcap, z, kDiscMinR, kDiscMaxR};
0073 }
0074
0075
0076 struct ToyDetector {
0077 std::vector<LayerSpec> layers;
0078 std::vector<std::pair<std::int32_t, std::int32_t>> links;
0079
0080 float maxOuterRadius{};
0081 };
0082
0083 ToyDetector barrelDetector() {
0084 return {{barrelLayer(80000, 40.f), barrelLayer(81000, 80.f),
0085 barrelLayer(82000, 120.f), barrelLayer(83000, 160.f)},
0086 {{81000, 80000}, {82000, 81000}, {83000, 82000}},
0087 200.f};
0088 }
0089
0090
0091
0092 ToyDetector forwardDetector() {
0093 constexpr std::array<float, 4> discZ = {200.f, 260.f, 320.f, 380.f};
0094
0095 ToyDetector detector = barrelDetector();
0096 detector.maxOuterRadius = 300.f;
0097
0098 for (const std::int32_t idBase : {90000, 70000}) {
0099 const float sign = idBase == 90000 ? 1.f : -1.f;
0100 std::int32_t previousId = 0;
0101
0102 for (std::size_t i = 0; i < discZ.size(); ++i) {
0103 const std::int32_t id = idBase + 1000 * static_cast<std::int32_t>(i);
0104 detector.layers.push_back(discLayer(id, sign * discZ[i]));
0105
0106 if (i == 0) {
0107
0108
0109 for (const std::int32_t innerId : {80000, 81000, 82000}) {
0110 detector.links.emplace_back(id, innerId);
0111 }
0112 } else {
0113 detector.links.emplace_back(id, previousId);
0114 }
0115 previousId = id;
0116 }
0117 }
0118
0119 return detector;
0120 }
0121
0122
0123
0124
0125
0126 std::string makeConnectorText(const ToyDetector& detector) {
0127 std::ostringstream os;
0128 os << detector.links.size() << " " << kEtaBinWidth << "\n";
0129 for (std::size_t i = 0; i < detector.links.size(); ++i) {
0130 const auto& [src, dst] = detector.links[i];
0131 os << i << " " << i << " " << src << " " << dst << " 0 0 0\n";
0132 }
0133 return os.str();
0134 }
0135
0136 std::shared_ptr<Experimental::GbtsGeometry> makeGeometry(
0137 const ToyDetector& detector) {
0138 std::vector<Experimental::GbtsLayerDescription> layers;
0139 layers.reserve(detector.layers.size());
0140 for (const LayerSpec& spec : detector.layers) {
0141 Experimental::GbtsLayerDescription layer;
0142 layer.id = spec.id;
0143 layer.type = spec.type;
0144 layer.refCoord = spec.refCoord;
0145 layer.minBound = spec.minBound;
0146 layer.maxBound = spec.maxBound;
0147 layers.push_back(layer);
0148 }
0149
0150 const std::string connectorText = makeConnectorText(detector);
0151 std::istringstream stream{connectorText};
0152 return std::make_shared<Experimental::GbtsGeometry>(
0153 layers, Experimental::GbtsLayerConnectionMap::fromStream(stream, false));
0154 }
0155
0156
0157
0158 struct Track {
0159 float phi{};
0160 float tau{};
0161 };
0162
0163
0164 std::optional<std::pair<float, float>> intersect(const LayerSpec& layer,
0165 const Track& track) {
0166 if (layer.type == GbtsLayerType::Barrel) {
0167 const float z = layer.refCoord * track.tau;
0168 if (z < layer.minBound || z > layer.maxBound) {
0169 return std::nullopt;
0170 }
0171 return std::make_pair(layer.refCoord, z);
0172 }
0173
0174
0175 if (track.tau == 0.f || (layer.refCoord > 0.f) != (track.tau > 0.f)) {
0176 return std::nullopt;
0177 }
0178 const float r = layer.refCoord / track.tau;
0179 if (r < layer.minBound || r > layer.maxBound) {
0180 return std::nullopt;
0181 }
0182 return std::make_pair(r, layer.refCoord);
0183 }
0184
0185
0186 std::vector<Track> makeTracks(std::size_t nTracks, float phiMin, float phiMax,
0187 float tauMin, float tauMax) {
0188 std::vector<Track> tracks;
0189 tracks.reserve(nTracks);
0190 for (std::size_t i = 0; i < nTracks; ++i) {
0191 const float frac = static_cast<float>(i) / nTracks;
0192 Track track;
0193
0194 track.phi = phiMin + (phiMax - phiMin) * (frac + 0.5f / nTracks);
0195 track.tau = tauMin + (tauMax - tauMin) * frac;
0196 tracks.push_back(track);
0197 }
0198 return tracks;
0199 }
0200
0201
0202
0203 std::vector<Track> makeSparseTracks() {
0204 return makeTracks(12, -std::numbers::pi_v<float>, std::numbers::pi_v<float>,
0205 -0.6f, 0.6f);
0206 }
0207
0208
0209
0210 std::vector<Track> makeDenseTracks() {
0211 return makeTracks(40, -0.05f, 0.05f, -0.05f, 0.05f);
0212 }
0213
0214
0215
0216
0217 std::vector<Track> makeForwardTracks() {
0218 constexpr float pi = std::numbers::pi_v<float>;
0219 std::vector<Track> tracks = makeTracks(8, -pi, 0.f, 1.05f, 6.3f);
0220 const std::vector<Track> negative = makeTracks(8, 0.f, pi, -1.05f, -6.3f);
0221 tracks.insert(tracks.end(), negative.begin(), negative.end());
0222 return tracks;
0223 }
0224
0225
0226
0227 std::vector<Track> makeDenseForwardTracks() {
0228 return makeTracks(20, -0.05f, 0.05f, 1.9f, 2.1f);
0229 }
0230
0231
0232
0233
0234 SpacePointContainer makeSpacePoints(const ToyDetector& detector,
0235 const std::vector<Track>& tracks) {
0236 SpacePointContainer container(SpacePointColumns::CopiedFromIndex |
0237 SpacePointColumns::X | SpacePointColumns::Y |
0238 SpacePointColumns::Z | SpacePointColumns::R |
0239 SpacePointColumns::Phi);
0240
0241 auto layerColumn = container.createColumn<std::uint32_t>("layerId");
0242 auto clusterWidthColumn = container.createColumn<float>("clusterWidth");
0243 auto localPositionColumn = container.createColumn<float>("localPositionY");
0244 auto trackColumn = container.createColumn<std::uint32_t>("trackId");
0245
0246 container.reserve(tracks.size() * detector.layers.size());
0247
0248 for (std::size_t track = 0; track < tracks.size(); ++track) {
0249 for (std::size_t layer = 0; layer < detector.layers.size(); ++layer) {
0250 const auto crossing = intersect(detector.layers[layer], tracks[track]);
0251 if (!crossing.has_value()) {
0252 continue;
0253 }
0254 const auto [r, z] = *crossing;
0255
0256 auto sp = container.createSpacePoint();
0257 sp.x() = r * std::cos(tracks[track].phi);
0258 sp.y() = r * std::sin(tracks[track].phi);
0259 sp.z() = z;
0260 sp.r() = r;
0261 sp.phi() = tracks[track].phi;
0262 sp.copiedFromIndex() = sp.index();
0263
0264 sp.extra(layerColumn) = static_cast<std::uint32_t>(layer);
0265 sp.extra(clusterWidthColumn) = 0.f;
0266 sp.extra(localPositionColumn) = 0.f;
0267 sp.extra(trackColumn) = static_cast<std::uint32_t>(track);
0268 }
0269 }
0270
0271 return container;
0272 }
0273
0274 SeedContainer runSeeding(const ToyDetector& detector,
0275 const SpacePointContainer& spacePoints) {
0276 auto geometry = makeGeometry(detector);
0277
0278 const auto numLayers = static_cast<std::uint32_t>(detector.layers.size());
0279
0280 Experimental::GraphBasedTrackSeeder::Config config;
0281 config.minPt = 1_GeV;
0282 config.minZ0 = -kBarrelHalfZ;
0283 config.maxZ0 = kBarrelHalfZ;
0284 config.maxOuterRadius = detector.maxOuterRadius;
0285
0286 config.useMl = false;
0287
0288 const Experimental::GraphBasedTrackSeeder seeder(
0289 Experimental::GraphBasedTrackSeeder::DerivedConfig(config), geometry,
0290 getDefaultLogger("GbtsTest", Logging::Level::WARNING));
0291
0292 const Experimental::GbtsTrackingFilter filter(
0293 Experimental::GbtsTrackingFilter::Config{}, geometry);
0294
0295 const Experimental::GbtsRoiDescriptor roi(0, -4.5, 4.5, 0, -std::numbers::pi,
0296 std::numbers::pi, 0, -kBarrelHalfZ,
0297 kBarrelHalfZ);
0298
0299 const Experimental::GraphBasedTrackSeeder::Options options(2_T);
0300
0301 const std::vector<bool> isPixelLayer(numLayers, true);
0302
0303 SeedContainer seeds;
0304 seeds.assignSpacePointContainer(spacePoints);
0305
0306 seeder.createSeeds(spacePoints, roi, isPixelLayer, filter, options, seeds);
0307
0308 return seeds;
0309 }
0310
0311
0312
0313
0314 std::string formatSeeds(const SeedContainer& seeds) {
0315 std::vector<std::pair<std::uint32_t, std::string>> lines;
0316 for (const auto& seed : seeds) {
0317 const auto indices = seed.spacePointIndices();
0318 std::ostringstream line;
0319 line << seed.quality() << ":";
0320 for (const auto index : indices) {
0321 line << index << ",";
0322 }
0323 lines.emplace_back(indices[0], line.str());
0324 }
0325 std::ranges::sort(lines);
0326
0327 std::ostringstream os;
0328 for (const auto& entry : lines) {
0329 os << entry.second << "\n";
0330 }
0331 return os.str();
0332 }
0333
0334
0335 std::vector<std::size_t> hitsPerTrack(const SpacePointContainer& spacePoints,
0336 std::size_t nTracks) {
0337 auto trackColumn = spacePoints.column<std::uint32_t>("trackId");
0338 std::vector<std::size_t> counts(nTracks, 0);
0339 for (const auto& sp : spacePoints) {
0340 counts.at(sp.extra(trackColumn)) += 1;
0341 }
0342 return counts;
0343 }
0344
0345
0346
0347 void checkSeedsAreWellFormed(const SeedContainer& seeds,
0348 const SpacePointContainer& spacePoints) {
0349 auto trackColumn = spacePoints.column<std::uint32_t>("trackId");
0350
0351 for (const auto& seed : seeds) {
0352 const auto indices = seed.spacePointIndices();
0353 BOOST_REQUIRE_GE(indices.size(), 3u);
0354
0355 BOOST_REQUIRE_LT(indices[0], spacePoints.size());
0356 const std::uint32_t track = spacePoints.at(indices[0]).extra(trackColumn);
0357
0358 float previousR = -1.f;
0359 for (const auto index : indices) {
0360 BOOST_REQUIRE_LT(index, spacePoints.size());
0361 const auto sp = spacePoints.at(index);
0362 BOOST_CHECK_EQUAL(sp.extra(trackColumn), track);
0363 BOOST_CHECK_GT(sp.r(), previousR);
0364 previousR = sp.r();
0365 }
0366 }
0367 }
0368
0369
0370 void checkOneSeedPerTrack(const SeedContainer& seeds,
0371 const SpacePointContainer& spacePoints,
0372 const std::vector<Track>& tracks) {
0373 BOOST_CHECK_EQUAL(seeds.size(), tracks.size());
0374 checkSeedsAreWellFormed(seeds, spacePoints);
0375
0376 auto trackColumn = spacePoints.column<std::uint32_t>("trackId");
0377 const std::vector<std::size_t> hits =
0378 hitsPerTrack(spacePoints, tracks.size());
0379
0380 std::vector<std::size_t> seedsPerTrack(tracks.size(), 0);
0381 for (const auto& seed : seeds) {
0382 const auto indices = seed.spacePointIndices();
0383 const std::uint32_t track = spacePoints.at(indices[0]).extra(trackColumn);
0384 BOOST_CHECK_EQUAL(indices.size(), hits.at(track));
0385 seedsPerTrack.at(track) += 1;
0386 }
0387 for (const std::size_t count : seedsPerTrack) {
0388 BOOST_CHECK_EQUAL(count, 1u);
0389 }
0390 }
0391
0392 }
0393
0394 BOOST_AUTO_TEST_SUITE(GbtsSeeding)
0395
0396
0397 BOOST_AUTO_TEST_CASE(BarrelInputIsWellFormed) {
0398 const ToyDetector detector = barrelDetector();
0399 const std::vector<Track> tracks = makeSparseTracks();
0400 const SpacePointContainer spacePoints = makeSpacePoints(detector, tracks);
0401
0402 BOOST_CHECK_EQUAL(spacePoints.size(), tracks.size() * detector.layers.size());
0403
0404 auto layerColumn = spacePoints.column<std::uint32_t>("layerId");
0405 for (const auto& sp : spacePoints) {
0406 BOOST_CHECK_LT(sp.extra(layerColumn), detector.layers.size());
0407 BOOST_CHECK_LE(std::abs(sp.z()), kBarrelHalfZ);
0408 }
0409 }
0410
0411
0412 BOOST_AUTO_TEST_CASE(SeedsFromSeparatedTracks) {
0413 const ToyDetector detector = barrelDetector();
0414 const std::vector<Track> tracks = makeSparseTracks();
0415 const SpacePointContainer spacePoints = makeSpacePoints(detector, tracks);
0416
0417 const SeedContainer seeds = runSeeding(detector, spacePoints);
0418
0419 checkOneSeedPerTrack(seeds, spacePoints, tracks);
0420
0421 const std::string expected =
0422 "-10.5:0,1,2,3,\n"
0423 "-10.5:4,5,6,7,\n"
0424 "-10.5:8,9,10,11,\n"
0425 "-10.5:12,13,14,15,\n"
0426 "-10.5:16,17,18,19,\n"
0427 "-10.5:20,21,22,23,\n"
0428 "-10.5:24,25,26,27,\n"
0429 "-10.5:28,29,30,31,\n"
0430 "-10.5:32,33,34,35,\n"
0431 "-10.5:36,37,38,39,\n"
0432 "-10.5:40,41,42,43,\n"
0433 "-10.5:44,45,46,47,\n";
0434 BOOST_CHECK_EQUAL(formatSeeds(seeds), expected);
0435 }
0436
0437
0438 BOOST_AUTO_TEST_CASE(SeedsFromDenseTracks) {
0439 const ToyDetector detector = barrelDetector();
0440 const std::vector<Track> tracks = makeDenseTracks();
0441 const SpacePointContainer spacePoints = makeSpacePoints(detector, tracks);
0442
0443 const SeedContainer seeds = runSeeding(detector, spacePoints);
0444
0445
0446
0447 BOOST_TEST_MESSAGE("dense seeds:\n" << formatSeeds(seeds));
0448
0449
0450 checkOneSeedPerTrack(seeds, spacePoints, tracks);
0451 }
0452
0453
0454
0455 BOOST_AUTO_TEST_CASE(ForwardInputIsWellFormed) {
0456 const ToyDetector detector = forwardDetector();
0457 const std::vector<Track> tracks = makeForwardTracks();
0458 const SpacePointContainer spacePoints = makeSpacePoints(detector, tracks);
0459
0460 for (const std::size_t count : hitsPerTrack(spacePoints, tracks.size())) {
0461 BOOST_CHECK_GE(count, 4u);
0462 }
0463
0464 auto layerColumn = spacePoints.column<std::uint32_t>("layerId");
0465 auto trackColumn = spacePoints.column<std::uint32_t>("trackId");
0466
0467 std::vector<bool> hasBarrel(tracks.size(), false);
0468 std::vector<bool> hasEndcap(tracks.size(), false);
0469 for (const auto& sp : spacePoints) {
0470 const LayerSpec& layer = detector.layers.at(sp.extra(layerColumn));
0471 const std::uint32_t track = sp.extra(trackColumn);
0472 if (layer.type == GbtsLayerType::Barrel) {
0473 hasBarrel[track] = true;
0474 BOOST_CHECK_LE(std::abs(sp.z()), kBarrelHalfZ);
0475 } else {
0476 hasEndcap[track] = true;
0477 BOOST_CHECK_GE(sp.r(), kDiscMinR);
0478 BOOST_CHECK_LE(sp.r(), kDiscMaxR);
0479 }
0480 }
0481
0482 auto isSet = [](bool value) { return value; };
0483 BOOST_CHECK(std::ranges::all_of(hasEndcap, isSet));
0484 BOOST_CHECK(std::ranges::any_of(hasBarrel, isSet));
0485 BOOST_CHECK(!std::ranges::all_of(hasBarrel, isSet));
0486 }
0487
0488
0489
0490
0491
0492 BOOST_AUTO_TEST_CASE(SeedsFromForwardTracks) {
0493 const ToyDetector detector = forwardDetector();
0494 const std::vector<Track> tracks = makeForwardTracks();
0495 const SpacePointContainer spacePoints = makeSpacePoints(detector, tracks);
0496
0497 const SeedContainer seeds = runSeeding(detector, spacePoints);
0498
0499 checkOneSeedPerTrack(seeds, spacePoints, tracks);
0500
0501
0502
0503 const std::string expected =
0504 "-10.5:0,1,2,3,\n"
0505 "-11.2:4,5,6,7,8,\n"
0506 "-11.2:9,10,11,12,13,\n"
0507 "-11.2:14,15,16,17,18,\n"
0508 "-11.2:19,20,21,22,23,\n"
0509 "-10.5:24,25,26,27,\n"
0510 "-10.5:28,29,30,31,\n"
0511 "-10.5:32,33,34,35,\n"
0512 "-10.5:36,37,38,39,\n"
0513 "-11.2:40,41,42,43,44,\n"
0514 "-11.2:45,46,47,48,49,\n"
0515 "-11.2:50,51,52,53,54,\n"
0516 "-11.2:55,56,57,58,59,\n"
0517 "-10.5:60,61,62,63,\n"
0518 "-10.5:64,65,66,67,\n"
0519 "-10.5:68,69,70,71,\n";
0520 BOOST_CHECK_EQUAL(formatSeeds(seeds), expected);
0521 }
0522
0523
0524 BOOST_AUTO_TEST_CASE(SeedsFromDenseForwardTracks) {
0525 const ToyDetector detector = forwardDetector();
0526 const std::vector<Track> tracks = makeDenseForwardTracks();
0527 const SpacePointContainer spacePoints = makeSpacePoints(detector, tracks);
0528
0529 const SeedContainer seeds = runSeeding(detector, spacePoints);
0530
0531 BOOST_TEST_MESSAGE("dense forward seeds:\n" << formatSeeds(seeds));
0532
0533 checkOneSeedPerTrack(seeds, spacePoints, tracks);
0534 }
0535
0536 BOOST_AUTO_TEST_SUITE_END()
0537
0538 }