usearch 2.15.1 → 2.15.3

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.
package/README.md CHANGED
@@ -32,7 +32,7 @@ Spatial • Binary • Probabilistic • User-Defined Metrics
32
32
  <a href="https://unum-cloud.github.io/usearch/golang">GoLang</a> •
33
33
  <a href="https://unum-cloud.github.io/usearch/wolfram">Wolfram</a>
34
34
  <br/>
35
- Linux • MacOS • Windows • iOS • WebAssembly •
35
+ Linux • MacOS • Windows • iOS • Android • WebAssembly •
36
36
  <a href="https://unum-cloud.github.io/usearch/sqlite">SQLite3</a>
37
37
  </p>
38
38
 
@@ -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.1},
547
+ version = {2.15.3},
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_; }
@@ -9,7 +9,7 @@
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
11
  #define USEARCH_VERSION_MINOR 15
12
- #define USEARCH_VERSION_PATCH 1
12
+ #define USEARCH_VERSION_PATCH 3
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -27,6 +27,9 @@
27
27
  #define USEARCH_DEFINED_APPLE
28
28
  #elif defined(__linux__)
29
29
  #define USEARCH_DEFINED_LINUX
30
+ #if defined(__ANDROID_API__)
31
+ #define USEARCH_DEFINED_ANDROID
32
+ #endif
30
33
  #endif
31
34
 
32
35
  // Inferring the compiler: Clang vs GCC
@@ -934,18 +937,6 @@ class sorted_buffer_gt {
934
937
  return true;
935
938
  }
936
939
 
937
- inline bool insert_sorted(element_t const* elements, std::size_t elements_count, std::size_t limit) noexcept {
938
- if (!size_) {
939
- }
940
- // If we are inserting elements, we only perform full-scale binary search once,
941
- // and then only compute successive insertion offsets based only on the tail.
942
- std::size_t slot = std::lower_bound(elements_, elements_ + size_, elements[0], &less) - elements_;
943
- if (slot == limit)
944
- return false;
945
- std::size_t to_move = size_ - slot - (size_ == limit);
946
- std::size_t next_slot = std::lower_bound(elements_ + slot, elements_ + size_, elements[1], &less) - elements_;
947
- }
948
-
949
940
  inline element_t pop() noexcept {
950
941
  size_--;
951
942
  element_t result = elements_[size_];
@@ -2616,6 +2607,16 @@ class index_gt {
2616
2607
  node_t node = nodes_[candidate.slot];
2617
2608
  return {member_cref_t{node.ckey(), candidate.slot}, candidate.distance};
2618
2609
  }
2610
+
2611
+ /**
2612
+ * @brief Extracts the search results into a user-provided buffer, that unlike `dump_to`,
2613
+ * may already contain some data, so the new and old results are merged together.
2614
+ * @return The number of results stored in the buffer.
2615
+ * @param[in] keys The buffer to store the keys of the search results.
2616
+ * @param[in] distances The buffer to store the distances to the search results.
2617
+ * @param[in] old_count The number of results already stored in the buffers.
2618
+ * @param[in] max_count The maximum number of results that can be stored in the buffers.
2619
+ */
2619
2620
  inline std::size_t merge_into( //
2620
2621
  vector_key_t* keys, distance_t* distances, //
2621
2622
  std::size_t old_count, std::size_t max_count) const noexcept {
@@ -2637,6 +2638,13 @@ class index_gt {
2637
2638
  }
2638
2639
  return merged_count;
2639
2640
  }
2641
+
2642
+ /**
2643
+ * @brief Extracts the search results into a user-provided buffer.
2644
+ * @return The number of results stored in the buffer.
2645
+ * @param[in] keys The buffer to store the keys of the search results.
2646
+ * @param[in] distances The buffer to store the distances to the search results.
2647
+ */
2640
2648
  inline std::size_t dump_to(vector_key_t* keys, distance_t* distances) const noexcept {
2641
2649
  for (std::size_t i = 0; i != count; ++i) {
2642
2650
  match_t result = operator[](i);
@@ -2645,6 +2653,12 @@ class index_gt {
2645
2653
  }
2646
2654
  return count;
2647
2655
  }
2656
+
2657
+ /**
2658
+ * @brief Extracts the search results into a user-provided buffer.
2659
+ * @return The number of results stored in the buffer.
2660
+ * @param[in] keys The buffer to store the keys of the search results.
2661
+ */
2648
2662
  inline std::size_t dump_to(vector_key_t* keys) const noexcept {
2649
2663
  for (std::size_t i = 0; i != count; ++i) {
2650
2664
  match_t result = operator[](i);
@@ -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
 
@@ -767,41 +753,41 @@ class index_dense_gt {
767
753
  };
768
754
 
769
755
  // 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); }
756
+ 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); }
757
+ 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); }
758
+ 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); }
759
+ 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); }
760
+ 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); }
761
+
762
+ 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); }
763
+ 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); }
764
+ 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); }
765
+ 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); }
766
+ 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); }
767
+
768
+ 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); }
769
+ 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); }
770
+ 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); }
771
+ 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); }
772
+ 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); }
773
+
774
+ 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); }
775
+ 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); }
776
+ 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); }
777
+ 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); }
778
+ 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); }
779
+
780
+ 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); }
781
+ 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); }
782
+ 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); }
783
+ 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); }
784
+ 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); }
785
+
786
+ 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); }
787
+ 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); }
788
+ 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); }
789
+ 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); }
790
+ 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
791
  // clang-format on
