|
1 | 1 | #pragma once |
2 | 2 |
|
3 | 3 | #include <array> |
| 4 | +#include <algorithm> |
4 | 5 | #include <cstdint> // for types like uint32_t |
5 | 6 | #include <cstring> |
6 | 7 | #include <limits> |
|
10 | 11 | #include <unordered_map> |
11 | 12 | #include <fstream> |
12 | 13 | #include <iostream> |
| 14 | +#include <random> |
13 | 15 |
|
14 | | -#include "deglib/graph.h" |
| 16 | +#include "deglib/graph/mutable_graph.h" |
15 | 17 | #include "deglib/search.h" |
16 | 18 | #include "deglib/graph/visited_list_pool.h" |
17 | 19 | #include "deglib/utils/memory.h" |
| 20 | +#include "deglib/utils/random.h" |
18 | 21 |
|
19 | 22 | namespace deglib::graph |
20 | 23 | { |
@@ -196,6 +199,132 @@ class SizeBoundedGraph : public deglib::graph::MutableGraph { |
196 | 199 | } |
197 | 200 | } |
198 | 201 |
|
| 202 | + /** |
| 203 | + * Create an empty SizeBoundedGraph with the given capacity, edges per vertex, and feature space. |
| 204 | + * The graph starts with zero vertices; vertices can be added via addVertex(). |
| 205 | + */ |
| 206 | + static SizeBoundedGraph create_empty( |
| 207 | + const uint32_t max_vertex_count, |
| 208 | + const uint8_t edges_per_vertex, |
| 209 | + const deglib::distances::FloatSpace& feature_space) |
| 210 | + { |
| 211 | + return SizeBoundedGraph(max_vertex_count, edges_per_vertex, feature_space); |
| 212 | + } |
| 213 | + |
| 214 | + /** |
| 215 | + * Create a random exploration graph from the given feature data. |
| 216 | + * |
| 217 | + * The graph is built by first fully connecting the initial (edges_per_vertex + 1) |
| 218 | + * vertices, then iteratively inserting each remaining vertex by connecting it |
| 219 | + * to edges_per_vertex neighbors chosen from existing vertices. |
| 220 | + * |
| 221 | + * @param feature_data Pointer to a contiguous array of feature vectors. |
| 222 | + * Each vector is feature_space.get_data_size() bytes. |
| 223 | + * @param vertex_count Number of vertices to insert (must be <= max_vertex_count). |
| 224 | + * @param edges_per_vertex Number of edges per vertex (must be even). |
| 225 | + * @param feature_space The feature space defining dimensionality and metric. |
| 226 | + * @param seed Random seed for deterministic graph construction. |
| 227 | + * @return A fully constructed random exploration graph. |
| 228 | + */ |
| 229 | + static SizeBoundedGraph create_random_graph( |
| 230 | + const std::byte* feature_data, |
| 231 | + const uint32_t vertex_count, |
| 232 | + const uint8_t edges_per_vertex, |
| 233 | + const deglib::distances::FloatSpace& feature_space, |
| 234 | + const uint32_t seed = 7) |
| 235 | + { |
| 236 | + const auto dist_func = feature_space.get_dist_func(); |
| 237 | + const auto dist_func_param = feature_space.get_dist_func_param(); |
| 238 | + |
| 239 | + auto graph = SizeBoundedGraph(vertex_count, edges_per_vertex, feature_space); |
| 240 | + |
| 241 | + // add the initial vertices (edges_per_vertex + 1) |
| 242 | + { |
| 243 | + const auto size = (uint32_t)(edges_per_vertex + 1); |
| 244 | + for (uint32_t y = 0; y < size; y++) { |
| 245 | + const auto query = feature_data + size_t(y) * feature_space.get_data_size(); |
| 246 | + const auto internal_index = graph.addVertex(y, query); |
| 247 | + |
| 248 | + auto neighbor_indices = std::vector<uint32_t>(); |
| 249 | + auto neighbor_weights = std::vector<float>(); |
| 250 | + for (uint32_t x = 0; x < size; x++) { |
| 251 | + if (x == internal_index) continue; |
| 252 | + neighbor_indices.emplace_back(x); |
| 253 | + neighbor_weights.emplace_back(dist_func(query, feature_data + size_t(x) * feature_space.get_data_size(), dist_func_param)); |
| 254 | + } |
| 255 | + graph.changeEdges(internal_index, neighbor_indices.data(), neighbor_weights.data()); |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + // random order of vertices |
| 260 | + auto rnd = std::mt19937(seed); |
| 261 | + auto rnd_neighbor = deglib::random::DeterministicUniformIntDistribution<uint32_t>(0, edges_per_vertex - 1); |
| 262 | + |
| 263 | + // add the remaining vertices |
| 264 | + for (uint32_t label = edges_per_vertex + 1; label < vertex_count; label++) { |
| 265 | + const auto new_vertex_feature = feature_data + size_t(label) * feature_space.get_data_size(); |
| 266 | + const auto internal_index = graph.addVertex(label, new_vertex_feature); |
| 267 | + auto top_list = deglib::random::DeterministicUniformIntDistribution<uint32_t>(0, label - 1); |
| 268 | + |
| 269 | + // remove the worst edge of the good neighbors and connect them with this new vertex |
| 270 | + auto new_neighbors = std::vector<std::pair<uint32_t, float>>(); |
| 271 | + while (new_neighbors.size() < edges_per_vertex) { |
| 272 | + const auto candidate_index = (uint32_t)top_list(rnd); |
| 273 | + |
| 274 | + // check if the vertex is already in the edge list of the new vertex (added during a previous loop-run) |
| 275 | + // since all edges are undirected and the edge information of the new vertex does not yet exist, we search the other way around. |
| 276 | + if (graph.hasEdge(candidate_index, internal_index)) |
| 277 | + continue; |
| 278 | + |
| 279 | + // find a new random neighbor |
| 280 | + uint32_t new_neighbor_index = 0; |
| 281 | + float new_neighbor_weight = -1; |
| 282 | + const auto neighbor_weights = graph.getNeighborWeights(candidate_index); |
| 283 | + const auto neighbor_indices = graph.getNeighborIndices(candidate_index); |
| 284 | + while (new_neighbor_weight < 0) { |
| 285 | + const auto edge_idx = (uint32_t)rnd_neighbor(rnd); |
| 286 | + const auto neighbor_index = neighbor_indices[edge_idx]; |
| 287 | + const auto neighbor_weight = neighbor_weights[edge_idx]; |
| 288 | + |
| 289 | + // the suggest neighbors might already be in the edge list of the new vertex |
| 290 | + if (graph.hasEdge(neighbor_index, internal_index) == false) { |
| 291 | + new_neighbor_index = neighbor_index; |
| 292 | + new_neighbor_weight = neighbor_weight; |
| 293 | + } |
| 294 | + } |
| 295 | + |
| 296 | + // this should not be possible, otherwise the new vertex is connected to every vertex in the neighbor-list of the result-vertex and still has space for more |
| 297 | + if (new_neighbor_weight < 0) { |
| 298 | + std::fprintf(stderr, "it was not possible to find an edge (best weight %f) in the neighbor list of vertex %u which would connect to vertex %u \n", new_neighbor_weight, candidate_index, internal_index); |
| 299 | + std::perror(""); |
| 300 | + std::abort(); |
| 301 | + } |
| 302 | + |
| 303 | + // place the new vertex in the edge list of the result-vertex |
| 304 | + const auto candidate_dist = dist_func(new_vertex_feature, graph.getFeatureVector(candidate_index), dist_func_param); |
| 305 | + graph.changeEdge(candidate_index, new_neighbor_index, internal_index, candidate_dist); |
| 306 | + new_neighbors.emplace_back(candidate_index, candidate_dist); |
| 307 | + |
| 308 | + // place the new vertex in the edge list of the worst edge neighbor |
| 309 | + const auto new_neighbor_dist = dist_func(new_vertex_feature, graph.getFeatureVector(new_neighbor_index), dist_func_param); |
| 310 | + graph.changeEdge(new_neighbor_index, candidate_index, internal_index, new_neighbor_dist); |
| 311 | + new_neighbors.emplace_back(new_neighbor_index, new_neighbor_dist); |
| 312 | + } |
| 313 | + |
| 314 | + // sort the neighbors by their neighbor indices and store them in the new vertex |
| 315 | + std::sort(new_neighbors.begin(), new_neighbors.end(), [](const auto& x, const auto& y) { return x.first < y.first; }); |
| 316 | + auto neighbor_indices = std::vector<uint32_t>(); |
| 317 | + auto neighbor_weights = std::vector<float>(); |
| 318 | + for (auto &&neighbor : new_neighbors) { |
| 319 | + neighbor_indices.emplace_back(neighbor.first); |
| 320 | + neighbor_weights.emplace_back(neighbor.second); |
| 321 | + } |
| 322 | + graph.changeEdges(internal_index, neighbor_indices.data(), neighbor_weights.data()); |
| 323 | + } |
| 324 | + |
| 325 | + return graph; |
| 326 | + } |
| 327 | + |
199 | 328 | /** |
200 | 329 | * Current maximal capacity of vertices |
201 | 330 | */ |
|
0 commit comments