usearch 2.7.2 → 2.7.6
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/.github/workflows/Dockerfile.libs +4 -6
- package/.github/workflows/prerelease.yml +4 -1
- package/.github/workflows/release.yml +8 -28
- package/.github/workflows/update_version.sh +1 -0
- package/CITATION.cff +2 -2
- package/CMakeLists.txt +1 -1
- package/Cargo.toml +1 -1
- package/README.md +4 -4
- package/binding.gyp +1 -1
- package/build.gradle +20 -5
- package/conanfile.py +1 -1
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +0 -2
- package/golang/README.md +0 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +119 -243
- package/javascript/lib.cpp +4 -3
- package/package.json +1 -1
- package/simsimd/.github/workflows/prerelease.yml +33 -58
- package/simsimd/.github/workflows/release.yml +22 -9
- package/simsimd/.github/workflows/update_version.sh +3 -1
- package/simsimd/.releaserc +9 -1
- package/simsimd/.vscode/launch.json +3 -0
- package/simsimd/.vscode/settings.json +15 -2
- package/simsimd/.vscode/tasks.json +2 -2
- package/simsimd/CMakeLists.txt +36 -55
- package/simsimd/README.md +163 -109
- package/simsimd/binding.gyp +18 -0
- package/simsimd/cpp/bench.cxx +187 -0
- package/simsimd/go.mod +3 -0
- package/simsimd/golang/bench_test.go +40 -0
- package/simsimd/golang/simsimd.go +47 -0
- package/simsimd/golang/unit_test.go +40 -0
- package/simsimd/include/simsimd/binary.h +186 -0
- package/simsimd/include/simsimd/probability.h +645 -0
- package/simsimd/include/simsimd/simsimd.h +335 -575
- package/simsimd/include/simsimd/spatial.h +924 -0
- package/simsimd/include/simsimd/types.h +196 -0
- package/simsimd/javascript/bench.js +117 -0
- package/simsimd/javascript/lib.c +102 -0
- package/simsimd/javascript/simsimd.js +45 -0
- package/simsimd/javascript/test.js +29 -0
- package/simsimd/package.json +31 -2
- package/simsimd/pyproject.toml +5 -6
- package/simsimd/python/bench.ipynb +187 -0
- package/simsimd/python/bench.py +268 -0
- package/simsimd/python/lib.c +453 -72
- package/simsimd/python/test.py +167 -35
- package/simsimd/setup.py +51 -8
- package/wasmer.toml +1 -1
- package/simsimd/bench.cxx +0 -137
- package/simsimd/include/simsimd/simsimd_bio.h +0 -54
- package/simsimd/include/simsimd/simsimd_chem.h +0 -167
- package/simsimd/include/simsimd/simsimd_text.h +0 -92
- package/simsimd/python/run.sh +0 -1
- package/simsimd/python/test.c +0 -109
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Compiling for Arm: SIMSIMD_TARGET_ARM
|
|
4
|
+
#if defined(__aarch64__) || defined(_M_ARM64)
|
|
5
|
+
#define SIMSIMD_TARGET_ARM 1
|
|
6
|
+
|
|
7
|
+
// Compiling for Arm: SIMSIMD_TARGET_ARM_NEON
|
|
8
|
+
#if !defined(SIMSIMD_TARGET_ARM_NEON)
|
|
9
|
+
#if defined(__ARM_NEON)
|
|
10
|
+
#define SIMSIMD_TARGET_ARM_NEON 1
|
|
11
|
+
#else
|
|
12
|
+
#define SIMSIMD_TARGET_ARM_NEON 0
|
|
13
|
+
#endif
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
// Compiling for Arm: SIMSIMD_TARGET_ARM_SVE
|
|
17
|
+
#if !defined(SIMSIMD_TARGET_ARM_SVE)
|
|
18
|
+
#if defined(__ARM_FEATURE_SVE)
|
|
19
|
+
#define SIMSIMD_TARGET_ARM_SVE 1
|
|
20
|
+
#else
|
|
21
|
+
#define SIMSIMD_TARGET_ARM_SVE 0
|
|
22
|
+
#endif
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
#undef SIMSIMD_TARGET_X86
|
|
26
|
+
#undef SIMSIMD_TARGET_X86_AVX2
|
|
27
|
+
#undef SIMSIMD_TARGET_X86_AVX512
|
|
28
|
+
#define SIMSIMD_TARGET_X86 0
|
|
29
|
+
#define SIMSIMD_TARGET_X86_AVX2 0
|
|
30
|
+
#define SIMSIMD_TARGET_X86_AVX512 0
|
|
31
|
+
|
|
32
|
+
// Compiling for x86: SIMSIMD_TARGET_X86
|
|
33
|
+
#elif defined(__x86_64__) || defined(_M_X64)
|
|
34
|
+
#define SIMSIMD_TARGET_X86 1
|
|
35
|
+
|
|
36
|
+
// Compiling for x86: SIMSIMD_TARGET_X86_AVX2
|
|
37
|
+
#if !defined(SIMSIMD_TARGET_X86_AVX2)
|
|
38
|
+
#if defined(__AVX2__)
|
|
39
|
+
#define SIMSIMD_TARGET_X86_AVX2 1
|
|
40
|
+
#else
|
|
41
|
+
#define SIMSIMD_TARGET_X86_AVX2 0
|
|
42
|
+
#endif
|
|
43
|
+
#endif
|
|
44
|
+
|
|
45
|
+
// Compiling for x86: SIMSIMD_TARGET_X86_AVX512
|
|
46
|
+
#if !defined(SIMSIMD_TARGET_X86_AVX512)
|
|
47
|
+
#if defined(__AVX512F__) || defined(__AVX512VPOPCNTDQ__)
|
|
48
|
+
#define SIMSIMD_TARGET_X86_AVX512 1
|
|
49
|
+
#else
|
|
50
|
+
#define SIMSIMD_TARGET_X86_AVX512 0
|
|
51
|
+
#endif
|
|
52
|
+
#endif
|
|
53
|
+
|
|
54
|
+
#undef SIMSIMD_TARGET_ARM
|
|
55
|
+
#undef SIMSIMD_TARGET_ARM_NEON
|
|
56
|
+
#undef SIMSIMD_TARGET_ARM_SVE
|
|
57
|
+
#define SIMSIMD_TARGET_ARM 0
|
|
58
|
+
#define SIMSIMD_TARGET_ARM_NEON 0
|
|
59
|
+
#define SIMSIMD_TARGET_ARM_SVE 0
|
|
60
|
+
|
|
61
|
+
// Compiling for unknown hardware architecture
|
|
62
|
+
#else
|
|
63
|
+
#error Unknown hardware architecture!
|
|
64
|
+
#endif
|
|
65
|
+
|
|
66
|
+
#ifdef _MSC_VER
|
|
67
|
+
#include <intrin.h>
|
|
68
|
+
#else
|
|
69
|
+
|
|
70
|
+
#if SIMSIMD_TARGET_ARM_NEON
|
|
71
|
+
#include <arm_neon.h>
|
|
72
|
+
#endif
|
|
73
|
+
|
|
74
|
+
#if SIMSIMD_TARGET_ARM_SVE
|
|
75
|
+
#include <arm_sve.h>
|
|
76
|
+
#endif
|
|
77
|
+
|
|
78
|
+
#if SIMSIMD_TARGET_X86_AVX2 || SIMSIMD_TARGET_X86_AVX512
|
|
79
|
+
#include <immintrin.h>
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
#endif
|
|
83
|
+
|
|
84
|
+
#ifndef SIMSIMD_RSQRT
|
|
85
|
+
#include <math.h>
|
|
86
|
+
#define SIMSIMD_RSQRT(x) (1 / sqrtf(x))
|
|
87
|
+
#endif
|
|
88
|
+
|
|
89
|
+
#ifndef SIMSIMD_LOG
|
|
90
|
+
#include <math.h>
|
|
91
|
+
#define SIMSIMD_LOG(x) (logf(x))
|
|
92
|
+
#endif
|
|
93
|
+
|
|
94
|
+
#ifdef __cplusplus
|
|
95
|
+
extern "C" {
|
|
96
|
+
#endif
|
|
97
|
+
|
|
98
|
+
typedef int simsimd_i32_t;
|
|
99
|
+
typedef float simsimd_f32_t;
|
|
100
|
+
typedef double simsimd_f64_t;
|
|
101
|
+
typedef signed char simsimd_i8_t;
|
|
102
|
+
typedef unsigned char simsimd_b8_t;
|
|
103
|
+
typedef unsigned long long simsimd_size_t;
|
|
104
|
+
|
|
105
|
+
#if !defined(SIMSIMD_NATIVE_F16)
|
|
106
|
+
/**
|
|
107
|
+
* @brief Half-precision floating-point type.
|
|
108
|
+
*
|
|
109
|
+
* - GCC or Clang on 64-bit ARM: `__fp16`, may require `-mfp16-format` option.
|
|
110
|
+
* - GCC or Clang on 64-bit x86: `_Float16`.
|
|
111
|
+
* - Default: `unsigned short`.
|
|
112
|
+
*/
|
|
113
|
+
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__ARM_ARCH) || defined(__aarch64__)) && \
|
|
114
|
+
(defined(__ARM_FP16_FORMAT_IEEE))
|
|
115
|
+
#define SIMSIMD_NATIVE_F16 1
|
|
116
|
+
typedef __fp16 simsimd_f16_t;
|
|
117
|
+
#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__)) && \
|
|
118
|
+
(defined(__SSE2__) || defined(__AVX512F__)))
|
|
119
|
+
typedef _Float16 simsimd_f16_t;
|
|
120
|
+
#define SIMSIMD_NATIVE_F16 1
|
|
121
|
+
#else // Unknown compiler or architecture
|
|
122
|
+
#define SIMSIMD_NATIVE_F16 0
|
|
123
|
+
#endif // Unknown compiler or architecture
|
|
124
|
+
#endif // !SIMSIMD_NATIVE_F16
|
|
125
|
+
|
|
126
|
+
#if !SIMSIMD_NATIVE_F16
|
|
127
|
+
typedef unsigned short simsimd_f16_t;
|
|
128
|
+
#endif
|
|
129
|
+
|
|
130
|
+
#define SIMSIMD_IDENTIFY(x) (x)
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @brief Returns the value of the half-precision floating-point number,
|
|
134
|
+
* potentially decompressed into single-precision.
|
|
135
|
+
*/
|
|
136
|
+
#ifndef SIMSIMD_UNCOMPRESS_F16
|
|
137
|
+
#if SIMSIMD_NATIVE_F16
|
|
138
|
+
#define SIMSIMD_UNCOMPRESS_F16(x) SIMSIMD_IDENTIFY(x)
|
|
139
|
+
#else
|
|
140
|
+
#define SIMSIMD_UNCOMPRESS_F16(x) simsimd_uncompress_f16(x)
|
|
141
|
+
#endif
|
|
142
|
+
#endif
|
|
143
|
+
|
|
144
|
+
typedef union {
|
|
145
|
+
unsigned i;
|
|
146
|
+
float f;
|
|
147
|
+
} simsimd_f32i32_t;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @brief Computes `1/sqrt(x)` using the trick from Quake 3, replacing
|
|
151
|
+
* magic numbers with the ones suggested by Jan Kadlec.
|
|
152
|
+
*/
|
|
153
|
+
inline static simsimd_f32_t simsimd_approximate_inverse_square_root(simsimd_f32_t number) {
|
|
154
|
+
simsimd_f32i32_t conv;
|
|
155
|
+
conv.f = number;
|
|
156
|
+
conv.i = 0x5F1FFFF9 - (conv.i >> 1);
|
|
157
|
+
conv.f *= 0.703952253f * (2.38924456f - number * conv.f * conv.f);
|
|
158
|
+
return conv.f;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @brief Computes `log(x)` using the Mercator series.
|
|
163
|
+
* The series converges to the natural logarithm for args between -1 and 1.
|
|
164
|
+
* Published in 1668 in Logarithmotechnia.
|
|
165
|
+
*/
|
|
166
|
+
inline static simsimd_f32_t simsimd_approximate_log(simsimd_f32_t number) {
|
|
167
|
+
simsimd_f32_t x = number - 1;
|
|
168
|
+
simsimd_f32_t x2 = x * x;
|
|
169
|
+
simsimd_f32_t x3 = x * x * x;
|
|
170
|
+
return x - x2 / 2 + x3 / 3;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @brief For compilers that don't natively support the `_Float16` type,
|
|
175
|
+
* upcasts contents into a more conventional `float`.
|
|
176
|
+
*
|
|
177
|
+
* @warning This function won't handle boundary conditions well.
|
|
178
|
+
*
|
|
179
|
+
* https://stackoverflow.com/a/60047308
|
|
180
|
+
* https://gist.github.com/milhidaka/95863906fe828198f47991c813dbe233
|
|
181
|
+
* https://github.com/OpenCyphal/libcanard/blob/636795f4bc395f56af8d2c61d3757b5e762bb9e5/canard.c#L811-L834
|
|
182
|
+
*/
|
|
183
|
+
inline static float simsimd_uncompress_f16(unsigned short x) {
|
|
184
|
+
unsigned int exponent = (x & 0x7C00) >> 10;
|
|
185
|
+
unsigned int mantissa = (x & 0x03FF) << 13;
|
|
186
|
+
float mantissa_as_float = (float)mantissa;
|
|
187
|
+
unsigned int v = (*(unsigned int*)&mantissa_as_float) >> 23;
|
|
188
|
+
unsigned int result_as_uint =
|
|
189
|
+
(x & 0x8000) << 16 | (exponent != 0) * ((exponent + 112) << 23 | mantissa) |
|
|
190
|
+
((exponent == 0) & (mantissa != 0)) * ((v - 37) << 23 | ((mantissa << (150 - v)) & 0x007FE000));
|
|
191
|
+
return *(float*)&result_as_uint;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#ifdef __cplusplus
|
|
195
|
+
} // extern "C"
|
|
196
|
+
#endif
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import benchmark from 'benchmark';
|
|
2
|
+
import bindings from 'bindings';
|
|
3
|
+
import * as math from 'mathjs';
|
|
4
|
+
import usearch, { MetricKind } from 'usearch';
|
|
5
|
+
|
|
6
|
+
const simsimd = bindings('simsimd');
|
|
7
|
+
|
|
8
|
+
// Assuming the vectors are of the same length
|
|
9
|
+
function cosineDistance(a, b) {
|
|
10
|
+
let dotProduct = 0;
|
|
11
|
+
let magA = 0;
|
|
12
|
+
let magB = 0;
|
|
13
|
+
for (let i = 0; i < a.length; i++) {
|
|
14
|
+
dotProduct += a[i] * b[i];
|
|
15
|
+
magA += a[i] * a[i];
|
|
16
|
+
magB += b[i] * b[i];
|
|
17
|
+
}
|
|
18
|
+
return 1 - (dotProduct / (Math.sqrt(magA) * Math.sqrt(magB)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function cosineDistanceMathJS(a, b) {
|
|
22
|
+
let dotProduct = math.dot(a, b);
|
|
23
|
+
let magA = math.sqrt(math.dot(a, a));
|
|
24
|
+
let magB = math.sqrt(math.dot(b, b));
|
|
25
|
+
return 1 - (dotProduct / (magA * magB));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Generate random data for testing
|
|
29
|
+
const dimensions = 1536; // Adjust dimensions as needed
|
|
30
|
+
const array1 = Array.from({ length: dimensions }, () => Math.random() * 100);
|
|
31
|
+
const array2 = Array.from({ length: dimensions }, () => Math.random() * 100);
|
|
32
|
+
const floatArray1 = new Float32Array(array1);
|
|
33
|
+
const floatArray2 = new Float32Array(array2);
|
|
34
|
+
const intArray1 = new Int8Array(array1);
|
|
35
|
+
const intArray2 = new Int8Array(array2);
|
|
36
|
+
|
|
37
|
+
// Generate random batch data for testing
|
|
38
|
+
const batchSize = 1000;
|
|
39
|
+
const matrix1 = Array.from({ length: dimensions * batchSize }, () => Math.random() * 100);
|
|
40
|
+
const matrix2 = Array.from({ length: dimensions * batchSize }, () => Math.random() * 100);
|
|
41
|
+
const floatMatrix1 = new Float32Array(matrix1);
|
|
42
|
+
const floatMatrix2 = new Float32Array(matrix2);
|
|
43
|
+
const intMatrix1 = new Int8Array(matrix1);
|
|
44
|
+
const intMatrix2 = new Int8Array(matrix2);
|
|
45
|
+
|
|
46
|
+
// Create benchmark suite
|
|
47
|
+
const singleSuite = new benchmark.Suite('Single Vector Processing');
|
|
48
|
+
const batchSuite = new benchmark.Suite('Batch Vector Processing');
|
|
49
|
+
|
|
50
|
+
// Single-vector processing benchmarks
|
|
51
|
+
singleSuite
|
|
52
|
+
|
|
53
|
+
// Pure JavaScript
|
|
54
|
+
.add('Array of Numbers', () => {
|
|
55
|
+
cosineDistance(array1, array2);
|
|
56
|
+
})
|
|
57
|
+
.add('TypedArray of Float32', () => {
|
|
58
|
+
cosineDistance(floatArray1, floatArray2);
|
|
59
|
+
})
|
|
60
|
+
.add('TypedArray of Int8', () => {
|
|
61
|
+
cosineDistance(intArray1, intArray2);
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Math JS
|
|
65
|
+
.add('Array of Numbers with MathJS', () => {
|
|
66
|
+
cosineDistanceMathJS(array1, array2);
|
|
67
|
+
})
|
|
68
|
+
.add('TypedArray of Float32 with MathJS', () => {
|
|
69
|
+
cosineDistanceMathJS(floatArray1, floatArray2);
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// SimSIMD
|
|
73
|
+
.add('TypedArray of Float32 with SimSIMD', () => {
|
|
74
|
+
simsimd.cosine(floatArray1, floatArray2);
|
|
75
|
+
})
|
|
76
|
+
.add('TypedArray of Int8 with SimSIMD', () => {
|
|
77
|
+
simsimd.cosine(intArray1, intArray2);
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
.on('cycle', (event) => {
|
|
81
|
+
console.log(String(event.target));
|
|
82
|
+
})
|
|
83
|
+
.on('complete', () => {
|
|
84
|
+
console.log('Fastest Single-Vector Processing is ' + singleSuite.filter('fastest').map('name'));
|
|
85
|
+
})
|
|
86
|
+
.run({
|
|
87
|
+
noCache: true,
|
|
88
|
+
async: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Batch-vector processing benchmarks
|
|
92
|
+
batchSuite
|
|
93
|
+
.add('2D Array of Numbers', () => {
|
|
94
|
+
for (let i = 0; i < batchSize; i++) {
|
|
95
|
+
for (let j = 0; j < batchSize; j++) {
|
|
96
|
+
const start = i * dimensions;
|
|
97
|
+
const end = start + dimensions;
|
|
98
|
+
cosineDistance(matrix1.slice(start, end), matrix2.slice(start, end));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
.add('2D TypedArray of Float32 with exact USearch', () => {
|
|
103
|
+
usearch.exactSearch(floatMatrix1, floatMatrix2, dimensions, 1, MetricKind.Cos);
|
|
104
|
+
})
|
|
105
|
+
.add('2D TypedArray of Int8 with exact USearch', () => {
|
|
106
|
+
usearch.exactSearch(intMatrix1, intMatrix2, dimensions, 1, MetricKind.Cos);
|
|
107
|
+
})
|
|
108
|
+
.on('cycle', (event) => {
|
|
109
|
+
console.log(String(event.target));
|
|
110
|
+
})
|
|
111
|
+
.on('complete', () => {
|
|
112
|
+
console.log('Fastest Batch-Vector Processing is ' + batchSuite.filter('fastest').map('name'));
|
|
113
|
+
})
|
|
114
|
+
.run({
|
|
115
|
+
noCache: true,
|
|
116
|
+
async: false,
|
|
117
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib.c
|
|
3
|
+
* @brief JavaScript bindings for SimSIMD.
|
|
4
|
+
* @author Ash Vardanian
|
|
5
|
+
* @date October 18, 2023
|
|
6
|
+
*
|
|
7
|
+
* @copyright Copyright (c) 2023
|
|
8
|
+
* @see NodeJS docs: https://nodejs.org/api/n-api.html
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include <node_api.h> // `napi_*` functions
|
|
12
|
+
#include <simsimd/simsimd.h> // `simsimd_*` functions
|
|
13
|
+
|
|
14
|
+
/// @brief Global variable that caches the CPU capabilities, and is computed just onc, when the module is loaded.
|
|
15
|
+
simsimd_capability_t static_capabilities = simsimd_cap_serial_k;
|
|
16
|
+
|
|
17
|
+
napi_value runAPI(napi_env env, napi_callback_info info, simsimd_metric_kind_t metric_kind) {
|
|
18
|
+
size_t argc = 2;
|
|
19
|
+
napi_value args[2];
|
|
20
|
+
napi_status status;
|
|
21
|
+
|
|
22
|
+
// Get callback info and ensure the argument count is correct
|
|
23
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
24
|
+
if (status != napi_ok || argc != 2) {
|
|
25
|
+
napi_throw_error(env, NULL, "Wrong number of arguments");
|
|
26
|
+
return NULL;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Obtain the typed arrays from the arguments
|
|
30
|
+
void *data_a, *data_b;
|
|
31
|
+
size_t length_a, length_b;
|
|
32
|
+
napi_typedarray_type type_a, type_b;
|
|
33
|
+
napi_status status_a, status_b;
|
|
34
|
+
status_a = napi_get_typedarray_info(env, args[0], &type_a, &length_a, &data_a, NULL, NULL);
|
|
35
|
+
status_b = napi_get_typedarray_info(env, args[1], &type_b, &length_b, &data_b, NULL, NULL);
|
|
36
|
+
if (status_a != napi_ok || status_b != napi_ok || type_a != type_b || length_a != length_b) {
|
|
37
|
+
napi_throw_error(env, NULL, "Both arguments must be typed arrays of matching types and dimensionality");
|
|
38
|
+
return NULL;
|
|
39
|
+
}
|
|
40
|
+
if (type_a != napi_float32_array && type_a != napi_int8_array && type_a != napi_uint8_array) {
|
|
41
|
+
napi_throw_error(env, NULL, "Only `float32`, `int8` and `uint8` arrays are supported in JavaScript bindings");
|
|
42
|
+
return NULL;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
simsimd_datatype_t datatype = simsimd_datatype_unknown_k;
|
|
46
|
+
switch (type_a) {
|
|
47
|
+
case napi_float32_array: datatype = simsimd_datatype_f32_k; break;
|
|
48
|
+
case napi_int8_array: datatype = simsimd_datatype_i8_k; break;
|
|
49
|
+
case napi_uint8_array: datatype = simsimd_datatype_b8_k; break;
|
|
50
|
+
default: break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
simsimd_metric_punned_t metric = NULL;
|
|
54
|
+
simsimd_capability_t capability = simsimd_cap_serial_k;
|
|
55
|
+
simsimd_find_metric_punned(metric_kind, datatype, static_capabilities, simsimd_cap_any_k, &metric, &capability);
|
|
56
|
+
if (metric == NULL) {
|
|
57
|
+
napi_throw_error(env, NULL, "Unsupported datatype");
|
|
58
|
+
return NULL;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
simsimd_f32_t result = metric(data_a, data_b, length_a, length_b);
|
|
62
|
+
|
|
63
|
+
// Convert the result to a JavaScript number
|
|
64
|
+
napi_value js_result;
|
|
65
|
+
status = napi_create_double(env, result, &js_result);
|
|
66
|
+
if (status != napi_ok)
|
|
67
|
+
return NULL;
|
|
68
|
+
|
|
69
|
+
return js_result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
napi_value l2sqAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_sqeuclidean_k); }
|
|
73
|
+
napi_value cosAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_cosine_k); }
|
|
74
|
+
napi_value ipAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_ip_k); }
|
|
75
|
+
napi_value klAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_kl_k); }
|
|
76
|
+
napi_value jsAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_js_k); }
|
|
77
|
+
napi_value hammingAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_hamming_k); }
|
|
78
|
+
napi_value jaccardAPI(napi_env env, napi_callback_info info) { return runAPI(env, info, simsimd_metric_jaccard_k); }
|
|
79
|
+
|
|
80
|
+
napi_value Init(napi_env env, napi_value exports) {
|
|
81
|
+
|
|
82
|
+
// Define an array of property descriptors
|
|
83
|
+
napi_property_descriptor sqeuclideanDesc = {"sqeuclidean", 0, l2sqAPI, 0, 0, 0, napi_default, 0};
|
|
84
|
+
napi_property_descriptor innerDesc = {"inner", 0, ipAPI, 0, 0, 0, napi_default, 0};
|
|
85
|
+
napi_property_descriptor cosineDesc = {"cosine", 0, cosAPI, 0, 0, 0, napi_default, 0};
|
|
86
|
+
napi_property_descriptor hammingDesc = {"hamming", 0, hammingAPI, 0, 0, 0, napi_default, 0};
|
|
87
|
+
napi_property_descriptor jaccardDesc = {"jaccard", 0, jaccardAPI, 0, 0, 0, napi_default, 0};
|
|
88
|
+
napi_property_descriptor klDesc = {"kullbackleibler", 0, klAPI, 0, 0, 0, napi_default, 0};
|
|
89
|
+
napi_property_descriptor jsDesc = {"jensenshannon", 0, jsAPI, 0, 0, 0, napi_default, 0};
|
|
90
|
+
napi_property_descriptor properties[] = {
|
|
91
|
+
sqeuclideanDesc, innerDesc, cosineDesc, hammingDesc, jaccardDesc, klDesc, jsDesc,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Define the properties on the `exports` object
|
|
95
|
+
size_t propertyCount = sizeof(properties) / sizeof(properties[0]);
|
|
96
|
+
napi_define_properties(env, exports, propertyCount, properties);
|
|
97
|
+
|
|
98
|
+
static_capabilities = simsimd_capabilities();
|
|
99
|
+
return exports;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const compiled = require('bindings')('simsimd');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @brief Computes the squared Euclidean distance between two vectors.
|
|
7
|
+
* @param {Float32Array|Int8Array} a - The first vector.
|
|
8
|
+
* @param {Float32Array|Int8Array} b - The second vector.
|
|
9
|
+
* @returns {number} The squared Euclidean distance between vectors a and b.
|
|
10
|
+
*/
|
|
11
|
+
sqeuclidean: compiled.sqeuclidean,
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @brief Computes the cosine similarity between two vectors.
|
|
15
|
+
* @param {Float32Array|Int8Array} a - The first vector.
|
|
16
|
+
* @param {Float32Array|Int8Array} b - The second vector.
|
|
17
|
+
* @returns {number} The cosine similarity between vectors a and b.
|
|
18
|
+
*/
|
|
19
|
+
cosine: compiled.cosine,
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @brief Computes the inner product of two vectors.
|
|
23
|
+
* @param {Float32Array} a - The first vector.
|
|
24
|
+
* @param {Float32Array} b - The second vector.
|
|
25
|
+
* @returns {number} The inner product of vectors a and b.
|
|
26
|
+
*/
|
|
27
|
+
inner: compiled.inner,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @brief Computes the bitwise Hamming distance between two vectors.
|
|
31
|
+
* @param {Uint8Array} a - The first vector.
|
|
32
|
+
* @param {Uint8Array} b - The second vector.
|
|
33
|
+
* @returns {number} The Hamming distance between vectors a and b.
|
|
34
|
+
*/
|
|
35
|
+
hamming: compiled.hamming,
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @brief Computes the bitwise Jaccard similarity coefficient between two vectors.
|
|
39
|
+
* @param {Uint8Array} a - The first vector.
|
|
40
|
+
* @param {Uint8Array} b - The second vector.
|
|
41
|
+
* @returns {number} The Jaccard similarity coefficient between vectors a and b.
|
|
42
|
+
*/
|
|
43
|
+
jaccard: compiled.jaccard,
|
|
44
|
+
|
|
45
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import bindings from 'bindings';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
|
|
5
|
+
const simsimd = bindings('simsimd');
|
|
6
|
+
|
|
7
|
+
const typedArray1 = new Float32Array([1.0, 2.0, 3.0]);
|
|
8
|
+
const typedArray2 = new Float32Array([4.0, 5.0, 6.0]);
|
|
9
|
+
|
|
10
|
+
function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
|
|
11
|
+
const lowerBound = expected - tolerance;
|
|
12
|
+
const upperBound = expected + tolerance;
|
|
13
|
+
assert(actual >= lowerBound && actual <= upperBound, `Expected ${actual} to be almost equal to ${expected}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('Squared Euclidean Distance', () => {
|
|
17
|
+
const result = simsimd.sqeuclidean(typedArray1, typedArray2);
|
|
18
|
+
assertAlmostEqual(result, 27.0, 0.01);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('Inner Product', () => {
|
|
22
|
+
const result = simsimd.inner(typedArray1, typedArray2);
|
|
23
|
+
assertAlmostEqual(result, -31.0, 0.01);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('Cosine Similarity', () => {
|
|
27
|
+
const result = simsimd.cosine(typedArray1, typedArray2);
|
|
28
|
+
assertAlmostEqual(result, 0.029, 0.01);
|
|
29
|
+
});
|
package/simsimd/package.json
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
+
"name": "simsimd",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Vector Similarity Functions 3x-200x Faster than SciPy and NumPy",
|
|
5
|
+
"author": "Ash Vardanian",
|
|
6
|
+
"license": "Apache 2.0",
|
|
7
|
+
"main": "javascript/simsimd.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ashvardanian/simsimd.git"
|
|
12
|
+
},
|
|
13
|
+
"gypfile": true,
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "~10 >=10.20 || >=12.17"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@types/node": "^20.4.5",
|
|
19
|
+
"bindings": "~1.2.1",
|
|
20
|
+
"node-addon-api": "^3.0.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test ./javascript/test.js",
|
|
24
|
+
"bench": "node ./javascript/bench.js"
|
|
25
|
+
},
|
|
2
26
|
"devDependencies": {
|
|
3
27
|
"@semantic-release/exec": "^6.0.3",
|
|
4
28
|
"@semantic-release/git": "^10.0.1",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
29
|
+
"benchmark": "^2.1.4",
|
|
30
|
+
"conventional-changelog-eslint": "^3.0.9",
|
|
31
|
+
"mathjs": "^11.11.2",
|
|
32
|
+
"node-gyp": "^8.3.0",
|
|
33
|
+
"semantic-release": "^21.1.2",
|
|
34
|
+
"typescript": "^5.1.6",
|
|
35
|
+
"usearch": "^2.7.2"
|
|
7
36
|
}
|
|
8
37
|
}
|
package/simsimd/pyproject.toml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=42", "wheel", "
|
|
2
|
+
requires = ["setuptools>=42", "wheel", "numpy"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[tool.pytest.ini_options]
|
|
@@ -9,10 +9,10 @@ xfail_strict = true
|
|
|
9
9
|
filterwarnings = ["error"]
|
|
10
10
|
|
|
11
11
|
[tool.cibuildwheel]
|
|
12
|
-
test-requires = ["pytest"]
|
|
13
|
-
test-command = "pytest {project}/python/test.py -x"
|
|
12
|
+
test-requires = ["pytest", "pytest-repeat", "scipy"]
|
|
13
|
+
test-command = "pytest {project}/python/test.py -s -x"
|
|
14
14
|
build-verbosity = 0
|
|
15
|
-
skip = ["*musllinux*", "*i686*", "pp*"]
|
|
15
|
+
skip = ["*musllinux*", "*i686*", "pp*", "cp36-*", "cp37-*"]
|
|
16
16
|
|
|
17
17
|
[tool.cibuildwheel.linux]
|
|
18
18
|
before-build = [
|
|
@@ -30,7 +30,6 @@ repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
|
|
|
30
30
|
[tool.cibuildwheel.macos]
|
|
31
31
|
before-build = [
|
|
32
32
|
"rm -rf {project}/build",
|
|
33
|
-
"brew install gcc@12",
|
|
34
33
|
]
|
|
35
34
|
archs = ["x86_64", "arm64"]
|
|
36
35
|
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
|
@@ -40,4 +39,4 @@ before-build = [
|
|
|
40
39
|
"rd /s /q {project}\\build || echo Done",
|
|
41
40
|
]
|
|
42
41
|
archs = ["x86", "AMD64"]
|
|
43
|
-
skip = ["*win32*", "pp*"]
|
|
42
|
+
skip = ["*win32*", "pp*"]
|