806
792
 
807
793
  /**
@@ -1154,7 +1140,7 @@ class index_dense_gt {
1154
1140
  cast_buffer_ = cast_buffer_t(available_threads_.size() * metric_.bytes_per_vector());
1155
1141
  if (!cast_buffer_)
1156
1142
  return result.failed("Failed to allocate memory for the casts");
1157
- casts_ = make_casts_(head.kind_scalar);
1143
+ casts_ = casts_punned_t::make(head.kind_scalar);
1158
1144
  }
1159
1145
 
1160
1146
  // Pull the actual proximity graph
@@ -1266,7 +1252,7 @@ class index_dense_gt {
1266
1252
  cast_buffer_ = cast_buffer_t(available_threads_.size() * metric_.bytes_per_vector());
1267
1253
  if (!cast_buffer_)
1268
1254
  return result.failed("Failed to allocate memory for the casts");
1269
- casts_ = make_casts_(head.kind_scalar);
1255
+ casts_ = casts_punned_t::make(head.kind_scalar);
1270
1256
  offset += sizeof(buffer);
1271
1257
  }
1272
1258
 
@@ -1994,7 +1980,7 @@ class index_dense_gt {
1994
1980
  template <typename scalar_at>
1995
1981
  add_result_t add_( //
1996
1982
  vector_key_t key, scalar_at const* vector, //
1997
- std::size_t thread, bool force_vector_copy, cast_t const& cast) {
1983
+ std::size_t thread, bool force_vector_copy, cast_punned_t const& cast) {
1998
1984
 
1999
1985
  if (!multi() && config().enable_key_lookups && contains(key))
2000
1986
  return add_result_t{}.failed("Duplicate keys not allowed in high-level wrappers");
@@ -2044,7 +2030,7 @@ class index_dense_gt {
2044
2030
 
2045
2031
  template <typename scalar_at, typename predicate_at>
2046
2032
  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 {
2033
+ bool exact, cast_punned_t const& cast) const {
2048
2034
 
2049
2035
  // Cast the vector, if needed for compatibility with `metric_`
2050
2036
  thread_lock_t lock = thread_lock_(thread);
@@ -2080,7 +2066,7 @@ class index_dense_gt {
2080
2066
  template <typename scalar_at>
2081
2067
  cluster_result_t cluster_( //
2082
2068
  scalar_at const* vector, std::size_t level, //
2083
- std::size_t thread, cast_t const& cast) const {
2069
+ std::size_t thread, cast_punned_t const& cast) const {
2084
2070
 
2085
2071
  // Cast the vector, if needed for compatibility with `metric_`
2086
2072
  thread_lock_t lock = thread_lock_(thread);
@@ -2104,7 +2090,7 @@ class index_dense_gt {
2104
2090
  template <typename scalar_at>
2105
2091
  aggregated_distances_t distance_between_( //
2106
2092
  vector_key_t key, scalar_at const* vector, //
2107
- std::size_t thread, cast_t const& cast) const {
2093
+ std::size_t thread, cast_punned_t const& cast) const {
2108
2094
 
2109
2095
  // Cast the vector, if needed for compatibility with `metric_`
2110
2096
  thread_lock_t lock = thread_lock_(thread);
@@ -2181,7 +2167,8 @@ class index_dense_gt {
2181
2167
  }
2182
2168
 
2183
2169
  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 {
2170
+ std::size_t get_(vector_key_t key, scalar_at* reconstructed, std::size_t vectors_limit,
2171
+ cast_punned_t const& cast) const {
2185
2172
 
2186
2173
  if (!multi()) {
2187
2174
  compressed_slot_t slot;
@@ -2216,36 +2203,6 @@ class index_dense_gt {
2216
2203
  return count_exported;
2217
2204
  }
2218
2205
  }
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
2206
  };
2250
2207
 
2251
2208
  using index_dense_t = index_dense_gt<>;
@@ -42,20 +42,38 @@
42
42
 
43
43
  #if USEARCH_USE_SIMSIMD
44
44
  // Propagate the `f16` settings
45
- #if !defined(SIMSIMD_NATIVE_F16)
46
- #define SIMSIMD_NATIVE_F16 !USEARCH_USE_FP16LIB
45
+ #if defined(USEARCH_CAN_COMPILE_FP16) || defined(USEARCH_CAN_COMPILE_FLOAT16)
46
+ #if USEARCH_CAN_COMPILE_FP16 || USEARCH_CAN_COMPILE_FLOAT16
47
+ #define SIMSIMD_NATIVE_F16 1
48
+ #else
49
+ #define SIMSIMD_NATIVE_F16 0
50
+ #endif
51
+ #endif
52
+ // Propagate the `bf16` settings
53
+ #if defined(USEARCH_CAN_COMPILE_BF16) || defined(USEARCH_CAN_COMPILE_BFLOAT16)
54
+ #if USEARCH_CAN_COMPILE_BF16 || USEARCH_CAN_COMPILE_BFLOAT16
55
+ #define SIMSIMD_NATIVE_BF16 1
56
+ #else
57
+ #define SIMSIMD_NATIVE_BF16 0
58
+ #endif
47
59
  #endif
48
-
49
- // Overwrite the dynamic dispatch settings
50
- #undef SIMSIMD_DYNAMIC_DISPATCH
51
- #define SIMSIMD_DYNAMIC_DISPATCH 0
52
60
  // No problem, if some of the functions are unused or undefined
53
61
  #pragma GCC diagnostic push
62
+ #pragma GCC diagnostic ignored "-Wall"
63
+ #pragma GCC diagnostic ignored "-Wunused"
54
64
  #pragma GCC diagnostic ignored "-Wunused-function"
65
+ #pragma GCC diagnostic ignored "-Wunused-parameter"
66
+ #pragma GCC diagnostic ignored "-Wunused-variable"
67
+ #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
68
+ #ifdef _MSC_VER
55
69
  #pragma warning(push)
56
- #pragma warning(disable : 4101)
70
+ #pragma warning(disable : 4101) // "Unused variables"
71
+ #pragma warning(disable : 4068) // "Unknown pragmas", when MSVC tries to read GCC pragmas
72
+ #endif // _MSC_VER
57
73
  #include <simsimd/simsimd.h>
74
+ #ifdef _MSC_VER
58
75
  #pragma warning(pop)
76
+ #endif // _MSC_VER
59
77
  #pragma GCC diagnostic pop
60
78
  #endif
61
79
 
@@ -380,9 +398,10 @@ inline float f16_to_f32(std::uint16_t u16) noexcept {
380
398
  #if USEARCH_USE_FP16LIB
381
399
  return fp16_ieee_to_fp32_value(u16);
382
400
  #elif USEARCH_USE_SIMSIMD
383
- return simsimd_uncompress_f16(u16);
401
+ return simsimd_f16_to_f32((simsimd_f16_t const*)&u16);
384
402
  #else
385
- f16_native_t f16;
403
+ #warning "It's recommended to use SimSIMD and fp16lib for half-precision numerics"
404
+ _Float16 f16;
386
405
  std::memcpy(&f16, &u16, sizeof(std::uint16_t));
387
406
  return float(f16);
388
407
  #endif
@@ -395,9 +414,12 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
395
414
  #if USEARCH_USE_FP16LIB
396
415
  return fp16_ieee_from_fp32_value(f32);
397
416
  #elif USEARCH_USE_SIMSIMD
398
- return simsimd_compress_f16(f32);
417
+ std::uint16_t result;
418
+ simsimd_f32_to_f16(f32, (simsimd_f16_t*)&result);
419
+ return result;
399
420
  #else
400
- f16_native_t f16 = f16_native_t(f32);
421
+ #warning "It's recommended to use SimSIMD and fp16lib for half-precision numerics"
422
+ _Float16 f16 = _Float16(f32);
401
423
  std::uint16_t u16;
402
424
  std::memcpy(&u16, &f16, sizeof(std::uint16_t));
403
425
  return u16;
@@ -410,15 +432,14 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
410
432
  */
