File indexing completed on 2025-08-06 08:11:42
0001
0002
0003
0004
0005
0006
0007
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
0044 auto random_features = at::randn({n_nodes, emb_dim});
0045
0046
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
0069
0070
0071
0072 BOOST_REQUIRE(max_edges <= knn);
0073
0074
0075 auto edges_test = edgeBuilder(random_features, r, knn);
0076
0077
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
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
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
0161 std::vector<int64_t> edges = {
0162 1,1,
0163 2,3,
0164 2,2,
0165 5,4,
0166 };
0167
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
0185 const std::vector<int64_t> ref = {
0186 2,3,
0187 5,4,
0188 };
0189
0190
0191 BOOST_CHECK_EQUAL(ref, postEdges);
0192 }
0193
0194 BOOST_AUTO_TEST_CASE(test_duplicate_removal) {
0195
0196 std::vector<int64_t> edges = {
0197 1,2,
0198 2,1,
0199 3,2,
0200 3,2,
0201 7,6,
0202 };
0203
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
0221 const std::vector<int64_t> ref = {
0222 1,2,
0223 2,3,
0224 6,7,
0225 };
0226
0227
0228 BOOST_CHECK_EQUAL(ref, postEdges);
0229 }
0230
0231 BOOST_AUTO_TEST_CASE(test_random_flip) {
0232 torch::manual_seed(seed);
0233
0234
0235 std::vector<int64_t> edges = {
0236 1,2,
0237 2,3,
0238 3,4,
0239 4,5,
0240 };
0241
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 }