Skip to content

Commit 615ccea

Browse files
committed
refactor(graph): separate InternalGraph (internal 0..N-1) and DynamicExplorationGraph (external labels)
- Introduce deglib::graph::InternalGraph as abstract C++ base interface operating strictly on internal_index (0..N-1). - Add deglib::DynamicExplorationGraph facade in deglib/graph.h for external users, transparently mapping external_label <-> internal_index. - Update C++ core graphs (ReadOnlyGraph, MutableGraph, SizeBoundedGraph) to inherit from InternalGraph . - Update Python bindings and deglib.graph wrapper to delegate external label translation to C++ InternalGraph.
1 parent 1ca04cb commit 615ccea

29 files changed

Lines changed: 852 additions & 459 deletions

‎.gitignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,9 @@ __pycache__/
6868
*.png
6969
*.egg-info/
7070
*.egg-info
71+
72+
# BanyanCode internal files
73+
.banyancode/
74+
75+
# Python build artifacts
76+
python/build_cmake/

‎cpp/bench/include/benchmark.h‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static uint64_t compute_linear_search_baseline(const deglib::FeatureRepository&
7575
return time_per_query_us;
7676
}
7777

78-
static float test_approx_anns(const deglib::search::SearchGraph& graph,
78+
static float test_approx_anns(const deglib::graph::InternalGraph& graph,
7979
const std::vector<uint32_t>& entry_vertex_indices,
8080
const deglib::FeatureRepository& query_repository,
8181
const std::vector<std::vector<uint32_t>>& ground_truth,
@@ -111,7 +111,7 @@ static float test_approx_anns(const deglib::search::SearchGraph& graph,
111111
return total_correct / (test_size * k);
112112
}
113113

114-
static float test_approx_explore(const deglib::search::SearchGraph& graph,
114+
static float test_approx_explore(const deglib::graph::InternalGraph& graph,
115115
const std::vector<std::vector<uint32_t>>& entry_vertex_indices,
116116
const bool include_entry,
117117
const std::vector<std::vector<uint32_t>>& ground_truth,
@@ -151,7 +151,7 @@ static float test_approx_explore(const deglib::search::SearchGraph& graph,
151151
return total_correct / (entry_vertex_indices.size() * k);
152152
}
153153

154-
static std::vector<float> estimate_recall(const deglib::search::SearchGraph& graph,
154+
static std::vector<float> estimate_recall(const deglib::graph::InternalGraph& graph,
155155
const deglib::FeatureRepository& query_repository,
156156
const std::vector<std::vector<uint32_t>>& answer,
157157
const uint32_t max_distance_count,
@@ -190,7 +190,7 @@ static std::vector<float> estimate_recall(const deglib::search::SearchGraph& gra
190190
return recalls;
191191
}
192192

193-
static void test_graph_anns(const deglib::search::SearchGraph& graph,
193+
static void test_graph_anns(const deglib::graph::InternalGraph& graph,
194194
const deglib::FeatureRepository& query_repository,
195195
const std::vector<std::vector<uint32_t>>& ground_truth,
196196
const uint32_t repeat,
@@ -253,7 +253,7 @@ static void test_graph_anns(const deglib::search::SearchGraph& graph,
253253
}
254254
}
255255

256-
static void test_graph_explore(const deglib::search::SearchGraph& graph,
256+
static void test_graph_explore(const deglib::graph::InternalGraph& graph,
257257
const std::vector<uint32_t>& entry_vertex_labels,
258258
const std::vector<std::vector<uint32_t>>& ground_truth,
259259
const bool include_entry,

‎cpp/bench/include/dataset.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ inline std::vector<uint32_t> compute_knn_groundtruth(const deglib::FeatureReposi
287287
const auto query = query_repo.getFeature((uint32_t)q);
288288

289289
auto worst_distance = (std::numeric_limits<float>::max)();
290-
auto results = deglib::search::ResultSet();
290+
auto results = deglib::graph::ResultSet();
291291

292292
for (uint32_t b = 0; b < base_size; b++) {
293293
const auto distance = dist_func(query, base_repo.getFeature(b), dist_func_param);

‎cpp/bench/include/stats.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ namespace deglib::benchmark {
4040
* @param graph The search graph to analyze
4141
* @return Number of vertices reachable from entry points
4242
*/
43-
inline uint32_t compute_search_reachability(const deglib::search::SearchGraph& graph) {
43+
inline uint32_t compute_search_reachability(const deglib::graph::InternalGraph& graph) {
4444
const auto graph_size = (uint32_t)graph.size();
4545
const auto edges_per_vertex = graph.getEdgesPerVertex();
4646
const auto entry_vertices = graph.getEntryVertexIndices();
@@ -103,7 +103,7 @@ struct VertexReach {
103103
* @param graph The search graph to analyze
104104
* @return Average number of vertices reachable per vertex
105105
*/
106-
inline float compute_exploration_reach(const deglib::search::SearchGraph& graph) {
106+
inline float compute_exploration_reach(const deglib::graph::InternalGraph& graph) {
107107
const auto graph_size = (uint32_t)graph.size();
108108
const auto edges_per_vertex = graph.getEdgesPerVertex();
109109
auto stopw = StopW();
@@ -248,7 +248,7 @@ struct GraphStats {
248248
* @param graph The search graph to analyze
249249
* @return GraphStats with all computed statistics
250250
*/
251-
inline GraphStats analyze_graph(const deglib::search::SearchGraph& graph) {
251+
inline GraphStats analyze_graph(const deglib::graph::InternalGraph& graph) {
252252
GraphStats stats;
253253
stats.vertex_count = graph.size();
254254
stats.feature_dims = graph.getFeatureSpace().dim();

‎cpp/benchmark/include/benchmark.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ static std::vector<std::unordered_set<uint32_t>> get_ground_truth(const uint32_t
3434
return answers;
3535
}
3636

37-
static float test_approx_anns(const deglib::search::SearchGraph& graph, const std::vector<uint32_t>& entry_vertex_indices,
37+
static float test_approx_anns(const deglib::graph::InternalGraph& graph, const std::vector<uint32_t>& entry_vertex_indices,
3838
const deglib::FeatureRepository& query_repository, const std::vector<std::unordered_set<uint32_t>>& ground_truth,
3939
const float eps, const uint32_t k, const uint32_t test_size, const uint32_t threads, const deglib::graph::Filter* filter = nullptr)
4040
{
@@ -69,7 +69,7 @@ static float test_approx_anns(const deglib::search::SearchGraph& graph, const st
6969
return total_correct / (test_size*k);
7070
}
7171

72-
static float test_approx_explore(const deglib::search::SearchGraph& graph, const std::vector<std::vector<uint32_t>>& entry_vertex_indices, const boolean include_entry,
72+
static float test_approx_explore(const deglib::graph::InternalGraph& graph, const std::vector<std::vector<uint32_t>>& entry_vertex_indices, const boolean include_entry,
7373
const std::vector<std::unordered_set<uint32_t>>& ground_truth, const uint32_t k, const uint32_t max_distance_count,
7474
const uint32_t threads, const deglib::graph::Filter* filter = nullptr)
7575
{
@@ -104,7 +104,7 @@ static float test_approx_explore(const deglib::search::SearchGraph& graph, const
104104
return total_correct / (entry_vertex_indices.size()*k);
105105
}
106106

107-
static void test_graph_anns(const deglib::search::SearchGraph& graph, const deglib::FeatureRepository& query_repository, const uint32_t* ground_truth, const uint32_t ground_truth_dims, const uint32_t repeat, const uint32_t threads, const uint32_t k, const deglib::graph::Filter* filter = nullptr)
107+
static void test_graph_anns(const deglib::graph::InternalGraph& graph, const deglib::FeatureRepository& query_repository, const uint32_t* ground_truth, const uint32_t ground_truth_dims, const uint32_t repeat, const uint32_t threads, const uint32_t k, const deglib::graph::Filter* filter = nullptr)
108108
{
109109
// reproduceable entry point for the graph search
110110
const auto entry_vertex_indices = graph.getEntryVertexIndices();
@@ -145,7 +145,7 @@ static void test_graph_anns(const deglib::search::SearchGraph& graph, const degl
145145
fmt::print("Max memory usage: {} Mb\n", getPeakRSS() / 1000000);
146146
}
147147

148-
static void test_graph_explore(const deglib::search::SearchGraph& graph, const uint32_t query_count, const uint32_t* ground_truth, const uint32_t ground_truth_dims, const uint32_t* entry_vertex_labels, const uint32_t entry_vertex_dims, const boolean include_entry, const uint32_t repeat, const uint32_t k, const uint32_t threads, const deglib::graph::Filter* filter = nullptr)
148+
static void test_graph_explore(const deglib::graph::InternalGraph& graph, const uint32_t query_count, const uint32_t* ground_truth, const uint32_t ground_truth_dims, const uint32_t* entry_vertex_labels, const uint32_t entry_vertex_dims, const boolean include_entry, const uint32_t repeat, const uint32_t k, const uint32_t threads, const deglib::graph::Filter* filter = nullptr)
149149
{
150150
if (ground_truth_dims < k)
151151
{

‎cpp/benchmark/src/deglib_build_bench.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ void test_graph(const std::string query_file, const std::string gt_file, const s
538538
deglib::benchmark::test_graph_anns(graph, query_repository, ground_truth, (uint32_t)dims_out, repeat, threads, k);
539539
}
540540

541-
static std::vector<float> estimate_recall(const deglib::search::SearchGraph& graph, const deglib::FeatureRepository& query_repository, const std::vector<std::unordered_set<uint32_t>>& answer, const uint32_t max_distance_count, const uint32_t k) {
541+
static std::vector<float> estimate_recall(const deglib::graph::InternalGraph& graph, const deglib::FeatureRepository& query_repository, const std::vector<std::unordered_set<uint32_t>>& answer, const uint32_t max_distance_count, const uint32_t k) {
542542

543543
const auto entry_vertex_indices = std::vector<uint32_t> { graph.getInternalIndex(0) };
544544

‎cpp/benchmark/src/deglib_groundtruth.cpp‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
/**
1212
* Convert the queue into a vector with ascending distance order
1313
**/
14-
static auto topListAscending(deglib::search::ResultSet& queue) {
14+
static auto topListAscending(deglib::graph::ResultSet& queue) {
1515
const auto size = (int32_t) queue.size();
16-
auto topList = std::vector<deglib::search::ObjectDistance>(size);
16+
auto topList = std::vector<deglib::graph::ObjectDistance>(size);
1717
for(int32_t i = size - 1; i >= 0; i--) {
18-
topList[i] = std::move(const_cast<deglib::search::ObjectDistance&>(queue.top()));
18+
topList[i] = std::move(const_cast<deglib::graph::ObjectDistance&>(queue.top()));
1919
queue.pop();
2020
}
2121
return topList;
@@ -86,7 +86,7 @@ std::vector<uint32_t> compute_knn_groundtruth(const deglib::FeatureRepository& b
8686
const auto query = query_repo.getFeature(q);
8787

8888
auto worst_distance = std::numeric_limits<float>::max();
89-
auto results = deglib::search::ResultSet();
89+
auto results = deglib::graph::ResultSet();
9090
for (uint32_t b = 0; b < base_size; b++) {
9191
const auto distance = dist_func(query, base_repo.getFeature(b), dist_func_param);
9292
if(distance < worst_distance) {

‎cpp/deglib/include/deglib/analysis.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace deglib::analysis
1818
*
1919
* @param check_back_link checks if all edges are undirected (quite expensive)
2020
*/
21-
static bool check_graph_regularity(const deglib::search::SearchGraph& graph, const uint32_t expected_vertices, const bool check_back_link = false) {
21+
static bool check_graph_regularity(const deglib::graph::InternalGraph& graph, const uint32_t expected_vertices, const bool check_back_link = false) {
2222

2323
// check vertex count
2424
auto vertex_count = graph.size();
@@ -200,7 +200,7 @@ namespace deglib::analysis
200200
/**
201201
* check if the graph is connected and contains only one graph component
202202
*/
203-
static bool check_graph_connectivity(const deglib::search::SearchGraph& graph) {
203+
static bool check_graph_connectivity(const deglib::graph::InternalGraph& graph) {
204204
const auto vertex_count = graph.size();
205205
const auto edges_per_vertex = graph.getEdgesPerVertex();
206206

‎cpp/deglib/include/deglib/builder.h‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,9 @@ class EvenRegularGraphBuilder {
418418
* @param queue The result set queue to convert.
419419
* @return A vector of ObjectDistance sorted in ascending order.
420420
*/
421-
static auto topListAscending(deglib::search::ResultSet& queue) {
421+
static auto topListAscending(deglib::graph::ResultSet& queue) {
422422
const auto size = (int32_t) queue.size();
423-
auto topList = std::vector<deglib::search::ObjectDistance>(size);
423+
auto topList = std::vector<deglib::graph::ObjectDistance>(size);
424424
for (int32_t i = size - 1; i >= 0; i--) {
425425
topList[i] = queue.top();
426426
queue.pop();
@@ -434,11 +434,11 @@ class EvenRegularGraphBuilder {
434434
* @param queue The result set queue to convert.
435435
* @return A vector of ObjectDistance sorted in descending order.
436436
*/
437-
static auto topListDescending(deglib::search::ResultSet& queue) {
437+
static auto topListDescending(deglib::graph::ResultSet& queue) {
438438
const auto size = queue.size();
439-
auto topList = std::vector<deglib::search::ObjectDistance>(size);
439+
auto topList = std::vector<deglib::graph::ObjectDistance>(size);
440440
for (size_t i = 0; i < size; i++) {
441-
topList[i] = std::move(const_cast<deglib::search::ObjectDistance&>(queue.top()));
441+
topList[i] = std::move(const_cast<deglib::graph::ObjectDistance&>(queue.top()));
442442
queue.pop();
443443
}
444444
return topList;

‎cpp/deglib/include/deglib/graph.h‎

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22

3-
// Graph Interfaces
4-
#include "deglib/graph/search_graph.h"
3+
// Core InternalGraph Interface (0..N-1 internal index)
4+
#include "deglib/graph/internal_graph.h"
55
#include "deglib/graph/mutable_graph.h"
66
#include "deglib/graph/dynamic_graph.h"
77

@@ -11,3 +11,108 @@
1111
// Concrete Graph Implementations
1212
#include "deglib/graph/readonly_graph.h"
1313
#include "deglib/graph/sizebounded_graph.h"
14+
15+
#include <vector>
16+
#include <span>
17+
#include <stdexcept>
18+
#include <memory>
19+
20+
namespace deglib {
21+
22+
/**
23+
* Public DynamicExplorationGraph Facade for End Users.
24+
* All public methods accept and return external_labels (User Object IDs).
25+
* Internal calls map transparently between external_label and internal_index.
26+
*/
27+
class DynamicExplorationGraph {
28+
private:
29+
deglib::graph::InternalGraph& internal_graph_;
30+
31+
public:
32+
explicit DynamicExplorationGraph(deglib::graph::InternalGraph& graph) : internal_graph_(graph) {}
33+
34+
const uint32_t size() const {
35+
return internal_graph_.size();
36+
}
37+
38+
const uint8_t getEdgesPerVertex() const {
39+
return internal_graph_.getEdgesPerVertex();
40+
}
41+
42+
const deglib::FloatSpace& getFeatureSpace() const {
43+
return internal_graph_.getFeatureSpace();
44+
}
45+
46+
bool hasVertex(const uint32_t external_label) const {
47+
return internal_graph_.hasVertex(external_label);
48+
}
49+
50+
deglib::graph::InternalGraph& internal() {
51+
return internal_graph_;
52+
}
53+
const deglib::graph::InternalGraph& internal() const {
54+
return internal_graph_;
55+
}
56+
57+
bool isMutable() const {
58+
return dynamic_cast<const deglib::graph::MutableGraph*>(&internal_graph_) != nullptr;
59+
}
60+
/**
61+
* Search for similar feature vectors using query data.
62+
* Returns a ResultSet where internal indices are mapped to external_labels.
63+
*/
64+
template <typename T>
65+
deglib::graph::ResultSet search(
66+
std::span<const T> query,
67+
const uint32_t k,
68+
const float eps = 0.0f,
69+
const deglib::graph::Filter* filter = nullptr,
70+
const uint32_t max_distance_computation_count = 0) const
71+
{
72+
auto internal_res = internal_graph_.search(query, k, eps, filter, max_distance_computation_count);
73+
deglib::graph::ResultSet external_res;
74+
for (const auto& od : internal_res) {
75+
uint32_t ext_label = internal_graph_.getExternalLabel(od.getInternalIndex());
76+
external_res.push(deglib::graph::ObjectDistance(ext_label, od.getDistance()));
77+
}
78+
return external_res;
79+
}
80+
81+
/**
82+
* Exploration starting at a specific external_label.
83+
* Maps entry external_label to internal_index, performs search, and maps result back to external_labels.
84+
*/
85+
deglib::graph::ResultSet explore(
86+
const uint32_t entry_external_label,
87+
const uint32_t k,
88+
const uint32_t max_distance_computation_count = 0,
89+
const float eps = 0.0f,
90+
const bool include_entry = true,
91+
const deglib::graph::Filter* filter = nullptr) const
92+
{
93+
uint32_t internal_entry = internal_graph_.getInternalIndex(entry_external_label);
94+
auto internal_res = internal_graph_.explore(internal_entry, k, max_distance_computation_count, eps, include_entry, filter);
95+
deglib::graph::ResultSet external_res;
96+
for (const auto& od : internal_res) {
97+
uint32_t ext_label = internal_graph_.getExternalLabel(od.getInternalIndex());
98+
external_res.push(deglib::graph::ObjectDistance(ext_label, od.getDistance()));
99+
}
100+
return external_res;
101+
}
102+
103+
/**
104+
* Get neighbors as external_labels for a given external_label.
105+
*/
106+
std::vector<uint32_t> getNeighbors(const uint32_t external_label) const {
107+
uint32_t internal_idx = internal_graph_.getInternalIndex(external_label);
108+
const uint32_t* internal_neighbors = internal_graph_.getNeighborIndices(internal_idx);
109+
uint8_t edges_count = internal_graph_.getEdgesPerVertex();
110+
std::vector<uint32_t> external_neighbors(edges_count);
111+
for (size_t i = 0; i < edges_count; ++i) {
112+
external_neighbors[i] = internal_graph_.getExternalLabel(internal_neighbors[i]);
113+
}
114+
return external_neighbors;
115+
}
116+
};
117+
118+
} // namespace deglib

0 commit comments

Comments
 (0)