411
433
  inline float bf16_to_f32(std::uint16_t u16) noexcept {
412
434
  #if USEARCH_USE_SIMSIMD
413
- return simsimd_uncompress_bf16(u16);
435
+ return simsimd_bf16_to_f32((simsimd_bf16_t const*)&u16);
414
436
  #else
415
437
  union float_or_unsigned_int_t {
416
438
  float f;
417
439
  unsigned int i;
418
- };
419
- union float_or_unsigned_int_t result_union;
420
- result_union.i = u16 << 16; // Zero extends the mantissa
421
- return result_union.f;
440
+ } conv;
441
+ conv.i = u16 << 16; // Zero extends the mantissa
442
+ return conv.f;
422
443
  #endif
423
444
  }
424
445
 
@@ -428,17 +449,18 @@ inline float bf16_to_f32(std::uint16_t u16) noexcept {
428
449
  */
429
450
  inline std::uint16_t f32_to_bf16(float f32) noexcept {
430
451
  #if USEARCH_USE_SIMSIMD
431
- return simsimd_compress_bf16(f32);
452
+ std::uint16_t result;
453
+ simsimd_f32_to_bf16(f32, (simsimd_bf16_t*)&result);
454
+ return result;
432
455
  #else
433
456
  union float_or_unsigned_int_t {
434
457
  float f;
435
458
  unsigned int i;
436
- };
437
- union float_or_unsigned_int_t value;
438
- value.f = f32;
439
- value.i >>= 16;
440
- value.i &= 0xFFFF;
441
- return (unsigned short)value.i;
459
+ } conv;
460
+ conv.f = f32;
461
+ conv.i >>= 16;
462
+ conv.i &= 0xFFFF;
463
+ return (unsigned short)conv.i;
442
464
  #endif
443
465
  }
