usearch 2.15.2 → 2.16.0

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/README.md +1 -1
  2. package/binding.gyp +1 -1
  3. package/c/CMakeLists.txt +10 -3
  4. package/cpp/README.md +1 -1
  5. package/cpp/bench.cpp +2 -2
  6. package/cpp/test.cpp +16 -0
  7. package/include/usearch/index.hpp +44 -17
  8. package/include/usearch/index_dense.hpp +51 -89
  9. package/include/usearch/index_plugins.hpp +468 -43
  10. package/javascript/lib.cpp +17 -5
  11. package/javascript/usearch.test.js +205 -34
  12. package/package.json +1 -1
  13. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  14. package/prebuilds/linux-arm64/usearch.node +0 -0
  15. package/prebuilds/linux-x64/usearch.node +0 -0
  16. package/simsimd/.git-blame-ignore-revs +2 -0
  17. package/simsimd/CODE_OF_CONDUCT.md +128 -0
  18. package/simsimd/CONTRIBUTING.md +84 -26
  19. package/simsimd/Cargo.lock +1 -1
  20. package/simsimd/Cargo.toml +4 -4
  21. package/simsimd/MANIFEST.in +3 -0
  22. package/simsimd/Package.swift +15 -3
  23. package/simsimd/README.md +534 -177
  24. package/simsimd/binding.gyp +6 -2
  25. package/simsimd/build.rs +13 -1
  26. package/simsimd/c/lib.c +266 -53
  27. package/simsimd/include/simsimd/binary.h +321 -112
  28. package/simsimd/include/simsimd/curved.h +803 -0
  29. package/simsimd/include/simsimd/dot.h +694 -521
  30. package/simsimd/include/simsimd/elementwise.h +1437 -0
  31. package/simsimd/include/simsimd/geospatial.h +9 -3
  32. package/simsimd/include/simsimd/mesh.h +69 -0
  33. package/simsimd/include/simsimd/probability.h +234 -206
  34. package/simsimd/include/simsimd/simsimd.h +1476 -617
  35. package/simsimd/include/simsimd/sparse.h +1428 -0
  36. package/simsimd/include/simsimd/spatial.h +1556 -742
  37. package/simsimd/include/simsimd/types.h +276 -132
  38. package/simsimd/javascript/lib.c +1 -2
  39. package/simsimd/javascript/simsimd.ts +4 -4
  40. package/simsimd/package.json +4 -4
  41. package/simsimd/rust/lib.rs +179 -1
  42. package/simsimd/scripts/bench.cxx +1070 -0
  43. package/simsimd/scripts/bench.ipynb +187 -0
  44. package/simsimd/{javascript → scripts}/bench.js +1 -1
  45. package/simsimd/{rust/benches/sqeuclidean.rs → scripts/bench_sqeuclidean.rs} +6 -6
  46. package/simsimd/scripts/bench_vectors.py +700 -0
  47. package/simsimd/scripts/bench_vectors_live.py +213 -0
  48. package/simsimd/scripts/test.c +155 -0
  49. package/simsimd/{javascript → scripts}/test.mjs +2 -2
  50. package/simsimd/scripts/test.py +1410 -0
  51. package/simsimd/swift/{source/SimSIMD.swift → SimSIMD.swift} +6 -2
  52. package/simsimd/swift/{tests/SimSIMDTests.swift → Test.swift} +1 -1
  53. /package/simsimd/{rust/benches/cosine.rs → scripts/bench_cosine.rs} +0 -0
  54. /package/simsimd/{rust/benches/native.rs → scripts/bench_native.rs} +0 -0
package/README.md CHANGED
@@ -544,7 +544,7 @@ doi = {10.5281/zenodo.7949416},
544
544
  author = {Vardanian, Ash},
545
545
  title = {{USearch by Unum Cloud}},
546
546
  url = {https://github.com/unum-cloud/usearch},
547
- version = {2.15.2},
547
+ version = {2.16.0},
548
548
  year = {2023},
549
549
  month = oct,
550
550
  }
