Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:13:09

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