deglib 0.2.4__tar.gz → 0.2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {deglib-0.2.4 → deglib-0.2.6}/CMakeLists.txt +11 -2
  2. {deglib-0.2.4/src/deglib.egg-info → deglib-0.2.6}/PKG-INFO +16 -5
  3. {deglib-0.2.4 → deglib-0.2.6}/README.md +15 -4
  4. {deglib-0.2.4 → deglib-0.2.6}/lib/CMakeLists.txt +4 -5
  5. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/build.h +3 -1
  6. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/builder.h +101 -2
  7. deglib-0.2.6/lib/deglib/include/deglib/deglib.h +118 -0
  8. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/fp16_ip.h +30 -2
  9. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/fp16_l2.h +32 -1
  10. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/fp32_ip.h +53 -16
  11. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/fp32_l2.h +52 -16
  12. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/int8_ip.h +76 -0
  13. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/int8_l2.h +28 -18
  14. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/uint8_ip.h +84 -0
  15. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/uint8_l2.h +28 -18
  16. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph/dynamic_graph.h +12 -96
  17. deglib-0.2.6/lib/deglib/include/deglib/graph/internal_graph.h +912 -0
  18. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph/readonly_graph.h +69 -111
  19. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph/sizebounded_graph.h +44 -176
  20. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph.h +12 -46
  21. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/flas/junker_volgenant_solver.h +4 -2
  22. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/quantization/evp_quantize.h +96 -0
  23. deglib-0.2.6/lib/deglib/include/deglib/optimization/quantization/quantizer_concept.h +41 -0
  24. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/quantization/scalar_quantize.h +137 -0
  25. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization.h +6 -23
  26. deglib-0.2.6/lib/deglib/include/deglib/search/kmeans.h +428 -0
  27. deglib-0.2.6/lib/deglib/include/deglib/search/result_list.h +240 -0
  28. deglib-0.2.6/lib/deglib/include/deglib/search/searcher.h +902 -0
  29. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/search.h +13 -24
  30. {deglib-0.2.4 → deglib-0.2.6}/setup.py +28 -24
  31. {deglib-0.2.4 → deglib-0.2.6}/src/deg_cpp/deglib_cpp.cpp +120 -80
  32. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/__init__.py +1 -3
  33. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/graph.py +8 -13
  34. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/optimization.py +5 -17
  35. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/search.py +30 -5
  36. {deglib-0.2.4 → deglib-0.2.6/src/deglib.egg-info}/PKG-INFO +16 -5
  37. {deglib-0.2.4 → deglib-0.2.6}/src/deglib.egg-info/SOURCES.txt +3 -0
  38. {deglib-0.2.4 → deglib-0.2.6}/tests/test_builder.py +75 -0
  39. {deglib-0.2.4 → deglib-0.2.6}/tests/test_graph.py +3 -7
  40. {deglib-0.2.4 → deglib-0.2.6}/tests/test_searcher.py +65 -18
  41. deglib-0.2.4/lib/deglib/include/deglib/deglib.h +0 -17
  42. deglib-0.2.4/lib/deglib/include/deglib/graph/internal_graph.h +0 -528
  43. deglib-0.2.4/lib/deglib/include/deglib/search/searcher.h +0 -493
  44. {deglib-0.2.4 → deglib-0.2.6}/LICENSE +0 -0
  45. {deglib-0.2.4 → deglib-0.2.6}/MANIFEST.in +0 -0
  46. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/CMakeLists.txt +0 -0
  47. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/benchmark.h +0 -0
  48. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/dataset.h +0 -0
  49. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/file_io.h +0 -0
  50. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/logging.h +0 -0
  51. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/repository.h +0 -0
  52. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/stats.h +0 -0
  53. {deglib-0.2.4 → deglib-0.2.6}/lib/bench/include/stopwatch.h +0 -0
  54. {deglib-0.2.4 → deglib-0.2.6}/lib/cmake_modules/DetectCPUFeatures.cmake +0 -0
  55. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/CMakeLists.txt +0 -0
  56. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/analysis.h +0 -0
  57. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/concurrent.h +0 -0
  58. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/config.h +0 -0
  59. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/evp_ip.h +0 -0
  60. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/fp16.h +0 -0
  61. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/fp32.h +0 -0
  62. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/int8.h +0 -0
  63. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/residual_mode.h +0 -0
  64. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distance/uint8.h +0 -0
  65. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/distances.h +0 -0
  66. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/filter.h +0 -0
  67. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph/intrusive_list.h +0 -0
  68. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph/mutable_graph.h +0 -0
  69. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/graph/visited_list_pool.h +0 -0
  70. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/flas/fast_linear_assignment_sorter.h +0 -0
  71. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/flas/fast_linear_assignment_sorter_mt.h +0 -0
  72. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/pruning.h +0 -0
  73. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/optimization/transform.h +0 -0
  74. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/utils/cpu.h +0 -0
  75. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/utils/memory.h +0 -0
  76. {deglib-0.2.4 → deglib-0.2.6}/lib/deglib/include/deglib/utils/random.h +0 -0
  77. {deglib-0.2.4 → deglib-0.2.6}/lib/test/CMakeLists.txt +0 -0
  78. {deglib-0.2.4 → deglib-0.2.6}/lib/test/src/common/test_helpers.h +0 -0
  79. {deglib-0.2.4 → deglib-0.2.6}/pyproject.toml +0 -0
  80. {deglib-0.2.4 → deglib-0.2.6}/setup.cfg +0 -0
  81. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/analysis.py +0 -0
  82. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/builder.py +0 -0
  83. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/cpu.py +0 -0
  84. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/distances.py +0 -0
  85. {deglib-0.2.4 → deglib-0.2.6}/src/deglib/utils.py +0 -0
  86. {deglib-0.2.4 → deglib-0.2.6}/src/deglib.egg-info/dependency_links.txt +0 -0
  87. {deglib-0.2.4 → deglib-0.2.6}/src/deglib.egg-info/requires.txt +0 -0
  88. {deglib-0.2.4 → deglib-0.2.6}/src/deglib.egg-info/top_level.txt +0 -0
  89. {deglib-0.2.4 → deglib-0.2.6}/tests/test_analysis.py +0 -0
  90. {deglib-0.2.4 → deglib-0.2.6}/tests/test_filter.py +0 -0
  91. {deglib-0.2.4 → deglib-0.2.6}/tests/test_presort.py +0 -0
  92. {deglib-0.2.4 → deglib-0.2.6}/tests/test_rerank.py +0 -0
  93. {deglib-0.2.4 → deglib-0.2.6}/tests/test_scalar_quantizer.py +0 -0
  94. {deglib-0.2.4 → deglib-0.2.6}/tests/test_transform.py +0 -0
