usearch 2.7.7 → 2.8.1

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 (54) hide show
  1. package/.dockerignore +93 -0
  2. package/.github/workflows/Dockerfile.libs +6 -2
  3. package/.github/workflows/prerelease.yml +242 -245
  4. package/.github/workflows/release.yml +399 -357
  5. package/.gitignore +1 -0
  6. package/.vscode/settings.json +3 -1
  7. package/CITATION.cff +1 -1
  8. package/CMakeLists.txt +35 -5
  9. package/Cargo.toml +1 -1
  10. package/README.md +1 -1
  11. package/build.gradle +6 -0
  12. package/c/README.md +3 -3
  13. package/c/lib.cpp +75 -11
  14. package/c/usearch.h +22 -1
  15. package/conanfile.py +1 -1
  16. package/cpp/README.md +2 -1
  17. package/cpp/bench.cpp +3 -0
  18. package/cpp/test.cpp +12 -12
  19. package/csharp/nuget/nuget-package.props +1 -1
  20. package/docs/compilation.md +5 -13
  21. package/golang/lib.go +4 -1
  22. package/include/usearch/index.hpp +31 -30
  23. package/include/usearch/index_dense.hpp +49 -48
  24. package/include/usearch/index_plugins.hpp +70 -8
  25. package/java/cloud/unum/usearch/Index.java +118 -26
  26. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +8 -8
  27. package/javascript/usearch.js +1 -0
  28. package/objc/USearchObjective.mm +4 -1
  29. package/objc/include/USearchObjective.h +1 -0
  30. package/package.json +1 -1
  31. package/pyproject.toml +10 -8
  32. package/python/lib.cpp +3 -2
  33. package/python/scripts/bench.ipynb +24 -2
  34. package/python/usearch/index.py +8 -3
  35. package/rust/lib.cpp +14 -12
  36. package/rust/lib.hpp +13 -13
  37. package/rust/lib.rs +47 -0
  38. package/setup.py +15 -3
  39. package/simsimd/.github/workflows/prerelease.yml +7 -8
  40. package/simsimd/.github/workflows/release.yml +18 -20
  41. package/simsimd/README.md +13 -8
  42. package/simsimd/binding.gyp +1 -1
  43. package/simsimd/cpp/bench.cxx +17 -3
  44. package/simsimd/include/simsimd/binary.h +1 -3
  45. package/simsimd/include/simsimd/probability.h +234 -361
  46. package/simsimd/include/simsimd/simsimd.h +95 -65
  47. package/simsimd/include/simsimd/spatial.h +0 -3
  48. package/simsimd/include/simsimd/types.h +12 -7
  49. package/simsimd/javascript/test.js +23 -5
  50. package/simsimd/package.json +2 -2
  51. package/wasmer.toml +1 -1
  52. package/wolfram/lib.cpp +1 -1
  53. package/csharp/eng/build_lib.sh +0 -65
  54. package/csharp/eng/clean.sh +0 -12
@@ -10,8 +10,8 @@
10
10
  #define UNUM_USEARCH_HPP
11
11
 
12
12
  #define USEARCH_VERSION_MAJOR 2
13
- #define USEARCH_VERSION_MINOR 7
14
- #define USEARCH_VERSION_PATCH 7
13
+ #define USEARCH_VERSION_MINOR 8
14
+ #define USEARCH_VERSION_PATCH 1
15
15
 
16
16
  // Inferring C++ version
17
17
  // https://stackoverflow.com/a/61552074
