usearch 2.11.1 → 2.13.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.
- package/README.md +100 -30
- package/binding.gyp +11 -12
- package/c/README.md +3 -0
- package/c/lib.cpp +50 -13
- package/c/test.c +60 -55
- package/c/usearch.h +16 -0
- package/cpp/CMakeLists.txt +6 -0
- package/cpp/README.md +34 -13
- package/cpp/bench.cpp +99 -47
- package/cpp/test.cpp +947 -132
- package/include/usearch/index.hpp +895 -273
- package/include/usearch/index_dense.hpp +418 -156
- package/include/usearch/index_plugins.hpp +99 -49
- package/javascript/dist/cjs/usearch.d.ts +39 -1
- package/javascript/dist/cjs/usearch.js +11 -1
- package/javascript/dist/esm/usearch.d.ts +39 -1
- package/javascript/dist/esm/usearch.js +11 -1
- package/javascript/tsconfig-esm.json +3 -2
- package/javascript/usearch.ts +18 -7
- package/package.json +2 -2
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/.cmake-format.py +19 -0
- package/simsimd/CONTRIBUTING.md +49 -8
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +10 -5
- package/simsimd/Package.swift +16 -0
- package/simsimd/README.md +69 -15
- package/simsimd/binding.gyp +14 -19
- package/simsimd/build.rs +2 -0
- package/simsimd/c/lib.c +18 -0
- package/simsimd/include/module.modulemap +4 -0
- package/simsimd/include/simsimd/binary.h +2 -0
- package/simsimd/include/simsimd/dot.h +466 -32
- package/simsimd/include/simsimd/probability.h +20 -10
- package/simsimd/include/simsimd/simsimd.h +444 -98
- package/simsimd/include/simsimd/spatial.h +480 -44
- package/simsimd/include/simsimd/types.h +161 -39
- package/simsimd/javascript/simsimd.ts +26 -2
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/cosine.rs +8 -2
- package/simsimd/rust/benches/native.rs +240 -2
- package/simsimd/rust/benches/sqeuclidean.rs +8 -2
- package/simsimd/rust/lib.rs +50 -13
- package/simsimd/swift/source/SimSIMD.swift +123 -0
- package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
- package/prebuilds/win32-ia32/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
- 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
|
|
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>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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_(
|
|
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_(
|
|
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_(
|
|
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
|
-
|
|
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
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|
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
|
|
1381
|
+
using metric_routed_t = result_t (metric_punned_t::*)(uptr_t, uptr_t) const;
|
|
1347
1382
|
|
|
1348
|
-
|
|
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
|
|
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
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
|
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<
|
|
1537
|
-
: reinterpret_cast<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/javascript/usearch.ts
CHANGED
|
@@ -609,14 +609,15 @@ function exactSearch(
|
|
|
609
609
|
}
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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.
|
|
3
|
+
"version": "2.13.0",
|
|
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": "
|
|
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
|
|
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
|