package/binding.gyp CHANGED
@@ -2,7 +2,7 @@
2
2
  "targets": [
3
3
  {
4
4
  "target_name": "usearch",
5
- "sources": ["javascript/lib.cpp"],
5
+ "sources": ["javascript/lib.cpp", "simsimd/c/lib.c"],
6
6
  "cflags": [
7
7
  "-fexceptions",
8
8
  "-Wno-unknown-pragmas",
package/c/CMakeLists.txt CHANGED
@@ -1,5 +1,12 @@
1
+
2
+ # Define the source files conditionally
3
+ set(USEARCH_LIB_SOURCES lib.cpp)
4
+ if (USEARCH_USE_SIMSIMD)
5
+ list(APPEND USEARCH_LIB_SOURCES ../simsimd/c/lib.c)
6
+ endif ()
7
+
1
8
  if (USEARCH_BUILD_TEST_C)
2
- add_executable(test_c test.c lib.cpp)
9
+ add_executable(test_c test.c ${USEARCH_LIB_SOURCES})
3
10
  setup_target(test_c)
4
11
  include(CTest)
5
12
  enable_testing()
@@ -9,8 +16,8 @@ endif ()
9
16
  # This article discusses a better way to allow building either static or shared libraries:
10
17
  # https://alexreinking.com/blog/building-a-dual-shared-and-static-library-with-cmake.html
11
18
  if (USEARCH_BUILD_LIB_C)
12
- add_library(usearch_c SHARED lib.cpp)
13
- add_library(usearch_static_c STATIC lib.cpp)
19
+ add_library(usearch_c SHARED ${USEARCH_LIB_SOURCES})
20
+ add_library(usearch_static_c STATIC ${USEARCH_LIB_SOURCES})
14
21
 
15
22
  # Set the compile options for the static library
16
23
  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
package/cpp/README.md CHANGED
@@ -36,7 +36,7 @@ int main(int argc, char **argv) {
36
36
  for (std::size_t i = 0; i != results.size(); ++i)
37
37
  // You can access the following properties of every match:
38
38
  // results[i].element.key, results[i].element.vector, results[i].distance;
39
- std::printf("Found matching key: %zu", results[i].element.key);
39
+ std::printf("Found matching key: %zu", results[i].member.key);
40
40
  return 0;
41
41
  }
42
42
  ```
package/cpp/bench.cpp CHANGED
@@ -185,7 +185,7 @@ struct persisted_dataset_gt {
185
185
  std::size_t vectors_count() const noexcept {
186
186
  return vectors_to_take_ ? vectors_to_take_ : (vectors_.rows - vectors_to_skip_);
187
187
  }
188
- vectors_view_gt<scalar_t> vectors_view() const noexcept {
188
+ matrix_slice_gt<scalar_t const> vectors_view() const noexcept {
189
189
  return {vector(vectors_to_skip_), vectors_count(), dimensions()};
190
190
  }
191
191
  };
@@ -224,7 +224,7 @@ struct in_memory_dataset_gt {
224
224
  scalar_t* query(std::size_t i) noexcept { return queries_.data() + i * dimensions_; }
225
225
  compressed_slot_t* neighborhood(std::size_t i) noexcept { return neighborhoods_.data() + i * neighborhood_size_; }
226
226
 
227
- vectors_view_gt<scalar_t> vectors_view() const noexcept { return {vector(0), vectors_count(), dimensions()}; }
227
+ matrix_slice_gt<scalar_t const> vectors_view() const noexcept { return {vector(0), vectors_count(), dimensions()}; }
228
228
  };
229
229
 
230
230
  char const* getenv_or(char const* name, char const* default_) { return getenv(name) ? getenv(name) : default_; }
package/cpp/test.cpp CHANGED
@@ -1100,6 +1100,22 @@ int main(int, char**) {
1100
1100
  test_uint40();
1101
1101
  test_cosine<float, std::int64_t, uint40_t>(10, 10);
1102
1102
 
1103
+ // Test plugins, like K-Means clustering.
1104
+ {
1105
+ std::size_t vectors_count = 1000, centroids_count = 10, dimensions = 256;
1106
+ kmeans_clustering_t clustering;
1107
+ clustering.max_iterations = 2;
1108
+ std::vector<float> vectors(vectors_count * dimensions), centroids(centroids_count * dimensions);
1109
+ matrix_slice_gt<float const> vectors_slice(vectors.data(), dimensions, vectors_count);
1110
+ matrix_slice_gt<float> centroids_slice(centroids.data(), dimensions, centroids_count);
1111
+ std::generate(vectors.begin(), vectors.end(), [] { return float(std::rand()) / float(INT_MAX); });
1112
+ std::vector<std::size_t> assignments(vectors_count);
1113
+ std::vector<distance_punned_t> distances(vectors_count);
1114
+ auto clustering_result = clustering(vectors_slice, centroids_slice, {assignments.data(), assignments.size()},
1115
+ {distances.data(), distances.size()});
1116
+ expect(clustering_result);
1117
+ }
1118
+
1103
1119
  // Exact search without constructing indexes.
1104
1120
  // Great for validating the distance functions.
1105
1121
  std::printf("Testing exact search\n");
@@ -8,8 +8,8 @@
8
8
  #define UNUM_USEARCH_HPP
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
- #define USEARCH_VERSION_MINOR 15
12
- #define USEARCH_VERSION_PATCH 2
11
+ #define USEARCH_VERSION_MINOR 16
12
+ #define USEARCH_VERSION_PATCH 0
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -370,7 +370,7 @@ template <typename scalar_at, typename allocator_at = std::allocator<scalar_at>>
370
370
  void reset() noexcept {
371
371
  if (!std::is_trivially_destructible<scalar_at>::value)
372
372
  for (std::size_t i = 0; i != size_; ++i)
373
- destroy_at(data_ + i);
373
+ unum::usearch::destroy_at(data_ + i); //< Facing some symbol visibility/ambiguity issues
374
374
  allocator_at{}.deallocate(data_, size_);
375
375
  data_ = nullptr;
376
376
  size_ = 0;
@@ -937,18 +937,6 @@ class sorted_buffer_gt {
937
937
  return true;
938
938
  }
939
939
 
940
- inline bool insert_sorted(element_t const* elements, std::size_t elements_count, std::size_t limit) noexcept {
941
- if (!size_) {
942
- }
943
- // If we are inserting elements, we only perform full-scale binary search once,
944
- // and then only compute successive insertion offsets based only on the tail.
945
- std::size_t slot = std::lower_bound(elements_, elements_ + size_, elements[0], &less) - elements_;
946
- if (slot == limit)
947
- return false;
948
- std::size_t to_move = size_ - slot - (size_ == limit);
949
- std::size_t next_slot = std::lower_bound(elements_ + slot, elements_ + size_, elements[1], &less) - elements_;
950
- }
951
-
952
940
  inline element_t pop() noexcept {
953
941
  size_--;
954
942
  element_t result = elements_[size_];
@@ -972,6 +960,10 @@ class sorted_buffer_gt {
972
960
 
973
961
  /**
974
962
  * @brief Five-byte integer type to address node clouds with over 4B entries.
963
+ *
964
+ * 40 bits is enough to address a @b Trillion entries potentially colocated on 1 machine.
965
+ * At roughly 5 bytes * 20 neighbors + 100 bytes per entry, this translates to 200 TB of data,
966
+ * which is similar to a single-server capacity of modern NVME arrays.
975
967
  */
976
968
  class usearch_pack_m uint40_t {
977
969
  unsigned char octets[5];
@@ -1226,8 +1218,8 @@ class ring_gt {
1226
1218
  using element_t = element_at;
1227
1219
  using allocator_t = allocator_at;
1228
1220
 
1229
- static_assert(std::is_trivially_destructible<element_t>(), "This heap is designed for trivial structs");
1230
- static_assert(std::is_trivially_copy_constructible<element_t>(), "This heap is designed for trivial structs");
1221
+ static_assert(std::is_trivially_destructible<element_t>(), "This ring is designed for trivial structs");
1222
+ static_assert(std::is_trivially_copy_constructible<element_t>(), "This ring is designed for trivial structs");
1231
1223
 
1232
1224
  using value_type = element_t;
1233
1225
 
@@ -2619,6 +2611,16 @@ class index_gt {
2619
2611
  node_t node = nodes_[candidate.slot];
2620
2612
  return {member_cref_t{node.ckey(), candidate.slot}, candidate.distance};
2621
2613
  }
2614
+
2615
+ /**
2616
+ * @brief Extracts the search results into a user-provided buffer, that unlike `dump_to`,
2617
+ * may already contain some data, so the new and old results are merged together.
2618
+ * @return The number of results stored in the buffer.
2619
+ * @param[in] keys The buffer to store the keys of the search results.
2620
+ * @param[in] distances The buffer to store the distances to the search results.
2621
+ * @param[in] old_count The number of results already stored in the buffers.
2622
+ * @param[in] max_count The maximum number of results that can be stored in the buffers.
2623
+ */
2622
2624
  inline std::size_t merge_into( //
2623
2625
  vector_key_t* keys, distance_t* distances, //
2624
2626
  std::size_t old_count, std::size_t max_count) const noexcept {
@@ -2640,6 +2642,13 @@ class index_gt {
2640
2642
  }
2641
2643
  return merged_count;
2642
2644
  }
2645
+
2646
+ /**
2647
+ * @brief Extracts the search results into a user-provided buffer.
2648
+ * @return The number of results stored in the buffer.
2649
+ * @param[in] keys The buffer to store the keys of the search results.
2650
+ * @param[in] distances The buffer to store the distances to the search results.
2651
+ */
2643
2652
  inline std::size_t dump_to(vector_key_t* keys, distance_t* distances) const noexcept {
2644
2653
  for (std::size_t i = 0; i != count; ++i) {
2645
2654
  match_t result = operator[](i);
@@ -2648,6 +2657,12 @@ class index_gt {
2648
2657
  }
2649
2658
  return count;
2650
2659
  }
2660
+
2661
+ /**
2662
+ * @brief Extracts the search results into a user-provided buffer.
2663
+ * @return The number of results stored in the buffer.
2664
+ * @param[in] keys The buffer to store the keys of the search results.
2665
+ */
2651
2666
  inline std::size_t dump_to(vector_key_t* keys) const noexcept {
2652
2667
  for (std::size_t i = 0; i != count; ++i) {
2653
2668
  match_t result = operator[](i);
@@ -3164,6 +3179,18 @@ class index_gt {
3164
3179
 
3165
3180
  std::size_t memory_usage_per_node(level_t level) const noexcept { return node_bytes_(level); }
3166
3181
 
3182
+ double inverse_log_connectivity() const {
3183
+ return pre_.inverse_log_connectivity;
3184
+ }
3185
+
3186
+ std::size_t neighbors_base_bytes() const {
3187
+ return pre_.neighbors_base_bytes;
3188
+ }
3189
+
3190
+ std::size_t neighbors_bytes() const {
3191
+ return pre_.neighbors_bytes;
3192
+ }
3193
+
3167
3194
  #if defined(USEARCH_USE_PRAGMA_REGION)
3168
3195
  #pragma endregion
3169
3196
 
@@ -406,8 +406,6 @@ class index_dense_gt {
406
406
  using tape_allocator_t = memory_mapping_allocator_gt<64>;
407
407
 
408
408
  private:
409
- /// @brief Schema: input buffer, bytes in input buffer, output buffer.
410
- using cast_t = bool (*)(byte_t const*, std::size_t, byte_t*);
411
409
  /// @brief Punned index.
412
410
  using index_t = index_gt< //
413
411
  distance_t, vector_key_t, compressed_slot_t, //
@@ -446,19 +444,7 @@ class index_dense_gt {
446
444
 
447
445
  /// @brief Temporary memory for every thread to store a casted vector.
448
446
  mutable cast_buffer_t cast_buffer_;
449
- struct casts_t {
450
- cast_t from_b1x8;
451
- cast_t from_i8;
452
- cast_t from_f16;
453
- cast_t from_f32;
454
- cast_t from_f64;
455
-
456
- cast_t to_b1x8;
457
- cast_t to_i8;
458
- cast_t to_f16;
459
- cast_t to_f32;
460
- cast_t to_f64;
461
- } casts_;
447
+ casts_punned_t casts_;
462
448
 
463
449
  /// @brief An instance of a potentially stateful `metric_t` used to initialize copies and forks.
464
450
  metric_t metric_;
@@ -677,7 +663,7 @@ class index_dense_gt {
677
663
  // In some cases the metric is not provided, and will be set later.
678
664
  if (metric) {
679
665
  scalar_kind_t scalar_kind = metric.scalar_kind();
680
- index.casts_ = make_casts_(scalar_kind);
666
+ index.casts_ = casts_punned_t::make(scalar_kind);
681
667
  index.metric_ = metric;
682
668
  }
683
669
 
@@ -707,6 +693,9 @@ class index_dense_gt {
707
693
  std::size_t max_level() const { return typed_->max_level(); }
708
694
  index_dense_config_t const& config() const { return config_; }
709
695
  index_limits_t const& limits() const { return typed_->limits(); }
696
+ double inverse_log_connectivity() const { return typed_->inverse_log_connectivity(); }
697
+ std::size_t neighbors_base_bytes() const { return typed_->neighbors_base_bytes(); }
698
+ std::size_t neighbors_bytes() const { return typed_->neighbors_bytes(); }
710
699
  bool multi() const { return config_.multi; }
711
700
  std::size_t currently_available_threads() const {
712
701
  std::unique_lock<std::mutex> available_threads_lock(available_threads_mutex_);
@@ -767,41 +756,41 @@ class index_dense_gt {
767
756
  };
768
757
 
769
758
  // clang-format off
770
- 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); }
771
- 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); }
772
- 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); }
773
- 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); }
774
- 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); }
775
-
776
- 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, dummy_predicate_t {}, thread, exact, casts_.from_b1x8); }
777
- 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, dummy_predicate_t {}, thread, exact, casts_.from_i8); }
778
- search_result_t search(f16_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from_f16); }
779
- 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, dummy_predicate_t {}, thread, exact, casts_.from_f32); }
780
- 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, dummy_predicate_t {}, thread, exact, casts_.from_f64); }
781
-
782
- template <typename predicate_at> search_result_t filtered_search(b1x8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_b1x8); }
783
- template <typename predicate_at> search_result_t filtered_search(i8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_i8); }
784
- template <typename predicate_at> search_result_t filtered_search(f16_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_f16); }
785
- template <typename predicate_at> search_result_t filtered_search(f32_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_f32); }
786
- template <typename predicate_at> search_result_t filtered_search(f64_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_f64); }
787
-
788
- 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); }
789
- 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); }
790
- 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); }
791
- 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); }
792
- 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); }
793
-
794
- 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); }
795
- 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); }
796
- cluster_result_t cluster(f16_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_f16); }
797
- 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); }
798
- 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); }
799
-
800
- 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); }
801
- 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); }
802
- 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); }
803
- 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); }
804
- 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); }
759
+ 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); }
760
+ 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); }
761
+ 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); }
762
+ 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); }
763
+ 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); }
764
+
765
+ 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, dummy_predicate_t {}, thread, exact, casts_.from.b1x8); }
766
+ 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, dummy_predicate_t {}, thread, exact, casts_.from.i8); }
767
+ search_result_t search(f16_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.f16); }
768
+ 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, dummy_predicate_t {}, thread, exact, casts_.from.f32); }
769
+ 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, dummy_predicate_t {}, thread, exact, casts_.from.f64); }
770
+
771
+ template <typename predicate_at> search_result_t filtered_search(b1x8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.b1x8); }
772
+ template <typename predicate_at> search_result_t filtered_search(i8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.i8); }
773
+ template <typename predicate_at> search_result_t filtered_search(f16_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.f16); }
774
+ template <typename predicate_at> search_result_t filtered_search(f32_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.f32); }
775
+ template <typename predicate_at> search_result_t filtered_search(f64_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.f64); }
776
+
777
+ 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); }
778
+ 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); }
779
+ 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); }
780
+ 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); }
781
+ 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); }
782
+
783
+ 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); }
784
+ 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); }
785
+ cluster_result_t cluster(f16_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.f16); }
786
+ 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); }
787
+ 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); }
788
+
789
+ 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); }
790
+ 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); }
791
+ 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); }
792
+ 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); }
793
+ 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); }
805
794
  // clang-format on
806
795
 
807
796
  /**
@@ -1154,7 +1143,7 @@ class index_dense_gt {
1154
1143
  cast_buffer_ = cast_buffer_t(available_threads_.size() * metric_.bytes_per_vector());
1155
1144
  if (!cast_buffer_)
1156
1145
  return result.failed("Failed to allocate memory for the casts");
1157
- casts_ = make_casts_(head.kind_scalar);
1146
+ casts_ = casts_punned_t::make(head.kind_scalar);
1158
1147
  }
1159
1148
 
1160
1149
  // Pull the actual proximity graph
@@ -1266,7 +1255,7 @@ class index_dense_gt {
1266
1255
  cast_buffer_ = cast_buffer_t(available_threads_.size() * metric_.bytes_per_vector());
1267
1256
  if (!cast_buffer_)
1268
1257
  return result.failed("Failed to allocate memory for the casts");
1269
- casts_ = make_casts_(head.kind_scalar);
1258
+ casts_ = casts_punned_t::make(head.kind_scalar);
1270
1259
  offset += sizeof(buffer);
1271
1260
  }
1272
1261
 
@@ -1474,6 +1463,8 @@ class index_dense_gt {
1474
1463
  labeling_result_t remove(vector_key_t key) {
1475
1464
  usearch_assert_m(config().enable_key_lookups, "Key lookups are disabled");
1476
1465
  labeling_result_t result;
1466
+ if (typed_->is_immutable())
1467
+ return result.failed("Can't remove from an immutable index");
1477
1468
 
1478
1469
  unique_lock_t lookup_lock(slot_lookup_mutex_);
1479
1470
  auto matching_slots = slot_lookup_.equal_range(key_and_slot_t::any_slot(key));
@@ -1994,7 +1985,7 @@ class index_dense_gt {
1994
1985
  template <typename scalar_at>
1995
1986
  add_result_t add_( //
1996
1987
  vector_key_t key, scalar_at const* vector, //
1997
- std::size_t thread, bool force_vector_copy, cast_t const& cast) {
1988
+ std::size_t thread, bool force_vector_copy, cast_punned_t const& cast) {
1998
1989
 
1999
1990
  if (!multi() && config().enable_key_lookups && contains(key))
2000
1991
  return add_result_t{}.failed("Duplicate keys not allowed in high-level wrappers");
@@ -2044,7 +2035,7 @@ class index_dense_gt {
2044
2035
 
2045
2036
  template <typename scalar_at, typename predicate_at>
2046
2037
  search_result_t search_(scalar_at const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread,
2047
- bool exact, cast_t const& cast) const {
2038
+ bool exact, cast_punned_t const& cast) const {
2048
2039
 
2049
2040
  // Cast the vector, if needed for compatibility with `metric_`
2050
2041
  thread_lock_t lock = thread_lock_(thread);
@@ -2080,7 +2071,7 @@ class index_dense_gt {
2080
2071
  template <typename scalar_at>
2081
2072
  cluster_result_t cluster_( //
2082
2073
  scalar_at const* vector, std::size_t level, //
2083
- std::size_t thread, cast_t const& cast) const {
2074
+ std::size_t thread, cast_punned_t const& cast) const {
2084
2075
 
2085
2076
  // Cast the vector, if needed for compatibility with `metric_`
2086
2077
  thread_lock_t lock = thread_lock_(thread);
@@ -2104,7 +2095,7 @@ class index_dense_gt {
2104
2095
  template <typename scalar_at>
2105
2096
  aggregated_distances_t distance_between_( //
2106
2097
  vector_key_t key, scalar_at const* vector, //
2107
- std::size_t thread, cast_t const& cast) const {
2098
+ std::size_t thread, cast_punned_t const& cast) const {
2108
2099
 
2109
2100
  // Cast the vector, if needed for compatibility with `metric_`
2110
2101
  thread_lock_t lock = thread_lock_(thread);
@@ -2181,7 +2172,8 @@ class index_dense_gt {
2181
2172
  }
2182
2173
 
2183
2174
  template <typename scalar_at>
2184
- std::size_t get_(vector_key_t key, scalar_at* reconstructed, std::size_t vectors_limit, cast_t const& cast) const {
2175
+ std::size_t get_(vector_key_t key, scalar_at* reconstructed, std::size_t vectors_limit,
2176
+ cast_punned_t const& cast) const {
2185
2177
 
2186
2178
  if (!multi()) {
2187
2179
  compressed_slot_t slot;
@@ -2216,36 +2208,6 @@ class index_dense_gt {
2216
2208
  return count_exported;
2217
2209
  }
2218
2210
  }
2219
-
2220
- template <typename to_scalar_at> static casts_t make_casts_() {
2221
- casts_t result;
2222
-
2223
- result.from_b1x8 = &cast_gt<b1x8_t, to_scalar_at>::try_;
2224
- result.from_i8 = &cast_gt<i8_t, to_scalar_at>::try_;
2225
- result.from_f16 = &cast_gt<f16_t, to_scalar_at>::try_;
2226
- result.from_f32 = &cast_gt<f32_t, to_scalar_at>::try_;
2227
- result.from_f64 = &cast_gt<f64_t, to_scalar_at>::try_;
2228
-
2229
- result.to_b1x8 = &cast_gt<to_scalar_at, b1x8_t>::try_;
2230
- result.to_i8 = &cast_gt<to_scalar_at, i8_t>::try_;
2231
- result.to_f16 = &cast_gt<to_scalar_at, f16_t>::try_;
2232
- result.to_f32 = &cast_gt<to_scalar_at, f32_t>::try_;
2233
- result.to_f64 = &cast_gt<to_scalar_at, f64_t>::try_;
2234
-
2235
- return result;
2236
- }
2237
-
2238
- static casts_t make_casts_(scalar_kind_t scalar_kind) {
2239
- switch (scalar_kind) {
2240
- case scalar_kind_t::f64_k: return make_casts_<f64_t>();
2241
- case scalar_kind_t::f32_k: return make_casts_<f32_t>();
2242
- case scalar_kind_t::f16_k: return make_casts_<f16_t>();
2243
- case scalar_kind_t::bf16_k: return make_casts_<bf16_t>();
2244
- case scalar_kind_t::i8_k: return make_casts_<i8_t>();
2245
- case scalar_kind_t::b1x8_k: return make_casts_<b1x8_t>();
2246
- default: return {};
2247
- }
2248
- }
2249
2211
  };
2250
2212
 
2251
2213
  using index_dense_t = index_dense_gt<>;
@@ -2292,4 +2254,4 @@ static join_result_t join( //
2292
2254
  }
2293
2255
 
2294
2256
  } // namespace usearch
2295
- } // namespace unum
2257
+ } // namespace unum