usearch 2.11.1 → 2.13.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 (50) hide show
  1. package/README.md +100 -30
  2. package/binding.gyp +11 -12
  3. package/c/README.md +3 -0
  4. package/c/lib.cpp +50 -13
  5. package/c/test.c +60 -55
  6. package/c/usearch.h +16 -0
  7. package/cpp/CMakeLists.txt +6 -0
  8. package/cpp/README.md +34 -13
  9. package/cpp/bench.cpp +99 -47
  10. package/cpp/test.cpp +947 -132
  11. package/include/usearch/index.hpp +894 -272
  12. package/include/usearch/index_dense.hpp +418 -156
  13. package/include/usearch/index_plugins.hpp +99 -49
  14. package/javascript/dist/cjs/usearch.d.ts +39 -1
  15. package/javascript/dist/cjs/usearch.js +11 -1
  16. package/javascript/dist/esm/usearch.d.ts +39 -1
  17. package/javascript/dist/esm/usearch.js +11 -1
  18. package/javascript/tsconfig-esm.json +3 -2
  19. package/javascript/usearch.ts +18 -7
  20. package/package.json +2 -2
  21. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  22. package/prebuilds/linux-arm64/usearch.node +0 -0
  23. package/prebuilds/linux-x64/usearch.node +0 -0
  24. package/simsimd/.cmake-format.py +19 -0
  25. package/simsimd/CONTRIBUTING.md +49 -8
  26. package/simsimd/Cargo.lock +1 -1
  27. package/simsimd/Cargo.toml +10 -5
  28. package/simsimd/Package.swift +16 -0
  29. package/simsimd/README.md +69 -15
  30. package/simsimd/binding.gyp +14 -19
  31. package/simsimd/build.rs +2 -0
  32. package/simsimd/c/lib.c +18 -0
  33. package/simsimd/include/module.modulemap +4 -0
  34. package/simsimd/include/simsimd/binary.h +2 -0
  35. package/simsimd/include/simsimd/dot.h +466 -32
  36. package/simsimd/include/simsimd/probability.h +20 -10
  37. package/simsimd/include/simsimd/simsimd.h +444 -98
  38. package/simsimd/include/simsimd/spatial.h +480 -44
  39. package/simsimd/include/simsimd/types.h +161 -39
  40. package/simsimd/javascript/simsimd.ts +26 -2
  41. package/simsimd/package.json +1 -1
  42. package/simsimd/rust/benches/cosine.rs +8 -2
  43. package/simsimd/rust/benches/native.rs +240 -2
  44. package/simsimd/rust/benches/sqeuclidean.rs +8 -2
  45. package/simsimd/rust/lib.rs +50 -13
  46. package/simsimd/swift/source/SimSIMD.swift +123 -0
  47. package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
  48. package/prebuilds/win32-ia32/usearch.node +0 -0
  49. package/prebuilds/win32-x64/usearch.node +0 -0
  50. package/simsimd/.releaserc +0 -87
@@ -3,13 +3,9 @@
3
3
  #include <float.h> // `_Float16`
4
4
  #include <stdlib.h> // `aligned_alloc`
5
5
 
6
+ #include <atomic> // `std::atomic`
6
7
  #include <cstring> // `std::strncmp`
7
- #include <numeric> // `std::iota`
8
8
  #include <thread> // `std::thread`
9
- #include <vector> // `std::vector`
10
-
11
- #include <atomic> // `std::atomic`
12
- #include <thread> // `std::thread`
13
9
 
14
10
  #include <usearch/index.hpp> // `expected_gt` and macros
15
11
 
@@ -46,7 +42,12 @@
46
42
 
47
43
  #if USEARCH_USE_SIMSIMD
48
44
  // Propagate the `f16` settings
45
+ #if !defined(SIMSIMD_NATIVE_F16)
49
46
  #define SIMSIMD_NATIVE_F16 !USEARCH_USE_FP16LIB
47
+ #endif
48
+
49
+ // Overwrite the dynamic dispatch settings
50
+ #undef SIMSIMD_DYNAMIC_DISPATCH
50
51
  #define SIMSIMD_DYNAMIC_DISPATCH 0
51
52
  // No problem, if some of the functions are unused or undefined
52
53
  #pragma GCC diagnostic push
@@ -72,7 +73,9 @@ class f16_bits_t;
72
73
  class i8_converted_t;
73
74
 
74
75
  #if !USEARCH_USE_FP16LIB