444
466
 
@@ -774,9 +796,9 @@ class aligned_allocator_gt {
774
796
  std::size_t alignment = alignment_ak;
775
797
  #if defined(USEARCH_DEFINED_WINDOWS)
776
798
  return (pointer)_aligned_malloc(length_bytes, alignment);
777
- #elif defined(USEARCH_DEFINED_APPLE)
799
+ #elif defined(USEARCH_DEFINED_APPLE) || defined(USEARCH_DEFINED_ANDROID)
778
800
  // Apple Clang keeps complaining that `aligned_alloc` is only available
779
- // with macOS 10.15 and newer, so let's use `posix_memalign` there.
801
+ // with macOS 10.15 and newer or Android API >= 28, so let's use `posix_memalign` there.
780
802
  void* result = nullptr;
781
803
  int status = posix_memalign(&result, alignment, length_bytes);
782
804
  return status == 0 ? (pointer)result : nullptr;
@@ -1183,6 +1205,71 @@ template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
1183
1205
  template <> struct cast_gt<b1x8_t, i8_t> : public cast_from_b1x8_gt<i8_t> {};
1184
1206
  template <> struct cast_gt<i8_t, b1x8_t> : public cast_to_b1x8_gt<i8_t> {};
1185
1207
 
1208
+ /**
1209
+ * @brief Type-punned array casting function.
1210
+ * Arguments: input buffer, bytes in input buffer, output buffer.
1211
+ * Returns `true` if the casting was performed successfully, `false` otherwise.
1212
+ */
1213
+ using cast_punned_t = bool (*)(byte_t const*, std::size_t, byte_t*);
1214
+
1215
+ /**
1216
+ * @brief A collection of casting functions for typical vector types.
1217
+ * Covers to/from conversions for boolean, integer, half-precision,
1218
+ * single-precision, and double-precision scalars.
1219
+ */
1220
+ struct casts_punned_t {
1221
+ struct group_t {
1222
+ cast_punned_t b1x8{};
1223
+ cast_punned_t i8{};
1224
+ cast_punned_t f16{};
1225
+ cast_punned_t f32{};
1226
+ cast_punned_t f64{};
1227
+
1228
+ cast_punned_t operator[](scalar_kind_t scalar_kind) const noexcept {
1229
+ switch (scalar_kind) {
1230
+ case scalar_kind_t::f64_k: return f64;
1231
+ case scalar_kind_t::f32_k: return f32;
1232
+ case scalar_kind_t::f16_k: return f16;
1233
+ case scalar_kind_t::bf16_k: return f16;
1234
+ case scalar_kind_t::i8_k: return i8;
1235
+ case scalar_kind_t::b1x8_k: return b1x8;
1236
+ default: return nullptr;
1237
+ }
1238
+ }
1239
+
1240
+ } from, to;
1241
+
1242
+ template <typename scalar_at> static casts_punned_t make() noexcept {
1243
+ casts_punned_t result;
1244
+
1245
+ result.from.b1x8 = &cast_gt<b1x8_t, scalar_at>::try_;
1246
+ result.from.i8 = &cast_gt<i8_t, scalar_at>::try_;
1247
+ result.from.f16 = &cast_gt<f16_t, scalar_at>::try_;
1248
+ result.from.f32 = &cast_gt<f32_t, scalar_at>::try_;
1249
+ result.from.f64 = &cast_gt<f64_t, scalar_at>::try_;
1250
+
1251
+ result.to.b1x8 = &cast_gt<scalar_at, b1x8_t>::try_;
1252
+ result.to.i8 = &cast_gt<scalar_at, i8_t>::try_;
1253
+ result.to.f16 = &cast_gt<scalar_at, f16_t>::try_;
1254
+ result.to.f32 = &cast_gt<scalar_at, f32_t>::try_;
1255
+ result.to.f64 = &cast_gt<scalar_at, f64_t>::try_;
1256
+
1257
+ return result;
1258
+ }
1259
+
1260
+ static casts_punned_t make(scalar_kind_t scalar_kind) noexcept {
1261
+ switch (scalar_kind) {
1262
+ case scalar_kind_t::f64_k: return casts_punned_t::make<f64_t>();
1263
+ case scalar_kind_t::f32_k: return casts_punned_t::make<f32_t>();
1264
+ case scalar_kind_t::f16_k: return casts_punned_t::make<f16_t>();
1265
+ case scalar_kind_t::bf16_k: return casts_punned_t::make<bf16_t>();
1266
+ case scalar_kind_t::i8_k: return casts_punned_t::make<i8_t>();
1267
+ case scalar_kind_t::b1x8_k: return casts_punned_t::make<b1x8_t>();
1268
+ default: return {};
1269
+ }
1270
+ }
1271
+ };
1272
+
1186
1273
  /* Don't complain if the vectorization of the inner loops fails:
1187
1274
  *
1188
1275
  * > warning: loop not vectorized: the optimizer was unable to perform the requested transformation;
@@ -1910,32 +1997,33 @@ class metric_punned_t {
1910
1997
  * @brief View over a potentially-strided memory buffer, containing a row-major matrix.
1911
1998
  */
1912
1999
  template <typename scalar_at> //
1913
- class vectors_view_gt {
2000
+ class matrix_slice_gt {
1914
2001
  using scalar_t = scalar_at;
2002
+ using byte_addressable_t = typename std::conditional<std::is_const<scalar_t>::value, byte_t const, byte_t>::type;
1915
2003
 
1916
- scalar_t const* begin_{};
2004
+ scalar_t* begin_{};
1917
2005
  std::size_t dimensions_{};
1918
2006
  std::size_t count_{};
1919
2007
  std::size_t stride_bytes_{};
1920
2008
 
1921
2009
  public:
1922
- vectors_view_gt() noexcept = default;
1923
- vectors_view_gt(vectors_view_gt const&) noexcept = default;
1924
- vectors_view_gt& operator=(vectors_view_gt const&) noexcept = default;
2010
+ matrix_slice_gt() noexcept = default;
2011
+ matrix_slice_gt(matrix_slice_gt const&) noexcept = default;
2012
+ matrix_slice_gt& operator=(matrix_slice_gt const&) noexcept = default;
1925
2013
 
1926
- vectors_view_gt(scalar_t const* begin, std::size_t dimensions, std::size_t count = 1) noexcept
1927
- : vectors_view_gt(begin, dimensions, count, dimensions * sizeof(scalar_at)) {}
2014
+ matrix_slice_gt(scalar_t* begin, std::size_t dimensions, std::size_t count = 1) noexcept
2015
+ : matrix_slice_gt(begin, dimensions, count, dimensions * sizeof(scalar_at)) {}
1928
2016
 
1929
- vectors_view_gt(scalar_t const* begin, std::size_t dimensions, std::size_t count, std::size_t stride_bytes) noexcept
2017
+ matrix_slice_gt(scalar_t* begin, std::size_t dimensions, std::size_t count, std::size_t stride_bytes) noexcept
1930
2018
  : begin_(begin), dimensions_(dimensions), count_(count), stride_bytes_(stride_bytes) {}
1931
2019
 
1932
2020
  explicit operator bool() const noexcept { return begin_; }
1933
2021
  std::size_t size() const noexcept { return count_; }
1934
2022
  std::size_t dimensions() const noexcept { return dimensions_; }
1935
2023
  std::size_t stride() const noexcept { return stride_bytes_; }
1936
- scalar_t const* data() const noexcept { return begin_; }
1937
- scalar_t const* at(std::size_t i) const noexcept {
1938
- return reinterpret_cast<scalar_t const*>(reinterpret_cast<byte_t const*>(begin_) + i * stride_bytes_);
2024
+ scalar_t* data() const noexcept { return begin_; }
2025
+ scalar_t* at(std::size_t i) const noexcept {
2026
+ return reinterpret_cast<scalar_t*>(reinterpret_cast<byte_addressable_t*>(begin_) + i * stride_bytes_);
1939
2027
  }
1940
2028
  };
1941
2029
 
@@ -1944,7 +2032,7 @@ struct exact_offset_and_distance_t {
1944
2032
  f32_t distance;
1945
2033
  };
1946
2034
 
1947
- using exact_search_results_t = vectors_view_gt<exact_offset_and_distance_t>;
2035
+ using exact_search_results_t = matrix_slice_gt<exact_offset_and_distance_t>;
1948
2036
 
1949
2037
  /**
1950
2038
  * @brief Helper-structure for exact search operations.
@@ -1965,9 +2053,9 @@ class exact_search_t {
1965
2053
 
1966
2054
  public:
1967
2055
  template <typename scalar_at, typename executor_at = dummy_executor_t, typename progress_at = dummy_progress_t>
1968
- exact_search_results_t operator()( //
1969
- vectors_view_gt<scalar_at> dataset, vectors_view_gt<scalar_at> queries, //
1970
- std::size_t wanted, metric_punned_t const& metric, //
2056
+ exact_search_results_t operator()( //
2057
+ matrix_slice_gt<scalar_at const> dataset, matrix_slice_gt<scalar_at const> queries, //
2058
+ std::size_t wanted, metric_punned_t const& metric, //
1971
2059
  executor_at&& executor = executor_at{}, progress_at&& progress = progress_at{}) {
1972
2060
  return operator()( //
1973
2061
  metric, //