usearch 2.17.0 → 2.19.4

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 (47) hide show
  1. package/README.md +6 -2
  2. package/c/lib.cpp +5 -0
  3. package/c/module.modulemap +4 -0
  4. package/c/usearch.h +13 -7
  5. package/cpp/README.md +1 -1
  6. package/cpp/bench.cpp +1 -1
  7. package/cpp/test.cpp +110 -16
  8. package/include/usearch/index.hpp +48 -35
  9. package/include/usearch/index_dense.hpp +34 -19
  10. package/include/usearch/index_plugins.hpp +29 -7
  11. package/javascript/README.md +8 -6
  12. package/javascript/dist/cjs/usearch.d.ts +9 -4
  13. package/javascript/dist/cjs/usearch.js +23 -8
  14. package/javascript/dist/esm/usearch.d.ts +9 -4
  15. package/javascript/dist/esm/usearch.js +21 -7
  16. package/javascript/lib.cpp +137 -59
  17. package/javascript/usearch.test.js +359 -192
  18. package/javascript/usearch.ts +35 -11
  19. package/package.json +1 -1
  20. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  21. package/prebuilds/linux-arm64/usearch.node +0 -0
  22. package/prebuilds/linux-x64/usearch.node +0 -0
  23. package/simsimd/CONTRIBUTING.md +23 -8
  24. package/simsimd/Cargo.lock +1 -1
  25. package/simsimd/Cargo.toml +1 -1
  26. package/simsimd/README.md +49 -9
  27. package/simsimd/build.rs +7 -3
  28. package/simsimd/c/lib.c +6 -1
  29. package/simsimd/include/simsimd/binary.h +3 -3
  30. package/simsimd/include/simsimd/dot.h +2 -2
  31. package/simsimd/include/simsimd/elementwise.h +26 -17
  32. package/simsimd/include/simsimd/simsimd.h +125 -49
  33. package/simsimd/include/simsimd/sparse.h +30 -29
  34. package/simsimd/include/simsimd/spatial.h +4 -3
  35. package/simsimd/include/simsimd/types.h +19 -8
  36. package/simsimd/javascript/lib.c +0 -1
  37. package/simsimd/package-lock.json +411 -362
  38. package/simsimd/package.json +9 -9
  39. package/simsimd/rust/lib.rs +63 -29
  40. package/simsimd/scripts/bench.cxx +13 -0
  41. package/simsimd/scripts/bench_cosine.rs +8 -0
  42. package/simsimd/scripts/bench_native.rs +4 -0
  43. package/simsimd/scripts/bench_sqeuclidean.rs +8 -0
  44. package/simsimd/scripts/test.c +22 -0
  45. package/simsimd/scripts/test.py +80 -4
  46. package/simsimd/swift/SimSIMD.swift +17 -0
  47. package/simsimd/swift/Test.swift +36 -0
package/README.md CHANGED
@@ -161,7 +161,7 @@ This can result in __20x cost reduction__ on AWS and other public clouds.
161
161
  index.save("index.usearch")
162
162
 
163
163
  loaded_copy = index.load("index.usearch")
164
- view = Index.restore("index.usearch", view=True)
164
+ view = Index.restore("index.usearch", view=True, ...)
165
165
 
166
166
  other_view = Index(ndim=..., metric=...)
167
167
  other_view.view("index.usearch")