@@ -1686,14 +1686,15 @@ template <typename distance_at = default_distance_t, //
1686
1686
  class index_gt {
1687
1687
  public:
1688
1688
  using distance_t = distance_at;
1689
- using key_t = key_at;
1689
+ using vector_key_t = key_at;
1690
+ using key_t = vector_key_t;
1690
1691
  using compressed_slot_t = compressed_slot_at;
1691
1692
  using dynamic_allocator_t = dynamic_allocator_at;
1692
1693
  using tape_allocator_t = tape_allocator_at;
1693
- static_assert(sizeof(key_t) >= sizeof(compressed_slot_t), "Having tiny keys doesn't make sense.");
1694
+ static_assert(sizeof(vector_key_t) >= sizeof(compressed_slot_t), "Having tiny keys doesn't make sense.");
1694
1695
 
1695
- using member_cref_t = member_cref_gt<key_t>;
1696
- using member_ref_t = member_ref_gt<key_t>;
1696
+ using member_cref_t = member_cref_gt<vector_key_t>;
1697
+ using member_ref_t = member_ref_gt<vector_key_t>;
1697
1698
 
1698
1699
  template <typename ref_at, typename index_at> class member_iterator_gt {
1699
1700
  using ref_t = ref_at;
@@ -1714,10 +1715,10 @@ class index_gt {
1714
1715
  using reference = ref_t;
1715
1716
 
1716
1717
  reference operator*() const noexcept { return {index_->node_at_(slot_).key(), slot_}; }
1717
- key_t key() const noexcept { return index_->node_at_(slot_).key(); }
1718
+ vector_key_t key() const noexcept { return index_->node_at_(slot_).key(); }
1718
1719
 
1719
1720
  friend inline std::size_t get_slot(member_iterator_gt const& it) noexcept { return it.slot_; }
1720
- friend inline key_t get_key(member_iterator_gt const& it) noexcept { return it.key(); }
1721
+ friend inline vector_key_t get_key(member_iterator_gt const& it) noexcept { return it.key(); }
1721
1722
 
1722
1723
  member_iterator_gt operator++(int) noexcept { return member_iterator_gt(index_, slot_ + 1); }
1723
1724
  member_iterator_gt operator--(int) noexcept { return member_iterator_gt(index_, slot_ - 1); }
@@ -1738,7 +1739,7 @@ class index_gt {
1738
1739
  using member_citerator_t = member_iterator_gt<member_cref_t, index_gt const>;
1739
1740
 
1740
1741
  // STL compatibility:
1741
- using value_type = key_t;
1742
+ using value_type = vector_key_t;
1742
1743
  using allocator_type = dynamic_allocator_t;
1743
1744
  using size_type = std::size_t;
1744
1745
  using difference_type = std::ptrdiff_t;
@@ -1774,7 +1775,7 @@ class index_gt {
1774
1775
  /**
1775
1776
  * @brief How many bytes of memory are needed to form the "head" of the node.
1776
1777
  */
1777
- static constexpr std::size_t node_head_bytes_() { return sizeof(key_t) + sizeof(level_t); }
1778
+ static constexpr std::size_t node_head_bytes_() { return sizeof(vector_key_t) + sizeof(level_t); }
1778
1779
 
1779
1780
  using nodes_mutexes_t = bitset_gt<dynamic_allocator_t>;
1780
1781
 
@@ -1800,7 +1801,7 @@ class index_gt {
1800
1801
  /**
1801
1802
  * @brief A loosely-structured handle for every node. One such node is created for every member.
1802
1803
  * To minimize memory usage and maximize the number of entries per cache-line, it only
1803
- * stores to pointers. The internal tape starts with a `key_t` @b key, then
1804
+ * stores to pointers. The internal tape starts with a `vector_key_t` @b key, then
1804
1805
  * a `level_t` for the number of graph @b levels in which this member appears,
1805
1806
  * then the { `neighbors_count_t`, `compressed_slot_t`, `compressed_slot_t` ... } sequences
1806
1807
  * for @b each-level.
@@ -1818,12 +1819,12 @@ class index_gt {
1818
1819
  node_t(node_t const&) = default;
1819
1820
  node_t& operator=(node_t const&) = default;
1820
1821
 
1821
- misaligned_ref_gt<key_t const> ckey() const noexcept { return {tape_}; }
1822
- misaligned_ref_gt<key_t> key() const noexcept { return {tape_}; }
1823
- misaligned_ref_gt<level_t> level() const noexcept { return {tape_ + sizeof(key_t)}; }
1822
+ misaligned_ref_gt<vector_key_t const> ckey() const noexcept { return {tape_}; }
1823
+ misaligned_ref_gt<vector_key_t> key() const noexcept { return {tape_}; }
1824
+ misaligned_ref_gt<level_t> level() const noexcept { return {tape_ + sizeof(vector_key_t)}; }
1824
1825
 
1825
- void key(key_t v) noexcept { return misaligned_store<key_t>(tape_, v); }
1826
- void level(level_t v) noexcept { return misaligned_store<level_t>(tape_ + sizeof(key_t), v); }
1826
+ void key(vector_key_t v) noexcept { return misaligned_store<vector_key_t>(tape_, v); }
1827
+ void level(level_t v) noexcept { return misaligned_store<level_t>(tape_ + sizeof(vector_key_t), v); }
1827
1828
  };
1828
1829
 
1829
1830
  static_assert(std::is_trivially_copy_constructible<node_t>::value, "Nodes must be light!");
@@ -2192,7 +2193,7 @@ class index_gt {
2192
2193
  inline match_t operator[](std::size_t i) const noexcept { return at(i); }
2193
2194
  inline match_t front() const noexcept { return at(0); }
2194
2195
  inline match_t back() const noexcept { return at(count - 1); }
2195
- inline bool contains(key_t key) const noexcept {
2196
+ inline bool contains(vector_key_t key) const noexcept {
2196
2197
  for (std::size_t i = 0; i != count; ++i)
2197
2198
  if (at(i).member.key == key)
2198
2199
  return true;
@@ -2205,7 +2206,7 @@ class index_gt {
2205
2206
  return {member_cref_t{node.ckey(), candidate.slot}, candidate.distance};
2206
2207
  }
2207
2208
  inline std::size_t merge_into( //
2208
- key_t* keys, distance_t* distances, //
2209
+ vector_key_t* keys, distance_t* distances, //
2209
2210
  std::size_t old_count, std::size_t max_count) const noexcept {
2210
2211
 
2211
2212
  std::size_t merged_count = old_count;
@@ -2217,7 +2218,7 @@ class index_gt {
2217
2218
  continue;
2218
2219
 
2219
2220
  std::size_t count_worse = merged_count - offset - (max_count == merged_count);
2220
- std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(key_t));
2221
+ std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(vector_key_t));
2221
2222
  std::memmove(distances + offset + 1, distances + offset, count_worse * sizeof(distance_t));
2222
2223
  keys[offset] = result.member.key;
2223
2224
  distances[offset] = result.distance;
@@ -2225,7 +2226,7 @@ class index_gt {
2225
2226
  }
2226
2227
  return merged_count;
2227
2228
  }
2228
- inline std::size_t dump_to(key_t* keys, distance_t* distances) const noexcept {
2229
+ inline std::size_t dump_to(vector_key_t* keys, distance_t* distances) const noexcept {
2229
2230
  for (std::size_t i = 0; i != count; ++i) {
2230
2231
  match_t result = operator[](i);
2231
2232
  keys[i] = result.member.key;
@@ -2233,7 +2234,7 @@ class index_gt {
2233
2234
  }
2234
2235
  return count;
2235
2236
  }
2236
- inline std::size_t dump_to(key_t* keys) const noexcept {
2237
+ inline std::size_t dump_to(vector_key_t* keys) const noexcept {
2237
2238
  for (std::size_t i = 0; i != count; ++i) {
2238
2239
  match_t result = operator[](i);
2239
2240
  keys[i] = result.member.key;
@@ -2264,7 +2265,7 @@ class index_gt {
2264
2265
  * It should be callable into distinctly different scenarios:
2265
2266
  * - `distance_t operator() (value_at, entry_at)` - from new object to existing entries.
2266
2267
  * - `distance_t operator() (entry_at, entry_at)` - between existing entries.
2267
- * Where any possible `entry_at` has both two interfaces: `std::size_t slot()`, `key_t key()`.
2268
+ * Where any possible `entry_at` has both two interfaces: `std::size_t slot()`, `vector_key_t key()`.
2268
2269
  *
2269
2270
  * @param[in] key External identifier/name/descriptor for the new entry.
2270
2271
  * @param[in] value Content that will be compared against other entries to index.
@@ -2279,7 +2280,7 @@ class index_gt {
2279
2280
  typename prefetch_at = dummy_prefetch_t //
2280
2281
  >
2281
2282
  add_result_t add( //
2282
- key_t key, value_at&& value, metric_at&& metric, //
2283
+ vector_key_t key, value_at&& value, metric_at&& metric, //
2283
2284
  index_update_config_t config = {}, //
2284
2285
  callback_at&& callback = callback_at{}, //
2285
2286
  prefetch_at&& prefetch = prefetch_at{}) usearch_noexcept_m {
@@ -2371,7 +2372,7 @@ class index_gt {
2371
2372
  * - `distance_t operator() (entry_at, entry_at)` - between existing entries.
2372
2373
  * For any possible `entry_at` following interfaces will work:
2373
2374
  * - `std::size_t get_slot(entry_at const &)`
2374
- * - `key_t get_key(entry_at const &)`
2375
+ * - `vector_key_t get_key(entry_at const &)`
2375
2376
  *
2376
2377
  * @param[in] iterator Iterator pointing to an existing entry to be replaced.
2377
2378
  * @param[in] key External identifier/name/descriptor for the entry.
@@ -2388,7 +2389,7 @@ class index_gt {
2388
2389
  >
2389
2390
  add_result_t update( //
2390
2391
  member_iterator_t iterator, //
2391
- key_t key, //
2392
+ vector_key_t key, //
2392
2393
  value_at&& value, //
2393
2394
  metric_at&& metric, //
2394
2395
  index_update_config_t config = {}, //
@@ -3142,7 +3143,7 @@ class index_gt {
3142
3143
  return data ? span_bytes_t{data, node_bytes} : span_bytes_t{};
3143
3144
  }
3144
3145
 
3145
- node_t node_make_(key_t key, level_t level) noexcept {
3146
+ node_t node_make_(vector_key_t key, level_t level) noexcept {
3146
3147
  span_bytes_t node_bytes = node_malloc_(level);
3147
3148
  if (!node_bytes)
3148
3149
  return {};
@@ -3334,10 +3335,10 @@ class index_gt {
3334
3335
  bool operator==(candidates_iterator_t const& other) noexcept { return current_ == other.current_; }
3335
3336
  bool operator!=(candidates_iterator_t const& other) noexcept { return current_ != other.current_; }
3336
3337
 
3337
- key_t key() const noexcept { return index_->node_at_(slot()).key(); }
3338
+ vector_key_t key() const noexcept { return index_->node_at_(slot()).key(); }
3338
3339
  compressed_slot_t slot() const noexcept { return neighbors_[current_]; }
3339
3340
  friend inline std::size_t get_slot(candidates_iterator_t const& it) noexcept { return it.slot(); }
3340
- friend inline key_t get_key(candidates_iterator_t const& it) noexcept { return it.key(); }
3341
+ friend inline vector_key_t get_key(candidates_iterator_t const& it) noexcept { return it.key(); }
3341
3342
  };
3342
3343
 
3343
3344
  struct candidates_range_t {
@@ -3683,8 +3684,8 @@ static join_result_t join( //
3683
3684
 
3684
3685
  using distance_t = typename men_at::distance_t;
3685
3686
  using dynamic_allocator_traits_t = typename men_at::dynamic_allocator_traits_t;
3686
- using man_key_t = typename men_at::key_t;
3687
- using woman_key_t = typename women_at::key_t;
3687
+ using man_key_t = typename men_at::vector_key_t;
3688
+ using woman_key_t = typename women_at::vector_key_t;
3688
3689
 
3689
3690
  // Use the `compressed_slot_t` type of the larger collection
3690
3691
  using compressed_slot_t = typename women_at::compressed_slot_t;
@@ -301,13 +301,14 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
301
301
  template <typename key_at = default_key_t, typename compressed_slot_at = default_slot_t> //
302
302
  class index_dense_gt {
303
303
  public:
304
- using key_t = key_at;
304
+ using vector_key_t = key_at;
305
+ using key_t = vector_key_t;
305
306
  using compressed_slot_t = compressed_slot_at;
306
307
  using distance_t = distance_punned_t;
307
308
  using metric_t = metric_punned_t;
308
309
 
309
- using member_ref_t = member_ref_gt<key_t>;
310
- using member_cref_t = member_cref_gt<key_t>;
310
+ using member_ref_t = member_ref_gt<vector_key_t>;
311
+ using member_cref_t = member_cref_gt<vector_key_t>;
311
312
 
312
313
  using head_t = index_dense_head_t;
313
314
  using head_buffer_t = index_dense_head_buffer_t;
@@ -323,7 +324,7 @@ class index_dense_gt {
323
324
  using cast_t = std::function<bool(byte_t const*, std::size_t, byte_t*)>;
324
325
  /// @brief Punned index.
325
326
  using index_t = index_gt< //
326
- distance_t, key_t, compressed_slot_t, //
327
+ distance_t, vector_key_t, compressed_slot_t, //
327
328
  dynamic_allocator_t, tape_allocator_t>;
328
329
  using index_allocator_t = aligned_allocator_gt<index_t, 64>;
329
330
 
@@ -395,23 +396,23 @@ class index_dense_gt {
395
396
  using unique_lock_t = std::unique_lock<shared_mutex_t>;
396
397
 
397
398
  struct key_and_slot_t {
398
- key_t key;
399
+ vector_key_t key;
399
400
  compressed_slot_t slot;
400
401
 
401
402
  bool any_slot() const { return slot == default_free_value<compressed_slot_t>(); }
402
- static key_and_slot_t any_slot(key_t key) { return {key, default_free_value<compressed_slot_t>()}; }
403
+ static key_and_slot_t any_slot(vector_key_t key) { return {key, default_free_value<compressed_slot_t>()}; }
403
404
  };
404
405
 
405
406
  struct lookup_key_hash_t {
406
407
  using is_transparent = void;
407
- std::size_t operator()(key_and_slot_t const& k) const noexcept { return std::hash<key_t>{}(k.key); }
408
- std::size_t operator()(key_t const& k) const noexcept { return std::hash<key_t>{}(k); }
408
+ std::size_t operator()(key_and_slot_t const& k) const noexcept { return std::hash<vector_key_t>{}(k.key); }
409
+ std::size_t operator()(vector_key_t const& k) const noexcept { return std::hash<vector_key_t>{}(k); }
409
410
  };
410
411
 
411
412
  struct lookup_key_same_t {
412
413
  using is_transparent = void;
413
- bool operator()(key_and_slot_t const& a, key_t const& b) const noexcept { return a.key == b; }
414
- bool operator()(key_t const& a, key_and_slot_t const& b) const noexcept { return a == b.key; }
414
+ bool operator()(key_and_slot_t const& a, vector_key_t const& b) const noexcept { return a.key == b; }
415
+ bool operator()(vector_key_t const& a, key_and_slot_t const& b) const noexcept { return a == b.key; }
415
416
  bool operator()(key_and_slot_t const& a, key_and_slot_t const& b) const noexcept { return a.key == b.key; }
416
417
  };
417
418
 
@@ -428,7 +429,7 @@ class index_dense_gt {
428
429
  mutable std::mutex free_keys_mutex_;
429
430
 
430
431
  /// @brief A constant for the reserved key value, used to mark deleted entries.
431
- key_t free_key_ = default_free_value<key_t>();
432
+ vector_key_t free_key_ = default_free_value<vector_key_t>();
432
433
 
433
434
  public:
434
435
  using search_result_t = typename index_t::search_result_t;
@@ -497,7 +498,7 @@ class index_dense_gt {
497
498
  static index_dense_gt make( //
498
499
  metric_t metric, //
499
500
  index_dense_config_t config = {}, //
500
- key_t free_key = default_free_value<key_t>()) {
501
+ vector_key_t free_key = default_free_value<vector_key_t>()) {
501
502
 
502
503
  scalar_kind_t scalar_kind = metric.scalar_kind();
503
504
  std::size_t hardware_threads = std::thread::hardware_concurrency();
@@ -573,7 +574,7 @@ class index_dense_gt {
573
574
  }
574
575
 
575
576
  dynamic_allocator_t const& allocator() const { return typed_->dynamic_allocator(); }
576
- key_t const& free_key() const { return free_key_; }
577
+ vector_key_t const& free_key() const { return free_key_; }
577
578
 
578
579
  /**
579
580
  * @brief A relatively accurate lower bound on the amount of memory consumed by the system.
@@ -600,11 +601,11 @@ class index_dense_gt {
600
601
  };
601
602
 
602
603
  // clang-format off
603
- add_result_t add(key_t key, b1x8_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_b1x8); }
604
- add_result_t add(key_t key, i8_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_i8); }
605
- add_result_t add(key_t key, f16_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f16); }
606
- add_result_t add(key_t key, f32_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f32); }
607
- add_result_t add(key_t key, f64_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f64); }
604
+ add_result_t add(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_b1x8); }
605
+ add_result_t add(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_i8); }
606
+ add_result_t add(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f16); }
607
+ add_result_t add(vector_key_t key, f32_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f32); }
608
+ add_result_t add(vector_key_t key, f64_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f64); }
608
609
 
609
610
  search_result_t search(b1x8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_b1x8); }
610
611
  search_result_t search(i8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_i8); }
@@ -612,11 +613,11 @@ class index_dense_gt {
612
613
  search_result_t search(f32_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f32); }
613
614
  search_result_t search(f64_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f64); }
614
615
 
615
- std::size_t get(key_t key, b1x8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_b1x8); }
616
- std::size_t get(key_t key, i8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_i8); }
617
- std::size_t get(key_t key, f16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f16); }
618
- std::size_t get(key_t key, f32_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f32); }
619
- std::size_t get(key_t key, f64_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f64); }
616
+ std::size_t get(vector_key_t key, b1x8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_b1x8); }
617
+ std::size_t get(vector_key_t key, i8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_i8); }
618
+ std::size_t get(vector_key_t key, f16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f16); }
619
+ std::size_t get(vector_key_t key, f32_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f32); }
620
+ std::size_t get(vector_key_t key, f64_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f64); }
620
621
 
621
622
  cluster_result_t cluster(b1x8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_b1x8); }
622
623
  cluster_result_t cluster(i8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_i8); }
@@ -624,11 +625,11 @@ class index_dense_gt {
624
625
  cluster_result_t cluster(f32_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_f32); }
625
626
  cluster_result_t cluster(f64_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_f64); }
626
627
 
627
- aggregated_distances_t distance_between(key_t key, b1x8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_b1x8); }
628
- aggregated_distances_t distance_between(key_t key, i8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_i8); }
629
- aggregated_distances_t distance_between(key_t key, f16_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f16); }
630
- aggregated_distances_t distance_between(key_t key, f32_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f32); }
631
- aggregated_distances_t distance_between(key_t key, f64_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f64); }
628
+ aggregated_distances_t distance_between(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_b1x8); }
629
+ aggregated_distances_t distance_between(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_i8); }
630
+ aggregated_distances_t distance_between(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f16); }
631
+ aggregated_distances_t distance_between(vector_key_t key, f32_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f32); }
632
+ aggregated_distances_t distance_between(vector_key_t key, f64_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f64); }
632
633
  // clang-format on
633
634
 
634
635
  /**
@@ -636,7 +637,7 @@ class index_dense_gt {
636
637
  * If either key maps into more than one vector, will aggregate results
637
638
  * exporting the mean, maximum, and minimum values.
638
639
  */
639
- aggregated_distances_t distance_between(key_t a, key_t b, std::size_t = any_thread()) const {
640
+ aggregated_distances_t distance_between(vector_key_t a, vector_key_t b, std::size_t = any_thread()) const {
640
641
  shared_lock_t lock(slot_lookup_mutex_);
641
642
  aggregated_distances_t result;
642
643
  if (!multi()) {
@@ -696,7 +697,7 @@ class index_dense_gt {
696
697
  /**
697
698
  * @brief Identifies a node in a given `level`, that is the closest to the `key`.
698
699
  */
699
- cluster_result_t cluster(key_t key, std::size_t level, std::size_t thread = any_thread()) const {
700
+ cluster_result_t cluster(vector_key_t key, std::size_t level, std::size_t thread = any_thread()) const {
700
701
 
701
702
  // Check if such `key` is even present.
702
703
  shared_lock_t slots_lock(slot_lookup_mutex_);
@@ -837,7 +838,7 @@ class index_dense_gt {
837
838
  // Describes types used
838
839
  head.kind_metric = metric_.metric_kind();
839
840
  head.kind_scalar = metric_.scalar_kind();
840
- head.kind_key = unum::usearch::scalar_kind<key_t>();
841
+ head.kind_key = unum::usearch::scalar_kind<vector_key_t>();
841
842
  head.kind_compressed_slot = unum::usearch::scalar_kind<compressed_slot_t>();
842
843
 
843
844
  head.count_present = size();
@@ -926,7 +927,7 @@ class index_dense_gt {
926
927
  return result.failed("File format may be different, please rebuild");
927
928
 
928
929
  // Check the types used
929
- if (head.kind_key != unum::usearch::scalar_kind<key_t>())
930
+ if (head.kind_key != unum::usearch::scalar_kind<vector_key_t>())
930
931
  return result.failed("Key type doesn't match, consider rebuilding");
931
932
  if (head.kind_compressed_slot != unum::usearch::scalar_kind<compressed_slot_t>())
932
933
  return result.failed("Slot type doesn't match, consider rebuilding");
@@ -1010,7 +1011,7 @@ class index_dense_gt {
1010
1011
  return result.failed("File format may be different, please rebuild");
1011
1012
 
1012
1013
  // Check the types used
1013
- if (head.kind_key != unum::usearch::scalar_kind<key_t>())
1014
+ if (head.kind_key != unum::usearch::scalar_kind<vector_key_t>())
1014
1015
  return result.failed("Key type doesn't match, consider rebuilding");
1015
1016
  if (head.kind_compressed_slot != unum::usearch::scalar_kind<compressed_slot_t>())
1016
1017
  return result.failed("Slot type doesn't match, consider rebuilding");
@@ -1164,7 +1165,7 @@ class index_dense_gt {
1164
1165
  * @brief Checks if a vector with specified key is present.
1165
1166
  * @return `true` if the key is present in the index, `false` otherwise.
1166
1167
  */
1167
- bool contains(key_t key) const {
1168
+ bool contains(vector_key_t key) const {
1168
1169
  shared_lock_t lock(slot_lookup_mutex_);
1169
1170
  return slot_lookup_.contains(key_and_slot_t::any_slot(key));
1170
1171
  }
@@ -1173,7 +1174,7 @@ class index_dense_gt {
1173
1174
  * @brief Count the number of vectors with specified key present.
1174
1175
  * @return Zero if nothing is found, a positive integer otherwise.
1175
1176
  */
1176
- std::size_t count(key_t key) const {
1177
+ std::size_t count(vector_key_t key) const {
1177
1178
  shared_lock_t lock(slot_lookup_mutex_);
1178
1179
  return slot_lookup_.count(key_and_slot_t::any_slot(key));
1179
1180
  }
@@ -1197,7 +1198,7 @@ class index_dense_gt {
1197
1198
  * If the key was not found in the index, `result.completed` will be `false`.
1198
1199
  * If an error occurred during the removal operation, `result.error` will contain an error message.
1199
1200
  */
1200
- labeling_result_t remove(key_t key) {
1201
+ labeling_result_t remove(vector_key_t key) {
1201
1202
  labeling_result_t result;
1202
1203
 
1203
1204
  unique_lock_t lookup_lock(slot_lookup_mutex_);
@@ -1250,7 +1251,7 @@ class index_dense_gt {
1250
1251
 
1251
1252
  // Remove them one-by-one
1252
1253
  for (auto keys_it = keys_begin; keys_it != keys_end; ++keys_it) {
1253
- key_t key = *keys_it;
1254
+ vector_key_t key = *keys_it;
1254
1255
  auto matching_slots = slot_lookup_.equal_range(key_and_slot_t::any_slot(key));
1255
1256
  // A removed entry would be:
1256
1257
  // - present in `free_keys_`
@@ -1279,7 +1280,7 @@ class index_dense_gt {
1279
1280
  * If the rename was successful, `result.completed` will be `true`.
1280
1281
  * If the entry with the current key was not found, `result.completed` will be `false`.
1281
1282
  */
1282
- labeling_result_t rename(key_t from, key_t to) {
1283
+ labeling_result_t rename(vector_key_t from, vector_key_t to) {
1283
1284
  labeling_result_t result;
1284
1285
  unique_lock_t lookup_lock(slot_lookup_mutex_);
1285
1286
 
@@ -1307,7 +1308,7 @@ class index_dense_gt {
1307
1308
  * @param[in] offset The number of keys to skip. Useful for pagination.
1308
1309
  * @param[in] limit The maximum number of keys to export, that can fit in ::keys.
1309
1310
  */
1310
- void export_keys(key_t* keys, std::size_t offset, std::size_t limit) const {
1311
+ void export_keys(vector_key_t* keys, std::size_t offset, std::size_t limit) const {
1311
1312
  shared_lock_t lock(slot_lookup_mutex_);
1312
1313
  offset = (std::min)(offset, slot_lookup_.size());
1313
1314
  slot_lookup_.for_each([&](key_and_slot_t const& key_and_slot) {
@@ -1455,7 +1456,7 @@ class index_dense_gt {
1455
1456
  std::vector<byte_t*> new_vectors_lookup(vectors_lookup_.size());
1456
1457
  vectors_tape_allocator_t new_vectors_allocator;
1457
1458
 
1458
- auto track_slot_change = [&](key_t, compressed_slot_t old_slot, compressed_slot_t new_slot) {
1459
+ auto track_slot_change = [&](vector_key_t, compressed_slot_t old_slot, compressed_slot_t new_slot) {
1459
1460
  byte_t* new_vector = new_vectors_allocator.allocate(metric_.bytes_per_vector());
1460
1461
  byte_t* old_vector = vectors_lookup_[old_slot];
1461
1462
  std::memcpy(new_vector, old_vector, metric_.bytes_per_vector());
@@ -1525,7 +1526,7 @@ class index_dense_gt {
1525
1526
  queries_iterator_at queries_begin, //
1526
1527
  queries_iterator_at queries_end, //
1527
1528
  index_dense_clustering_config_t config, //
1528
- key_t* cluster_keys, //
1529
+ vector_key_t* cluster_keys, //
1529
1530
  distance_t* cluster_distances, //
1530
1531
  executor_at&& executor = executor_at{}, //
1531
1532
  progress_at&& progress = progress_at{}) {
@@ -1548,8 +1549,8 @@ class index_dense_gt {
1548
1549
 
1549
1550
  // A structure used to track the popularity of a specific cluster
1550
1551
  struct cluster_t {
1551
- key_t centroid;
1552
- key_t merged_into;
1552
+ vector_key_t centroid;
1553
+ vector_key_t merged_into;
1553
1554
  std::size_t popularity;
1554
1555
  byte_t* vector;
1555
1556
  };
@@ -1658,7 +1659,7 @@ class index_dense_gt {
1658
1659
  std::sort(clusters.data(), clusters_end, centroid_id);
1659
1660
 
1660
1661
  executor.dynamic(queries_count, [&](std::size_t thread_idx, std::size_t query_idx) {
1661
- key_t& cluster_key = cluster_keys[query_idx];
1662
+ vector_key_t& cluster_key = cluster_keys[query_idx];
1662
1663
  distance_t& cluster_distance = cluster_distances[query_idx];
1663
1664
 
1664
1665
  // Recursively trace replacements of that cluster
@@ -1715,7 +1716,7 @@ class index_dense_gt {
1715
1716
 
1716
1717
  template <typename scalar_at>
1717
1718
  add_result_t add_( //
1718
- key_t key, scalar_at const* vector, //
1719
+ vector_key_t key, scalar_at const* vector, //
1719
1720
  std::size_t thread, bool force_vector_copy, cast_t const& cast) {
1720
1721
 
1721
1722
  if (!multi() && contains(key))
@@ -1811,7 +1812,7 @@ class index_dense_gt {
1811
1812
 
1812
1813
  template <typename scalar_at>
1813
1814
  aggregated_distances_t distance_between_( //
1814
- key_t key, scalar_at const* vector, //
1815
+ vector_key_t key, scalar_at const* vector, //
1815
1816
  std::size_t thread, cast_t const& cast) const {
1816
1817
 
1817
1818
  // Cast the vector, if needed for compatibility with `metric_`
@@ -1881,12 +1882,12 @@ class index_dense_gt {
1881
1882
  if (member.key == free_key_)
1882
1883
  free_keys_.push(static_cast<compressed_slot_t>(i));
1883
1884
  else if (config_.enable_key_lookups)
1884
- slot_lookup_.try_emplace(key_and_slot_t{key_t(member.key), static_cast<compressed_slot_t>(i)});
1885
+ slot_lookup_.try_emplace(key_and_slot_t{vector_key_t(member.key), static_cast<compressed_slot_t>(i)});
1885
1886
  }
1886
1887
  }
1887
1888
 
1888
1889
  template <typename scalar_at>
1889
- std::size_t get_(key_t key, scalar_at* reconstructed, std::size_t vectors_limit, cast_t const& cast) const {
1890
+ std::size_t get_(vector_key_t key, scalar_at* reconstructed, std::size_t vectors_limit, cast_t const& cast) const {
1890
1891
 
1891
1892
  if (!multi()) {
1892
1893
  compressed_slot_t slot;