75
- #if defined(USEARCH_DEFINED_ARM)
76
+ #if USEARCH_USE_SIMSIMD
77
+ using f16_native_t = simsimd_f16_t;
78
+ #elif defined(USEARCH_DEFINED_ARM)
76
79
  using f16_native_t = __fp16;
77
80
  #else
78
81
  using f16_native_t = _Float16;
@@ -331,10 +334,13 @@ class f16_bits_t {
331
334
  inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
332
335
 
333
336
  inline f16_bits_t(i8_converted_t) noexcept;
337
+ inline f16_bits_t(std::int8_t v) noexcept : uint16_(v) {}
334
338
  inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
335
339
  inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
336
340
  inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(static_cast<float>(v))) {}
337
341
 
342
+ inline bool operator<(const f16_bits_t& other) const noexcept { return float(*this) < float(other); }
343
+
338
344
  inline f16_bits_t operator+(f16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
339
345
  inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
340
346
  inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
@@ -378,14 +384,16 @@ class executor_stl_t {
378
384
 
379
385
  struct jthread_t {
380
386
  std::thread native_;
387
+ bool initialized_ = false;
381
388
 
382
389
  jthread_t() = default;
383
390
  jthread_t(jthread_t&&) = default;
384
391
  jthread_t(jthread_t const&) = delete;
385
- template <typename callable_at> jthread_t(callable_at&& func) : native_([=]() { func(); }) {}
392
+ template <typename callable_at>
393
+ jthread_t(callable_at&& func) : native_([=]() { func(); }), initialized_(true) {}
386
394
 
387
395
  ~jthread_t() {
388
- if (native_.joinable())
396
+ if (initialized_ && native_.joinable())
389
397
  native_.join();
390
398
  }
391
399
  };
@@ -410,13 +418,13 @@ class executor_stl_t {
410
418
  */
411
419
  template <typename thread_aware_function_at>
412
420
  void fixed(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
413
- std::vector<jthread_t> threads_pool;
421
+ buffer_gt<jthread_t> threads_pool(threads_count_ - 1); // Allocate space for threads minus the main thread
414
422
  std::size_t tasks_per_thread = tasks;
415
423
  std::size_t threads_count = (std::min)(threads_count_, tasks);
416
424
  if (threads_count > 1) {
417
425
  tasks_per_thread = (tasks / threads_count) + ((tasks % threads_count) != 0);
418
426
  for (std::size_t thread_idx = 1; thread_idx < threads_count; ++thread_idx) {
419
- threads_pool.emplace_back([=]() {
427
+ new (&threads_pool[thread_idx - 1]) jthread_t([=]() {
420
428
  for (std::size_t task_idx = thread_idx * tasks_per_thread;
421
429
  task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
422
430
  thread_aware_function(thread_idx, task_idx);
@@ -435,14 +443,14 @@ class executor_stl_t {
435
443
  */
436
444
  template <typename thread_aware_function_at>
437
445
  void dynamic(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
438
- std::vector<jthread_t> threads_pool;
446
+ buffer_gt<jthread_t> threads_pool(threads_count_ - 1);
439
447
  std::size_t tasks_per_thread = tasks;
440
448
  std::size_t threads_count = (std::min)(threads_count_, tasks);
441
449
  std::atomic_bool stop{false};
442
450
  if (threads_count > 1) {
443
451
  tasks_per_thread = (tasks / threads_count) + ((tasks % threads_count) != 0);
444
452
  for (std::size_t thread_idx = 1; thread_idx < threads_count; ++thread_idx) {
445
- threads_pool.emplace_back([=, &stop]() {
453
+ new (&threads_pool[thread_idx - 1]) jthread_t([=, &stop]() {
446
454
  for (std::size_t task_idx = thread_idx * tasks_per_thread;
447
455
  task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread) &&
448
456
  !stop.load(std::memory_order_relaxed);
@@ -467,9 +475,9 @@ class executor_stl_t {
467
475
  void parallel(thread_aware_function_at&& thread_aware_function) noexcept(false) {
468
476
  if (threads_count_ == 1)
469
477
  return thread_aware_function(0);
470
- std::vector<jthread_t> threads_pool;
478
+ buffer_gt<jthread_t> threads_pool(threads_count_ - 1);
471
479
  for (std::size_t thread_idx = 1; thread_idx < threads_count_; ++thread_idx)
472
- threads_pool.emplace_back([=]() { thread_aware_function(thread_idx); });
480
+ new (&threads_pool[thread_idx - 1]) jthread_t([=]() { thread_aware_function(thread_idx); });
473
481
  thread_aware_function(0);
474
482
  }
475
483
  };
@@ -557,7 +565,8 @@ using executor_default_t = executor_stl_t;
557
565
  #endif
558
566
 
559
567
  /**
560
- * @brief Uses OS-specific APIs for aligned memory allocations.
568
+ * @brief Uses OS-specific APIs for aligned memory allocations.
569
+ * Available since C11, but only C++17, so we wrap the C version.
561
570
  */
562
571
  template <typename element_at = char, std::size_t alignment_ak = 64> //
563
572
  class aligned_allocator_gt {
@@ -574,12 +583,18 @@ class aligned_allocator_gt {
574
583
 
575
584
  pointer allocate(size_type length) const {
576
585
  std::size_t length_bytes = alignment_ak * divide_round_up<alignment_ak>(length * sizeof(value_type));
586
+ // Avoid overflow
587
+ if (length > length_bytes)
588
+ return nullptr;
577
589
  std::size_t alignment = alignment_ak;
578
- // void* result = nullptr;
579
- // int status = posix_memalign(&result, alignment, length_bytes);
580
- // return status == 0 ? (pointer)result : nullptr;
581
590
  #if defined(USEARCH_DEFINED_WINDOWS)
582
591
  return (pointer)_aligned_malloc(length_bytes, alignment);
592
+ #elif defined(USEARCH_DEFINED_APPLE)
593
+ // Apple Clang keeps complaining that `aligned_alloc` is only available
594
+ // with macOS 10.15 and newer, so let's use `posix_memalign` there.
595
+ void* result = nullptr;
596
+ int status = posix_memalign(&result, alignment, length_bytes);
597
+ return status == 0 ? (pointer)result : nullptr;
583
598
  #else
584
599
  return (pointer)aligned_alloc(alignment, length_bytes);
585
600
  #endif
@@ -596,6 +611,10 @@ class aligned_allocator_gt {
596
611
 
597
612
  using aligned_allocator_t = aligned_allocator_gt<>;
598
613
 
614
+ /**
615
+ * @brief A simple RAM-page allocator that uses the OS-specific APIs for memory allocation.
616
+ * Shouldn't be used frequently, as system calls are slow.
617
+ */
599
618
  class page_allocator_t {
600
619
  public:
601
620
  static constexpr std::size_t page_size() { return 4096; }
@@ -859,7 +878,7 @@ template <typename mutex_at = unfair_shared_mutex_t> class shared_lock_gt {
859
878
  * avoiding unnecessary conversions.
860
879
  */
861
880
  template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
862
- inline bool operator()(byte_t const* input, std::size_t dim, byte_t* output) const {
881
+ static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
863
882
  from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
864
883
  to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
865
884
  auto converter = [](from_scalar_at from) { return to_scalar_at(from); };
@@ -869,29 +888,30 @@ template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
869
888
  };
870
889
 
871
890
  template <> struct cast_gt<f32_t, f32_t> {
872
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
891
+ static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
873
892
  };
874
893
 
875
894
  template <> struct cast_gt<f64_t, f64_t> {
876
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
895
+ static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
877
896
  };
878
897
 
879
898
  template <> struct cast_gt<f16_bits_t, f16_bits_t> {
880
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
899
+ static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
881
900
  };
882
901
 
883
902
  template <> struct cast_gt<i8_t, i8_t> {
884
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
903
+ static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
885
904
  };
886
905
 
887
906
  template <> struct cast_gt<b1x8_t, b1x8_t> {
888
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
907
+ static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
889
908
  };
890
909
 
891
910
  template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
892
- inline bool operator()(byte_t const* input, std::size_t dim, byte_t* output) const {
911
+ inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
893
912
  from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
894
913
  unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
914
+ std::memset(typed_output, 0, dim / CHAR_BIT);
895
915
  for (std::size_t i = 0; i != dim; ++i)
896
916
  // Converting from scalar types to boolean isn't trivial and depends on the type.
897
917
  // The most common case is to consider all positive values as `true` and all others as `false`.
@@ -909,7 +929,7 @@ template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
909
929
  };
910
930
 
911
931
  template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
912
- inline bool operator()(byte_t const* input, std::size_t dim, byte_t* output) const {
932
+ static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
913
933
  unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
914
934
  to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
915
935
  for (std::size_t i = 0; i != dim; ++i)
@@ -949,12 +969,13 @@ class i8_converted_t {
949
969
  inline explicit operator std::int32_t() const noexcept { return int8_; }
950
970
  inline explicit operator std::int64_t() const noexcept { return int8_; }
951
971
 
972
+ // Even with `f16_t` arguments we may want to use `f32_t` for clamping and comparions.
952
973
  inline i8_converted_t(f16_t v)
953
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
974
+ : int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
954
975
  inline i8_converted_t(f32_t v)
955
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
976
+ : int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
956
977
  inline i8_converted_t(f64_t v)
957
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
978
+ : int8_(static_cast<std::int8_t>(usearch::clamp<f64_t>(v * divisor_k, min_k, max_k))) {}
958
979
  };
959
980
 
960
981
  f16_bits_t::f16_bits_t(i8_converted_t v) noexcept : uint16_(f32_to_f16(v)) {}
@@ -967,6 +988,16 @@ template <> struct cast_gt<f16_t, i8_t> : public cast_gt<f16_t, i8_converted_t>
967
988
  template <> struct cast_gt<f32_t, i8_t> : public cast_gt<f32_t, i8_converted_t> {};
968
989
  template <> struct cast_gt<f64_t, i8_t> : public cast_gt<f64_t, i8_converted_t> {};
969
990
 
991
+ /* Don't complain if the vectorization of the inner loops fails:
992
+ *
993
+ * > warning: loop not vectorized: the optimizer was unable to perform the requested transformation;
994
+ * > the transformation might be disabled or specified as part of an unsupported transformation ordering
995
+ */
996
+ #if defined(__clang__)
997
+ #pragma clang diagnostic push
998
+ #pragma clang diagnostic ignored "-Wpass-failed"
999
+ #endif
1000
+
970
1001
  /**
971
1002
  * @brief Inner (Dot) Product distance.
972
1003
  */
@@ -1151,18 +1182,21 @@ template <typename scalar_at = std::int32_t, typename result_at = float> struct
1151
1182
  using scalar_t = scalar_at;
1152
1183
  using result_t = result_at;
1153
1184
  static_assert(!std::is_floating_point<scalar_t>::value, "Jaccard distance requires integral scalars");
1185
+ static_assert(std::is_floating_point<result_t>::value, "Jaccard distance returns a fraction");
1154
1186
 
1155
1187
  inline result_t operator()( //
1156
1188
  scalar_t const* a, scalar_t const* b, std::size_t a_length, std::size_t b_length) const noexcept {
1157
- result_t intersection{};
1189
+ std::size_t intersection{};
1158
1190
  std::size_t i{};
1159
1191
  std::size_t j{};
1160
1192
  while (i != a_length && j != b_length) {
1161
- intersection += a[i] == b[j];
1162
- i += a[i] < b[j];
1163
- j += a[i] >= b[j];
1193
+ scalar_t ai = a[i];
1194
+ scalar_t bj = b[j];
1195
+ intersection += ai == bj;
1196
+ i += ai < bj;
1197
+ j += ai >= bj;
1164
1198
  }
1165
- return 1 - intersection / (a_length + b_length - intersection);
1199
+ return 1 - static_cast<result_t>(intersection) / (a_length + b_length - intersection);
1166
1200
  }
1167
1201
  };
1168
1202
 
@@ -1290,7 +1324,8 @@ struct l2sq_i8_t {
1290
1324
  template <typename scalar_at = float, typename result_at = scalar_at> struct metric_haversine_gt {
1291
1325
  using scalar_t = scalar_at;
1292
1326
  using result_t = result_at;
1293
- static_assert(!std::is_integral<scalar_t>::value, "Latitude and longitude must be floating-node");
1327
+ static_assert(!std::is_integral<scalar_t>::value && !std::is_same<scalar_t, f16_t>::value,
1328
+ "Latitude and longitude must be floating-node");
1294
1329
 
1295
1330
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t = 2) const noexcept {
1296
1331
  result_t lat_a = a[0], lon_a = a[1];
@@ -1343,9 +1378,9 @@ class metric_punned_t {
1343
1378
  /// Distance function that takes two arrays and some callback state and returns a scalar.
1344
1379
  using metric_array_array_state_t = result_t (*)(uptr_t, uptr_t, uptr_t);
1345
1380
  /// Distance function callback, like `metric_array_array_size_t`, but depends on member variables.
1346
- using metric_rounted_t = result_t (metric_punned_t::*)(uptr_t, uptr_t) const;
1381
+ using metric_routed_t = result_t (metric_punned_t::*)(uptr_t, uptr_t) const;
1347
1382
 
1348
- metric_rounted_t metric_routed_ = nullptr;
1383
+ metric_routed_t metric_routed_ = nullptr;
1349
1384
  uptr_t metric_ptr_ = 0;
1350
1385
  uptr_t metric_third_arg_ = 0;
1351
1386
 
@@ -1439,7 +1474,7 @@ class metric_punned_t {
1439
1474
  }
1440
1475
 
1441
1476
  /**
1442
- * @brief Creates a metric using the provided function pointer for a statefull metric.
1477
+ * @brief Creates a metric using the provided function pointer for a stateful metric.
1443
1478
  * The third argument is the state that will be passed to the metric function.
1444
1479
  *
1445
1480
  * @param metric_uintptr The function pointer to the metric function.
@@ -1448,9 +1483,9 @@ class metric_punned_t {
1448
1483
  * @param scalar_kind The kind of scalar to use.
1449
1484
  * @return A metric object that can be used to compute distances between vectors.
1450
1485
  */
1451
- inline static metric_punned_t statefull(std::uintptr_t metric_uintptr, std::uintptr_t metric_state,
1452
- metric_kind_t metric_kind = metric_kind_t::unknown_k,
1453
- scalar_kind_t scalar_kind = scalar_kind_t::unknown_k) noexcept {
1486
+ inline static metric_punned_t stateful(std::uintptr_t metric_uintptr, std::uintptr_t metric_state,
1487
+ metric_kind_t metric_kind = metric_kind_t::unknown_k,
1488
+ scalar_kind_t scalar_kind = scalar_kind_t::unknown_k) noexcept {
1454
1489
  metric_punned_t metric;
1455
1490
  metric.metric_routed_ = &metric_punned_t::invoke_array_array_third;
1456
1491
  metric.metric_ptr_ = metric_uintptr;
@@ -1467,7 +1502,7 @@ class metric_punned_t {
1467
1502
  inline explicit operator bool() const noexcept { return metric_routed_ && metric_ptr_; }
1468
1503
 
1469
1504
  /**
1470
- * @brief Checks fi we've failed to initialized the metric with provided arguments.
1505
+ * @brief Checks if we've failed to initialize the metric with provided arguments.
1471
1506
  *
1472
1507
  * It's different from `operator bool()` when it comes to explicitly uninitialized metrics.
1473
1508
  * It's a common case, where a NULL state is created only to be overwritten later, when
@@ -1483,7 +1518,13 @@ class metric_punned_t {
1483
1518
  switch (isa_kind_) {
1484
1519
  case simsimd_cap_serial_k: return "serial";
1485
1520
  case simsimd_cap_neon_k: return "neon";
1521
+ case simsimd_cap_neon_i8_k: return "neon_i8";
1522
+ case simsimd_cap_neon_f16_k: return "neon_f16";
1523
+ case simsimd_cap_neon_bf16_k: return "neon_bf16";
1486
1524
  case simsimd_cap_sve_k: return "sve";
1525
+ case simsimd_cap_sve_i8_k: return "sve_i8";
1526
+ case simsimd_cap_sve_f16_k: return "sve_f16";
1527
+ case simsimd_cap_sve_bf16_k: return "sve_bf16";
1487
1528
  case simsimd_cap_haswell_k: return "haswell";
1488
1529
  case simsimd_cap_skylake_k: return "skylake";
1489
1530
  case simsimd_cap_ice_k: return "ice";
@@ -1533,8 +1574,8 @@ class metric_punned_t {
1533
1574
 
1534
1575
  std::memcpy(&metric_ptr_, &simd_metric, sizeof(simd_metric));
1535
1576
  metric_routed_ = metric_kind_ == metric_kind_t::ip_k
1536
- ? reinterpret_cast<metric_rounted_t>(&metric_punned_t::invoke_simsimd_reverse)
1537
- : reinterpret_cast<metric_rounted_t>(&metric_punned_t::invoke_simsimd);
1577
+ ? reinterpret_cast<metric_routed_t>(&metric_punned_t::invoke_simsimd_reverse)
1578
+ : reinterpret_cast<metric_routed_t>(&metric_punned_t::invoke_simsimd);
1538
1579
  isa_kind_ = simd_kind;
1539
1580
  return true;
1540
1581
  }
@@ -1542,9 +1583,14 @@ class metric_punned_t {
1542
1583
  static simsimd_capability_t static_capabilities = simsimd_capabilities();
1543
1584
  return configure_with_simsimd(static_capabilities);
1544
1585
  }
1545
- result_t invoke_simsimd(uptr_t a, uptr_t b) const noexcept {
1586
+
1587
+ #if defined(USEARCH_DEFINED_CLANG) || defined(USEARCH_DEFINED_GCC)
1588
+ __attribute__((no_sanitize("all")))
1589
+ #endif
1590
+ result_t
1591
+ invoke_simsimd(uptr_t a, uptr_t b) const noexcept {
1546
1592
  simsimd_distance_t result;
1547
- // Here `reinterpret_cast` raises warning... we know what we are doing!
1593
+ // Here `reinterpret_cast` raises warning and UBSan reports an issue... we know what we are doing!
1548
1594
  auto function_pointer = (simsimd_metric_punned_t)(metric_ptr_);
1549
1595
  function_pointer(reinterpret_cast<void const*>(a), reinterpret_cast<void const*>(b), metric_third_arg_,
1550
1596
  &result);
@@ -1608,9 +1654,7 @@ class metric_punned_t {
1608
1654
  }
1609
1655
  case metric_kind_t::haversine_k: {
1610
1656
  switch (scalar_kind_) {
1611
- case scalar_kind_t::f16_k:
1612
- metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f16_t, f32_t>>;
1613
- break;
1657
+ case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Having half-precision 2D coordinates is a bit silly.
1614
1658
  case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f32_t>>; break;
1615
1659
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f64_t>>; break;
1616
1660
  default: metric_ptr_ = 0; break;
@@ -1643,6 +1687,11 @@ class metric_punned_t {
1643
1687
  }
1644
1688
  };
1645
1689
 
1690
+ /* Allow complaining about vectorization after this point. */
1691
+ #if defined(__clang__)
1692
+ #pragma clang diagnostic pop
1693
+ #endif
1694
+
1646
1695
  /**
1647
1696
  * @brief View over a potentially-strided memory buffer, containing a row-major matrix.
1648
1697
  */
@@ -1953,6 +2002,7 @@ class flat_hash_multi_set_gt {
1953
2002
  clear(); // Clear all elements
1954
2003
  if (data_)
1955
2004
  allocator_t{}.deallocate(data_, buckets_ * bytes_per_bucket());
2005
+ data_ = nullptr;
1956
2006
  buckets_ = 0;
1957
2007
  populated_slots_ = 0;
1958
2008
  capacity_slots_ = 0;
@@ -209,4 +209,42 @@ export declare class Index {
209
209
  */
210
210
  view(path: string): void;
211
211
  }
212
- export {};
212
+ /**
213
+ * Performs an exact search on the given dataset to find the best matching vectors for each query.
214
+ *
215
+ * @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} dataset - The dataset containing vectors to be searched. It can be a TypedArray or an array of arrays.
216
+ * @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} queries - The queries containing vectors to search for in the dataset. It can be a TypedArray or an array of arrays.
217
+ * @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
218
+ * @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
219
+ * @param {MetricKind} metric - The distance metric to be used for the search.
220
+ * @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
221
+ * @throws Will throw an error if `dimensions` and `count` are not positive integers.
222
+ * @throws Will throw an error if `metric` is not a valid MetricKind.
223
+ * @throws Will throw an error if `dataset` and `queries` are not valid input types (TypedArray or an array of arrays).
224
+ * @throws Will throw an error if the sizes of the flattened `dataset` and `queries` are not multiples of `dimensions`.
225
+ * @throws Will throw an error if `count` is greater than the number of vectors in the `dataset`.
226
+ *
227
+ * @example
228
+ * const dataset = [[1.0, 2.0], [3.0, 4.0]]; // Two vectors: [1.0, 2.0] and [3.0, 4.0]
229
+ * const queries = [[1.5, 2.5]]; // One vector: [1.5, 2.5]
230
+ * const dimensions = 2; // The number of elements in each vector.
231
+ * const count = 1; // The number of nearest neighbors to return for each query.
232
+ * const metric = MetricKind.IP; // Using the Inner Product distance metric.
233
+ *
234
+ * const result = exactSearch(dataset, queries, dimensions, count, metric);
235
+ * // result might be:
236
+ * // {
237
+ * // keys: BigUint64Array [ 1n ],
238
+ * // distances: Float32Array [ some_value ],
239
+ * // }
240
+ */
241
+ declare function exactSearch(dataset: VectorOrMatrix, queries: VectorOrMatrix, dimensions: number, count: number, metric: MetricKind): Matches | BatchMatches;
242
+ declare const usearch: {
243
+ Index: typeof Index;
244
+ MetricKind: typeof MetricKind;
245
+ ScalarKind: typeof ScalarKind;
246
+ Matches: typeof Matches;
247
+ BatchMatches: typeof BatchMatches;
248
+ exactSearch: typeof exactSearch;
249
+ };
250
+ export default usearch;
@@ -497,7 +497,7 @@ function exactSearch(dataset, queries, dimensions, count, metric) {
497
497
  return new BatchMatches(...result, count);
498
498
  }
499
499
  }
500
- module.exports = {
500
+ const usearch = {
501
501
  Index,
502
502
  MetricKind,
503
503
  ScalarKind,
@@ -505,6 +505,7 @@ module.exports = {
505
505
  BatchMatches,
506
506
  exactSearch,
507
507
  };
508
+ exports.default = usearch;
508
509
  // utility functions to help find native builds
509
510
  function getBuildDir(dir) {
510
511
  if ((0, fs_1.existsSync)(path.join(dir, "build")))
@@ -529,3 +530,12 @@ function getDirName() {
529
530
  catch (e) { }
530
531
  return (0, bindings_1.getRoot)((0, bindings_1.getFileName)());
531
532
  }
533
+ // dummy code for ncc to include the native module
534
+ if (process.uptime() < 0) {
535
+ require(__dirname + "/../../../prebuilds/darwin-arm64+x64/usearch.node");
536
+ require(__dirname + "/../../../prebuilds/linux-arm64/usearch.node");
537
+ require(__dirname + "/../../../prebuilds/linux-x64/usearch.node");
538
+ require(__dirname + "/../../../prebuilds/win32-ia32/usearch.node");
539
+ require(__dirname + "/../../../prebuilds/win32-x64/usearch.node");
540
+ require(__dirname + "/../../../build/Release/usearch.node");
541
+ }
@@ -209,4 +209,42 @@ export declare class Index {
209
209
  */
210
210
  view(path: string): void;
211
211
  }
212
- export {};
212
+ /**
213
+ * Performs an exact search on the given dataset to find the best matching vectors for each query.
214
+ *
215
+ * @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} dataset - The dataset containing vectors to be searched. It can be a TypedArray or an array of arrays.
216
+ * @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} queries - The queries containing vectors to search for in the dataset. It can be a TypedArray or an array of arrays.
217
+ * @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
218
+ * @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
219
+ * @param {MetricKind} metric - The distance metric to be used for the search.
220
+ * @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
221
+ * @throws Will throw an error if `dimensions` and `count` are not positive integers.
222
+ * @throws Will throw an error if `metric` is not a valid MetricKind.
223
+ * @throws Will throw an error if `dataset` and `queries` are not valid input types (TypedArray or an array of arrays).
224
+ * @throws Will throw an error if the sizes of the flattened `dataset` and `queries` are not multiples of `dimensions`.
225
+ * @throws Will throw an error if `count` is greater than the number of vectors in the `dataset`.
226
+ *
227
+ * @example
228
+ * const dataset = [[1.0, 2.0], [3.0, 4.0]]; // Two vectors: [1.0, 2.0] and [3.0, 4.0]
229
+ * const queries = [[1.5, 2.5]]; // One vector: [1.5, 2.5]
230
+ * const dimensions = 2; // The number of elements in each vector.
231
+ * const count = 1; // The number of nearest neighbors to return for each query.
232
+ * const metric = MetricKind.IP; // Using the Inner Product distance metric.
233
+ *
234
+ * const result = exactSearch(dataset, queries, dimensions, count, metric);
235
+ * // result might be:
236
+ * // {
237
+ * // keys: BigUint64Array [ 1n ],
238
+ * // distances: Float32Array [ some_value ],
239
+ * // }
240
+ */
241
+ declare function exactSearch(dataset: VectorOrMatrix, queries: VectorOrMatrix, dimensions: number, count: number, metric: MetricKind): Matches | BatchMatches;
242
+ declare const usearch: {
243
+ Index: typeof Index;
244
+ MetricKind: typeof MetricKind;
245
+ ScalarKind: typeof ScalarKind;
246
+ Matches: typeof Matches;
247
+ BatchMatches: typeof BatchMatches;
248
+ exactSearch: typeof exactSearch;
249
+ };
250
+ export default usearch;
@@ -458,7 +458,7 @@ function exactSearch(dataset, queries, dimensions, count, metric) {
458
458
  return new BatchMatches(...result, count);
459
459
  }
460
460
  }
461
- module.exports = {
461
+ const usearch = {
462
462
  Index,
463
463
  MetricKind,
464
464
  ScalarKind,
@@ -466,6 +466,7 @@ module.exports = {
466
466
  BatchMatches,
467
467
  exactSearch,
468
468
  };
469
+ export default usearch;
469
470
  // utility functions to help find native builds
470
471
  function getBuildDir(dir) {
471
472
  if (existsSync(path.join(dir, "build")))
@@ -490,3 +491,12 @@ function getDirName() {
490
491
  catch (e) { }
491
492
  return getRoot(getFileName());
492
493
  }
494
+ // dummy code for ncc to include the native module
495
+ if (process.uptime() < 0) {
496
+ require(__dirname + "/../../../prebuilds/darwin-arm64+x64/usearch.node");
497
+ require(__dirname + "/../../../prebuilds/linux-arm64/usearch.node");
498
+ require(__dirname + "/../../../prebuilds/linux-x64/usearch.node");
499
+ require(__dirname + "/../../../prebuilds/win32-ia32/usearch.node");
500
+ require(__dirname + "/../../../prebuilds/win32-x64/usearch.node");
501
+ require(__dirname + "/../../../build/Release/usearch.node");
502
+ }
@@ -3,6 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "module": "esnext",
5
5
  "outDir": "dist/esm",
6
- "target": "esnext"
6
+ "target": "esnext",
7
+ "esModuleInterop": true,
7
8
  }
8
- }
9
+ }
@@ -609,14 +609,15 @@ function exactSearch(
609
609
  }
610
610
  }
611
611
 
612
- module.exports = {
613
- Index,
614
- MetricKind,
615
- ScalarKind,
616
- Matches,
617
- BatchMatches,
618
- exactSearch,
612
+ const usearch = {
613
+ Index,
614
+ MetricKind,
615
+ ScalarKind,
616
+ Matches,
617
+ BatchMatches,
618
+ exactSearch,
619
619
  };
620
+ export default usearch;
620
621
 
621
622
  // utility functions to help find native builds
622
623
 
@@ -638,3 +639,13 @@ function getDirName() {
638
639
  } catch (e) { }
639
640
  return getRoot(getFileName());
640
641
  }
642
+
643
+ // dummy code for ncc to include the native module
644
+ if (process.uptime() < 0) {
645
+ require(__dirname + "/../../../prebuilds/darwin-arm64+x64/usearch.node");
646
+ require(__dirname + "/../../../prebuilds/linux-arm64/usearch.node");
647
+ require(__dirname + "/../../../prebuilds/linux-x64/usearch.node");
648
+ require(__dirname + "/../../../prebuilds/win32-ia32/usearch.node");
649
+ require(__dirname + "/../../../prebuilds/win32-x64/usearch.node");
650
+ require(__dirname + "/../../../build/Release/usearch.node");
651
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.11.1",
3
+ "version": "2.13.1",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
@@ -38,7 +38,7 @@
38
38
  "prebuild-single": "prebuildify --napi --strip",
39
39
  "prebuild-arm64": "prebuildify --arch arm64 --napi",
40
40
  "prebuild-darwin-x64+arm64": "prebuildify --arch arm64+x64 --napi --strip",
41
- "build-js": "rm -fr javascript/dist/* && tsc -p javascript/tsconfig-esm.json && tsc -p javascript/tsconfig-cjs.json && cp javascript/dist-package-esm.json javascript/dist/esm/package.json && cp javascript/dist-package-cjs.json javascript/dist/cjs/package.json"
41
+ "build-js": "tsc -p javascript/tsconfig-esm.json && tsc -p javascript/tsconfig-cjs.json && cp javascript/dist-package-esm.json javascript/dist/esm/package.json && cp javascript/dist-package-cjs.json javascript/dist/cjs/package.json"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/bindings": "^1.5.5",
Binary file
Binary file
@@ -0,0 +1,19 @@
1
+ # -----------------------------
2
+ # Options effecting formatting.
3
+ # -----------------------------
4
+ with section("format"):
5
+ # How wide to allow formatted cmake files
6
+ line_width = 120
7
+
8
+ # How many spaces to tab for indent
9
+ tab_size = 4
10
+
11
+ # If true, separate flow control names from their parentheses with a space
12
+ separate_ctrl_name_with_space = True
13
+
14
+ # If true, separate function names from parentheses with a space
15
+ separate_fn_name_with_space = False
16
+
17
+ # If a statement is wrapped to more than one line, than dangle the closing
18
+ # parenthesis on its own line.
19
+ dangle_parens = True