@@ -528,7 +528,11 @@ index = Index(ndim=ndim, metric=CompiledMetric(
528
528
 
529
529
  - [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447), [docs](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch).
530
530
  - [x] DuckDB: [post](https://duckdb.org/2024/05/03/vector-similarity-search-vss.html).
531
+ - [x] ScyllaDB: [Rust](https://github.com/scylladb/vector-store), [presentation](https://www.slideshare.net/slideshow/vector-search-with-scylladb-by-szymon-wasik/276571548).
532
+ - [x] TiDB & TiFlash: [C++](https://github.com/pingcap/tiflash), [announcement](https://www.pingcap.com/article/introduce-vector-search-indexes-in-tidb/).
533
+ - [x] YugaByte: [C++](https://github.com/yugabyte/yugabyte-db/blob/366b9f5e3c4df3a1a17d553db41d6dc50146f488/src/yb/vector_index/usearch_wrapper.cc).
531
534
  - [x] Google: [UniSim](https://github.com/google/unisim), [RetSim](https://arxiv.org/abs/2311.17264) paper.
535
+ - [x] MemGraph: [C++](https://github.com/memgraph/memgraph/blob/784dd8520f65050d033aea8b29446e84e487d091/src/storage/v2/indices/vector_index.cpp), [announcement](https://memgraph.com/blog/simplify-data-retrieval-memgraph-vector-search).
532
536
  - [x] LanternDB: [C++](https://github.com/lanterndata/lantern), [Rust](https://github.com/lanterndata/lantern_extras), [docs](https://lantern.dev/blog/hnsw-index-creation).
533
537
  - [x] LangChain: [Python](https://github.com/langchain-ai/langchain/releases/tag/v0.0.257) and [JavaScript](https://github.com/hwchase17/langchainjs/releases/tag/0.0.125).
534
538
  - [x] Microsoft Semantic Kernel: [Python](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and C#.
@@ -546,7 +550,7 @@ doi = {10.5281/zenodo.7949416},
546
550
  author = {Vardanian, Ash},
547
551
  title = {{USearch by Unum Cloud}},
548
552
  url = {https://github.com/unum-cloud/usearch},
549
- version = {2.17.0},
553
+ version = {2.19.4},
550
554
  year = {2023},
551
555
  month = oct,
552
556
  }
package/c/lib.cpp CHANGED
@@ -493,4 +493,9 @@ USEARCH_EXPORT void usearch_exact_search( //
493
493
  query_distances[i] = static_cast<usearch_distance_t>(query_result[i].distance);
494
494
  }
495
495
  }
496
+
497
+ USEARCH_EXPORT void usearch_clear(usearch_index_t index, usearch_error_t* error) {
498
+ USEARCH_ASSERT(index && error && "Missing arguments");
499
+ reinterpret_cast<index_dense_t*>(index)->clear();
500
+ }
496
501
  }
@@ -0,0 +1,4 @@
1
+ module USearchC {
2
+ header "usearch.h"
3
+ export *
4
+ }
package/c/usearch.h CHANGED
@@ -1,6 +1,10 @@
1
1
  #ifndef UNUM_USEARCH_H
2
2
  #define UNUM_USEARCH_H
3
3
 
4
+ #include <stdbool.h> // `bool`
5
+ #include <stddef.h> // `size_t`
6
+ #include <stdint.h> // `uint64_t`
7
+
4
8
  #ifdef __cplusplus
5
9
  extern "C" {
6
10
  #endif
@@ -13,10 +17,6 @@ extern "C" {
13
17
  #endif
14
18
  #endif
15
19
 
16
- #include <stdbool.h> // `bool`
17
- #include <stddef.h> // `size_t`
18
- #include <stdint.h> // `uint64_t`
19
-
20
20
  USEARCH_EXPORT typedef void* usearch_index_t;
21
21
  USEARCH_EXPORT typedef uint64_t usearch_key_t;
22
22
  USEARCH_EXPORT typedef float usearch_distance_t;
@@ -181,7 +181,6 @@ USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearc
181
181
  * @param[in] path The file path from where the index will be loaded.
182
182
  * @param[out] options Pointer to the `usearch_init_options_t` structure to be populated.
183
183
  * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
184
- * @return A handle to the initialized USearch index, or `NULL` on failure.
185
184
  */
186
185
  USEARCH_EXPORT void usearch_metadata(char const* path, usearch_init_options_t* options, usearch_error_t* error);
187
186
 
@@ -219,7 +218,6 @@ USEARCH_EXPORT void usearch_view_buffer(usearch_index_t index, void const* buffe
219
218
  * @param[in] buffer The in-memory continuous buffer from where the view will be created.
220
219
  * @param[out] options Pointer to the `usearch_init_options_t` structure to be populated.
221
220
  * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
222
- * @return A handle to the initialized USearch index, or `NULL` on failure.
223
221
  */
224
222
  USEARCH_EXPORT void usearch_metadata_buffer(void const* buffer, size_t length, usearch_init_options_t* options,
225
223
  usearch_error_t* error);
@@ -356,7 +354,7 @@ USEARCH_EXPORT bool usearch_contains(usearch_index_t index, usearch_key_t key, u
356
354
  * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
357
355
  * @return Number of vectors found under that key.
358
356
  */
359
- USEARCH_EXPORT size_t usearch_count(usearch_index_t index, usearch_key_t, usearch_error_t* error);
357
+ USEARCH_EXPORT size_t usearch_count(usearch_index_t index, usearch_key_t key, usearch_error_t* error);
360
358
 
361
359
  /**
362
360
  * @brief Performs k-Approximate Nearest Neighbors (kANN) Search for closest vectors to query.
@@ -474,6 +472,14 @@ USEARCH_EXPORT void usearch_exact_search( //
474
472
  usearch_distance_t* distances, size_t distances_stride, //
475
473
  usearch_error_t* error);
476
474
 
475
+ /**
476
+ * @brief Erases all the vectors from the index.
477
+ * @param[inout] index The handle to the USearch index to be modified.
478
+ * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
479
+ */
480
+ USEARCH_EXPORT void usearch_clear(usearch_index_t index,
481
+ usearch_error_t* error);
482
+
477
483
  #ifdef __cplusplus
478
484
  }
479
485
  #endif
package/cpp/README.md CHANGED
@@ -164,7 +164,7 @@ As in many other bulk-processing APIs, the `executor` and `progress` are optiona
164
164
  In its high-level interface, USearch supports a variety of metrics, including the most popular ones:
165
165
 
166
166
  - `metric_cos_gt<scalar_t>` for "Cosine" or "Angular" distance.
167
- - `metric_ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance.
167
+ - `metric_ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance with normalized vectors.
168
168
  - `metric_l2sq_gt<scalar_t>` for the squared "L2" or "Euclidean" distance.
169
169
  - `metric_jaccard_gt<scalar_t>` for "Jaccard" distance between two ordered sets of unique elements.
170
170
  - `metric_hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
package/cpp/bench.cpp CHANGED
@@ -514,7 +514,7 @@ struct args_t {
514
514
  };
515
515
 
516
516
  template <typename index_at, typename dataset_at> //
517
- void run_punned(dataset_at& dataset, args_t const& args, index_config_t config, index_limits_t limits) {
517
+ void run_punned(dataset_at& dataset, args_t const& args, index_dense_config_t config, index_limits_t limits) {
518
518
 
519
519
  scalar_kind_t quantization = args.quantization();
520
520
  std::printf("-- Quantization: %s\n", scalar_kind_name(quantization));
package/cpp/test.cpp CHANGED
@@ -14,6 +14,7 @@
14
14
  */
15
15
  #include <algorithm> // `std::shuffle`
16
16
  #include <cassert> // `assert`
17
+ #include <cmath> // `std::abs`
17
18
  #include <random> // `std::default_random_engine`
18
19
  #include <stdexcept> // `std::terminate`
19
20
  #include <unordered_map> // `std::unordered_map`
@@ -35,7 +36,7 @@ void __expect(bool must_be_true, char const* file, int line, char const* message
35
36
  message = message ? message : "C++ unit test failed";
36
37
  char buffer[512];
37
38
  std::snprintf(buffer, sizeof(buffer), "%s at %s:%d", message, file, line);
38
- __usearch_raise_runtime_error(buffer);
39
+ usearch_raise_runtime_error(buffer);
39
40
  }
40
41
 
41
42
  template <typename value_at>
@@ -673,16 +674,16 @@ void test_cosine(std::size_t collection_size, std::size_t dimensions) {
673
674
  scalar_t const* row(std::size_t i) const noexcept { return (*vector_of_vectors_ptr)[i].data(); }
674
675
 
675
676
  float operator()(member_cref_t const& a, member_cref_t const& b) const {
676
- return metric_cos_gt<scalar_t>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
677
+ return metric_cos_gt<scalar_t, float>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
677
678
  }
678
679
  float operator()(scalar_t const* some_vector, member_cref_t const& member) const {
679
- return metric_cos_gt<scalar_t>{}(some_vector, row(get_slot(member)), dimensions);
680
+ return metric_cos_gt<scalar_t, float>{}(some_vector, row(get_slot(member)), dimensions);
680
681
  }
681
682
  float operator()(member_citerator_t const& a, member_citerator_t const& b) const {
682
- return metric_cos_gt<scalar_t>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
683
+ return metric_cos_gt<scalar_t, float>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
683
684
  }
684
685
  float operator()(scalar_t const* some_vector, member_citerator_t const& member) const {
685
- return metric_cos_gt<scalar_t>{}(some_vector, row(get_slot(member)), dimensions);
686
+ return metric_cos_gt<scalar_t, float>{}(some_vector, row(get_slot(member)), dimensions);
686
687
  }
687
688
  };
688
689
 
@@ -872,21 +873,23 @@ void test_absurd(std::size_t dimensions, std::size_t connectivity, std::size_t e
872
873
  * @param dataset_count Number of vectors in the dataset.
873
874
  * @param queries_count Number of query vectors.
874
875
  * @param wanted_count Number of top matches required from each query.
876
+ * @tparam scalar_at Data type of the elements in the vectors.
875
877
  */
878
+ template <typename scalar_at>
876
879
  void test_exact_search(std::size_t dataset_count, std::size_t queries_count, std::size_t wanted_count) {
877
880
  std::size_t dimensions = 32;
878
- metric_punned_t metric(dimensions, metric_kind_t::cos_k);
881
+ metric_punned_t metric(dimensions, metric_kind_t::cos_k, scalar_kind<scalar_at>());
879
882
 
880
- std::random_device rd;
881
- std::mt19937 gen(rd());
882
- std::uniform_real_distribution<> dis(0.0, 1.0);
883
- std::vector<float> dataset(dataset_count * dimensions);
884
- std::generate(dataset.begin(), dataset.end(), [&] { return dis(gen); });
883
+ std::random_device seed_source;
884
+ std::mt19937 generator(seed_source());
885
+ std::uniform_real_distribution<> distribution(0.0, 1.0); // ! We can't pass `scalar_at` to the distribution
886
+ std::vector<scalar_at> dataset(dataset_count * dimensions);
887
+ std::generate(dataset.begin(), dataset.end(), [&] { return static_cast<scalar_at>(distribution(generator)); });
885
888
 
886
889
  exact_search_t search;
887
- auto results = search( //
888
- (byte_t const*)dataset.data(), dataset_count, dimensions * sizeof(float), //
889
- (byte_t const*)dataset.data(), queries_count, dimensions * sizeof(float), //
890
+ auto results = search( //
891
+ (byte_t const*)dataset.data(), dataset_count, dimensions * sizeof(scalar_at), //
892
+ (byte_t const*)dataset.data(), queries_count, dimensions * sizeof(scalar_at), //
890
893
  wanted_count, metric);
891
894
 
892
895
  for (std::size_t i = 0; i < results.size(); ++i)
@@ -1096,10 +1099,94 @@ template <typename key_at, typename slot_at> void test_replacing_update() {
1096
1099
  expect_eq(final_search[2].member.key, 44);
1097
1100
  }
1098
1101
 
1102
+ /**
1103
+ * Tests the filtered search functionality of the index.
1104
+ */
1105
+ void test_filtered_search() {
1106
+ constexpr std::size_t dataset_count = 2048;
1107
+ constexpr std::size_t dimensions = 32;
1108
+ metric_punned_t metric(dimensions, metric_kind_t::cos_k);
1109
+
1110
+ std::random_device seed_source;
1111
+ std::mt19937 generator(seed_source());
1112
+ std::uniform_real_distribution<float> distribution(0.0, 1.0);
1113
+ using vector_of_vectors_t = std::vector<std::vector<float>>;
1114
+
1115
+ vector_of_vectors_t vector_of_vectors(dataset_count);
1116
+ for (auto& vector : vector_of_vectors) {
1117
+ vector.resize(dimensions);
1118
+ std::generate(vector.begin(), vector.end(), [&] { return distribution(generator); });
1119
+ }
1120
+
1121
+ index_dense_t index = index_dense_t::make(metric);
1122
+ index.reserve(dataset_count);
1123
+ for (std::size_t idx = 0; idx < dataset_count; ++idx)
1124
+ index.add(idx, vector_of_vectors[idx].data());
1125
+ expect_eq(index.size(), dataset_count);
1126
+
1127
+ {
1128
+ auto predicate = [](index_dense_t::key_t key) { return key != 0; };
1129
+ auto results = index.filtered_search(vector_of_vectors[0].data(), 10, predicate);
1130
+ expect_eq(10, results.size()); // ! Should not contain 0
1131
+ for (std::size_t i = 0; i != results.size(); ++i)
1132
+ expect(0 != results[i].member.key);
1133
+ }
1134
+ {
1135
+ auto predicate = [](index_dense_t::key_t) { return false; };
1136
+ auto results = index.filtered_search(vector_of_vectors[0].data(), 10, predicate);
1137
+ expect_eq(0, results.size()); // ! Should not contain 0
1138
+ }
1139
+ {
1140
+ auto predicate = [](index_dense_t::key_t key) { return key == 10; };
1141
+ auto results = index.filtered_search(vector_of_vectors[0].data(), 10, predicate);
1142
+ expect_eq(1, results.size()); // ! Should not contain 0
1143
+ expect_eq(10, results[0].member.key);
1144
+ }
1145
+ }
1146
+
1147
+ void test_isolate() {
1148
+ constexpr std::size_t dataset_count = 16;
1149
+ constexpr std::size_t dimensions = 32;
1150
+ metric_punned_t metric(dimensions, metric_kind_t::cos_k);
1151
+
1152
+ std::random_device seed_source;
1153
+ std::mt19937 generator(seed_source());
1154
+ std::uniform_real_distribution<float> distribution(0.0, 1.0);
1155
+ using vector_of_vectors_t = std::vector<std::vector<float>>;
1156
+
1157
+ vector_of_vectors_t vector_of_vectors(dataset_count);
1158
+ for (auto& vector : vector_of_vectors) {
1159
+ vector.resize(dimensions);
1160
+ std::generate(vector.begin(), vector.end(), [&] { return distribution(generator); });
1161
+ }
1162
+
1163
+ index_dense_t index = index_dense_t::make(metric);
1164
+ index.reserve(dataset_count);
1165
+ for (std::size_t idx = 0; idx < dataset_count; ++idx) {
1166
+ index.add(idx, vector_of_vectors[idx].data());
1167
+ }
1168
+ expect_eq(index.size(), dataset_count);
1169
+
1170
+ for (std::size_t idx = 0; idx < dataset_count; ++idx) {
1171
+ if (idx % 2 == 0)
1172
+ index.remove(idx);
1173
+ }
1174
+
1175
+ auto result = index.isolate();
1176
+ for (std::size_t idx = 0; idx < dataset_count; ++idx) {
1177
+ auto result = index.search(vector_of_vectors[idx].data(), 16);
1178
+ expect_eq(result.size(), dataset_count / 2);
1179
+ }
1180
+ }
1181
+
1099
1182
  int main(int, char**) {
1100
1183
  test_uint40();
1101
1184
  test_cosine<float, std::int64_t, uint40_t>(10, 10);
1102
1185
 
1186
+ // Non-default floating-point types may result in many compilation & rounding issues.
1187
+ test_cosine<f16_t, std::int64_t, uint40_t>(10, 10);
1188
+ test_cosine<bf16_t, std::int64_t, uint40_t>(10, 10);
1189
+
1103
1190
  // Test plugins, like K-Means clustering.
1104
1191
  {
1105
1192
  std::size_t vectors_count = 1000, centroids_count = 10, dimensions = 256;
@@ -1121,8 +1208,11 @@ int main(int, char**) {
1121
1208
  std::printf("Testing exact search\n");
1122
1209
  for (std::size_t dataset_count : {10, 100})
1123
1210
  for (std::size_t queries_count : {1, 10})
1124
- for (std::size_t wanted_count : {1, 5})
1125
- test_exact_search(dataset_count, queries_count, wanted_count);
1211
+ for (std::size_t wanted_count : {1, 5}) {
1212
+ test_exact_search<float>(dataset_count, queries_count, wanted_count);
1213
+ test_exact_search<f16_t>(dataset_count, queries_count, wanted_count);
1214
+ test_exact_search<bf16_t>(dataset_count, queries_count, wanted_count);
1215
+ }
1126
1216
 
1127
1217
  // Make sure the initializers and the algorithms can work with inadequately small values.
1128
1218
  // Be warned - this combinatorial explosion of tests produces close to __500'000__ tests!
@@ -1149,6 +1239,8 @@ int main(int, char**) {
1149
1239
  test_cosine<float, std::int64_t, slot32_t>(collection_size, dimensions);
1150
1240
  std::printf("- Indexing %zu vectors with cos: <float, std::int64_t, uint40_t> \n", collection_size);
1151
1241
  test_cosine<float, std::int64_t, uint40_t>(collection_size, dimensions);
1242
+ std::printf("- Indexing %zu vectors with cos: <bf16, std::int64_t, uint40_t> \n", collection_size);
1243
+ test_cosine<bf16_t, std::int64_t, uint40_t>(collection_size, dimensions);
1152
1244
  }
1153
1245
 
1154
1246
  // Test with binary vectors
@@ -1163,5 +1255,7 @@ int main(int, char**) {
1163
1255
  test_sets<std::int64_t, slot32_t>(set_size, 20, 30);
1164
1256
  test_strings<std::int64_t, slot32_t>();
1165
1257
 
1258
+ test_filtered_search();
1259
+ test_isolate();
1166
1260
  return 0;
1167
1261
  }
@@ -8,8 +8,8 @@
8
8
  #define UNUM_USEARCH_HPP
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
- #define USEARCH_VERSION_MINOR 17
12
- #define USEARCH_VERSION_PATCH 0
11
+ #define USEARCH_VERSION_MINOR 19
12
+ #define USEARCH_VERSION_PATCH 4
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -149,15 +149,15 @@
149
149
  #else
150
150
  #define usearch_assert_m(must_be_true, message) \
151
151
  if (!(must_be_true)) { \
152
- __usearch_raise_runtime_error(message); \
152
+ usearch_raise_runtime_error(message); \
153
153
  }
154
154
  #define usearch_noexcept_m
155
155
  #endif
156
156
 
157
157
  extern "C" {
158
- /// @brief Helper function to simplify debugging - trace just one symbol - `__usearch_raise_runtime_error`.
158
+ /// @brief Helper function to simplify debugging - trace just one symbol - `usearch_raise_runtime_error`.
159
159
  /// Assuming the `extern C` block, the name won't be mangled.
160
- inline static void __usearch_raise_runtime_error(char const* message) {
160
+ inline static void usearch_raise_runtime_error(char const* message) {
161
161
  // On Windows we compile with `/EHc` flag, which specifies that functions
162
162
  // with C linkage do not throw C++ exceptions.
163
163
  #if !defined(__cpp_exceptions) || defined(USEARCH_DEFINED_WINDOWS)
@@ -202,7 +202,7 @@ template <typename at> void misaligned_store(void* ptr, at v) noexcept {
202
202
  }
203
203
 
204
204
  /// @brief Simply dereferencing misaligned pointers can be dangerous.
205
- template <typename at> at misaligned_load(void* ptr) noexcept {
205
+ template <typename at> at misaligned_load(void const* ptr) noexcept {
206
206
  static_assert(!std::is_reference<at>::value, "Can't load a reference");
207
207
  at v;
208
208
  std::memcpy(&v, ptr, sizeof(at));
@@ -1751,6 +1751,9 @@ class memory_mapped_file_t {
1751
1751
  {
1752
1752
  }
1753
1753
 
1754
+ memory_mapped_file_t(memory_mapped_file_t const&) = delete;
1755
+ memory_mapped_file_t& operator=(memory_mapped_file_t const&) = delete;
1756
+
1754
1757
  memory_mapped_file_t(byte_t* data, std::size_t length) noexcept : ptr_(data), length_(length) {}
1755
1758
 
1756
1759
  memory_mapped_file_t& operator=(memory_mapped_file_t&& other) noexcept {
@@ -1900,7 +1903,7 @@ template <typename key_at> inline key_at get_key(member_ref_gt<key_at> const& m)
1900
1903
  * If classical containers store @b Key->Value mappings, this one can
1901
1904
  * be seen as a network of keys, accelerating approximate @b Value~>Key visited_members.
1902
1905
  *
1903
- * Unlike most implementations, this one is generic anc can be used for any search,
1906
+ * Unlike most implementations, this one is generic and can be used for any search,
1904
1907
  * not just within equi-dimensional vectors. Examples range from Texts to similar Chess
1905
1908
  * positions, Geo-Spatial Search, and even Graphs.
1906
1909
  *
@@ -2174,6 +2177,20 @@ class index_gt {
2174
2177
  misaligned_store<compressed_slot_t>(tape_ + shift(n), slot);
2175
2178
  misaligned_store<neighbors_count_t>(tape_, n + 1);
2176
2179
  }
2180
+ template <typename allow_slot_at> std::size_t erase_if(allow_slot_at&& allow_slot) noexcept {
2181
+ std::size_t old_count = misaligned_load<neighbors_count_t>(tape_);
2182
+ std::size_t removed_count = 0;
2183
+ for (std::size_t i = 0; i < old_count; ++i) {
2184
+ compressed_slot_t slot = misaligned_load<compressed_slot_t>(tape_ + shift(i));
2185
+ if (allow_slot(slot)) {
2186
+ removed_count++;
2187
+ } else {
2188
+ misaligned_store<compressed_slot_t>(tape_ + shift(i - removed_count), slot);
2189
+ }
2190
+ }
2191
+ misaligned_store<neighbors_count_t>(tape_, old_count - removed_count);
2192
+ return removed_count;
2193
+ }
2177
2194
  };
2178
2195
 
2179
2196
  /**
@@ -2183,6 +2200,7 @@ class index_gt {
2183
2200
  */
2184
2201
  struct usearch_align_m context_t {
2185
2202
  top_candidates_t top_candidates{};
2203
+ top_candidates_t top_for_refine{};
2186
2204
  next_candidates_t next_candidates{};
2187
2205
  visits_hash_set_t visits{};
2188
2206
  std::default_random_engine level_generator{};
@@ -2326,7 +2344,7 @@ class index_gt {
2326
2344
  state_result_t failed(error_t message) noexcept { return {std::move(index), std::move(message)}; }
2327
2345
  operator index_gt&&() && {
2328
2346
  if (error)
2329
- __usearch_raise_runtime_error(error.what());
2347
+ usearch_raise_runtime_error(error.what());
2330
2348
  return std::move(index);
2331
2349
  }
2332
2350
  };
@@ -2498,6 +2516,13 @@ class index_gt {
2498
2516
  if (nodes_)
2499
2517
  std::memcpy(new_nodes.data(), nodes_.data(), sizeof(node_t) * size());
2500
2518
 
2519
+ // Pre-reserve the capacity for `top_for_refine`, which always contains at most one more
2520
+ // element than the connectivity factors.
2521
+ std::size_t connectivity_max = (std::max)(config_.connectivity_base, config_.connectivity);
2522
+ for (std::size_t i = 0; i != new_contexts.size(); ++i)
2523
+ if (!new_contexts[i].top_for_refine.reserve(connectivity_max + 1))
2524
+ return false;
2525
+
2501
2526
  limits_ = limits;
2502
2527
  nodes_capacity_ = limits.members;
2503
2528
  nodes_ = std::move(new_nodes);
@@ -3179,17 +3204,11 @@ class index_gt {
3179
3204
 
3180
3205
  std::size_t memory_usage_per_node(level_t level) const noexcept { return node_bytes_(level); }
3181
3206
 
3182
- double inverse_log_connectivity() const {
3183
- return pre_.inverse_log_connectivity;
3184
- }
3207
+ double inverse_log_connectivity() const { return pre_.inverse_log_connectivity; }
3185
3208
 
3186
- std::size_t neighbors_base_bytes() const {
3187
- return pre_.neighbors_base_bytes;
3188
- }
3209
+ std::size_t neighbors_base_bytes() const { return pre_.neighbors_base_bytes; }
3189
3210
 
3190
- std::size_t neighbors_bytes() const {
3191
- return pre_.neighbors_bytes;
3192
- }
3211
+ std::size_t neighbors_bytes() const { return pre_.neighbors_bytes; }
3193
3212
 
3194
3213
  #if defined(USEARCH_USE_PRAGMA_REGION)
3195
3214
  #pragma endregion
@@ -3649,14 +3668,10 @@ class index_gt {
3649
3668
  node_t node = node_at_(node_idx);
3650
3669
  for (level_t level = 0; level <= node.level(); ++level) {
3651
3670
  neighbors_ref_t neighbors = neighbors_(node, level);
3652
- std::size_t old_size = neighbors.size();
3653
- neighbors.clear();
3654
- for (std::size_t i = 0; i != old_size; ++i) {
3655
- compressed_slot_t neighbor_slot = neighbors[i];
3671
+ neighbors.erase_if([&](compressed_slot_t neighbor_slot) {
3656
3672
  node_t neighbor = node_at_(neighbor_slot);
3657
- if (allow_member(member_cref_t{neighbor.ckey(), neighbor_slot}))
3658
- neighbors.push_back(neighbor_slot);
3659
- }
3673
+ return !allow_member(member_cref_t{neighbor.ckey(), neighbor_slot});
3674
+ });
3660
3675
  }
3661
3676
  ++processed;
3662
3677
  if (thread_idx == 0)
@@ -3790,7 +3805,7 @@ class index_gt {
3790
3805
  metric_at&& metric, compressed_slot_t new_slot, candidates_view_t new_neighbors, value_at&& value,
3791
3806
  level_t level, context_t& context) usearch_noexcept_m {
3792
3807
 
3793
- top_candidates_t& top = context.top_candidates;
3808
+ top_candidates_t& top_for_refine = context.top_for_refine;
3794
3809
  std::size_t const connectivity_max = level ? config_.connectivity : config_.connectivity_base;
3795
3810
 
3796
3811
  // Reverse links from the neighbors:
@@ -3817,19 +3832,16 @@ class index_gt {
3817
3832
  continue;
3818
3833
  }
3819
3834
 
3820
- // To fit a new connection we need to drop an existing one.
3821
- top.clear();
3822
- usearch_assert_m((top.capacity() >= (close_header.size() + 1)),
3823
- "The memory must have been reserved in `add`");
3824
- top.insert_reserved({context.measure(value, citerator_at(close_slot), metric), new_slot});
3835
+ top_for_refine.clear();
3836
+ top_for_refine.insert_reserved({context.measure(value, citerator_at(close_slot), metric), new_slot});
3825
3837
  for (compressed_slot_t successor_slot : close_header)
3826
- top.insert_reserved(
3838
+ top_for_refine.insert_reserved(
3827
3839
  {context.measure(citerator_at(close_slot), citerator_at(successor_slot), metric), successor_slot});
3828
3840
 
3829
3841
  // Export the results:
3830
3842
  close_header.clear();
3831
- candidates_view_t top_view =
3832
- refine_(metric, connectivity_max, top, context, context.computed_distances_in_reverse_refines);
3843
+ candidates_view_t top_view = refine_(metric, connectivity_max, top_for_refine, context,
3844
+ context.computed_distances_in_reverse_refines);
3833
3845
  usearch_assert_m(top_view.size(), "This would lead to isolated nodes");
3834
3846
  for (std::size_t idx = 0; idx != top_view.size(); idx++)
3835
3847
  close_header.push_back(top_view[idx].slot);
@@ -4178,9 +4190,10 @@ class index_gt {
4178
4190
  // This can substantially grow our priority queue:
4179
4191
  next.insert({-successor_dist, successor_slot});
4180
4192
  if (is_dummy<predicate_at>() ||
4181
- predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot}))
4193
+ predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) {
4182
4194
  top.insert({successor_dist, successor_slot}, top_limit);
4183
- radius = top.top().distance;
4195
+ radius = top.top().distance;
4196
+ }
4184
4197
  }
4185
4198
  }
4186
4199
  }