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