File indexing completed on 2025-12-16 09:25:34
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/unit_test.hpp>
0010
0011 #include "ActsPlugins/Gnn/detail/CantorEdge.hpp"
0012 #include "ActsPlugins/Gnn/detail/TensorVectorConversion.hpp"
0013 #include "ActsPlugins/Gnn/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 namespace ActsPlugins;
0023 using namespace ActsPlugins::detail;
0024
0025 using CantorPair = CantorEdge<int>;
0026
0027 #define PRINT 0
0028
0029 float distance(const at::Tensor &a, const at::Tensor &b) {
0030 assert(a.sizes() == b.sizes());
0031 assert(a.sizes().size() == 1);
0032
0033 return std::sqrt(((a - b) * (a - b)).sum().item().to<float>());
0034 }
0035
0036 #if PRINT
0037 std::ostream &operator<<(std::ostream &os, CantorPair p) {
0038 auto [a, b] = p.inverse();
0039 os << "(" << a << "," << b << ")";
0040 return os;
0041 }
0042 #endif
0043
0044 template <typename edge_builder_t>
0045 void test_random_graph(int emb_dim, int n_nodes, float r, int knn,
0046 const edge_builder_t &edgeBuilder) {
0047
0048 auto random_features = at::randn({n_nodes, emb_dim});
0049
0050
0051 Eigen::MatrixXf distance_matrix(n_nodes, n_nodes);
0052
0053 std::vector<CantorPair> edges_ref_cantor;
0054 std::vector<int> edge_counts(n_nodes, 0);
0055
0056 for (int i = 0; i < n_nodes; ++i) {
0057 for (int j = i; j < n_nodes; ++j) {
0058 const auto d = distance(random_features[i], random_features[j]);
0059 distance_matrix(i, j) = d;
0060 distance_matrix(j, i) = d;
0061
0062 if (d < r && i != j) {
0063 edges_ref_cantor.emplace_back(i, j);
0064 edge_counts[i]++;
0065 }
0066 }
0067 }
0068
0069 const auto max_edges =
0070 *std::max_element(edge_counts.begin(), edge_counts.end());
0071
0072
0073
0074
0075
0076 BOOST_REQUIRE(max_edges <= knn);
0077
0078
0079 auto edges_test = edgeBuilder(random_features, r, knn);
0080
0081
0082 std::vector<CantorPair> edges_test_cantor;
0083
0084 for (int i = 0; i < edges_test.size(1); ++i) {
0085 const auto a = edges_test[0][i].template item<int>();
0086 const auto b = edges_test[1][i].template item<int>();
0087 edges_test_cantor.push_back(a < b ? CantorPair(a, b) : CantorPair(b, a));
0088 }
0089
0090 std::ranges::sort(edges_ref_cantor, std::less<CantorPair>{});
0091 std::ranges::sort(edges_test_cantor, std::less<CantorPair>{});
0092
0093 #if PRINT
0094 std::cout << "test size " << edges_test_cantor.size() << std::endl;
0095 std::cout << "ref size " << edges_ref_cantor.size() << std::endl;
0096 std::cout << "test: ";
0097 std::copy(
0098 edges_test_cantor.begin(),
0099 edges_test_cantor.begin() + std::min(edges_test_cantor.size(), 10ul),
0100 std::ostream_iterator<CantorPair>(std::cout, " "));
0101 std::cout << std::endl;
0102 std::cout << "ref: ";
0103 std::copy(edges_ref_cantor.begin(),
0104 edges_ref_cantor.begin() + std::min(edges_ref_cantor.size(), 10ul),
0105 std::ostream_iterator<CantorPair>(std::cout, " "));
0106 std::cout << std::endl;
0107 #endif
0108
0109
0110 BOOST_CHECK_EQUAL(edges_ref_cantor.size(), edges_test_cantor.size());
0111 BOOST_CHECK(std::equal(edges_test_cantor.begin(), edges_test_cantor.end(),
0112 edges_ref_cantor.begin()));
0113 }
0114
0115 namespace ActsTests {
0116
0117 BOOST_AUTO_TEST_SUITE(GnnSuite)
0118
0119 BOOST_AUTO_TEST_CASE(test_cantor_pair_functions) {
0120 int a = 345;
0121 int b = 23;
0122
0123 const auto [aa, bb] = CantorPair(a, b, false).inverse();
0124 BOOST_CHECK_EQUAL(a, aa);
0125 BOOST_CHECK_EQUAL(b, bb);
0126 }
0127
0128 BOOST_AUTO_TEST_CASE(test_cantor_pair_sorted) {
0129 int a = 345;
0130 int b = 23;
0131 CantorPair c1(a, b);
0132 CantorPair c2(b, a);
0133 BOOST_CHECK_EQUAL(c1.value(), c2.value());
0134 }
0135
0136 const int emb_dim = 3;
0137 const int n_nodes = 20;
0138 const float r = 1.5;
0139 const int knn = 50;
0140 const int seed = 42;
0141
0142 BOOST_AUTO_TEST_CASE(test_random_graph_edge_building_cuda,
0143 *boost::unit_test::precondition([](auto) {
0144 return torch::cuda::is_available();
0145 })) {
0146 torch::manual_seed(seed);
0147
0148 auto cudaEdgeBuilder = [](auto &features, auto radius, auto k) {
0149 auto features_cuda = features.to(torch::kCUDA);
0150 return buildEdgesFRNN(features_cuda, radius, k);
0151 };
0152
0153 test_random_graph(emb_dim, n_nodes, r, knn, cudaEdgeBuilder);
0154 }
0155
0156 BOOST_AUTO_TEST_CASE(test_random_graph_edge_building_kdtree) {
0157 torch::manual_seed(seed);
0158
0159 auto cpuEdgeBuilder = [](auto &features, auto radius, auto k) {
0160 auto features_cpu = features.to(torch::kCPU);
0161 return buildEdgesKDTree(features_cpu, radius, k);
0162 };
0163
0164 test_random_graph(emb_dim, n_nodes, r, knn, cpuEdgeBuilder);
0165 }
0166
0167 BOOST_AUTO_TEST_CASE(test_self_loop_removal) {
0168
0169 std::vector<std::int64_t> edges = {
0170 1,1,
0171 2,3,
0172 2,2,
0173 5,4,
0174 };
0175
0176
0177 auto opts = torch::TensorOptions().dtype(torch::kInt64);
0178 const auto edgeTensor =
0179 torch::from_blob(edges.data(), {static_cast<long>(edges.size() / 2), 2},
0180 opts)
0181 .transpose(0, 1);
0182
0183 const auto withoutSelfLoops =
0184 postprocessEdgeTensor(edgeTensor, true, false, false)
0185 .transpose(1, 0)
0186 .flatten();
0187
0188 const std::vector<std::int64_t> postEdges(
0189 withoutSelfLoops.data_ptr<std::int64_t>(),
0190 withoutSelfLoops.data_ptr<std::int64_t>() + withoutSelfLoops.numel());
0191
0192
0193 const std::vector<std::int64_t> ref = {
0194 2,3,
0195 5,4,
0196 };
0197
0198
0199 BOOST_CHECK_EQUAL(ref, postEdges);
0200 }
0201
0202 BOOST_AUTO_TEST_CASE(test_duplicate_removal) {
0203
0204 std::vector<std::int64_t> edges = {
0205 1,2,
0206 2,1,
0207 3,2,
0208 3,2,
0209 7,6,
0210 };
0211
0212
0213 auto opts = torch::TensorOptions().dtype(torch::kInt64);
0214 const auto edgeTensor =
0215 torch::from_blob(edges.data(), {static_cast<long>(edges.size() / 2), 2},
0216 opts)
0217 .transpose(0, 1);
0218
0219 const auto withoutDups = postprocessEdgeTensor(edgeTensor, false, true, false)
0220 .transpose(1, 0)
0221 .flatten();
0222
0223 const std::vector<std::int64_t> postEdges(
0224 withoutDups.data_ptr<std::int64_t>(),
0225 withoutDups.data_ptr<std::int64_t>() + withoutDups.numel());
0226
0227
0228 const std::vector<std::int64_t> ref = {
0229 1,2,
0230 2,3,
0231 6,7,
0232 };
0233
0234
0235 BOOST_CHECK_EQUAL(ref, postEdges);
0236 }
0237
0238 BOOST_AUTO_TEST_CASE(test_random_flip) {
0239 torch::manual_seed(seed);
0240
0241
0242 std::vector<std::int64_t> edges = {
0243 1,2,
0244 2,3,
0245 3,4,
0246 4,5,
0247 };
0248
0249
0250 auto opts = torch::TensorOptions().dtype(torch::kInt64);
0251 const auto edgeTensor =
0252 torch::from_blob(edges.data(), {static_cast<long>(edges.size() / 2), 2},
0253 opts)
0254 .transpose(0, 1);
0255
0256 const auto flipped = postprocessEdgeTensor(edgeTensor, false, false, true)
0257 .transpose(0, 1)
0258 .flatten();
0259
0260 const std::vector<std::int64_t> postEdges(
0261 flipped.data_ptr<std::int64_t>(),
0262 flipped.data_ptr<std::int64_t>() + flipped.numel());
0263
0264 BOOST_CHECK_EQUAL(postEdges.size(), edges.size());
0265 for (auto preIt = edges.begin(); preIt != edges.end(); preIt += 2) {
0266 int found = 0;
0267
0268 for (auto postIt = postEdges.begin(); postIt != postEdges.end();
0269 postIt += 2) {
0270 bool noflp = (*preIt == *postIt) and *(preIt + 1) == *(postIt + 1);
0271 bool flp = *preIt == *(postIt + 1) and *(preIt + 1) == *(postIt);
0272
0273 found += (flp or noflp);
0274 }
0275
0276 BOOST_CHECK_EQUAL(found, 1);
0277 }
0278 }
0279
0280 BOOST_AUTO_TEST_SUITE_END()
0281
0282 }