usearch 2.21.2 → 2.21.4
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 +1 -1
- package/include/usearch/index.hpp +13 -6
- package/package.json +1 -1
- 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/CONTRIBUTING.md +16 -9
- package/simsimd/Cargo.toml +7 -11
- package/simsimd/MANIFEST.in +1 -0
- package/simsimd/README.md +21 -20
- package/simsimd/build.rs +4 -3
- package/simsimd/c/lib.c +1 -1
- package/simsimd/include/simsimd/binary.h +11 -10
- package/simsimd/include/simsimd/dot.h +4 -4
- package/simsimd/include/simsimd/geospatial.h +1 -1
- package/simsimd/include/simsimd/mesh.h +1 -1
- package/simsimd/include/simsimd/probability.h +14 -14
- package/simsimd/include/simsimd/simsimd.h +86 -6
- package/simsimd/include/simsimd/sparse.h +60 -105
- package/simsimd/include/simsimd/types.h +4 -3
- package/simsimd/javascript/fallback.ts +10 -14
- package/simsimd/javascript/lib.c +1 -1
- package/simsimd/javascript/simsimd.ts +4 -4
- package/simsimd/package.json +1 -1
- package/simsimd/rust/lib.rs +288 -130
- package/simsimd/scripts/bench.cxx +38 -7
- package/simsimd/scripts/bench.rs +876 -0
- package/simsimd/scripts/test.py +1 -1
- package/simsimd/Cargo.lock +0 -713
- package/simsimd/scripts/bench_cosine.rs +0 -41
- package/simsimd/scripts/bench_native.rs +0 -272
- package/simsimd/scripts/bench_sqeuclidean.rs +0 -41
|
@@ -26,17 +26,19 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Intel Palm Cove was an irrelevant intermediate release extending Skylake with IFMA and VBMI.
|
|
28
28
|
* Intel Willow Cove was an irrelevant intermediate release extending Sunny Cove with VP2INTERSECT,
|
|
29
|
-
*
|
|
29
|
+
* which are not supported by other CPUs to date and are only available in Tiger Lake laptops.
|
|
30
30
|
* Intel Cooper Lake was the only intermediary platform, that supported BF16, but not FP16.
|
|
31
31
|
* It's mostly used in 4-socket and 8-socket high-memory configurations.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
33
|
+
* For us, it makes sense to differentiate only these AVX512 generations:
|
|
34
34
|
* 1. Intel Skylake (pre 2019): supports single-precision dot-products.
|
|
35
35
|
* 2. Intel Ice Lake (2019-2021): advanced integer algorithms.
|
|
36
36
|
* 3. AMD Genoa (2023+): brain-floating point support.
|
|
37
37
|
* 4. Intel Sapphire Rapids (2023+): advanced mixed-precision float processing.
|
|
38
38
|
* 5. AMD Turin (2024+): advanced sparse algorithms.
|
|
39
39
|
*
|
|
40
|
+
* Beyond those, we support AVX2 for old Haswell generation CPUs, and AVX2+VNNI for modern Sierra generation.
|
|
41
|
+
*
|
|
40
42
|
* To list all available macros for x86, take a recent compiler, like GCC 12 and run:
|
|
41
43
|
* gcc-12 -march=sapphirerapids -dM -E - < /dev/null | egrep "SSE|AVX" | sort
|
|
42
44
|
* On Arm machines you may want to check for other flags:
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
* The N2 core is very similar to V2 and is used by Microsoft @b Cobalt.
|
|
81
83
|
* https://developer.arm.com/Processors/Neoverse%20N2
|
|
82
84
|
*
|
|
83
|
-
* On
|
|
85
|
+
* On the consumer side, Apple is the biggest player with mobile @b A chips and desktop @b M chips.
|
|
84
86
|
* The M1 implements Armv8.5-A, both M2 and M3 implement Armv8.6-A, and M4 is expected to have Armv9.1-A.
|
|
85
87
|
*/
|
|
86
88
|
|
|
@@ -89,7 +91,7 @@
|
|
|
89
91
|
|
|
90
92
|
#define SIMSIMD_VERSION_MAJOR 6
|
|
91
93
|
#define SIMSIMD_VERSION_MINOR 5
|
|
92
|
-
#define SIMSIMD_VERSION_PATCH
|
|
94
|
+
#define SIMSIMD_VERSION_PATCH 5
|
|
93
95
|
|
|
94
96
|
/**
|
|
95
97
|
* @brief Removes compile-time dispatching, and replaces it with runtime dispatching.
|
|
@@ -116,6 +118,22 @@
|
|
|
116
118
|
#include <sys/sysctl.h> // `sysctlbyname`
|
|
117
119
|
#endif
|
|
118
120
|
|
|
121
|
+
// Detect POSIX extensions availability for signal handling.
|
|
122
|
+
// POSIX extensions provide `sigaction`, `sigjmp_buf`, and `sigsetjmp` for safe signal handling.
|
|
123
|
+
// These are needed on Linux ARM for safely testing `mrs` instruction availability.
|
|
124
|
+
#if defined(_SIMSIMD_DEFINED_LINUX) && defined(_POSIX_VERSION)
|
|
125
|
+
#include <setjmp.h> // `sigjmp_buf`, `sigsetjmp`, `siglongjmp`
|
|
126
|
+
#include <signal.h> // `sigaction`, `SIGILL`
|
|
127
|
+
#define _SIMSIMD_HAS_POSIX_EXTENSIONS 1
|
|
128
|
+
#else
|
|
129
|
+
#define _SIMSIMD_HAS_POSIX_EXTENSIONS 0
|
|
130
|
+
#endif
|
|
131
|
+
|
|
132
|
+
// On Windows ARM, we use IsProcessorFeaturePresent API for capability detection
|
|
133
|
+
#if defined(_SIMSIMD_DEFINED_WINDOWS) && _SIMSIMD_TARGET_ARM
|
|
134
|
+
#include <processthreadsapi.h> // `IsProcessorFeaturePresent`
|
|
135
|
+
#endif
|
|
136
|
+
|
|
119
137
|
#ifdef __cplusplus
|
|
120
138
|
extern "C" {
|
|
121
139
|
#endif
|
|
@@ -454,6 +472,15 @@ SIMSIMD_PUBLIC simsimd_capability_t _simsimd_capabilities_x86(void) {
|
|
|
454
472
|
#pragma GCC target("arch=armv8.5-a+sve")
|
|
455
473
|
#pragma clang attribute push(__attribute__((target("arch=armv8.5-a+sve"))), apply_to = function)
|
|
456
474
|
|
|
475
|
+
#if _SIMSIMD_HAS_POSIX_EXTENSIONS
|
|
476
|
+
/** @brief SIGILL handler for `mrs` instruction testing on Linux ARM */
|
|
477
|
+
static sigjmp_buf _simsimd_mrs_test_jump_buffer;
|
|
478
|
+
static void _simsimd_mrs_test_sigill_handler(int sig) {
|
|
479
|
+
(void)sig; // Unused parameter
|
|
480
|
+
siglongjmp(_simsimd_mrs_test_jump_buffer, 1);
|
|
481
|
+
}
|
|
482
|
+
#endif
|
|
483
|
+
|
|
457
484
|
/**
|
|
458
485
|
* @brief Function to flush denormalized numbers to zero on Arm CPUs.
|
|
459
486
|
* @note This should be called on each thread before any SIMD operations to avoid performance penalties.
|
|
@@ -504,6 +531,36 @@ SIMSIMD_PUBLIC simsimd_capability_t _simsimd_capabilities_arm(void) {
|
|
|
504
531
|
|
|
505
532
|
#elif defined(_SIMSIMD_DEFINED_LINUX)
|
|
506
533
|
|
|
534
|
+
// Depending on the environment, reading system registers may cause SIGILL.
|
|
535
|
+
// One option to avoid the crash is to use `getauxval(AT_HWCAP)` and `getauxval(AT_HWCAP2)`,
|
|
536
|
+
// Linux APIs, but those aren't as informative as reading the registers directly.
|
|
537
|
+
// So before reading the ID registers, we set up a signal handler to catch SIGILL
|
|
538
|
+
// and probe one of the registers, reverting back to the old signal handler afterwards.
|
|
539
|
+
//
|
|
540
|
+
// This issue was originally observed in: https://github.com/ashvardanian/SimSIMD/issues/279
|
|
541
|
+
#if _SIMSIMD_HAS_POSIX_EXTENSIONS
|
|
542
|
+
struct sigaction action_new, action_old;
|
|
543
|
+
action_new.sa_handler = _simsimd_mrs_test_sigill_handler;
|
|
544
|
+
sigemptyset(&action_new.sa_mask);
|
|
545
|
+
action_new.sa_flags = 0;
|
|
546
|
+
|
|
547
|
+
int mrs_works = 0;
|
|
548
|
+
if (sigaction(SIGILL, &action_new, &action_old) == 0) {
|
|
549
|
+
if (sigsetjmp(_simsimd_mrs_test_jump_buffer, 1) == 0) {
|
|
550
|
+
unsigned long midr_value;
|
|
551
|
+
__asm__ __volatile__("mrs %0, MIDR_EL1" : "=r"(midr_value));
|
|
552
|
+
mrs_works = 1;
|
|
553
|
+
}
|
|
554
|
+
sigaction(SIGILL, &action_old, NULL);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Early exit if `mrs` doesn't work - return conservative NEON-only capabilities
|
|
558
|
+
if (!mrs_works) return (simsimd_capability_t)(simsimd_cap_neon_k | simsimd_cap_serial_k);
|
|
559
|
+
#else // _SIMSIMD_HAS_POSIX_EXTENSIONS
|
|
560
|
+
// Without POSIX signal handlers, fall back to conservative NEON capabilities.
|
|
561
|
+
return (simsimd_capability_t)(simsimd_cap_neon_k | simsimd_cap_serial_k);
|
|
562
|
+
#endif // _SIMSIMD_HAS_POSIX_EXTENSIONS
|
|
563
|
+
|
|
507
564
|
// Read CPUID registers directly
|
|
508
565
|
unsigned long id_aa64isar0_el1 = 0, id_aa64isar1_el1 = 0, id_aa64pfr0_el1 = 0, id_aa64zfr0_el1 = 0;
|
|
509
566
|
|
|
@@ -561,8 +618,31 @@ SIMSIMD_PUBLIC simsimd_capability_t _simsimd_capabilities_arm(void) {
|
|
|
561
618
|
(simsimd_cap_sve2_k * (supports_sve2)) | //
|
|
562
619
|
(simsimd_cap_sve2p1_k * (supports_sve2p1)) | //
|
|
563
620
|
(simsimd_cap_serial_k));
|
|
564
|
-
|
|
565
|
-
|
|
621
|
+
|
|
622
|
+
#elif defined(_SIMSIMD_DEFINED_WINDOWS)
|
|
623
|
+
|
|
624
|
+
unsigned supports_neon = 0, supports_dp = 0;
|
|
625
|
+
|
|
626
|
+
// On Windows ARM, use the `IsProcessorFeaturePresent` API for capability detection.
|
|
627
|
+
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
|
|
628
|
+
#if defined(PF_ARM_V8_INSTRUCTIONS_AVAILABLE)
|
|
629
|
+
supports_neon = IsProcessorFeaturePresent(PF_ARM_V8_INSTRUCTIONS_AVAILABLE);
|
|
630
|
+
#endif
|
|
631
|
+
#if defined(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE)
|
|
632
|
+
supports_dp = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE);
|
|
633
|
+
#endif
|
|
634
|
+
|
|
635
|
+
// Windows API doesn't provide reliable detection for FP16, BF16.
|
|
636
|
+
return (simsimd_capability_t)( //
|
|
637
|
+
(simsimd_cap_neon_k * (supports_neon)) | //
|
|
638
|
+
(simsimd_cap_neon_i8_k * (supports_neon && supports_dp)) | //
|
|
639
|
+
(simsimd_cap_serial_k));
|
|
640
|
+
|
|
641
|
+
#else // Unknown platform
|
|
642
|
+
|
|
643
|
+
// Conservative fallback for unknown platforms: NEON is mandatory in ARMv8-A (ARM64)
|
|
644
|
+
return (simsimd_capability_t)(simsimd_cap_neon_k | simsimd_cap_serial_k);
|
|
645
|
+
|
|
566
646
|
#endif
|
|
567
647
|
}
|
|
568
648
|
|
|
@@ -410,7 +410,7 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_ice( //
|
|
|
410
410
|
simsimd_u8_t u8[64];
|
|
411
411
|
} a_vec, b_vec;
|
|
412
412
|
|
|
413
|
-
while (a + 32
|
|
413
|
+
while (a + 32 <= a_end && b + 32 <= b_end) {
|
|
414
414
|
a_vec.zmm = _mm512_loadu_si512((__m512i const *)a);
|
|
415
415
|
b_vec.zmm = _mm512_loadu_si512((__m512i const *)b);
|
|
416
416
|
|
|
@@ -422,13 +422,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_ice( //
|
|
|
422
422
|
simsimd_u16_t b_max = b_vec.u16[31];
|
|
423
423
|
|
|
424
424
|
// If the slices don't overlap, advance the appropriate pointer
|
|
425
|
-
while (a_max < b_min && a + 64
|
|
425
|
+
while (a_max < b_min && a + 64 <= a_end) {
|
|
426
426
|
a += 32;
|
|
427
427
|
a_vec.zmm = _mm512_loadu_si512((__m512i const *)a);
|
|
428
428
|
a_max = a_vec.u16[31];
|
|
429
429
|
}
|
|
430
430
|
a_min = a_vec.u16[0];
|
|
431
|
-
while (b_max < a_min && b + 64
|
|
431
|
+
while (b_max < a_min && b + 64 <= b_end) {
|
|
432
432
|
b += 32;
|
|
433
433
|
b_vec.zmm = _mm512_loadu_si512((__m512i const *)b);
|
|
434
434
|
b_max = b_vec.u16[31];
|
|
@@ -475,7 +475,7 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_ice( //
|
|
|
475
475
|
simsimd_u8_t u8[64];
|
|
476
476
|
} a_vec, b_vec;
|
|
477
477
|
|
|
478
|
-
while (a + 16
|
|
478
|
+
while (a + 16 <= a_end && b + 16 <= b_end) {
|
|
479
479
|
a_vec.zmm = _mm512_loadu_si512((__m512i const *)a);
|
|
480
480
|
b_vec.zmm = _mm512_loadu_si512((__m512i const *)b);
|
|
481
481
|
|
|
@@ -487,13 +487,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_ice( //
|
|
|
487
487
|
simsimd_u32_t b_max = b_vec.u32[15];
|
|
488
488
|
|
|
489
489
|
// If the slices don't overlap, advance the appropriate pointer
|
|
490
|
-
while (a_max < b_min && a + 32
|
|
490
|
+
while (a_max < b_min && a + 32 <= a_end) {
|
|
491
491
|
a += 16;
|
|
492
492
|
a_vec.zmm = _mm512_loadu_si512((__m512i const *)a);
|
|
493
493
|
a_max = a_vec.u32[15];
|
|
494
494
|
}
|
|
495
495
|
a_min = a_vec.u32[0];
|
|
496
|
-
while (b_max < a_min && b + 32
|
|
496
|
+
while (b_max < a_min && b + 32 <= b_end) {
|
|
497
497
|
b += 16;
|
|
498
498
|
b_vec.zmm = _mm512_loadu_si512((__m512i const *)b);
|
|
499
499
|
b_max = b_vec.u32[15];
|
|
@@ -526,11 +526,11 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_ice( //
|
|
|
526
526
|
|
|
527
527
|
#if SIMSIMD_TARGET_TURIN
|
|
528
528
|
#pragma GCC push_options
|
|
529
|
-
#pragma GCC target("avx2", "avx512f", "avx512vl", "bmi2", "lzcnt", "popcnt", "avx512bw", "avx512vbmi2",
|
|
530
|
-
"avx512vnni", "avx512vp2intersect", "avx512dq")
|
|
531
|
-
#pragma clang attribute push(
|
|
532
|
-
__attribute__((target(
|
|
533
|
-
"avx2,avx512f,avx512vl,bmi2,lzcnt,popcnt,avx512bw,avx512vbmi2,avx512bf16,avx512vnni,avx512vp2intersect,avx512dq"))), \
|
|
529
|
+
#pragma GCC target("avx2", "avx512f", "avx512vl", "bmi", "bmi2", "lzcnt", "popcnt", "avx512bw", "avx512vbmi2", \
|
|
530
|
+
"avx512bf16", "avx512vnni", "avx512vp2intersect", "avx512dq")
|
|
531
|
+
#pragma clang attribute push( \
|
|
532
|
+
__attribute__((target( \
|
|
533
|
+
"avx2,avx512f,avx512vl,bmi,bmi2,lzcnt,popcnt,avx512bw,avx512vbmi2,avx512bf16,avx512vnni,avx512vp2intersect,avx512dq"))), \
|
|
534
534
|
apply_to = function)
|
|
535
535
|
|
|
536
536
|
SIMSIMD_PUBLIC void simsimd_intersect_u16_turin( //
|
|
@@ -538,12 +538,6 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_turin( //
|
|
|
538
538
|
simsimd_size_t a_length, simsimd_size_t b_length, //
|
|
539
539
|
simsimd_distance_t *results) {
|
|
540
540
|
|
|
541
|
-
// The baseline implementation for very small arrays (2 registers or less) can be quite simple:
|
|
542
|
-
if (a_length < 64 && b_length < 64) {
|
|
543
|
-
simsimd_intersect_u16_serial(a, b, a_length, b_length, results);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
541
|
//! There is no such thing as `_mm512_2intersect_epi16`, only the 32-bit variant!
|
|
548
542
|
//! So instead of jumping through 32 entries at a time, like on Ice Lake, we will
|
|
549
543
|
//! step through 16 entries at a time.
|
|
@@ -556,32 +550,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_turin( //
|
|
|
556
550
|
simsimd_u8_t u8[32];
|
|
557
551
|
} a_vec, b_vec;
|
|
558
552
|
|
|
559
|
-
|
|
553
|
+
// Broadcast index for last element (hoisted outside loop)
|
|
554
|
+
__m256i const last_idx = _mm256_set1_epi16(15);
|
|
555
|
+
while (a + 16 <= a_end && b + 16 <= b_end) {
|
|
560
556
|
a_vec.ymm = _mm256_lddqu_si256((__m256i const *)a);
|
|
561
557
|
b_vec.ymm = _mm256_lddqu_si256((__m256i const *)b);
|
|
562
558
|
|
|
563
|
-
//
|
|
564
|
-
// and comparisons, so we want to avoid it if the slices don't overlap at all..
|
|
565
|
-
simsimd_u16_t a_min;
|
|
566
|
-
simsimd_u16_t a_max = a_vec.u16[15];
|
|
567
|
-
simsimd_u16_t b_min = b_vec.u16[0];
|
|
568
|
-
simsimd_u16_t b_max = b_vec.u16[15];
|
|
569
|
-
|
|
570
|
-
// If the slices don't overlap, advance the appropriate pointer
|
|
571
|
-
while (a_max < b_min && a + 32 < a_end) {
|
|
572
|
-
a += 16;
|
|
573
|
-
a_vec.ymm = _mm256_lddqu_si256((__m256i const *)a);
|
|
574
|
-
a_max = a_vec.u16[15];
|
|
575
|
-
}
|
|
576
|
-
a_min = a_vec.u16[0];
|
|
577
|
-
while (b_max < a_min && b + 32 < b_end) {
|
|
578
|
-
b += 16;
|
|
579
|
-
b_vec.ymm = _mm256_lddqu_si256((__m256i const *)b);
|
|
580
|
-
b_max = b_vec.u16[15];
|
|
581
|
-
}
|
|
582
|
-
b_min = b_vec.u16[0];
|
|
583
|
-
|
|
584
|
-
// Now we are likely to have some overlap, so we can intersect the registers
|
|
559
|
+
// Intersect the registers
|
|
585
560
|
__m512i a_i32_vec = _mm512_cvtepu16_epi32(a_vec.ymm);
|
|
586
561
|
__m512i b_i32_vec = _mm512_cvtepu16_epi32(b_vec.ymm);
|
|
587
562
|
__mmask16 a_matches_any_in_b, b_matches_any_in_a;
|
|
@@ -592,12 +567,12 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_turin( //
|
|
|
592
567
|
// _mm512_mask_compressstoreu_epi16(c, a_matches_any_in_b, a_vec);
|
|
593
568
|
c += _mm_popcnt_u32(a_matches_any_in_b); // MSVC has no `_popcnt32`
|
|
594
569
|
|
|
595
|
-
__m256i a_last_broadcasted =
|
|
596
|
-
__m256i b_last_broadcasted =
|
|
570
|
+
__m256i a_last_broadcasted = _mm256_permutexvar_epi16(last_idx, a_vec.ymm);
|
|
571
|
+
__m256i b_last_broadcasted = _mm256_permutexvar_epi16(last_idx, b_vec.ymm);
|
|
597
572
|
__mmask16 a_step_mask = _mm256_cmple_epu16_mask(a_vec.ymm, b_last_broadcasted);
|
|
598
573
|
__mmask16 b_step_mask = _mm256_cmple_epu16_mask(b_vec.ymm, a_last_broadcasted);
|
|
599
|
-
a +=
|
|
600
|
-
b +=
|
|
574
|
+
a += _tzcnt_u32(~(simsimd_u32_t)a_step_mask | 0x10000);
|
|
575
|
+
b += _tzcnt_u32(~(simsimd_u32_t)b_step_mask | 0x10000);
|
|
601
576
|
}
|
|
602
577
|
|
|
603
578
|
simsimd_intersect_u16_serial(a, b, a_end - a, b_end - b, results);
|
|
@@ -609,12 +584,6 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_turin( //
|
|
|
609
584
|
simsimd_size_t a_length, simsimd_size_t b_length, //
|
|
610
585
|
simsimd_distance_t *results) {
|
|
611
586
|
|
|
612
|
-
// The baseline implementation for very small arrays (2 registers or less) can be quite simple:
|
|
613
|
-
if (a_length < 32 && b_length < 32) {
|
|
614
|
-
simsimd_intersect_u32_serial(a, b, a_length, b_length, results);
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
587
|
simsimd_u32_t const *const a_end = a + a_length;
|
|
619
588
|
simsimd_u32_t const *const b_end = b + b_length;
|
|
620
589
|
simsimd_size_t c = 0;
|
|
@@ -624,32 +593,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_turin( //
|
|
|
624
593
|
simsimd_u8_t u8[64];
|
|
625
594
|
} a_vec, b_vec;
|
|
626
595
|
|
|
627
|
-
|
|
596
|
+
// Broadcast index for last element (hoisted outside loop)
|
|
597
|
+
__m512i const last_idx = _mm512_set1_epi32(15);
|
|
598
|
+
while (a + 16 <= a_end && b + 16 <= b_end) {
|
|
628
599
|
a_vec.zmm = _mm512_loadu_si512((__m512i const *)a);
|
|
629
600
|
b_vec.zmm = _mm512_loadu_si512((__m512i const *)b);
|
|
630
601
|
|
|
631
|
-
//
|
|
632
|
-
// and comparisons, so we want to avoid it if the slices don't overlap at all..
|
|
633
|
-
simsimd_u32_t a_min;
|
|
634
|
-
simsimd_u32_t a_max = a_vec.u32[15];
|
|
635
|
-
simsimd_u32_t b_min = b_vec.u32[0];
|
|
636
|
-
simsimd_u32_t b_max = b_vec.u32[15];
|
|
637
|
-
|
|
638
|
-
// If the slices don't overlap, advance the appropriate pointer
|
|
639
|
-
while (a_max < b_min && a + 32 < a_end) {
|
|
640
|
-
a += 16;
|
|
641
|
-
a_vec.zmm = _mm512_loadu_si512((__m512i const *)a);
|
|
642
|
-
a_max = a_vec.u32[15];
|
|
643
|
-
}
|
|
644
|
-
a_min = a_vec.u32[0];
|
|
645
|
-
while (b_max < a_min && b + 32 < b_end) {
|
|
646
|
-
b += 16;
|
|
647
|
-
b_vec.zmm = _mm512_loadu_si512((__m512i const *)b);
|
|
648
|
-
b_max = b_vec.u32[15];
|
|
649
|
-
}
|
|
650
|
-
b_min = b_vec.u32[0];
|
|
651
|
-
|
|
652
|
-
// Now we are likely to have some overlap, so we can intersect the registers
|
|
602
|
+
// Intersect the registers
|
|
653
603
|
__mmask16 a_matches_any_in_b, b_matches_any_in_a;
|
|
654
604
|
_mm512_2intersect_epi32(a_vec.zmm, b_vec.zmm, &a_matches_any_in_b, &b_matches_any_in_a);
|
|
655
605
|
|
|
@@ -658,12 +608,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_turin( //
|
|
|
658
608
|
// _mm512_mask_compressstoreu_epi32(c, a_matches_any_in_b, a_vec);
|
|
659
609
|
c += _mm_popcnt_u32(a_matches_any_in_b); // MSVC has no `_popcnt32`
|
|
660
610
|
|
|
661
|
-
|
|
662
|
-
__m512i
|
|
611
|
+
// Pure SIMD broadcasts - no scalar extraction needed
|
|
612
|
+
__m512i a_last_broadcasted = _mm512_permutexvar_epi32(last_idx, a_vec.zmm);
|
|
613
|
+
__m512i b_last_broadcasted = _mm512_permutexvar_epi32(last_idx, b_vec.zmm);
|
|
663
614
|
__mmask16 a_step_mask = _mm512_cmple_epu32_mask(a_vec.zmm, b_last_broadcasted);
|
|
664
615
|
__mmask16 b_step_mask = _mm512_cmple_epu32_mask(b_vec.zmm, a_last_broadcasted);
|
|
665
|
-
a +=
|
|
666
|
-
b +=
|
|
616
|
+
a += _tzcnt_u32(~(simsimd_u32_t)a_step_mask | 0x10000);
|
|
617
|
+
b += _tzcnt_u32(~(simsimd_u32_t)b_step_mask | 0x10000);
|
|
667
618
|
}
|
|
668
619
|
|
|
669
620
|
simsimd_intersect_u32_serial(a, b, a_end - a, b_end - b, results);
|
|
@@ -696,7 +647,9 @@ SIMSIMD_PUBLIC void simsimd_spdot_weights_u16_turin( //
|
|
|
696
647
|
} a_vec, b_vec, product_vec;
|
|
697
648
|
product_vec.ymmps = _mm256_setzero_ps();
|
|
698
649
|
|
|
699
|
-
|
|
650
|
+
// Broadcast index for last element (hoisted outside loop)
|
|
651
|
+
__m256i const last_idx = _mm256_set1_epi16(15);
|
|
652
|
+
while (a + 16 <= a_end && b + 16 <= b_end) {
|
|
700
653
|
a_vec.ymm = _mm256_lddqu_si256((__m256i const *)a);
|
|
701
654
|
b_vec.ymm = _mm256_lddqu_si256((__m256i const *)b);
|
|
702
655
|
|
|
@@ -708,13 +661,13 @@ SIMSIMD_PUBLIC void simsimd_spdot_weights_u16_turin( //
|
|
|
708
661
|
simsimd_u16_t b_max = b_vec.u16[15];
|
|
709
662
|
|
|
710
663
|
// If the slices don't overlap, advance the appropriate pointer
|
|
711
|
-
while (a_max < b_min && a + 32
|
|
664
|
+
while (a_max < b_min && a + 32 <= a_end) {
|
|
712
665
|
a += 16, a_weights += 16;
|
|
713
666
|
a_vec.ymm = _mm256_lddqu_si256((__m256i const *)a);
|
|
714
667
|
a_max = a_vec.u16[15];
|
|
715
668
|
}
|
|
716
669
|
a_min = a_vec.u16[0];
|
|
717
|
-
while (b_max < a_min && b + 32
|
|
670
|
+
while (b_max < a_min && b + 32 <= b_end) {
|
|
718
671
|
b += 16, b_weights += 16;
|
|
719
672
|
b_vec.ymm = _mm256_lddqu_si256((__m256i const *)b);
|
|
720
673
|
b_max = b_vec.u16[15];
|
|
@@ -742,12 +695,12 @@ SIMSIMD_PUBLIC void simsimd_spdot_weights_u16_turin( //
|
|
|
742
695
|
product_vec.ymmps = _mm256_dpbf16_ps(product_vec.ymmps, (__m256bh)a_weights_vec, (__m256bh)b_weights_vec);
|
|
743
696
|
}
|
|
744
697
|
|
|
745
|
-
__m256i a_last_broadcasted =
|
|
746
|
-
__m256i b_last_broadcasted =
|
|
698
|
+
__m256i a_last_broadcasted = _mm256_permutexvar_epi16(last_idx, a_vec.ymm);
|
|
699
|
+
__m256i b_last_broadcasted = _mm256_permutexvar_epi16(last_idx, b_vec.ymm);
|
|
747
700
|
__mmask16 a_step_mask = _mm256_cmple_epu16_mask(a_vec.ymm, b_last_broadcasted);
|
|
748
701
|
__mmask16 b_step_mask = _mm256_cmple_epu16_mask(b_vec.ymm, a_last_broadcasted);
|
|
749
|
-
|
|
750
|
-
|
|
702
|
+
simsimd_size_t a_step = _tzcnt_u32(~(simsimd_u32_t)a_step_mask | 0x10000);
|
|
703
|
+
simsimd_size_t b_step = _tzcnt_u32(~(simsimd_u32_t)b_step_mask | 0x10000);
|
|
751
704
|
a += a_step, a_weights += a_step;
|
|
752
705
|
b += b_step, b_weights += b_step;
|
|
753
706
|
}
|
|
@@ -781,7 +734,9 @@ SIMSIMD_PUBLIC void simsimd_spdot_counts_u16_turin( //
|
|
|
781
734
|
} a_vec, b_vec, product_vec;
|
|
782
735
|
product_vec.ymm = _mm256_setzero_si256();
|
|
783
736
|
|
|
784
|
-
|
|
737
|
+
// Broadcast index for last element (hoisted outside loop)
|
|
738
|
+
__m256i const last_idx = _mm256_set1_epi16(15);
|
|
739
|
+
while (a + 16 <= a_end && b + 16 <= b_end) {
|
|
785
740
|
a_vec.ymm = _mm256_lddqu_si256((__m256i const *)a);
|
|
786
741
|
b_vec.ymm = _mm256_lddqu_si256((__m256i const *)b);
|
|
787
742
|
|
|
@@ -793,13 +748,13 @@ SIMSIMD_PUBLIC void simsimd_spdot_counts_u16_turin( //
|
|
|
793
748
|
simsimd_u16_t b_max = b_vec.u16[15];
|
|
794
749
|
|
|
795
750
|
// If the slices don't overlap, advance the appropriate pointer
|
|
796
|
-
while (a_max < b_min && a + 32
|
|
751
|
+
while (a_max < b_min && a + 32 <= a_end) {
|
|
797
752
|
a += 16, a_weights += 16;
|
|
798
753
|
a_vec.ymm = _mm256_lddqu_si256((__m256i const *)a);
|
|
799
754
|
a_max = a_vec.u16[15];
|
|
800
755
|
}
|
|
801
756
|
a_min = a_vec.u16[0];
|
|
802
|
-
while (b_max < a_min && b + 32
|
|
757
|
+
while (b_max < a_min && b + 32 <= b_end) {
|
|
803
758
|
b += 16, b_weights += 16;
|
|
804
759
|
b_vec.ymm = _mm256_lddqu_si256((__m256i const *)b);
|
|
805
760
|
b_max = b_vec.u16[15];
|
|
@@ -827,12 +782,12 @@ SIMSIMD_PUBLIC void simsimd_spdot_counts_u16_turin( //
|
|
|
827
782
|
product_vec.ymm = _mm256_dpwssds_epi32(product_vec.ymm, a_weights_vec, b_weights_vec);
|
|
828
783
|
}
|
|
829
784
|
|
|
830
|
-
__m256i a_last_broadcasted =
|
|
831
|
-
__m256i b_last_broadcasted =
|
|
785
|
+
__m256i a_last_broadcasted = _mm256_permutexvar_epi16(last_idx, a_vec.ymm);
|
|
786
|
+
__m256i b_last_broadcasted = _mm256_permutexvar_epi16(last_idx, b_vec.ymm);
|
|
832
787
|
__mmask16 a_step_mask = _mm256_cmple_epu16_mask(a_vec.ymm, b_last_broadcasted);
|
|
833
788
|
__mmask16 b_step_mask = _mm256_cmple_epu16_mask(b_vec.ymm, a_last_broadcasted);
|
|
834
|
-
|
|
835
|
-
|
|
789
|
+
simsimd_size_t a_step = _tzcnt_u32(~(simsimd_u32_t)a_step_mask | 0x10000);
|
|
790
|
+
simsimd_size_t b_step = _tzcnt_u32(~(simsimd_u32_t)b_step_mask | 0x10000);
|
|
836
791
|
a += a_step, a_weights += a_step;
|
|
837
792
|
b += b_step, b_weights += b_step;
|
|
838
793
|
}
|
|
@@ -925,7 +880,7 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_neon( //
|
|
|
925
880
|
} a_vec, b_vec, c_counts_vec;
|
|
926
881
|
c_counts_vec.u16x8 = vdupq_n_u16(0);
|
|
927
882
|
|
|
928
|
-
while (a + 8
|
|
883
|
+
while (a + 8 <= a_end && b + 8 <= b_end) {
|
|
929
884
|
a_vec.u16x8 = vld1q_u16(a);
|
|
930
885
|
b_vec.u16x8 = vld1q_u16(b);
|
|
931
886
|
|
|
@@ -937,13 +892,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_neon( //
|
|
|
937
892
|
simsimd_u16_t b_max = b_vec.u16[7];
|
|
938
893
|
|
|
939
894
|
// If the slices don't overlap, advance the appropriate pointer
|
|
940
|
-
while (a_max < b_min && a + 16
|
|
895
|
+
while (a_max < b_min && a + 16 <= a_end) {
|
|
941
896
|
a += 8;
|
|
942
897
|
a_vec.u16x8 = vld1q_u16(a);
|
|
943
898
|
a_max = a_vec.u16[7];
|
|
944
899
|
}
|
|
945
900
|
a_min = a_vec.u16[0];
|
|
946
|
-
while (b_max < a_min && b + 16
|
|
901
|
+
while (b_max < a_min && b + 16 <= b_end) {
|
|
947
902
|
b += 8;
|
|
948
903
|
b_vec.u16x8 = vld1q_u16(b);
|
|
949
904
|
b_max = b_vec.u16[7];
|
|
@@ -1008,7 +963,7 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_neon( //
|
|
|
1008
963
|
} a_vec, b_vec, c_counts_vec;
|
|
1009
964
|
c_counts_vec.u32x4 = vdupq_n_u32(0);
|
|
1010
965
|
|
|
1011
|
-
while (a + 4
|
|
966
|
+
while (a + 4 <= a_end && b + 4 <= b_end) {
|
|
1012
967
|
a_vec.u32x4 = vld1q_u32(a);
|
|
1013
968
|
b_vec.u32x4 = vld1q_u32(b);
|
|
1014
969
|
|
|
@@ -1020,13 +975,13 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_neon( //
|
|
|
1020
975
|
simsimd_u32_t b_max = b_vec.u32[3];
|
|
1021
976
|
|
|
1022
977
|
// If the slices don't overlap, advance the appropriate pointer
|
|
1023
|
-
while (a_max < b_min && a + 8
|
|
978
|
+
while (a_max < b_min && a + 8 <= a_end) {
|
|
1024
979
|
a += 4;
|
|
1025
980
|
a_vec.u32x4 = vld1q_u32(a);
|
|
1026
981
|
a_max = a_vec.u32[3];
|
|
1027
982
|
}
|
|
1028
983
|
a_min = a_vec.u32[0];
|
|
1029
|
-
while (b_max < a_min && b + 8
|
|
984
|
+
while (b_max < a_min && b + 8 <= b_end) {
|
|
1030
985
|
b += 4;
|
|
1031
986
|
b_vec.u32x4 = vld1q_u32(b);
|
|
1032
987
|
b_max = b_vec.u32[3];
|
|
@@ -1118,14 +1073,14 @@ SIMSIMD_PUBLIC void simsimd_intersect_u16_sve2( //
|
|
|
1118
1073
|
simsimd_u16_t b_max = svlastb(b_progress, b_vec);
|
|
1119
1074
|
|
|
1120
1075
|
// If the slices don't overlap, advance the appropriate pointer
|
|
1121
|
-
while (a_max < b_min && (a_idx + register_size)
|
|
1076
|
+
while (a_max < b_min && (a_idx + register_size) <= a_length) {
|
|
1122
1077
|
a_idx += register_size;
|
|
1123
1078
|
a_progress = svwhilelt_b16_u64(a_idx, a_length);
|
|
1124
1079
|
a_vec = svld1_u16(a_progress, a + a_idx);
|
|
1125
1080
|
a_max = svlastb(a_progress, a_vec);
|
|
1126
1081
|
}
|
|
1127
1082
|
a_min = svlasta(svpfalse_b(), a_vec);
|
|
1128
|
-
while (b_max < a_min && (b_idx + register_size)
|
|
1083
|
+
while (b_max < a_min && (b_idx + register_size) <= b_length) {
|
|
1129
1084
|
b_idx += register_size;
|
|
1130
1085
|
b_progress = svwhilelt_b16_u64(b_idx, b_length);
|
|
1131
1086
|
b_vec = svld1_u16(b_progress, b + b_idx);
|
|
@@ -1185,14 +1140,14 @@ SIMSIMD_PUBLIC void simsimd_intersect_u32_sve2(simsimd_u32_t const *a, simsimd_u
|
|
|
1185
1140
|
simsimd_u32_t b_max = svlastb(b_progress, b_vec);
|
|
1186
1141
|
|
|
1187
1142
|
// If the slices don't overlap, advance the appropriate pointer
|
|
1188
|
-
while (a_max < b_min && (a_idx + register_size)
|
|
1143
|
+
while (a_max < b_min && (a_idx + register_size) <= a_length) {
|
|
1189
1144
|
a_idx += register_size;
|
|
1190
1145
|
a_progress = svwhilelt_b32_u64(a_idx, a_length);
|
|
1191
1146
|
a_vec = svld1_u32(a_progress, a + a_idx);
|
|
1192
1147
|
a_max = svlastb(a_progress, a_vec);
|
|
1193
1148
|
}
|
|
1194
1149
|
a_min = svlasta(svpfalse_b(), a_vec);
|
|
1195
|
-
while (b_max < a_min && (b_idx + register_size)
|
|
1150
|
+
while (b_max < a_min && (b_idx + register_size) <= b_length) {
|
|
1196
1151
|
b_idx += register_size;
|
|
1197
1152
|
b_progress = svwhilelt_b32_u64(b_idx, b_length);
|
|
1198
1153
|
b_vec = svld1_u32(b_progress, b + b_idx);
|
|
@@ -1284,14 +1239,14 @@ SIMSIMD_PUBLIC void simsimd_spdot_counts_u16_sve2( //
|
|
|
1284
1239
|
simsimd_u16_t b_max = svlastb(b_progress, b_vec);
|
|
1285
1240
|
|
|
1286
1241
|
// If the slices don't overlap, advance the appropriate pointer
|
|
1287
|
-
while (a_max < b_min && (a_idx + register_size)
|
|
1242
|
+
while (a_max < b_min && (a_idx + register_size) <= a_length) {
|
|
1288
1243
|
a_idx += register_size;
|
|
1289
1244
|
a_progress = svwhilelt_b16_u64(a_idx, a_length);
|
|
1290
1245
|
a_vec = svld1_u16(a_progress, a + a_idx);
|
|
1291
1246
|
a_max = svlastb(a_progress, a_vec);
|
|
1292
1247
|
}
|
|
1293
1248
|
a_min = svlasta(svpfalse_b(), a_vec);
|
|
1294
|
-
while (b_max < a_min && (b_idx + register_size)
|
|
1249
|
+
while (b_max < a_min && (b_idx + register_size) <= b_length) {
|
|
1295
1250
|
b_idx += register_size;
|
|
1296
1251
|
b_progress = svwhilelt_b16_u64(b_idx, b_length);
|
|
1297
1252
|
b_vec = svld1_u16(b_progress, b + b_idx);
|
|
@@ -1367,14 +1322,14 @@ SIMSIMD_PUBLIC void simsimd_spdot_weights_u16_sve2( //
|
|
|
1367
1322
|
simsimd_u16_t b_max = svlastb(b_progress, b_vec);
|
|
1368
1323
|
|
|
1369
1324
|
// If the slices don't overlap, advance the appropriate pointer
|
|
1370
|
-
while (a_max < b_min && (a_idx + register_size)
|
|
1325
|
+
while (a_max < b_min && (a_idx + register_size) <= a_length) {
|
|
1371
1326
|
a_idx += register_size;
|
|
1372
1327
|
a_progress = svwhilelt_b16_u64(a_idx, a_length);
|
|
1373
1328
|
a_vec = svld1_u16(a_progress, a + a_idx);
|
|
1374
1329
|
a_max = svlastb(a_progress, a_vec);
|
|
1375
1330
|
}
|
|
1376
1331
|
a_min = svlasta(svpfalse_b(), a_vec);
|
|
1377
|
-
while (b_max < a_min && (b_idx + register_size)
|
|
1332
|
+
while (b_max < a_min && (b_idx + register_size) <= b_length) {
|
|
1378
1333
|
b_idx += register_size;
|
|
1379
1334
|
b_progress = svwhilelt_b16_u64(b_idx, b_length);
|
|
1380
1335
|
b_vec = svld1_u16(b_progress, b + b_idx);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
#ifndef SIMSIMD_TYPES_H
|
|
13
13
|
#define SIMSIMD_TYPES_H
|
|
14
14
|
|
|
15
|
-
// Inferring target OS: Windows,
|
|
15
|
+
// Inferring target OS: Windows, macOS, or Linux
|
|
16
16
|
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
|
17
17
|
#define _SIMSIMD_DEFINED_WINDOWS 1
|
|
18
18
|
#elif defined(__APPLE__) && defined(__MACH__)
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// - `SIMSIMD_DYNAMIC` is used for functions that are part of the public API, but are dispatched at runtime.
|
|
29
29
|
//
|
|
30
30
|
// On GCC we mark the functions as `nonnull` informing that none of the arguments can be `NULL`.
|
|
31
|
-
// Marking with `pure` and `const` isn't possible as
|
|
31
|
+
// Marking with `pure` and `const` isn't possible as outputting to a pointer is a "side effect".
|
|
32
32
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
33
33
|
#define SIMSIMD_DYNAMIC __declspec(dllexport)
|
|
34
34
|
#define SIMSIMD_PUBLIC inline static
|
|
@@ -259,6 +259,7 @@
|
|
|
259
259
|
#define SIMSIMD_LOG(x) (log(x))
|
|
260
260
|
#endif
|
|
261
261
|
|
|
262
|
+
// Copy 16 bits (2 bytes) from source to destination
|
|
262
263
|
#if defined(__GNUC__) || defined(__clang__)
|
|
263
264
|
#define SIMSIMD_COPY16(destination_ptr, source_ptr) __builtin_memcpy((destination_ptr), (source_ptr), 2)
|
|
264
265
|
#else
|
|
@@ -395,7 +396,7 @@ typedef unsigned short simsimd_bf16_t;
|
|
|
395
396
|
|
|
396
397
|
/*
|
|
397
398
|
* Let's make sure the sizes of the types are as expected.
|
|
398
|
-
* In C the `_Static_assert` is only available with
|
|
399
|
+
* In C the `_Static_assert` is only available with C11 and later.
|
|
399
400
|
*/
|
|
400
401
|
#define SIMSIMD_STATIC_ASSERT(cond, msg) typedef char static_assertion_##msg[(cond) ? 1 : -1]
|
|
401
402
|
SIMSIMD_STATIC_ASSERT(sizeof(simsimd_b8_t) == 1, simsimd_b8_t_must_be_1_byte);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @brief Computes the inner
|
|
2
|
+
* @brief Computes the inner product of two vectors (dot product).
|
|
3
3
|
* @param {Float64Array|Float32Array} a - The first vector.
|
|
4
4
|
* @param {Float64Array|Float32Array} b - The second vector.
|
|
5
|
-
* @returns {number} The inner
|
|
5
|
+
* @returns {number} The inner product of vectors a and b.
|
|
6
6
|
*/
|
|
7
7
|
export function inner(a: Float64Array | Float32Array, b: Float64Array | Float32Array): number {
|
|
8
8
|
if (a.length !== b.length) {
|
|
@@ -17,10 +17,10 @@ export function inner(a: Float64Array | Float32Array, b: Float64Array | Float32A
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* @brief Computes the
|
|
20
|
+
* @brief Computes the dot product of two vectors (same as inner product).
|
|
21
21
|
* @param {Float64Array|Float32Array} a - The first vector.
|
|
22
22
|
* @param {Float64Array|Float32Array} b - The second vector.
|
|
23
|
-
* @returns {number} The
|
|
23
|
+
* @returns {number} The dot product of vectors a and b.
|
|
24
24
|
*/
|
|
25
25
|
export function dot(a: Float64Array | Float32Array, b: Float64Array | Float32Array): number {
|
|
26
26
|
return inner(a, b);
|
|
@@ -51,7 +51,7 @@ export function sqeuclidean(
|
|
|
51
51
|
* @brief Computes the L2 Euclidean distance between two vectors.
|
|
52
52
|
* @param {Float64Array|Float32Array|Int8Array | Uint8Array} a - The first vector.
|
|
53
53
|
* @param {Float64Array|Float32Array|Int8Array | Uint8Array} b - The second vector.
|
|
54
|
-
* @returns {number} The L2
|
|
54
|
+
* @returns {number} The L2 Euclidean distance between vectors a and b.
|
|
55
55
|
*/
|
|
56
56
|
export function euclidean(
|
|
57
57
|
a: Float64Array | Float32Array | Int8Array | Uint8Array,
|
|
@@ -96,12 +96,8 @@ export function cosine(
|
|
|
96
96
|
magnitudeA = Math.sqrt(magnitudeA);
|
|
97
97
|
magnitudeB = Math.sqrt(magnitudeB);
|
|
98
98
|
|
|
99
|
-
if (magnitudeA === 0
|
|
100
|
-
|
|
101
|
-
"Warning: One of the magnitudes is zero. Cosine similarity is undefined."
|
|
102
|
-
);
|
|
103
|
-
return 0;
|
|
104
|
-
}
|
|
99
|
+
if (magnitudeA === 0 && magnitudeB === 0) return 0; // distance when both zero
|
|
100
|
+
if (magnitudeA === 0 || magnitudeB === 0) return 1; // distance when one is zero
|
|
105
101
|
|
|
106
102
|
return 1 - dotProduct / (magnitudeA * magnitudeB);
|
|
107
103
|
}
|
|
@@ -133,10 +129,10 @@ export const hamming = (a: Uint8Array, b: Uint8Array): number => {
|
|
|
133
129
|
};
|
|
134
130
|
|
|
135
131
|
/**
|
|
136
|
-
* @brief Computes the bitwise Jaccard
|
|
132
|
+
* @brief Computes the bitwise Jaccard distance between two vectors.
|
|
137
133
|
* @param {Uint8Array} a - The first vector.
|
|
138
134
|
* @param {Uint8Array} b - The second vector.
|
|
139
|
-
* @returns {number} The Jaccard
|
|
135
|
+
* @returns {number} The Jaccard distance between vectors a and b.
|
|
140
136
|
*/
|
|
141
137
|
export const jaccard = (a: Uint8Array, b: Uint8Array): number => {
|
|
142
138
|
if (a.length !== b.length) {
|
|
@@ -174,7 +170,7 @@ export const jaccard = (a: Uint8Array, b: Uint8Array): number => {
|
|
|
174
170
|
* @brief Computes the Kullback-Leibler divergence between two probability distributions.
|
|
175
171
|
* @param {Float64Array|Float32Array} a - The first vector.
|
|
176
172
|
* @param {Float64Array|Float32Array} b - The second vector.
|
|
177
|
-
* @returns {number} The
|
|
173
|
+
* @returns {number} The Kullback-Leibler divergence between vectors a and b.
|
|
178
174
|
*/
|
|
179
175
|
export const kullbackleibler = (a: Float64Array | Float32Array, b: Float64Array | Float32Array): number => {
|
|
180
176
|
if (a.length !== b.length) {
|