usearch 2.9.1 → 2.10.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 +50 -52
- package/javascript/{types → dist/cjs}/usearch.d.ts +90 -115
- package/javascript/dist/cjs/usearch.js +531 -0
- package/javascript/dist/esm/usearch.d.ts +212 -0
- package/javascript/{usearch.js → dist/esm/usearch.js} +202 -153
- package/package.json +25 -23
- 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/prebuilds/win32-ia32/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
- package/.clang-format +0 -10
- package/.clang-tidy +0 -60
- package/.cmake-format.py +0 -19
- package/.dockerignore +0 -93
- package/.gitattributes +0 -9
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -64
- package/.github/workflows/Dockerfile.libs +0 -36
- package/.github/workflows/prerelease.yml +0 -331
- package/.github/workflows/release.yml +0 -722
- package/.github/workflows/update_version.sh +0 -15
- package/.gitmodules +0 -9
- package/.releaserc +0 -90
- package/CITATION.cff +0 -11
- package/CMakeLists.txt +0 -324
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -353
- package/Cargo.toml +0 -19
- package/Dockerfile +0 -16
- package/VERSION +0 -1
- package/assets/unum.png +0 -0
- package/assets/usearch-approaches-transparent.png +0 -0
- package/assets/usearch-approaches-white.png +0 -0
- package/assets/usearch-neighbor-types.png +0 -0
- package/binding.gyp +0 -87
- package/build.gradle +0 -204
- package/build.rs +0 -18
- package/c/CMakeLists.txt +0 -25
- package/c/README.md +0 -34
- package/c/lib.cpp +0 -358
- package/c/test.c +0 -374
- package/c/usearch.h +0 -311
- package/cmake/clipp.cmake +0 -7
- package/cmake/config.cmake.in +0 -15
- package/cmake/emscripten.cmake +0 -8
- package/cmake/jemalloc.cmake +0 -62
- package/cmake/pkg-config.pc.in +0 -4
- package/cmake/usearchConfigVersion.cmake.in +0 -20
- package/conanfile.py +0 -26
- package/cpp/CMakeLists.txt +0 -14
- package/cpp/README.md +0 -161
- package/cpp/bench.cpp +0 -618
- package/cpp/test.cpp +0 -323
- package/fp16/CMakeLists.txt +0 -176
- package/fp16/LICENSE +0 -11
- package/fp16/README.md +0 -20
- package/fp16/bench/alt-element.cc +0 -89
- package/fp16/bench/from-alt-array.cc +0 -175
- package/fp16/bench/from-ieee-array.cc +0 -399
- package/fp16/bench/ieee-element.cc +0 -272
- package/fp16/bench/to-alt-array.cc +0 -91
- package/fp16/bench/to-ieee-array.cc +0 -303
- package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
- package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
- package/fp16/cmake/DownloadPSimd.cmake +0 -15
- package/fp16/configure.py +0 -65
- package/fp16/confu.yaml +0 -8
- package/fp16/include/fp16/__init__.py +0 -0
- package/fp16/include/fp16/avx.py +0 -50
- package/fp16/include/fp16/avx2.py +0 -53
- package/fp16/include/fp16/bitcasts.h +0 -92
- package/fp16/include/fp16/fp16.h +0 -451
- package/fp16/include/fp16/psimd.h +0 -131
- package/fp16/include/fp16.h +0 -11
- package/fp16/jni/Android.mk +0 -6
- package/fp16/jni/Application.mk +0 -4
- package/fp16/test/alt-from-fp32-value.cc +0 -498
- package/fp16/test/alt-to-fp32-bits.cc +0 -263
- package/fp16/test/alt-to-fp32-psimd.cc +0 -145
- package/fp16/test/alt-to-fp32-value.cc +0 -348
- package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
- package/fp16/test/bitcasts.cc +0 -56
- package/fp16/test/ieee-from-fp32-value.cc +0 -523
- package/fp16/test/ieee-to-fp32-bits.cc +0 -333
- package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
- package/fp16/test/ieee-to-fp32-value.cc +0 -420
- package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
- package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
- package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
- package/fp16/test/peachpy/stubs.py +0 -38
- package/fp16/test/tables.cc +0 -530
- package/fp16/test/tables.h +0 -13
- package/fp16/third-party/THHalf.h +0 -92
- package/fp16/third-party/eigen-half.h +0 -170
- package/fp16/third-party/float16-compressor.h +0 -80
- package/fp16/third-party/half.hpp +0 -2916
- package/fp16/third-party/npy-halffloat.h +0 -328
- package/include/usearch/index.hpp +0 -3836
- package/include/usearch/index_dense.hpp +0 -2004
- package/include/usearch/index_plugins.hpp +0 -2203
- package/javascript/lib.cpp +0 -341
- package/javascript/types/usearch.d.ts.map +0 -1
- package/javascript/usearch.test.js +0 -74
- package/package-ci.json +0 -10
- package/pyproject.toml +0 -51
- package/setup.py +0 -201
- package/simsimd/.releaserc +0 -86
- package/simsimd/Cargo.lock +0 -708
- package/simsimd/Cargo.toml +0 -36
- package/simsimd/LICENSE +0 -201
- package/simsimd/README.md +0 -275
- package/simsimd/binding.gyp +0 -36
- package/simsimd/build.rs +0 -20
- package/simsimd/include/simsimd/binary.h +0 -201
- package/simsimd/include/simsimd/probability.h +0 -548
- package/simsimd/include/simsimd/simsimd.h +0 -456
- package/simsimd/include/simsimd/spatial.h +0 -1175
- package/simsimd/include/simsimd/types.h +0 -207
- package/simsimd/javascript/bench.js +0 -127
- package/simsimd/javascript/fallback.ts +0 -193
- package/simsimd/javascript/lib.c +0 -102
- package/simsimd/javascript/node-gyp-build.d.ts +0 -1
- package/simsimd/javascript/simsimd.ts +0 -124
- package/simsimd/javascript/test.mjs +0 -153
- package/simsimd/javascript/tsconfig-base.json +0 -25
- package/simsimd/javascript/tsconfig-cjs.json +0 -8
- package/simsimd/javascript/tsconfig-esm.json +0 -8
- package/simsimd/package-lock.json +0 -1688
- package/simsimd/package.json +0 -61
- package/simsimd/rust/benches/cosine.rs +0 -27
- package/simsimd/rust/benches/native.rs +0 -27
- package/simsimd/rust/benches/sqeuclidean.rs +0 -27
- package/simsimd/rust/lib.c +0 -68
- package/simsimd/rust/lib.rs +0 -232
- package/sqlite/README.md +0 -178
- package/sqlite/lib.cpp +0 -325
- package/stringzilla/CONTRIBUTING.md +0 -429
- package/stringzilla/Cargo.lock +0 -25
- package/stringzilla/Cargo.toml +0 -26
- package/stringzilla/LICENSE +0 -201
- package/stringzilla/README.md +0 -1281
- package/stringzilla/VERSION +0 -1
- package/stringzilla/assets/cover-strinzilla.jpeg +0 -0
- package/stringzilla/assets/meme-stringzilla-v2.jpeg +0 -0
- package/stringzilla/assets/meme-stringzilla-v3.jpeg +0 -0
- package/stringzilla/binding.gyp +0 -16
- package/stringzilla/build.rs +0 -14
- package/stringzilla/c/lib.c +0 -304
- package/stringzilla/cli/README.md +0 -48
- package/stringzilla/cli/split.py +0 -72
- package/stringzilla/cli/wc.py +0 -37
- package/stringzilla/include/stringzilla/experimental.h +0 -724
- package/stringzilla/include/stringzilla/module.modulemap +0 -9
- package/stringzilla/include/stringzilla/stringzilla.h +0 -5240
- package/stringzilla/include/stringzilla/stringzilla.hpp +0 -3830
- package/stringzilla/javascript/lib.c +0 -125
- package/stringzilla/javascript/stringzilla.js +0 -22
- package/stringzilla/package-ci.json +0 -10
- package/stringzilla/package.json +0 -32
- package/stringzilla/rust/lib.rs +0 -362
- package/stringzilla/swift/StringProtocol+StringZilla.swift +0 -280
- package/stringzilla/swift/Test.swift +0 -60
- package/tsconfig.json +0 -21
- package/wasmer.toml +0 -12
- package/winlibinstaller.bat +0 -42
- /package/{simsimd/javascript/dist-package-cjs.json → javascript/dist/cjs/package.json} +0 -0
- /package/{simsimd/javascript/dist-package-esm.json → javascript/dist/esm/package.json} +0 -0
|
@@ -1,45 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import build from "node-gyp-build";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { getFileName, getRoot } from "bindings";
|
|
5
|
+
const compiled = build(getBuildDir(getDirName()));
|
|
3
6
|
/**
|
|
4
7
|
* Enumeration representing the various metric kinds used to measure the distance between vectors in the index.
|
|
5
8
|
* @enum {string}
|
|
6
9
|
* @readonly
|
|
7
10
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
export var MetricKind;
|
|
12
|
+
(function (MetricKind) {
|
|
13
|
+
MetricKind["Unknown"] = "unknown";
|
|
14
|
+
MetricKind["Cos"] = "cos";
|
|
15
|
+
MetricKind["IP"] = "ip";
|
|
16
|
+
MetricKind["L2sq"] = "l2sq";
|
|
17
|
+
MetricKind["Haversine"] = "haversine";
|
|
18
|
+
MetricKind["Divergence"] = "divergence";
|
|
19
|
+
MetricKind["Pearson"] = "pearson";
|
|
20
|
+
MetricKind["Jaccard"] = "jaccard";
|
|
21
|
+
MetricKind["Hamming"] = "hamming";
|
|
22
|
+
MetricKind["Tanimoto"] = "tanimoto";
|
|
23
|
+
MetricKind["Sorensen"] = "sorensen";
|
|
24
|
+
})(MetricKind || (MetricKind = {}));
|
|
22
25
|
/**
|
|
23
26
|
* Enumeration representing the various scalar kinds used to define the type of scalar values in vectors.
|
|
24
27
|
* @enum {string}
|
|
25
28
|
* @readonly
|
|
26
29
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
export var ScalarKind;
|
|
31
|
+
(function (ScalarKind) {
|
|
32
|
+
ScalarKind["Unknown"] = "unknown";
|
|
33
|
+
ScalarKind["F32"] = "f32";
|
|
34
|
+
ScalarKind["F64"] = "f64";
|
|
35
|
+
ScalarKind["F16"] = "f16";
|
|
36
|
+
ScalarKind["I8"] = "i8";
|
|
37
|
+
ScalarKind["B1"] = "b1";
|
|
38
|
+
})(ScalarKind || (ScalarKind = {}));
|
|
36
39
|
/**
|
|
37
40
|
* Represents a set of search results.
|
|
38
41
|
*/
|
|
39
|
-
class Matches {
|
|
42
|
+
export class Matches {
|
|
43
|
+
keys;
|
|
44
|
+
distances;
|
|
40
45
|
/**
|
|
41
46
|
* Constructs a Matches object.
|
|
42
|
-
*
|
|
47
|
+
*
|
|
43
48
|
* @param {BigUint64Array} keys - The keys of the nearest neighbors found.
|
|
44
49
|
* @param {Float32Array} distances - The distances of the nearest neighbors found.
|
|
45
50
|
*/
|
|
@@ -48,18 +53,21 @@ class Matches {
|
|
|
48
53
|
this.distances = distances;
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
|
-
|
|
52
56
|
/**
|
|
53
57
|
* Represents a set of batched search results.
|
|
54
58
|
*/
|
|
55
|
-
class BatchMatches {
|
|
59
|
+
export class BatchMatches {
|
|
60
|
+
keys;
|
|
61
|
+
distances;
|
|
62
|
+
counts;
|
|
63
|
+
k;
|
|
56
64
|
/**
|
|
57
65
|
* Constructs a BatchMatches object.
|
|
58
|
-
*
|
|
66
|
+
*
|
|
59
67
|
* @param {BigUint64Array} keys - The keys of the nearest neighbors found in the batch.
|
|
60
68
|
* @param {Float32Array} distances - The distances of the nearest neighbors found in the batch.
|
|
61
69
|
* @param {BigUint64Array} counts - The number of neighbors found for each query in the batch.
|
|
62
|
-
* @param {
|
|
70
|
+
* @param {number} k - The limit for search results per query in the batch.
|
|
63
71
|
*/
|
|
64
72
|
constructor(keys, distances, counts, k) {
|
|
65
73
|
this.keys = keys;
|
|
@@ -67,10 +75,9 @@ class BatchMatches {
|
|
|
67
75
|
this.counts = counts;
|
|
68
76
|
this.k = k;
|
|
69
77
|
}
|
|
70
|
-
|
|
71
78
|
/**
|
|
72
79
|
* Retrieves a Matches object at the specified index in the batch.
|
|
73
|
-
*
|
|
80
|
+
*
|
|
74
81
|
* @param {number} i - The index at which to retrieve the Matches object.
|
|
75
82
|
* @returns {Matches} - A Matches object representing the search results at the specified index in the batch.
|
|
76
83
|
*/
|
|
@@ -82,61 +89,74 @@ class BatchMatches {
|
|
|
82
89
|
return new Matches(keysSlice, distancesSlice);
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
|
-
|
|
86
92
|
function isOneKey(keys) {
|
|
87
|
-
return (!Number.isNaN(keys) && typeof keys ===
|
|
93
|
+
return ((!Number.isNaN(keys) && typeof keys === "number") ||
|
|
94
|
+
typeof keys === "bigint");
|
|
88
95
|
}
|
|
89
|
-
|
|
90
96
|
function normalizeKeys(keys) {
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
if (keys instanceof BigUint64Array) {
|
|
98
|
+
return keys;
|
|
99
|
+
}
|
|
100
|
+
let normalizedKeys;
|
|
101
|
+
if ((typeof keys === "number" && !Number.isNaN(keys)) ||
|
|
102
|
+
typeof keys === "bigint") {
|
|
103
|
+
normalizedKeys = BigUint64Array.of(BigInt(keys));
|
|
104
|
+
}
|
|
105
|
+
else if (Array.isArray(keys)) {
|
|
106
|
+
const bigintkeys = keys.map((key) => {
|
|
107
|
+
if (typeof key === "bigint") {
|
|
108
|
+
return key;
|
|
109
|
+
}
|
|
110
|
+
else if (typeof key === "number" &&
|
|
111
|
+
!Number.isNaN(key) &&
|
|
112
|
+
Number.isInteger(key) &&
|
|
113
|
+
key >= 0) {
|
|
114
|
+
return BigInt(key);
|
|
115
|
+
}
|
|
116
|
+
throw new Error("All keys must be positive integers or bigints.");
|
|
98
117
|
});
|
|
99
|
-
|
|
100
|
-
}
|
|
118
|
+
normalizedKeys = BigUint64Array.from(bigintkeys);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
101
121
|
throw new Error("Keys must be a number, bigint, an array of numbers or bigints, or a BigUint64Array.");
|
|
102
122
|
}
|
|
103
|
-
return
|
|
123
|
+
return normalizedKeys;
|
|
104
124
|
}
|
|
105
|
-
|
|
106
125
|
function isVector(vectors) {
|
|
107
|
-
return vectors instanceof Float32Array ||
|
|
126
|
+
return (vectors instanceof Float32Array ||
|
|
127
|
+
vectors instanceof Float64Array ||
|
|
128
|
+
vectors instanceof Int8Array);
|
|
108
129
|
}
|
|
109
|
-
|
|
110
130
|
function normalizeVectors(vectors, dimensions, targetType = Float32Array) {
|
|
111
131
|
let flattenedVectors;
|
|
112
132
|
if (isVector(vectors)) {
|
|
113
|
-
flattenedVectors =
|
|
114
|
-
|
|
133
|
+
flattenedVectors =
|
|
134
|
+
vectors.constructor === targetType
|
|
135
|
+
? vectors
|
|
136
|
+
: new targetType(vectors);
|
|
137
|
+
}
|
|
138
|
+
else if (Array.isArray(vectors)) {
|
|
115
139
|
let totalLength = 0;
|
|
116
|
-
for (const vec of vectors)
|
|
117
|
-
|
|
140
|
+
for (const vec of vectors)
|
|
141
|
+
totalLength += vec.length;
|
|
118
142
|
flattenedVectors = new targetType(totalLength);
|
|
119
143
|
let offset = 0;
|
|
120
144
|
for (const vec of vectors) {
|
|
121
145
|
flattenedVectors.set(vec, offset);
|
|
122
146
|
offset += vec.length;
|
|
123
147
|
}
|
|
124
|
-
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
125
150
|
throw new Error("Vectors must be a TypedArray or an array of arrays.");
|
|
126
151
|
}
|
|
127
|
-
|
|
128
152
|
if (flattenedVectors.length % dimensions !== 0)
|
|
129
153
|
throw new Error("The size of the flattened vectors must be a multiple of the dimension of the vectors.");
|
|
130
|
-
|
|
131
154
|
return flattenedVectors;
|
|
132
155
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class Index {
|
|
136
|
-
|
|
156
|
+
export class Index {
|
|
137
157
|
/**
|
|
138
158
|
* Constructs a new index.
|
|
139
|
-
*
|
|
159
|
+
*
|
|
140
160
|
* @param {(number | {dimensions: number, metric: MetricKind = MetricKind.Cos, quantization: ScalarKind = ScalarKind.F32, connectivity: number = 0, expansion_add: number = 0, expansion_search: number = 0, multi: boolean = false})} dimensionsOrConfigs
|
|
141
161
|
* @param {MetricKind} [metric=MetricKind.Cos] - Optional, default is 'cos'.
|
|
142
162
|
* @param {ScalarKind} [quantization=ScalarKind.F32] - Optional, default is 'f32'.
|
|
@@ -148,50 +168,62 @@ class Index {
|
|
|
148
168
|
*/
|
|
149
169
|
constructor(dimensionsOrConfigs, metric = MetricKind.Cos, quantization = ScalarKind.F32, connectivity = 0, expansion_add = 0, expansion_search = 0, multi = false) {
|
|
150
170
|
let dimensions;
|
|
151
|
-
if (typeof dimensionsOrConfigs ===
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
} else if ((typeof dimensionsOrConfigs === 'number' && !Number.isNaN(dimensionsOrConfigs)) || typeof dimensionsOrConfigs === 'bigint') {
|
|
171
|
+
if ((typeof dimensionsOrConfigs === "number" &&
|
|
172
|
+
!Number.isNaN(dimensionsOrConfigs)) ||
|
|
173
|
+
typeof dimensionsOrConfigs === "bigint") {
|
|
155
174
|
// Parameters are provided as individual arguments
|
|
156
175
|
dimensions = dimensionsOrConfigs;
|
|
157
|
-
}
|
|
176
|
+
}
|
|
177
|
+
else if (typeof dimensionsOrConfigs === "object" &&
|
|
178
|
+
dimensionsOrConfigs !== null) {
|
|
179
|
+
// Parameters are provided as an object
|
|
180
|
+
({
|
|
181
|
+
dimensions,
|
|
182
|
+
metric = MetricKind.Cos,
|
|
183
|
+
quantization = ScalarKind.F32,
|
|
184
|
+
connectivity = 0,
|
|
185
|
+
expansion_add = 0,
|
|
186
|
+
expansion_search = 0,
|
|
187
|
+
multi = false,
|
|
188
|
+
} = dimensionsOrConfigs);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
158
191
|
throw new Error("Invalid arguments. Expected either individual arguments or a single object argument.");
|
|
159
192
|
}
|
|
160
|
-
|
|
161
|
-
|
|
193
|
+
if ((typeof dimensions !== 'bigint' && (!Number.isInteger(dimensions) || dimensions <= 0)) ||
|
|
194
|
+
(typeof connectivity !== 'bigint' && (!Number.isInteger(connectivity) || connectivity < 0)) ||
|
|
195
|
+
(typeof expansion_add !== 'bigint' && (!Number.isInteger(expansion_add) || expansion_add < 0)) ||
|
|
196
|
+
(typeof expansion_search !== 'bigint' && (!Number.isInteger(expansion_search) || expansion_search < 0))) {
|
|
162
197
|
throw new Error("`dimensions`, `connectivity`, `expansion_add`, and `expansion_search` must be non-negative integers, with `dimensions` being positive.");
|
|
163
198
|
}
|
|
164
|
-
|
|
165
|
-
if (typeof multi !== 'boolean') {
|
|
199
|
+
if (typeof multi !== "boolean") {
|
|
166
200
|
throw new Error("`multi` must be a boolean value.");
|
|
167
201
|
}
|
|
168
|
-
|
|
169
202
|
if (!Object.values(MetricKind).includes(metric)) {
|
|
170
|
-
throw new Error(`Invalid metric: ${metric}. It must be one of: ${Object.values(MetricKind).join(
|
|
203
|
+
throw new Error(`Invalid metric: ${metric}. It must be one of: ${Object.values(MetricKind).join(", ")}`);
|
|
171
204
|
}
|
|
172
|
-
|
|
173
205
|
if (!Object.values(ScalarKind).includes(quantization)) {
|
|
174
|
-
throw new Error(`Invalid quantization: ${quantization}. It must be one of: ${Object.values(ScalarKind).join(
|
|
206
|
+
throw new Error(`Invalid quantization: ${quantization}. It must be one of: ${Object.values(ScalarKind).join(", ")}`);
|
|
175
207
|
}
|
|
176
|
-
|
|
177
|
-
this
|
|
208
|
+
// @ts-expect-error
|
|
209
|
+
this.#compiledIndex = new compiled.CompiledIndex(dimensions, metric, quantization, connectivity, expansion_add, expansion_search, multi);
|
|
178
210
|
}
|
|
179
|
-
|
|
211
|
+
#compiledIndex;
|
|
180
212
|
/**
|
|
181
213
|
* Add vectors to the index.
|
|
182
|
-
*
|
|
214
|
+
*
|
|
183
215
|
* This method accepts vectors and their corresponding keys for indexing.
|
|
184
216
|
* Each key should correspond to a vector. If a single key is provided,
|
|
185
217
|
* it is broadcasted to match the number of provided vectors.
|
|
186
|
-
*
|
|
218
|
+
*
|
|
187
219
|
* Vectors should be provided as a flat typed array representing a matrix
|
|
188
220
|
* where each row is a vector to be indexed. The matrix should have a size
|
|
189
221
|
* of n * d, where n is the number of vectors, and d is the dimensionality
|
|
190
222
|
* of the vectors.
|
|
191
|
-
*
|
|
223
|
+
*
|
|
192
224
|
* Keys should be provided as a BigInt or an array-like object of BigInts
|
|
193
225
|
* representing the unique identifier for each vector.
|
|
194
|
-
*
|
|
226
|
+
*
|
|
195
227
|
* @param {bigint|bigint[]|BigUint64Array} keys - Input identifiers for every vector.
|
|
196
228
|
* If a single key is provided, it is associated with all provided vectors.
|
|
197
229
|
* @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
|
|
@@ -201,173 +233,169 @@ class Index {
|
|
|
201
233
|
*/
|
|
202
234
|
add(keys, vectors) {
|
|
203
235
|
let normalizedKeys = normalizeKeys(keys);
|
|
204
|
-
let normalizedVectors = normalizeVectors(vectors, this.
|
|
205
|
-
let countVectors = normalizedVectors.length / this.
|
|
206
|
-
|
|
236
|
+
let normalizedVectors = normalizeVectors(vectors, this.#compiledIndex.dimensions());
|
|
237
|
+
let countVectors = normalizedVectors.length / this.#compiledIndex.dimensions();
|
|
207
238
|
// If a single key is provided but there are multiple vectors,
|
|
208
239
|
// broadcast the single key value to match the number of vectors
|
|
209
240
|
if (normalizedKeys.length === 1 && countVectors > 1) {
|
|
210
241
|
normalizedKeys = BigUint64Array.from({ length: countVectors }, () => normalizedKeys[0]);
|
|
211
|
-
}
|
|
242
|
+
}
|
|
243
|
+
else if (normalizedKeys.length !== countVectors) {
|
|
212
244
|
throw new Error(`The length of keys (${normalizedKeys.length}) must match the number of vectors (${countVectors}) or be a single key.`);
|
|
213
245
|
}
|
|
214
|
-
|
|
215
246
|
// Call the compiled method
|
|
216
|
-
this.
|
|
247
|
+
this.#compiledIndex.add(normalizedKeys, normalizedVectors);
|
|
217
248
|
}
|
|
218
|
-
|
|
219
249
|
/**
|
|
220
250
|
* Perform a k-nearest neighbor search on the index.
|
|
221
|
-
*
|
|
251
|
+
*
|
|
222
252
|
* This method accepts a matrix of query vectors and returns the closest vectors
|
|
223
253
|
* from the index for each query. The method returns an object containing the keys,
|
|
224
254
|
* distances, and counts of the matches found.
|
|
225
|
-
*
|
|
255
|
+
*
|
|
226
256
|
* Vectors should be provided as a flat typed array representing a matrix where
|
|
227
257
|
* each row is a vector. The matrix should be of size n * d, where n is the
|
|
228
258
|
* number of query vectors, and d is their dimensionality.
|
|
229
|
-
*
|
|
259
|
+
*
|
|
230
260
|
* The parameter `k` specifies the number of nearest neighbors to return for each
|
|
231
261
|
* query vector. If there are not enough results for a query, the result array is
|
|
232
262
|
* padded with -1s.
|
|
233
|
-
*
|
|
234
|
-
* @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of
|
|
263
|
+
*
|
|
264
|
+
* @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
|
|
235
265
|
* @param {number} k - The number of nearest neighbors to search for each query vector.
|
|
236
266
|
* @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
|
|
237
267
|
* @throws Will throw an error if `k` is not a positive integer or if the size of the vectors is not a multiple of dimensions.
|
|
238
|
-
* @throws Will throw an error if `vectors` is not a valid input type (TypedArray or an array of
|
|
268
|
+
* @throws Will throw an error if `vectors` is not a valid input type (TypedArray or an array of TypedArray) or if its flattened size is not a multiple of dimensions.
|
|
239
269
|
*/
|
|
240
270
|
search(vectors, k) {
|
|
241
|
-
if ((!Number.isNaN(k) && typeof k !==
|
|
271
|
+
if ((!Number.isNaN(k) && typeof k !== "number") || k <= 0) {
|
|
242
272
|
throw new Error("`k` must be a positive integer representing the number of nearest neighbors to search for.");
|
|
243
273
|
}
|
|
244
|
-
|
|
245
|
-
const normalizedVectors = normalizeVectors(vectors, this._compiledIndex.dimensions());
|
|
246
|
-
|
|
274
|
+
const normalizedVectors = normalizeVectors(vectors, this.#compiledIndex.dimensions());
|
|
247
275
|
// Call the compiled method and create Matches or BatchMatches object with the result
|
|
248
|
-
const result = this.
|
|
249
|
-
const countInQueries = normalizedVectors.length / Number(this.
|
|
250
|
-
const batchMatches = new BatchMatches(result
|
|
251
|
-
|
|
276
|
+
const result = this.#compiledIndex.search(normalizedVectors, k);
|
|
277
|
+
const countInQueries = normalizedVectors.length / Number(this.#compiledIndex.dimensions());
|
|
278
|
+
const batchMatches = new BatchMatches(...result, k);
|
|
252
279
|
if (countInQueries === 1) {
|
|
253
280
|
return batchMatches.get(0);
|
|
254
|
-
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
255
283
|
return batchMatches;
|
|
256
284
|
}
|
|
257
285
|
}
|
|
258
|
-
|
|
259
286
|
/**
|
|
260
287
|
* Verifies the presence of one or more keys in the index.
|
|
261
|
-
*
|
|
262
|
-
* This method accepts one or multiple keys as input and returns a boolean or
|
|
288
|
+
*
|
|
289
|
+
* This method accepts one or multiple keys as input and returns a boolean or
|
|
263
290
|
* an array of booleans indicating whether each key is present in the index.
|
|
264
|
-
*
|
|
291
|
+
*
|
|
265
292
|
* @param {bigint|bigint[]|BigUint64Array} keys - The identifier(s) of the vector(s) to be checked for presence in the index.
|
|
266
293
|
* @return {boolean|boolean[]} - Returns true if a single key is contained in the index, false otherwise. Returns an array of booleans corresponding to the presence of each key in the index when multiple keys are provided.
|
|
267
294
|
* @throws Will throw an error if keys are not integers.
|
|
268
295
|
*/
|
|
269
296
|
contains(keys) {
|
|
270
297
|
let normalizedKeys = normalizeKeys(keys);
|
|
271
|
-
let normalizedResults = this.
|
|
298
|
+
let normalizedResults = this.#compiledIndex.contains(normalizedKeys);
|
|
272
299
|
if (isOneKey(keys))
|
|
273
300
|
return normalizedResults[0];
|
|
274
301
|
else
|
|
275
302
|
return normalizedResults;
|
|
276
303
|
}
|
|
277
|
-
|
|
278
304
|
/**
|
|
279
305
|
* Counts the number of times keys shows up in the index.
|
|
280
|
-
*
|
|
306
|
+
*
|
|
281
307
|
* @param {bigint|bigint[]|BigUint64Array} keys - The identifier(s) of the vector(s) to be enumerated.
|
|
282
308
|
* @return {number|number[]} - Returns the number of vectors found when a single key is provided. Returns an array of big integers corresponding to the number of vectors found for each key when multiple keys are provided.
|
|
283
309
|
* @throws Will throw an error if keys are not integers.
|
|
284
310
|
*/
|
|
285
311
|
count(keys) {
|
|
286
312
|
let normalizedKeys = normalizeKeys(keys);
|
|
287
|
-
let normalizedResults = this.
|
|
313
|
+
let normalizedResults = this.#compiledIndex.count(normalizedKeys);
|
|
288
314
|
if (isOneKey(keys))
|
|
289
315
|
return normalizedResults[0];
|
|
290
316
|
else
|
|
291
317
|
return normalizedResults;
|
|
292
318
|
}
|
|
293
|
-
|
|
294
319
|
/**
|
|
295
320
|
* Removes one or multiple vectors from the index.
|
|
296
|
-
*
|
|
321
|
+
*
|
|
297
322
|
* This method accepts one or multiple keys as input and removes the corresponding vectors from the index.
|
|
298
323
|
* It returns the number of vectors actually removed for each key provided.
|
|
299
|
-
*
|
|
324
|
+
*
|
|
300
325
|
* @param {bigint|bigint[]|BigUint64Array} keys - The identifier(s) of the vector(s) to be removed.
|
|
301
326
|
* @return {number|number[]} - Returns the number of vectors deleted when a single key is provided. Returns an array of big integers corresponding to the number of vectors deleted for each key when multiple keys are provided.
|
|
302
327
|
* @throws Will throw an error if keys are not integers.
|
|
303
328
|
*/
|
|
304
329
|
remove(keys) {
|
|
305
330
|
let normalizedKeys = normalizeKeys(keys);
|
|
306
|
-
let normalizedResults = this.
|
|
331
|
+
let normalizedResults = this.#compiledIndex.remove(normalizedKeys);
|
|
307
332
|
if (isOneKey(keys))
|
|
308
333
|
return normalizedResults[0];
|
|
309
334
|
else
|
|
310
335
|
return normalizedResults;
|
|
311
336
|
}
|
|
312
|
-
|
|
313
337
|
/**
|
|
314
338
|
* Returns the dimensionality of vectors.
|
|
315
339
|
* @return {number} The dimensionality of vectors.
|
|
316
340
|
*/
|
|
317
|
-
dimensions() {
|
|
318
|
-
|
|
341
|
+
dimensions() {
|
|
342
|
+
return this.#compiledIndex.dimensions();
|
|
343
|
+
}
|
|
319
344
|
/**
|
|
320
345
|
* Returns connectivity.
|
|
321
346
|
* @return {number} The connectivity of index.
|
|
322
347
|
*/
|
|
323
|
-
connectivity() {
|
|
324
|
-
|
|
348
|
+
connectivity() {
|
|
349
|
+
return this.#compiledIndex.connectivity();
|
|
350
|
+
}
|
|
325
351
|
/**
|
|
326
352
|
* Returns the number of vectors currently indexed.
|
|
327
353
|
* @return {number} The number of vectors currently indexed.
|
|
328
354
|
*/
|
|
329
|
-
size() {
|
|
330
|
-
|
|
355
|
+
size() {
|
|
356
|
+
return this.#compiledIndex.size();
|
|
357
|
+
}
|
|
331
358
|
/**
|
|
332
359
|
* Returns index capacity.
|
|
333
360
|
* @return {number} The capacity of index.
|
|
334
361
|
*/
|
|
335
|
-
capacity() {
|
|
336
|
-
|
|
337
|
-
|
|
362
|
+
capacity() {
|
|
363
|
+
return this.#compiledIndex.capacity();
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
338
366
|
* Write index to a file.
|
|
339
367
|
* @param {string} path File path to write.
|
|
340
368
|
* @throws Will throw an error if `path` is not a string.
|
|
341
369
|
*/
|
|
342
370
|
save(path) {
|
|
343
|
-
if (typeof path !==
|
|
344
|
-
|
|
371
|
+
if (typeof path !== "string")
|
|
372
|
+
throw new Error("`path` must be a string representing the file path to write.");
|
|
373
|
+
this.#compiledIndex.save(path);
|
|
345
374
|
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
375
|
+
/**
|
|
348
376
|
* Load index from a file.
|
|
349
377
|
* @param {string} path File path to read.
|
|
350
378
|
* @throws Will throw an error if `path` is not a string.
|
|
351
379
|
*/
|
|
352
380
|
load(path) {
|
|
353
|
-
if (typeof path !==
|
|
354
|
-
|
|
381
|
+
if (typeof path !== "string")
|
|
382
|
+
throw new Error("`path` must be a string representing the file path to read.");
|
|
383
|
+
this.#compiledIndex.load(path);
|
|
355
384
|
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
385
|
+
/**
|
|
358
386
|
* View index from a file, without loading into RAM.
|
|
359
387
|
* @param {string} path File path to read.
|
|
360
388
|
* @throws Will throw an error if `path` is not a string.
|
|
361
389
|
*/
|
|
362
390
|
view(path) {
|
|
363
|
-
if (typeof path !==
|
|
364
|
-
|
|
391
|
+
if (typeof path !== "string")
|
|
392
|
+
throw new Error("`path` must be a string representing the file path to read.");
|
|
393
|
+
this.#compiledIndex.view(path);
|
|
365
394
|
}
|
|
366
395
|
}
|
|
367
|
-
|
|
368
396
|
/**
|
|
369
397
|
* Performs an exact search on the given dataset to find the best matching vectors for each query.
|
|
370
|
-
*
|
|
398
|
+
*
|
|
371
399
|
* @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.
|
|
372
400
|
* @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.
|
|
373
401
|
* @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
|
|
@@ -379,41 +407,40 @@ class Index {
|
|
|
379
407
|
* @throws Will throw an error if `dataset` and `queries` are not valid input types (TypedArray or an array of arrays).
|
|
380
408
|
* @throws Will throw an error if the sizes of the flattened `dataset` and `queries` are not multiples of `dimensions`.
|
|
381
409
|
* @throws Will throw an error if `count` is greater than the number of vectors in the `dataset`.
|
|
382
|
-
*
|
|
410
|
+
*
|
|
383
411
|
* @example
|
|
384
412
|
* const dataset = [[1.0, 2.0], [3.0, 4.0]]; // Two vectors: [1.0, 2.0] and [3.0, 4.0]
|
|
385
413
|
* const queries = [[1.5, 2.5]]; // One vector: [1.5, 2.5]
|
|
386
414
|
* const dimensions = 2; // The number of elements in each vector.
|
|
387
415
|
* const count = 1; // The number of nearest neighbors to return for each query.
|
|
388
416
|
* const metric = MetricKind.IP; // Using the Inner Product distance metric.
|
|
389
|
-
*
|
|
417
|
+
*
|
|
390
418
|
* const result = exactSearch(dataset, queries, dimensions, count, metric);
|
|
391
|
-
* // result might be:
|
|
419
|
+
* // result might be:
|
|
392
420
|
* // {
|
|
393
421
|
* // keys: BigUint64Array [ 1n ],
|
|
394
422
|
* // distances: Float32Array [ some_value ],
|
|
395
423
|
* // }
|
|
396
424
|
*/
|
|
397
425
|
function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
398
|
-
|
|
399
426
|
// Validate and normalize the dimensions and count
|
|
400
427
|
dimensions = Number(dimensions);
|
|
401
428
|
count = Number(count);
|
|
402
429
|
if (count <= 0 || dimensions <= 0) {
|
|
403
430
|
throw new Error("Dimensions and count must be positive integers.");
|
|
404
431
|
}
|
|
405
|
-
|
|
406
432
|
// Validate metric
|
|
407
433
|
if (!Object.values(MetricKind).includes(metric)) {
|
|
408
|
-
throw new Error(`Invalid metric: ${metric}. It must be one of: ${Object.values(MetricKind).join(
|
|
434
|
+
throw new Error(`Invalid metric: ${metric}. It must be one of: ${Object.values(MetricKind).join(", ")}`);
|
|
409
435
|
}
|
|
410
|
-
|
|
411
436
|
// Flatten and normalize dataset and queries if they are arrays of arrays
|
|
412
437
|
let targetType;
|
|
413
|
-
if (dataset instanceof Float64Array)
|
|
414
|
-
|
|
415
|
-
else
|
|
416
|
-
|
|
438
|
+
if (dataset instanceof Float64Array)
|
|
439
|
+
targetType = Float64Array;
|
|
440
|
+
else if (dataset instanceof Int8Array)
|
|
441
|
+
targetType = Int8Array;
|
|
442
|
+
else
|
|
443
|
+
targetType = Float32Array; // default to Float32Array if dataset is not Float64Array or Int8Array
|
|
417
444
|
dataset = normalizeVectors(dataset, dimensions, targetType);
|
|
418
445
|
queries = normalizeVectors(queries, dimensions, targetType);
|
|
419
446
|
const countInDataset = dataset.length / dimensions;
|
|
@@ -421,18 +448,16 @@ function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
|
421
448
|
if (count > countInDataset) {
|
|
422
449
|
throw new Error("Count must be equal or smaller than the number of vectors in the dataset.");
|
|
423
450
|
}
|
|
424
|
-
|
|
425
451
|
// Call the compiled function with the normalized input
|
|
426
452
|
const result = compiled.exactSearch(dataset, queries, dimensions, count, metric);
|
|
427
|
-
|
|
428
453
|
// Create and return a Matches or BatchMatches object with the result
|
|
429
454
|
if (countInQueries == 1) {
|
|
430
455
|
return new Matches(result[0], result[1]);
|
|
431
|
-
}
|
|
432
|
-
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
return new BatchMatches(...result, count);
|
|
433
459
|
}
|
|
434
460
|
}
|
|
435
|
-
|
|
436
461
|
module.exports = {
|
|
437
462
|
Index,
|
|
438
463
|
MetricKind,
|
|
@@ -441,3 +466,27 @@ module.exports = {
|
|
|
441
466
|
BatchMatches,
|
|
442
467
|
exactSearch,
|
|
443
468
|
};
|
|
469
|
+
// utility functions to help find native builds
|
|
470
|
+
function getBuildDir(dir) {
|
|
471
|
+
if (existsSync(path.join(dir, "build")))
|
|
472
|
+
return dir;
|
|
473
|
+
if (existsSync(path.join(dir, "prebuilds")))
|
|
474
|
+
return dir;
|
|
475
|
+
if (path.basename(dir) === ".next") {
|
|
476
|
+
// special case for next.js on custom node (not vercel)
|
|
477
|
+
const sideways = path.join(dir, "..", "node_modules", "usearch");
|
|
478
|
+
if (existsSync(sideways))
|
|
479
|
+
return getBuildDir(sideways);
|
|
480
|
+
}
|
|
481
|
+
if (dir === "/")
|
|
482
|
+
throw new Error("Could not find native build for usearch");
|
|
483
|
+
return getBuildDir(path.join(dir, ".."));
|
|
484
|
+
}
|
|
485
|
+
function getDirName() {
|
|
486
|
+
try {
|
|
487
|
+
if (__dirname)
|
|
488
|
+
return __dirname;
|
|
489
|
+
}
|
|
490
|
+
catch (e) { }
|
|
491
|
+
return getRoot(getFileName());
|
|
492
|
+
}
|