Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:11:42

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2022 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 http://mozilla.org/MPL/2.0/.
0008 
0009 #include <boost/test/unit_test.hpp>
0010 
0011 #include "Acts/Plugins/ExaTrkX/detail/CantorEdge.hpp"
0012 #include "Acts/Plugins/ExaTrkX/detail/TensorVectorConversion.hpp"
0013 #include "Acts/Plugins/ExaTrkX/detail/buildEdges.hpp"
0014 
0015 #include <cassert>
0016 #include <iostream>
0017 
0018 #include <Eigen/Core>
0019 #include <torch/torch.h>
0020 
0021 using CantorPair = Acts::detail::CantorEdge<int>;
0022 
0023 #define PRINT 0
0024 
0025 float distance(const at::Tensor &a, const at::Tensor &b) {
0026   assert(a.sizes() == b.sizes());
0027   assert(a.sizes().size() == 1);
0028 
0029   return std::sqrt(((a - b) * (a - b)).sum().item().to<float>());
0030 }
0031 
0032 #if PRINT
0033 std::ostream &operator<<(std::ostream &os, CantorPair p) {
0034   auto [a, b] = p.inverse();
0035   os << "(" << a << "," << b << ")";
0036   return os;
0037 }
0038 #endif
0039 
0040 template <typename edge_builder_t>
0041 void test_random_graph(int emb_dim, int n_nodes, float r, int knn,
0042                        const edge_builder_t &edgeBuilder) {
0043   // Create a random point cloud
0044   auto random_features = at::randn({n_nodes, emb_dim});
0045 
0046   // Generate the truth via brute-force
0047   Eigen::MatrixXf distance_matrix(n_nodes, n_nodes);
0048 
0049   std::vector<CantorPair> edges_ref_cantor;
0050   std::vector<int> edge_counts(n_nodes, 0);
0051 
0052   for (int i = 0; i < n_nodes; ++i) {
0053     for (int j = i; j < n_nodes; ++j) {
0054       const auto d = distance(random_features[i], random_features[j]);
0055       distance_matrix(i, j) = d;
0056       distance_matrix(j, i) = d;
0057 
0058       if (d < r && i != j) {
0059         edges_ref_cantor.emplace_back(i, j);
0060         edge_counts[i]++;
0061       }
0062     }
0063   }
0064 
0065   const auto max_edges =
0066       *std::max_element(edge_counts.begin(), edge_counts.end());
0067 
0068   // If this is not the case, the test is ill-formed
0069   // knn specifies how many edges can be found by the function at max. Thus, we
0070   // should design the test in a way, that our brute-force test algorithm does
0071   // not find more edges than the algorithm that we test against it can find
0072   BOOST_REQUIRE(max_edges <= knn);
0073 
0074   // Run the edge building
0075   auto edges_test = edgeBuilder(random_features, r, knn);
0076 
0077   // Map the edges to cantor pairs
0078   std::vector<CantorPair> edges_test_cantor;
0079 
0080   for (int i = 0; i < edges_test.size(1); ++i) {
0081     const auto a = edges_test[0][i].template item<int>();
0082     const auto b = edges_test[1][i].template item<int>();
0083     edges_test_cantor.push_back(a < b ? CantorPair(a, b) : CantorPair(b, a));
0084   }
0085 
0086   std::sort(edges_ref_cantor.begin(), edges_ref_cantor.end());
0087   std::sort(edges_test_cantor.begin(), edges_test_cantor.end());
0088 
0089 #if PRINT
0090   std::cout << "test size " << edges_test_cantor.size() << std::endl;
0091   std::cout << "ref size " << edges_ref_cantor.size() << std::endl;
0092   std::cout << "test: ";
0093   std::copy(
0094       edges_test_cantor.begin(),
0095       edges_test_cantor.begin() + std::min(edges_test_cantor.size(), 10ul),
0096       std::ostream_iterator<CantorPair>(std::cout, " "));
0097   std::cout << std::endl;
0098   std::cout << "ref: ";
0099   std::copy(edges_ref_cantor.begin(),
0100             edges_ref_cantor.begin() + std::min(edges_ref_cantor.size(), 10ul),
0101             std::ostream_iterator<CantorPair>(std::cout, " "));
0102   std::cout << std::endl;
0103 #endif
0104 
0105   // Check
0106   BOOST_CHECK_EQUAL(edges_ref_cantor.size(), edges_test_cantor.size());
0107   BOOST_CHECK(std::equal(edges_test_cantor.begin(), edges_test_cantor.end(),
0108                          edges_ref_cantor.begin()));
0109 }
0110 
0111 BOOST_AUTO_TEST_CASE(test_cantor_pair_functions) {
0112   int a = 345;
0113   int b = 23;
0114   // Use non-sorted cantor pair to make this work
0115   const auto [aa, bb] = CantorPair(a, b, false).inverse();
0116   BOOST_CHECK_EQUAL(a, aa);
0117   BOOST_CHECK_EQUAL(b, bb);
0118 }
0119 
0120 BOOST_AUTO_TEST_CASE(test_cantor_pair_sorted) {
0121   int a = 345;
0122   int b = 23;
0123   CantorPair c1(a, b);
0124   CantorPair c2(b, a);
0125   BOOST_CHECK_EQUAL(c1.value(), c2.value());
0126 }
0127 
0128 const int emb_dim = 3;
0129 const int n_nodes = 20;
0130 const float r = 1.5;
0131 const int knn = 50;
0132 const int seed = 42;
0133 
0134 BOOST_AUTO_TEST_CASE(test_random_graph_edge_building_cuda,
0135                      *boost::unit_test::precondition([](auto) {
0136                        return torch::cuda::is_available();
0137                      })) {
0138   torch::manual_seed(seed);
0139 
0140   auto cudaEdgeBuilder = [](auto &features, auto radius, auto k) {
0141     auto features_cuda = features.to(torch::kCUDA);
0142     return Acts::detail::buildEdgesFRNN(features_cuda, radius, k);
0143   };
0144 
0145   test_random_graph(emb_dim, n_nodes, r, knn, cudaEdgeBuilder);
0146 }
0147 
0148 BOOST_AUTO_TEST_CASE(test_random_graph_edge_building_kdtree) {
0149   torch::manual_seed(seed);
0150 
0151   auto cpuEdgeBuilder = [](auto &features, auto radius, auto k) {
0152     auto features_cpu = features.to(torch::kCPU);
0153     return Acts::detail::buildEdgesKDTree(features_cpu, radius, k);
0154   };
0155 
0156   test_random_graph(emb_dim, n_nodes, r, knn, cpuEdgeBuilder);
0157 }
0158 
0159 BOOST_AUTO_TEST_CASE(test_self_loop_removal) {
0160   // clang-format off
0161   std::vector<int64_t> edges = {
0162     1,1,
0163     2,3,
0164     2,2,
0165     5,4,
0166   };
0167   // clang-format on
0168 
0169   auto opts = torch::TensorOptions().dtype(torch::kInt64);
0170   const auto edgeTensor =
0171       torch::from_blob(edges.data(), {static_cast<long>(edges.size() / 2), 2},
0172                        opts)
0173           .transpose(0, 1);
0174 
0175   const auto withoutSelfLoops =
0176       Acts::detail::postprocessEdgeTensor(edgeTensor, true, false, false)
0177           .transpose(1, 0)
0178           .flatten();
0179 
0180   const std::vector<int64_t> postEdges(
0181       withoutSelfLoops.data_ptr<int64_t>(),
0182       withoutSelfLoops.data_ptr<int64_t>() + withoutSelfLoops.numel());
0183 
0184   // clang-format off
0185   const std::vector<int64_t> ref = {
0186     2,3,
0187     5,4,
0188   };
0189   // clang-format on
0190 
0191   BOOST_CHECK_EQUAL(ref, postEdges);
0192 }
0193 
0194 BOOST_AUTO_TEST_CASE(test_duplicate_removal) {
0195   // clang-format off
0196   std::vector<int64_t> edges = {
0197     1,2,
0198     2,1,   // duplicate, flipped
0199     3,2,
0200     3,2,   // duplicate, not flipped
0201     7,6,   // should be flipped
0202   };
0203   // clang-format on
0204 
0205   auto opts = torch::TensorOptions().dtype(torch::kInt64);
0206   const auto edgeTensor =
0207       torch::from_blob(edges.data(), {static_cast<long>(edges.size() / 2), 2},
0208                        opts)
0209           .transpose(0, 1);
0210 
0211   const auto withoutDups =
0212       Acts::detail::postprocessEdgeTensor(edgeTensor, false, true, false)
0213           .transpose(1, 0)
0214           .flatten();
0215 
0216   const std::vector<int64_t> postEdges(
0217       withoutDups.data_ptr<int64_t>(),
0218       withoutDups.data_ptr<int64_t>() + withoutDups.numel());
0219 
0220   // clang-format off
0221   const std::vector<int64_t> ref = {
0222     1,2,
0223     2,3,
0224     6,7,
0225   };
0226   // clang-format on
0227 
0228   BOOST_CHECK_EQUAL(ref, postEdges);
0229 }
0230 
0231 BOOST_AUTO_TEST_CASE(test_random_flip) {
0232   torch::manual_seed(seed);
0233 
0234   // clang-format off
0235   std::vector<int64_t> edges = {
0236     1,2,
0237     2,3,
0238     3,4,
0239     4,5,
0240   };
0241   // clang-format on
0242 
0243   auto opts = torch::TensorOptions().dtype(torch::kInt64);
0244   const auto edgeTensor =
0245       torch::from_blob(edges.data(), {static_cast<long>(edges.size() / 2), 2},
0246                        opts)
0247           .transpose(0, 1);
0248 
0249   const auto flipped =
0250       Acts::detail::postprocessEdgeTensor(edgeTensor, false, false, true)
0251           .transpose(0, 1)
0252           .flatten();
0253 
0254   const std::vector<int64_t> postEdges(
0255       flipped.data_ptr<int64_t>(),
0256       flipped.data_ptr<int64_t>() + flipped.numel());
0257 
0258   BOOST_CHECK_EQUAL(postEdges.size(), edges.size());
0259   for (auto preIt = edges.begin(); preIt != edges.end(); preIt += 2) {
0260     int found = 0;
0261 
0262     for (auto postIt = postEdges.begin(); postIt != postEdges.end();
0263          postIt += 2) {
0264       bool noflp = (*preIt == *postIt) and *(preIt + 1) == *(postIt + 1);
0265       bool flp = *preIt == *(postIt + 1) and *(preIt + 1) == *(postIt);
0266 
0267       found += (flp or noflp);
0268     }
0269 
0270     BOOST_CHECK_EQUAL(found, 1);
0271   }
0272 }