@@ -15,11 +15,20 @@ find_package(pybind11 CONFIG REQUIRED)
15
15
  set(ENABLE_BENCHMARKS OFF CACHE BOOL "Disable benchmarks" FORCE)
16
16
  set(ENABLE_TESTING OFF CACHE BOOL "Disable tests" FORCE)
17
17
 
18
+ # Find deglib cpp source: prefer ../cpp in repository checkouts, fallback to local lib/ (PyPI sdist)
19
+ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../cpp/CMakeLists.txt")
20
+ set(DEGLIB_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../cpp")
21
+ elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/CMakeLists.txt")
22
+ set(DEGLIB_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib")
23
+ else()
24
+ message(FATAL_ERROR "Could not find deglib cpp source directory (checked ../cpp/ and lib/)")
25
+ endif()
26
+
18
27
  # Add cpp-deglib
19
- add_subdirectory(${CMAKE_SOURCE_DIR}/lib/)
28
+ add_subdirectory(${DEGLIB_CPP_DIR} deglib_cpp_build)
20
29
  pybind11_add_module(deglib_cpp src/deg_cpp/deglib_cpp.cpp)
21
30
 
22
31
  # Specify the include directories
23
- include_directories(${CMAKE_SOURCE_DIR}/lib/deglib/include)
32
+ include_directories(${DEGLIB_CPP_DIR}/deglib/include)
24
33
 
25
34
  target_link_libraries(deglib_cpp PRIVATE compile-options)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deglib
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Python bindings for the Dynamic Exploration Graph library by Nico Hezel
5
5
  Author: Nico Hezel
6
6
  Author-email: Bruno Schilling <bruno.schilling@htw-berlin.de>
@@ -278,10 +278,21 @@ Controls the topology optimization strategy:
278
278
  - `OptimizationTarget.HighLID`: Optimized for datasets with high local intrinsic dimensionality (supports multithreaded building).
279
279
  - `OptimizationTarget.StreamingData`: Optimized for continuous dynamic additions and deletions.
280
280
 
281
- ### Search Parameter `eps`
282
- - The epsilon parameter expands the search priority queue during graph exploration.
283
- - Small values (e.g. `eps=0.001` or `eps=0.01`): Faster query execution.
284
- - Higher values (e.g. `eps=0.1` to `eps=0.3`): Higher recall rate.
281
+ ### Unified Search Parameter: `eps_or_ef`
282
+ `Searcher` provides a unified exploration parameter `eps_or_ef`:
283
+ - **Relative Distance Margin (`eps_or_ef < 1.0`)**: Standard DEG dynamic distance exploration bound $(1 + \epsilon) \cdot d_{\text{cur}}$.
284
+ - Small values (e.g. `eps_or_ef=0.001` or `0.01`): Faster query execution.
285
+ - Higher values (e.g. `eps_or_ef=0.1` to `0.3`): Higher recall rate.
286
+ - **Fixed Candidate Pool (`eps_or_ef >= 1.0`)**: HNSW-style search with a fixed candidate pool size of `round(eps_or_ef)` during exploration.
287
+ - Typical values: `64`, `96`, `128`, `200`, `400`.
288
+
289
+ ```python
290
+ # Mode 1: Relative distance margin (default: eps_or_ef=0.1)
291
+ indices, distances = searcher.search(query, k=10, eps_or_ef=0.1)
292
+
293
+ # Mode 2: Fixed candidate pool (HNSW-style)
294
+ indices, distances = searcher.search(query, k=10, eps_or_ef=128)
295
+ ```
285
296
 
286
297
  ### Supported Metrics & Data Types
287
298
  - `Metric.FP32_L2`: Euclidean distance (`np.float32`)
@@ -250,10 +250,21 @@ Controls the topology optimization strategy:
250
250
  - `OptimizationTarget.HighLID`: Optimized for datasets with high local intrinsic dimensionality (supports multithreaded building).
251
251
  - `OptimizationTarget.StreamingData`: Optimized for continuous dynamic additions and deletions.
252
252
 
253
- ### Search Parameter `eps`
254
- - The epsilon parameter expands the search priority queue during graph exploration.
255
- - Small values (e.g. `eps=0.001` or `eps=0.01`): Faster query execution.
256
- - Higher values (e.g. `eps=0.1` to `eps=0.3`): Higher recall rate.
253
+ ### Unified Search Parameter: `eps_or_ef`
254
+ `Searcher` provides a unified exploration parameter `eps_or_ef`:
255
+ - **Relative Distance Margin (`eps_or_ef < 1.0`)**: Standard DEG dynamic distance exploration bound $(1 + \epsilon) \cdot d_{\text{cur}}$.
256
+ - Small values (e.g. `eps_or_ef=0.001` or `0.01`): Faster query execution.
257
+ - Higher values (e.g. `eps_or_ef=0.1` to `0.3`): Higher recall rate.
258
+ - **Fixed Candidate Pool (`eps_or_ef >= 1.0`)**: HNSW-style search with a fixed candidate pool size of `round(eps_or_ef)` during exploration.
259
+ - Typical values: `64`, `96`, `128`, `200`, `400`.
260
+
261
+ ```python
262
+ # Mode 1: Relative distance margin (default: eps_or_ef=0.1)
263
+ indices, distances = searcher.search(query, k=10, eps_or_ef=0.1)
264
+
265
+ # Mode 2: Fixed candidate pool (HNSW-style)
266
+ indices, distances = searcher.search(query, k=10, eps_or_ef=128)
267
+ ```
257
268
 
258
269
  ### Supported Metrics & Data Types
259
270
  - `Metric.FP32_L2`: Euclidean distance (`np.float32`)
@@ -70,12 +70,11 @@ else() # Native build
70
70
 
71
71
  # setup compiler flags
72
72
  if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
73
+ # Portable native build: generic code is compiled for the build machine's
74
+ # baseline ISA only (no explicit AVX-512), so the module imports cleanly on
75
+ # CPUs without AVX-512. AVX-512 kernels are per-function target attributes
76
+ # (DEGLIB_TARGET_AVX512*), gated at runtime by deglib::cpu::select_dist().
73
77
  target_compile_options(compile-options INTERFACE -O3 -march=native -fpic -w -pthread -ftree-vectorize)
74
- if(SUPPORT_AVX512F)
75
- target_compile_options(compile-options INTERFACE -mavx512f -mfma)
76
- elseif(SUPPORT_AVX2)
77
- target_compile_options(compile-options INTERFACE -mavx2 -mfma)
78
- endif()
79
78
  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
80
79
  target_compile_options(compile-options INTERFACE -O2 -DNDEBUG -march=native -fpic)
81
80
  elseif(MSVC)
@@ -41,7 +41,9 @@ inline deglib::graph::SizeBoundedGraph create_random_graph(
41
41
  const uint32_t vertex_count = (max_size > 0 && max_size < repository.size()) ? max_size : uint32_t(repository.size());
42
42
  const auto feature_data = repository.getFeature(0);
43
43
 
44
- return deglib::graph::SizeBoundedGraph::create_random_graph(feature_data, vertex_count, k, feature_space);
44
+ auto graph = deglib::graph::SizeBoundedGraph(vertex_count, k, feature_space);
45
+ deglib::builder::populate_random_graph(graph, feature_data, vertex_count);
46
+ return graph;
45
47
  }
46
48
 
47
49
  inline void create_graph(
@@ -649,7 +649,7 @@ class EvenRegularGraphBuilder {
649
649
  const auto dist_func_param = graph.getFeatureSpace().get_dist_func_param();
650
650
 
651
651
  // find good neighbors for the new vertex
652
- const std::vector<uint32_t> entry_vertex_indices = {0};
652
+ const auto entry_vertex_indices = graph.getEntryVertexIndices();
653
653
  auto top_list =
654
654
  graph.search_intern(entry_vertex_indices, new_vertex_feature, std::max(uint32_t(this->extend_k_), edges_per_vertex), this->extend_eps_);
655
655
  const auto results = topListAscending(top_list);
@@ -1535,7 +1535,7 @@ DynamicExplorationGraph build_from_data(
1535
1535
  }
1536
1536
 
1537
1537
  auto feature_space = deglib::distances::FloatSpace(dims, metric);
1538
- auto graph = deglib::DynamicExplorationGraph::create_empty(vertex_count, edges_per_vertex, feature_space);
1538
+ auto graph = DynamicExplorationGraph(std::make_unique<deglib::graph::SizeBoundedGraph>(vertex_count, edges_per_vertex, feature_space));
1539
1539
 
1540
1540
  std::mt19937 rng(seed);
1541
1541
  auto builder = EvenRegularGraphBuilder(
@@ -1555,4 +1555,103 @@ DynamicExplorationGraph build_from_data(
1555
1555
  return graph;
1556
1556
  }
1557
1557
 
1558
+ /**
1559
+ * Populate a MutableGraph with a random k-regular graph.
1560
+ *
1561
+ * @param graph The mutable graph instance to populate.
1562
+ * @param feature_data Pointer to contiguous feature data bytes.
1563
+ * @param vertex_count Number of vertices to insert.
1564
+ * @param seed Random seed for deterministic graph construction (default: 7).
1565
+ */
1566
+ inline void populate_random_graph(
1567
+ deglib::graph::MutableGraph& graph,
1568
+ const std::byte* feature_data,
1569
+ const uint32_t vertex_count,
1570
+ const uint32_t seed = 7
1571
+ ) {
1572
+ const auto& feature_space = graph.getFeatureSpace();
1573
+ const auto edges_per_vertex = graph.getEdgesPerVertex();
1574
+ const auto dist_func = feature_space.get_dist_func();
1575
+ const auto dist_func_param = feature_space.get_dist_func_param();
1576
+
1577
+ // add the initial vertices (edges_per_vertex + 1)
1578
+ {
1579
+ const auto size = static_cast<uint32_t>(edges_per_vertex + 1);
1580
+ for (uint32_t y = 0; y < size; y++) {
1581
+ const auto query = feature_data + size_t(y) * feature_space.get_data_size();
1582
+ const auto internal_index = graph.addVertex(y, query);
1583
+
1584
+ auto neighbor_indices = std::vector<uint32_t>();
1585
+ auto neighbor_weights = std::vector<float>();
1586
+ for (uint32_t x = 0; x < size; x++) {
1587
+ if (x == internal_index) continue;
1588
+ neighbor_indices.emplace_back(x);
1589
+ neighbor_weights.emplace_back(dist_func(query, feature_data + size_t(x) * feature_space.get_data_size(), dist_func_param));
1590
+ }
1591
+ graph.changeEdges(internal_index, neighbor_indices.data(), neighbor_weights.data());
1592
+ }
1593
+ }
1594
+
1595
+ // random order of vertices
1596
+ auto rnd = std::mt19937(seed);
1597
+ auto rnd_neighbor = deglib::random::DeterministicUniformIntDistribution<uint32_t>(0, edges_per_vertex - 1);
1598
+
1599
+ // add the remaining vertices
1600
+ for (uint32_t label = edges_per_vertex + 1; label < vertex_count; label++) {
1601
+ const auto new_vertex_feature = feature_data + size_t(label) * feature_space.get_data_size();
1602
+ const auto internal_index = graph.addVertex(label, new_vertex_feature);
1603
+ auto top_list = deglib::random::DeterministicUniformIntDistribution<uint32_t>(0, label - 1);
1604
+
1605
+ // remove the worst edge of the good neighbors and connect them with this new vertex
1606
+ auto new_neighbors = std::vector<std::pair<uint32_t, float>>();
1607
+ while (new_neighbors.size() < edges_per_vertex) {
1608
+ const auto candidate_index = static_cast<uint32_t>(top_list(rnd));
1609
+
1610
+ // check if the vertex is already in the edge list of the new vertex (added during a previous loop-run)
1611
+ // since all edges are undirected and the edge information of the new vertex does not yet exist, we search the other way around.
1612
+ if (graph.hasEdge(candidate_index, internal_index)) continue;
1613
+
1614
+ // find a new random neighbor
1615
+ uint32_t new_neighbor_index = 0;
1616
+ float new_neighbor_weight = 0;
1617
+ bool found = false;
1618
+ const auto neighbor_weights = graph.getNeighborWeights(candidate_index);
1619
+ const auto neighbor_indices = graph.getNeighborIndices(candidate_index);
1620
+ while (!found) {
1621
+ const auto edge_idx = static_cast<uint32_t>(rnd_neighbor(rnd));
1622
+ const auto neighbor_index = neighbor_indices[edge_idx];
1623
+ const auto neighbor_weight = neighbor_weights[edge_idx];
1624
+
1625
+ // the suggested neighbors might already be in the edge list of the new vertex
1626
+ if (!graph.hasEdge(neighbor_index, internal_index)) {
1627
+ new_neighbor_index = neighbor_index;
1628
+ new_neighbor_weight = neighbor_weight;
1629
+ found = true;
1630
+ }
1631
+ }
1632
+
1633
+ // place the new vertex in the edge list of the result-vertex
1634
+ const auto candidate_dist = dist_func(new_vertex_feature, graph.getFeatureVector(candidate_index), dist_func_param);
1635
+ graph.changeEdge(candidate_index, new_neighbor_index, internal_index, candidate_dist);
1636
+ new_neighbors.emplace_back(candidate_index, candidate_dist);
1637
+
1638
+ // place the new vertex in the edge list of the worst edge neighbor
1639
+ const auto new_neighbor_dist = dist_func(new_vertex_feature, graph.getFeatureVector(new_neighbor_index), dist_func_param);
1640
+ graph.changeEdge(new_neighbor_index, candidate_index, internal_index, new_neighbor_dist);
1641
+ new_neighbors.emplace_back(new_neighbor_index, new_neighbor_dist);
1642
+ }
1643
+
1644
+ // sort the neighbors by their neighbor indices and store them in the new vertex
1645
+ std::sort(new_neighbors.begin(), new_neighbors.end(), [](const auto& x, const auto& y) { return x.first < y.first; });
1646
+ auto neighbor_indices = std::vector<uint32_t>();
1647
+ auto neighbor_weights = std::vector<float>();
1648
+ for (auto&& neighbor : new_neighbors) {
1649
+ neighbor_indices.emplace_back(neighbor.first);
1650
+ neighbor_weights.emplace_back(neighbor.second);
1651
+ }
1652
+ graph.changeEdges(internal_index, neighbor_indices.data(), neighbor_weights.data());
1653
+ }
1654
+ }
1655
+
1558
1656
  } // end namespace deglib::builder
1657
+
@@ -0,0 +1,118 @@
1
+ #pragma once
2
+
3
+ #include "deglib/analysis.h"
4
+ #include "deglib/builder.h"
5
+ #include "deglib/config.h"
6
+ #include "deglib/distances.h"
7
+ #include "deglib/filter.h"
8
+ #include "deglib/graph.h"
9
+ #include "deglib/optimization.h"
10
+ #include "deglib/search.h"
11
+ #include "deglib/search/searcher.h"
12
+ #include "deglib/utils/memory.h"
13
+ #include "deglib/utils/random.h"
14
+
15
+ namespace deglib {
16
+ using builder::build_from_data;
17
+ using builder::EvenRegularGraphBuilder;
18
+ using builder::OptimizationTarget;
19
+
20
+ /**
21
+ * Create an EvenRegularGraphBuilder for building and incrementally modifying a DynamicExplorationGraph.
22
+ */
23
+ inline builder::EvenRegularGraphBuilder create_builder(
24
+ DynamicExplorationGraph& graph,
25
+ std::mt19937& rnd,
26
+ const builder::OptimizationTarget optimization_target = builder::OptimizationTarget::LowLID,
27
+ const uint8_t extend_k = 0,
28
+ const float extend_eps = 0.1f,
29
+ const uint8_t improve_k = 0,
30
+ const float improve_eps = 0.001f,
31
+ const uint8_t max_path_length = 5,
32
+ const uint32_t improve_tries = 0
33
+ ) {
34
+ return builder::EvenRegularGraphBuilder(
35
+ graph, rnd, optimization_target, extend_k, extend_eps, improve_k, improve_eps, max_path_length, improve_tries
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Create an empty mutable DynamicExplorationGraph with the given capacity,
41
+ * edges per vertex, and feature space (backed by SizeBoundedGraph).
42
+ */
43
+ inline DynamicExplorationGraph create_empty(
44
+ const uint32_t max_vertex_count,
45
+ const uint8_t edges_per_vertex,
46
+ const deglib::distances::FloatSpace& feature_space
47
+ ) {
48
+ auto graph = std::make_unique<deglib::graph::SizeBoundedGraph>(max_vertex_count, edges_per_vertex, feature_space);
49
+ return DynamicExplorationGraph(std::move(graph));
50
+ }
51
+
52
+ /**
53
+ * Create an empty mutable DynamicExplorationGraph with chunk-based dynamic memory allocation (backed by DynamicGraph).
54
+ *
55
+ * @param edges_per_vertex Number of edges per vertex (must be even).
56
+ * @param feature_space The feature space defining dimensionality and metric.
57
+ * @param chunk_size Target number of vertices per memory chunk (default = 1024).
58
+ * Will be automatically rounded up to the nearest power of 2 (e.g. 600 -> 1024).
59
+ */
60
+ inline DynamicExplorationGraph create_dynamic_empty(
61
+ const uint8_t edges_per_vertex,
62
+ const deglib::distances::FloatSpace& feature_space,
63
+ const uint32_t chunk_size = 1024
64
+ ) {
65
+ auto graph = std::make_unique<deglib::graph::DynamicGraph>(edges_per_vertex, feature_space, chunk_size);
66
+ return DynamicExplorationGraph(std::move(graph));
67
+ }
68
+
69
+ /**
70
+ * Create a random exploration graph from the given feature data (backed by SizeBoundedGraph).
71
+ *
72
+ * @param feature_data Pointer to a contiguous array of feature vectors.
73
+ * Each vector is feature_space.get_data_size() bytes.
74
+ * @param vertex_count Number of vertices to insert.
75
+ * @param edges_per_vertex Number of edges per vertex (must be even).
76
+ * @param feature_space The feature space defining dimensionality and metric.
77
+ * @param seed Random seed for deterministic graph construction.
78
+ * @return A new DynamicExplorationGraph wrapping the created random graph.
79
+ */
80
+ inline DynamicExplorationGraph create_random_graph(
81
+ const std::byte* feature_data,
82
+ const uint32_t vertex_count,
83
+ const uint8_t edges_per_vertex,
84
+ const deglib::distances::FloatSpace& feature_space,
85
+ const uint32_t seed = 7
86
+ ) {
87
+ auto graph = std::make_unique<deglib::graph::SizeBoundedGraph>(vertex_count, edges_per_vertex, feature_space);
88
+ deglib::builder::populate_random_graph(*graph, feature_data, vertex_count, seed);
89
+ return DynamicExplorationGraph(std::move(graph));
90
+ }
91
+
92
+ /**
93
+ * Load a saved graph from disk as a compact, immutable DynamicExplorationGraph (backed by ReadOnlyGraph).
94
+ */
95
+ inline DynamicExplorationGraph load_readonly_graph(const char* path_graph) {
96
+ auto graph = std::make_unique<deglib::graph::ReadOnlyGraph>(deglib::graph::load_readonly_graph(path_graph));
97
+ return DynamicExplorationGraph(std::move(graph));
98
+ }
99
+
100
+ /**
101
+ * Load a saved graph from disk as a chunk-allocated mutable DynamicExplorationGraph (backed by DynamicGraph).
102
+ */
103
+ inline DynamicExplorationGraph load_dynamic_graph(const char* path_graph, const uint32_t chunk_size = 1024) {
104
+ auto graph = std::make_unique<deglib::graph::DynamicGraph>(deglib::graph::load_dynamic_graph(path_graph, chunk_size));
105
+ return DynamicExplorationGraph(std::move(graph));
106
+ }
107
+
108
+ /**
109
+ * Load a saved graph from disk as a fixed-capacity mutable DynamicExplorationGraph (backed by SizeBoundedGraph).
110
+ *
111
+ * @param new_max_size Optional capacity override. If 0, uses the graph's serialized size.
112
+ */
113
+ inline DynamicExplorationGraph load_mutable_graph(const char* path_graph, const uint32_t new_max_size = 0) {
114
+ auto graph = std::make_unique<deglib::graph::SizeBoundedGraph>(deglib::graph::load_sizebounded_graph(path_graph, new_max_size));
115
+ return DynamicExplorationGraph(std::move(graph));
116
+ }
117
+
118
+ } // namespace deglib
@@ -110,8 +110,26 @@ class InnerProductFP16_AVX512 {
110
110
  __m512 sum512 = _mm512_add_ps(sum512_1, sum512_2);
111
111
  float result = fp16_hsum512(sum512);
112
112
 
113
- // Scalar residual for the unaligned tail — eliminated at compile-time if HasTail == false
113
+ // Vectorized residual tail for arbitrary dimensions
114
114
  if constexpr (HasTail) {
115
+ if ((last - a) >= 8) {
116
+ __m256 va8 = _mm256_cvtph_ps(_mm_loadu_si128(reinterpret_cast<const __m128i*>(a)));
117
+ __m256 vb8 = _mm256_cvtph_ps(_mm_loadu_si128(reinterpret_cast<const __m128i*>(b)));
118
+ __m256 prod8 = _mm256_mul_ps(va8, vb8);
119
+ result += fp16_hsum256(prod8);
120
+ a += 8;
121
+ b += 8;
122
+ }
123
+ if ((last - a) >= 4) {
124
+ __m128 va4 = _mm_cvtph_ps(_mm_loadu_si64(a));
125
+ __m128 vb4 = _mm_cvtph_ps(_mm_loadu_si64(b));
126
+ __m128 prod4 = _mm_mul_ps(va4, vb4);
127
+ prod4 = _mm_add_ps(prod4, _mm_movehl_ps(prod4, prod4));
128
+ prod4 = _mm_add_ss(prod4, _mm_shuffle_ps(prod4, prod4, 1));
129
+ result += _mm_cvtss_f32(prod4);
130
+ a += 4;
131
+ b += 4;
132
+ }
115
133
  while (a < last) {
116
134
  float fa = deglib::distances::fp16::fp16_to_float(*a++);
117
135
  float fb = deglib::distances::fp16::fp16_to_float(*b++);
@@ -243,8 +261,18 @@ class InnerProductFP16_AVX2 {
243
261
  __m256 sum256 = _mm256_add_ps(sum256_1, sum256_2);
244
262
  float result = fp16_hsum256(sum256);
245
263
 
246
- // Scalar residual for the unaligned tail — eliminated at compile-time if HasTail == false
264
+ // Vectorized residual tail for arbitrary dimensions
247
265
  if constexpr (HasTail) {
266
+ if ((last - a) >= 4) {
267
+ __m128 va4 = _mm_cvtph_ps(_mm_loadu_si64(a));
268
+ __m128 vb4 = _mm_cvtph_ps(_mm_loadu_si64(b));
269
+ __m128 prod4 = _mm_mul_ps(va4, vb4);
270
+ prod4 = _mm_add_ps(prod4, _mm_movehl_ps(prod4, prod4));
271
+ prod4 = _mm_add_ss(prod4, _mm_shuffle_ps(prod4, prod4, 1));
272
+ result += _mm_cvtss_f32(prod4);
273
+ a += 4;
274
+ b += 4;
275
+ }
248
276
  while (a < last) {
249
277
  float fa = deglib::distances::fp16::fp16_to_float(*a++);
250
278
  float fb = deglib::distances::fp16::fp16_to_float(*b++);
@@ -115,7 +115,27 @@ class L2FP16_AVX512 {
115
115
  float result = fp16_l2_hsum512(sum512);
116
116
 
117
117
  // Scalar residual for the unaligned tail — eliminated at compile-time if HasTail == false
118
+ // Vectorized residual tail for arbitrary dimensions
118
119
  if constexpr (HasTail) {
120
+ if ((last - a) >= 8) {
121
+ __m256 va8 = _mm256_cvtph_ps(_mm_loadu_si128(reinterpret_cast<const __m128i*>(a)));
122
+ __m256 vb8 = _mm256_cvtph_ps(_mm_loadu_si128(reinterpret_cast<const __m128i*>(b)));
123
+ __m256 diff8 = _mm256_sub_ps(va8, vb8);
124
+ result += fp16_l2_hsum256(_mm256_mul_ps(diff8, diff8));
125
+ a += 8;
126
+ b += 8;
127
+ }
128
+ if ((last - a) >= 4) {
129
+ __m128 va4 = _mm_cvtph_ps(_mm_loadu_si64(a));
130
+ __m128 vb4 = _mm_cvtph_ps(_mm_loadu_si64(b));
131
+ __m128 diff4 = _mm_sub_ps(va4, vb4);
132
+ __m128 prod4 = _mm_mul_ps(diff4, diff4);
133
+ prod4 = _mm_add_ps(prod4, _mm_movehl_ps(prod4, prod4));
134
+ prod4 = _mm_add_ss(prod4, _mm_shuffle_ps(prod4, prod4, 1));
135
+ result += _mm_cvtss_f32(prod4);
136
+ a += 4;
137
+ b += 4;
138
+ }
119
139
  while (a < last) {
120
140
  float fa = deglib::distances::fp16::fp16_to_float(*a++);
121
141
  float fb = deglib::distances::fp16::fp16_to_float(*b++);
@@ -251,8 +271,19 @@ class L2FP16_AVX2 {
251
271
  __m256 sum256 = _mm256_add_ps(sum256_1, sum256_2);
252
272
  float result = fp16_l2_hsum256(sum256);
253
273
 
254
- // Scalar residual for the unaligned tail — eliminated at compile-time if HasTail == false
274
+ // Vectorized residual tail for arbitrary dimensions
255
275
  if constexpr (HasTail) {
276
+ if ((last - a) >= 4) {
277
+ __m128 va4 = _mm_cvtph_ps(_mm_loadu_si64(a));
278
+ __m128 vb4 = _mm_cvtph_ps(_mm_loadu_si64(b));
279
+ __m128 diff4 = _mm_sub_ps(va4, vb4);
280
+ __m128 prod4 = _mm_mul_ps(diff4, diff4);
281
+ prod4 = _mm_add_ps(prod4, _mm_movehl_ps(prod4, prod4));
282
+ prod4 = _mm_add_ss(prod4, _mm_shuffle_ps(prod4, prod4, 1));
283
+ result += _mm_cvtss_f32(prod4);
284
+ a += 4;
285
+ b += 4;
286
+ }
256
287
  while (a < last) {
257
288
  float fa = deglib::distances::fp16::fp16_to_float(*a++);
258
289
  float fb = deglib::distances::fp16::fp16_to_float(*b++);
@@ -120,8 +120,29 @@ class InnerProductFloat_AVX512 {
120
120
  _mm_store_ps(f, sum128);
121
121
  float result = f[0] + f[1] + f[2] + f[3];
122
122
 
123
- // Scalar residual for the unaligned tail — eliminated at compile-time if HasTail == false
123
+ // Vectorized residual tail for arbitrary dimensions
124
124
  if constexpr (HasTail) {
125
+ if ((last - a) >= 8) {
126
+ __m256 va8 = _mm256_loadu_ps(a);
127
+ __m256 vb8 = _mm256_loadu_ps(b);
128
+ __m256 prod8 = _mm256_mul_ps(va8, vb8);
129
+ __m128 sum128 = _mm_add_ps(_mm256_castps256_ps128(prod8), _mm256_extractf128_ps(prod8, 1));
130
+ alignas(16) float f[4];
131
+ _mm_store_ps(f, sum128);
132
+ result += f[0] + f[1] + f[2] + f[3];
133
+ a += 8;
134
+ b += 8;
135
+ }
136
+ if ((last - a) >= 4) {
137
+ __m128 va4 = _mm_loadu_ps(a);
138
+ __m128 vb4 = _mm_loadu_ps(b);
139
+ __m128 prod4 = _mm_mul_ps(va4, vb4);
140
+ prod4 = _mm_add_ps(prod4, _mm_movehl_ps(prod4, prod4));
141
+ prod4 = _mm_add_ss(prod4, _mm_shuffle_ps(prod4, prod4, 1));
142
+ result += _mm_cvtss_f32(prod4);
143
+ a += 4;
144
+ b += 4;
145
+ }
125
146
  while (a < last) {
126
147
  result = std::fma(*a++, *b++, result);
127
148
  }
@@ -143,17 +164,20 @@ class InnerProductFloat_AVX512 {
143
164
  }
144
165
 
145
166
  if constexpr (HasDualSimd) {
146
- const size_t nc16 = dim / 16;
147
- offset = nc16 * 16;
167
+ const size_t nc32 = dim / 32;
168
+ offset = nc32 * 32;
148
169
 
149
- for (size_t c = 0; c < nc16; ++c) {
150
- size_t idx = c * 16;
151
- __m512 q_vec = _mm512_loadu_ps(&query[idx]);
170
+ for (size_t c = 0; c < nc32; ++c) {
171
+ size_t idx = c * 32;
172
+ __m512 q_vec_lo = _mm512_loadu_ps(&query[idx]);
173
+ __m512 q_vec_hi = _mm512_loadu_ps(&query[idx + 16]);
152
174
 
153
175
  for (size_t j = 0; j < BATCH_SIZE; ++j) {
154
176
  const float* db_ = static_cast<const float*>(db[j]);
155
- __m512 db_vec = _mm512_loadu_ps(&db_[idx]);
156
- s[j] = _mm512_fmadd_ps(q_vec, db_vec, s[j]);
177
+ __m512 db_vec_lo = _mm512_loadu_ps(&db_[idx]);
178
+ __m512 db_vec_hi = _mm512_loadu_ps(&db_[idx + 16]);
179
+ s[j] = _mm512_fmadd_ps(q_vec_lo, db_vec_lo, s[j]);
180
+ s[j] = _mm512_fmadd_ps(q_vec_hi, db_vec_hi, s[j]);
157
181
  }
158
182
  }
159
183
  }
@@ -242,8 +266,18 @@ class InnerProductFloat_AVX2 {
242
266
  __m256 sum256 = _mm256_add_ps(sum256_1, sum256_2);
243
267
  float result = fp32_hsum256(sum256);
244
268
 
245
- // Scalar residual for the unaligned tail — eliminated at compile-time if HasTail == false
269
+ // Vectorized residual tail for arbitrary dimensions
246
270
  if constexpr (HasTail) {
271
+ if ((last - a) >= 4) {
272
+ __m128 va4 = _mm_loadu_ps(a);
273
+ __m128 vb4 = _mm_loadu_ps(b);
274
+ __m128 prod4 = _mm_mul_ps(va4, vb4);
275
+ prod4 = _mm_add_ps(prod4, _mm_movehl_ps(prod4, prod4));
276
+ prod4 = _mm_add_ss(prod4, _mm_shuffle_ps(prod4, prod4, 1));
277
+ result += _mm_cvtss_f32(prod4);
278
+ a += 4;
279
+ b += 4;
280
+ }
247
281
  while (a < last) {
248
282
  result = std::fma(*a++, *b++, result);
249
283
  }
@@ -265,17 +299,20 @@ class InnerProductFloat_AVX2 {
265
299
  }
266
300
 
267
301
  if constexpr (HasDualSimd) {
268
- const size_t nc8 = dim / 8;
269
- offset = nc8 * 8;
302
+ const size_t nc16 = dim / 16;
303
+ offset = nc16 * 16;
270
304
 
271
- for (size_t c = 0; c < nc8; ++c) {
272
- size_t idx = c * 8;
273
- __m256 q_vec = _mm256_loadu_ps(&query[idx]);
305
+ for (size_t c = 0; c < nc16; ++c) {
306
+ size_t idx = c * 16;
307
+ __m256 q_vec_lo = _mm256_loadu_ps(&query[idx]);
308
+ __m256 q_vec_hi = _mm256_loadu_ps(&query[idx + 8]);
274
309
 
275
310
  for (size_t j = 0; j < BATCH_SIZE; ++j) {
276
311
  const float* db_ = static_cast<const float*>(db[j]);
277
- __m256 db_vec = _mm256_loadu_ps(&db_[idx]);
278
- s[j] = _mm256_fmadd_ps(q_vec, db_vec, s[j]);
312
+ __m256 db_vec_lo = _mm256_loadu_ps(&db_[idx]);
313
+ __m256 db_vec_hi = _mm256_loadu_ps(&db_[idx + 8]);
314
+ s[j] = _mm256_fmadd_ps(q_vec_lo, db_vec_lo, s[j]);
315
+ s[j] = _mm256_fmadd_ps(q_vec_hi, db_vec_hi, s[j]);
279
316
  }
280
317
  }
281
318
  }