usearch 2.10.2 → 2.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +1 -1
  2. package/binding.gyp +101 -0
  3. package/c/CMakeLists.txt +27 -0
  4. package/c/README.md +34 -0
  5. package/c/lib.cpp +362 -0
  6. package/c/test.c +382 -0
  7. package/c/usearch.h +356 -0
  8. package/cmake/clipp.cmake +7 -0
  9. package/cmake/config.cmake.in +15 -0
  10. package/cmake/emscripten.cmake +8 -0
  11. package/cmake/jemalloc.cmake +62 -0
  12. package/cmake/pkg-config.pc.in +4 -0
  13. package/cmake/usearchConfigVersion.cmake.in +20 -0
  14. package/cpp/CMakeLists.txt +14 -0
  15. package/cpp/README.md +161 -0
  16. package/cpp/bench.cpp +616 -0
  17. package/cpp/test.cpp +324 -0
  18. package/fp16/CMakeLists.txt +176 -0
  19. package/fp16/LICENSE +11 -0
  20. package/fp16/README.md +20 -0
  21. package/fp16/bench/alt-element.cc +89 -0
  22. package/fp16/bench/from-alt-array.cc +175 -0
  23. package/fp16/bench/from-ieee-array.cc +399 -0
  24. package/fp16/bench/ieee-element.cc +272 -0
  25. package/fp16/bench/to-alt-array.cc +91 -0
  26. package/fp16/bench/to-ieee-array.cc +303 -0
  27. package/fp16/cmake/DownloadGoogleBenchmark.cmake +15 -0
  28. package/fp16/cmake/DownloadGoogleTest.cmake +15 -0
  29. package/fp16/cmake/DownloadPSimd.cmake +15 -0
  30. package/fp16/configure.py +65 -0
  31. package/fp16/confu.yaml +8 -0
  32. package/fp16/include/fp16/__init__.py +0 -0
  33. package/fp16/include/fp16/avx.py +50 -0
  34. package/fp16/include/fp16/avx2.py +53 -0
  35. package/fp16/include/fp16/bitcasts.h +92 -0
  36. package/fp16/include/fp16/fp16.h +451 -0
  37. package/fp16/include/fp16/psimd.h +131 -0
  38. package/fp16/include/fp16.h +11 -0
  39. package/fp16/jni/Android.mk +6 -0
  40. package/fp16/jni/Application.mk +4 -0
  41. package/fp16/test/alt-from-fp32-value.cc +498 -0
  42. package/fp16/test/alt-to-fp32-bits.cc +263 -0
  43. package/fp16/test/alt-to-fp32-psimd.cc +145 -0
  44. package/fp16/test/alt-to-fp32-value.cc +348 -0
  45. package/fp16/test/alt-to-fp32x2-psimd.cc +245 -0
  46. package/fp16/test/bitcasts.cc +56 -0
  47. package/fp16/test/ieee-from-fp32-value.cc +523 -0
  48. package/fp16/test/ieee-to-fp32-bits.cc +333 -0
  49. package/fp16/test/ieee-to-fp32-psimd.cc +312 -0
  50. package/fp16/test/ieee-to-fp32-value.cc +420 -0
  51. package/fp16/test/ieee-to-fp32x2-psimd.cc +541 -0
  52. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +96 -0
  53. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +96 -0
  54. package/fp16/test/peachpy/stubs.py +38 -0
  55. package/fp16/test/tables.cc +530 -0
  56. package/fp16/test/tables.h +13 -0
  57. package/fp16/third-party/THHalf.h +92 -0
  58. package/fp16/third-party/eigen-half.h +170 -0
  59. package/fp16/third-party/float16-compressor.h +80 -0
  60. package/fp16/third-party/half.hpp +2916 -0
  61. package/fp16/third-party/npy-halffloat.h +328 -0
  62. package/include/usearch/index.hpp +3838 -0
  63. package/include/usearch/index_dense.hpp +2004 -0
  64. package/include/usearch/index_plugins.hpp +2255 -0
  65. package/javascript/dist-package-cjs.json +3 -0
  66. package/javascript/dist-package-esm.json +3 -0
  67. package/javascript/lib.cpp +350 -0
  68. package/javascript/node-gyp-build.d.ts +1 -0
  69. package/javascript/tsconfig-base.json +25 -0
  70. package/javascript/tsconfig-cjs.json +8 -0
  71. package/javascript/tsconfig-esm.json +8 -0
  72. package/javascript/usearch.test.js +84 -0
  73. package/javascript/usearch.ts +640 -0
  74. package/package.json +10 -3
  75. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  76. package/prebuilds/linux-arm64/usearch.node +0 -0
  77. package/prebuilds/linux-x64/usearch.node +0 -0
  78. package/prebuilds/win32-ia32/usearch.node +0 -0
  79. package/prebuilds/win32-x64/usearch.node +0 -0
  80. package/simsimd/.releaserc +87 -0
  81. package/simsimd/CONTRIBUTING.md +114 -0
  82. package/simsimd/Cargo.lock +709 -0
  83. package/simsimd/Cargo.toml +44 -0
  84. package/simsimd/LICENSE +201 -0
  85. package/simsimd/README.md +708 -0
  86. package/simsimd/binding.gyp +36 -0
  87. package/simsimd/build.rs +51 -0
  88. package/simsimd/c/lib.c +110 -0
  89. package/simsimd/include/simsimd/binary.h +258 -0
  90. package/simsimd/include/simsimd/dot.h +1237 -0
  91. package/simsimd/include/simsimd/geospatial.h +37 -0
  92. package/simsimd/include/simsimd/probability.h +563 -0
  93. package/simsimd/include/simsimd/simsimd.h +1129 -0
  94. package/simsimd/include/simsimd/spatial.h +1019 -0
  95. package/simsimd/include/simsimd/types.h +309 -0
  96. package/simsimd/javascript/bench.js +127 -0
  97. package/simsimd/javascript/dist-package-cjs.json +3 -0
  98. package/simsimd/javascript/dist-package-esm.json +3 -0
  99. package/simsimd/javascript/fallback.ts +203 -0
  100. package/simsimd/javascript/lib.c +107 -0
  101. package/simsimd/javascript/node-gyp-build.d.ts +1 -0
  102. package/simsimd/javascript/simsimd.ts +135 -0
  103. package/simsimd/javascript/test.mjs +179 -0
  104. package/simsimd/javascript/tsconfig-base.json +37 -0
  105. package/simsimd/javascript/tsconfig-cjs.json +8 -0
  106. package/simsimd/javascript/tsconfig-esm.json +8 -0
  107. package/simsimd/package-lock.json +1709 -0
  108. package/simsimd/package.json +61 -0
  109. package/simsimd/rust/benches/cosine.rs +27 -0
  110. package/simsimd/rust/benches/native.rs +30 -0
  111. package/simsimd/rust/benches/sqeuclidean.rs +27 -0
  112. package/simsimd/rust/lib.rs +778 -0
@@ -0,0 +1,640 @@
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
+
6
+ const compiled: Compiled = build(getBuildDir(getDirName()));
7
+
8
+ type Vector = Float32Array | Float64Array | Int8Array;
9
+ type Matrix = Vector[];
10
+ type VectorOrMatrix = Vector | Matrix;
11
+
12
+ type CompiledSearchResult = [
13
+ keys: BigUint64Array,
14
+ distances: Float32Array,
15
+ counts: BigUint64Array
16
+ ];
17
+
18
+ interface CompiledIndex {
19
+ add(keys: BigUint64Array, vectors: Vector): void;
20
+ search(vectors: VectorOrMatrix, k: number): CompiledSearchResult;
21
+ contains(keys: BigUint64Array): boolean[];
22
+ count(keys: BigUint64Array): number | number[];
23
+ remove(keys: BigUint64Array): number[];
24
+ dimensions(): number;
25
+ connectivity(): number;
26
+ size(): number;
27
+ capacity(): number;
28
+ save(path: string): void;
29
+ load(path: string): void;
30
+ view(path: string): void;
31
+ }
32
+
33
+ interface Compiled {
34
+ CompiledIndex: CompiledIndex;
35
+ exactSearch(
36
+ dataset: VectorOrMatrix,
37
+ queries: VectorOrMatrix,
38
+ dimensions: number,
39
+ count: number,
40
+ metric: MetricKind
41
+ ): CompiledSearchResult;
42
+ }
43
+
44
+ /**
45
+ * Enumeration representing the various metric kinds used to measure the distance between vectors in the index.
46
+ * @enum {string}
47
+ * @readonly
48
+ */
49
+ export enum MetricKind {
50
+ Unknown = "unknown",
51
+ Cos = "cos",
52
+ IP = "ip",
53
+ L2sq = "l2sq",
54
+ Haversine = "haversine",
55
+ Divergence = "divergence",
56
+ Pearson = "pearson",
57
+ Jaccard = "jaccard",
58
+ Hamming = "hamming",
59
+ Tanimoto = "tanimoto",
60
+ Sorensen = "sorensen",
61
+ }
62
+
63
+ /**
64
+ * Enumeration representing the various scalar kinds used to define the type of scalar values in vectors.
65
+ * @enum {string}
66
+ * @readonly
67
+ */
68
+ export enum ScalarKind {
69
+ Unknown = "unknown",
70
+ F32 = "f32",
71
+ F64 = "f64",
72
+ F16 = "f16",
73
+ I8 = "i8",
74
+ B1 = "b1",
75
+ }
76
+
77
+ /**
78
+ * Represents a set of search results.
79
+ */
80
+ export class Matches {
81
+ /**
82
+ * Constructs a Matches object.
83
+ *
84
+ * @param {BigUint64Array} keys - The keys of the nearest neighbors found.
85
+ * @param {Float32Array} distances - The distances of the nearest neighbors found.
86
+ */
87
+ constructor(public keys: BigUint64Array, public distances: Float32Array) { }
88
+ }
89
+
90
+ /**
91
+ * Represents a set of batched search results.
92
+ */
93
+ export class BatchMatches {
94
+ /**
95
+ * Constructs a BatchMatches object.
96
+ *
97
+ * @param {BigUint64Array} keys - The keys of the nearest neighbors found in the batch.
98
+ * @param {Float32Array} distances - The distances of the nearest neighbors found in the batch.
99
+ * @param {BigUint64Array} counts - The number of neighbors found for each query in the batch.
100
+ * @param {number} k - The limit for search results per query in the batch.
101
+ */
102
+ constructor(
103
+ public keys: BigUint64Array,
104
+ public distances: Float32Array,
105
+ public counts: BigUint64Array,
106
+ public k: number
107
+ ) { }
108
+
109
+ /**
110
+ * Retrieves a Matches object at the specified index in the batch.
111
+ *
112
+ * @param {number} i - The index at which to retrieve the Matches object.
113
+ * @returns {Matches} - A Matches object representing the search results at the specified index in the batch.
114
+ */
115
+ get(i: number): Matches {
116
+ const index = Number(i) * Number(this.k);
117
+ const count = Number(this.counts[i]);
118
+ const keysSlice = this.keys.slice(index, index + count);
119
+ const distancesSlice = this.distances.slice(index, index + count);
120
+ return new Matches(keysSlice, distancesSlice);
121
+ }
122
+ }
123
+
124
+ function isOneKey(keys: number | bigint | BigUint64Array | bigint[]): boolean {
125
+ return (
126
+ (!Number.isNaN(keys) && typeof keys === "number") ||
127
+ typeof keys === "bigint"
128
+ );
129
+ }
130
+
131
+ function normalizeKeys(keys: unknown): BigUint64Array {
132
+ if (keys instanceof BigUint64Array) {
133
+ return keys;
134
+ }
135
+ let normalizedKeys: BigUint64Array;
136
+ if (
137
+ (typeof keys === "number" && !Number.isNaN(keys)) ||
138
+ typeof keys === "bigint"
139
+ ) {
140
+ normalizedKeys = BigUint64Array.of(BigInt(keys));
141
+ } else if (Array.isArray(keys)) {
142
+ const bigintkeys = keys.map((key) => {
143
+ if (typeof key === "bigint") {
144
+ return key;
145
+ } else if (
146
+ typeof key === "number" &&
147
+ !Number.isNaN(key) &&
148
+ Number.isInteger(key) &&
149
+ key >= 0
150
+ ) {
151
+ return BigInt(key);
152
+ }
153
+ throw new Error("All keys must be positive integers or bigints.");
154
+ });
155
+ normalizedKeys = BigUint64Array.from(bigintkeys);
156
+ } else {
157
+ throw new Error(
158
+ "Keys must be a number, bigint, an array of numbers or bigints, or a BigUint64Array."
159
+ );
160
+ }
161
+ return normalizedKeys;
162
+ }
163
+
164
+ function isVector(vectors: unknown) {
165
+ return (
166
+ vectors instanceof Float32Array ||
167
+ vectors instanceof Float64Array ||
168
+ vectors instanceof Int8Array
169
+ );
170
+ }
171
+
172
+ function normalizeVectors(
173
+ vectors: VectorOrMatrix,
174
+ dimensions: number,
175
+ targetType: NumberArrayConstructor = Float32Array
176
+ ): Vector {
177
+ let flattenedVectors: Vector;
178
+ if (isVector(vectors)) {
179
+ flattenedVectors =
180
+ vectors.constructor === targetType
181
+ ? vectors
182
+ : new targetType(vectors as Vector);
183
+ } else if (Array.isArray(vectors)) {
184
+ let totalLength = 0;
185
+ for (const vec of vectors) totalLength += vec.length;
186
+
187
+ flattenedVectors = new targetType(totalLength);
188
+ let offset = 0;
189
+ for (const vec of vectors) {
190
+ flattenedVectors.set(vec, offset);
191
+ offset += vec.length;
192
+ }
193
+ } else {
194
+ throw new Error("Vectors must be a TypedArray or an array of arrays.");
195
+ }
196
+
197
+ if (flattenedVectors.length % dimensions !== 0)
198
+ throw new Error(
199
+ "The size of the flattened vectors must be a multiple of the dimension of the vectors."
200
+ );
201
+
202
+ return flattenedVectors;
203
+ }
204
+
205
+ export interface IndexCongif {
206
+ dimensions: number;
207
+ metric: MetricKind;
208
+ quantization: ScalarKind;
209
+ connectivity: number;
210
+ expansion_add: number;
211
+ expansion_search: number;
212
+ multi: boolean;
213
+ }
214
+
215
+ export class Index {
216
+ /**
217
+ * Constructs a new index.
218
+ *
219
+ * @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
220
+ * @param {MetricKind} [metric=MetricKind.Cos] - Optional, default is 'cos'.
221
+ * @param {ScalarKind} [quantization=ScalarKind.F32] - Optional, default is 'f32'.
222
+ * @param {number} [connectivity=0] - Optional, default is 0.
223
+ * @param {number} [expansion_add=0] - Optional, default is 0.
224
+ * @param {number} [expansion_search=0] - Optional, default is 0.
225
+ * @param {boolean} [multi=false] - Optional, default is false.
226
+ * @throws Will throw an error if any of the parameters are of incorrect type or invalid value.
227
+ */
228
+ constructor(
229
+ dimensionsOrConfigs: number | bigint | IndexCongif,
230
+ metric: MetricKind = MetricKind.Cos,
231
+ quantization: ScalarKind = ScalarKind.F32,
232
+ connectivity: number = 0,
233
+ expansion_add: number = 0,
234
+ expansion_search: number = 0,
235
+ multi: boolean = false
236
+ ) {
237
+ let dimensions: number | bigint;
238
+ if (
239
+ (typeof dimensionsOrConfigs === "number" &&
240
+ !Number.isNaN(dimensionsOrConfigs)) ||
241
+ typeof dimensionsOrConfigs === "bigint"
242
+ ) {
243
+ // Parameters are provided as individual arguments
244
+ dimensions = dimensionsOrConfigs;
245
+ } else if (
246
+ typeof dimensionsOrConfigs === "object" &&
247
+ dimensionsOrConfigs !== null
248
+ ) {
249
+ // Parameters are provided as an object
250
+ ({
251
+ dimensions,
252
+ metric = MetricKind.Cos,
253
+ quantization = ScalarKind.F32,
254
+ connectivity = 0,
255
+ expansion_add = 0,
256
+ expansion_search = 0,
257
+ multi = false,
258
+ } = dimensionsOrConfigs);
259
+ } else {
260
+ throw new Error(
261
+ "Invalid arguments. Expected either individual arguments or a single object argument."
262
+ );
263
+ }
264
+
265
+ if (
266
+ (typeof dimensions !== 'bigint' && (!Number.isInteger(dimensions) || dimensions <= 0)) ||
267
+ (typeof connectivity !== 'bigint' && (!Number.isInteger(connectivity) || connectivity < 0)) ||
268
+ (typeof expansion_add !== 'bigint' && (!Number.isInteger(expansion_add) || expansion_add < 0)) ||
269
+ (typeof expansion_search !== 'bigint' && (!Number.isInteger(expansion_search) || expansion_search < 0))) {
270
+ throw new Error(
271
+ "`dimensions`, `connectivity`, `expansion_add`, and `expansion_search` must be non-negative integers, with `dimensions` being positive."
272
+ );
273
+ }
274
+
275
+ if (typeof multi !== "boolean") {
276
+ throw new Error("`multi` must be a boolean value.");
277
+ }
278
+
279
+ if (!Object.values(MetricKind).includes(metric)) {
280
+ throw new Error(
281
+ `Invalid metric: ${metric}. It must be one of: ${Object.values(
282
+ MetricKind
283
+ ).join(", ")}`
284
+ );
285
+ }
286
+
287
+ if (!Object.values(ScalarKind).includes(quantization)) {
288
+ throw new Error(
289
+ `Invalid quantization: ${quantization}. It must be one of: ${Object.values(
290
+ ScalarKind
291
+ ).join(", ")}`
292
+ );
293
+ }
294
+
295
+ // @ts-expect-error
296
+ this.#compiledIndex = new compiled.CompiledIndex(
297
+ dimensions,
298
+ metric,
299
+ quantization,
300
+ connectivity,
301
+ expansion_add,
302
+ expansion_search,
303
+ multi
304
+ );
305
+ }
306
+
307
+ #compiledIndex: CompiledIndex;
308
+
309
+ /**
310
+ * Add vectors to the index.
311
+ *
312
+ * This method accepts vectors and their corresponding keys for indexing.
313
+ * Each key should correspond to a vector. If a single key is provided,
314
+ * it is broadcasted to match the number of provided vectors.
315
+ *
316
+ * Vectors should be provided as a flat typed array representing a matrix
317
+ * where each row is a vector to be indexed. The matrix should have a size
318
+ * of n * d, where n is the number of vectors, and d is the dimensionality
319
+ * of the vectors.
320
+ *
321
+ * Keys should be provided as a BigInt or an array-like object of BigInts
322
+ * representing the unique identifier for each vector.
323
+ *
324
+ * @param {bigint|bigint[]|BigUint64Array} keys - Input identifiers for every vector.
325
+ * If a single key is provided, it is associated with all provided vectors.
326
+ * @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
327
+ * matrix of size n * d, where n is the number of vectors, and d is their dimensionality.
328
+ * @throws Will throw an error if the length of keys doesn't match the number of vectors
329
+ * or if it's not a single key.
330
+ */
331
+ add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector) {
332
+ let normalizedKeys = normalizeKeys(keys);
333
+ let normalizedVectors = normalizeVectors(
334
+ vectors,
335
+ this.#compiledIndex.dimensions()
336
+ );
337
+ let countVectors =
338
+ normalizedVectors.length / this.#compiledIndex.dimensions();
339
+
340
+ // If a single key is provided but there are multiple vectors,
341
+ // broadcast the single key value to match the number of vectors
342
+ if (normalizedKeys.length === 1 && countVectors > 1) {
343
+ normalizedKeys = BigUint64Array.from(
344
+ { length: countVectors },
345
+ () => normalizedKeys[0]
346
+ );
347
+ } else if (normalizedKeys.length !== countVectors) {
348
+ throw new Error(
349
+ `The length of keys (${normalizedKeys.length}) must match the number of vectors (${countVectors}) or be a single key.`
350
+ );
351
+ }
352
+
353
+ // Call the compiled method
354
+ this.#compiledIndex.add(normalizedKeys, normalizedVectors);
355
+ }
356
+
357
+ /**
358
+ * Perform a k-nearest neighbor search on the index.
359
+ *
360
+ * This method accepts a matrix of query vectors and returns the closest vectors
361
+ * from the index for each query. The method returns an object containing the keys,
362
+ * distances, and counts of the matches found.
363
+ *
364
+ * Vectors should be provided as a flat typed array representing a matrix where
365
+ * each row is a vector. The matrix should be of size n * d, where n is the
366
+ * number of query vectors, and d is their dimensionality.
367
+ *
368
+ * The parameter `k` specifies the number of nearest neighbors to return for each
369
+ * query vector. If there are not enough results for a query, the result array is
370
+ * padded with -1s.
371
+ *
372
+ * @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
373
+ * @param {number} k - The number of nearest neighbors to search for each query vector.
374
+ * @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
375
+ * @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.
376
+ * @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.
377
+ */
378
+ search(vectors: VectorOrMatrix, k: number): Matches | BatchMatches {
379
+ if ((!Number.isNaN(k) && typeof k !== "number") || k <= 0) {
380
+ throw new Error(
381
+ "`k` must be a positive integer representing the number of nearest neighbors to search for."
382
+ );
383
+ }
384
+
385
+ const normalizedVectors = normalizeVectors(
386
+ vectors,
387
+ this.#compiledIndex.dimensions()
388
+ );
389
+
390
+ // Call the compiled method and create Matches or BatchMatches object with the result
391
+ const result = this.#compiledIndex.search(normalizedVectors, k);
392
+ const countInQueries =
393
+ normalizedVectors.length / Number(this.#compiledIndex.dimensions());
394
+ const batchMatches = new BatchMatches(...result, k);
395
+
396
+ if (countInQueries === 1) {
397
+ return batchMatches.get(0);
398
+ } else {
399
+ return batchMatches;
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Verifies the presence of one or more keys in the index.
405
+ *
406
+ * This method accepts one or multiple keys as input and returns a boolean or
407
+ * an array of booleans indicating whether each key is present in the index.
408
+ *
409
+ * @param {bigint|bigint[]|BigUint64Array} keys - The identifier(s) of the vector(s) to be checked for presence in the index.
410
+ * @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.
411
+ * @throws Will throw an error if keys are not integers.
412
+ */
413
+ contains(keys: bigint | bigint[] | BigUint64Array): boolean | boolean[] {
414
+ let normalizedKeys = normalizeKeys(keys);
415
+ let normalizedResults = this.#compiledIndex.contains(normalizedKeys);
416
+ if (isOneKey(keys)) return normalizedResults[0];
417
+ else return normalizedResults;
418
+ }
419
+
420
+ /**
421
+ * Counts the number of times keys shows up in the index.
422
+ *
423
+ * @param {bigint|bigint[]|BigUint64Array} keys - The identifier(s) of the vector(s) to be enumerated.
424
+ * @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.
425
+ * @throws Will throw an error if keys are not integers.
426
+ */
427
+ count(keys: bigint | bigint[] | BigUint64Array): number | number[] {
428
+ let normalizedKeys = normalizeKeys(keys);
429
+ let normalizedResults = this.#compiledIndex.count(normalizedKeys);
430
+ if (isOneKey(keys)) return (normalizedResults as unknown as number[])[0];
431
+ else return normalizedResults;
432
+ }
433
+
434
+ /**
435
+ * Removes one or multiple vectors from the index.
436
+ *
437
+ * This method accepts one or multiple keys as input and removes the corresponding vectors from the index.
438
+ * It returns the number of vectors actually removed for each key provided.
439
+ *
440
+ * @param {bigint|bigint[]|BigUint64Array} keys - The identifier(s) of the vector(s) to be removed.
441
+ * @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.
442
+ * @throws Will throw an error if keys are not integers.
443
+ */
444
+ remove(keys: bigint | bigint[] | BigUint64Array): number | number[] {
445
+ let normalizedKeys = normalizeKeys(keys);
446
+ let normalizedResults = this.#compiledIndex.remove(normalizedKeys);
447
+ if (isOneKey(keys)) return normalizedResults[0];
448
+ else return normalizedResults;
449
+ }
450
+
451
+ /**
452
+ * Returns the dimensionality of vectors.
453
+ * @return {number} The dimensionality of vectors.
454
+ */
455
+ dimensions(): number {
456
+ return this.#compiledIndex.dimensions();
457
+ }
458
+
459
+ /**
460
+ * Returns connectivity.
461
+ * @return {number} The connectivity of index.
462
+ */
463
+ connectivity(): number {
464
+ return this.#compiledIndex.connectivity();
465
+ }
466
+
467
+ /**
468
+ * Returns the number of vectors currently indexed.
469
+ * @return {number} The number of vectors currently indexed.
470
+ */
471
+ size(): number {
472
+ return this.#compiledIndex.size();
473
+ }
474
+
475
+ /**
476
+ * Returns index capacity.
477
+ * @return {number} The capacity of index.
478
+ */
479
+ capacity(): number {
480
+ return this.#compiledIndex.capacity();
481
+ }
482
+
483
+ /**
484
+ * Write index to a file.
485
+ * @param {string} path File path to write.
486
+ * @throws Will throw an error if `path` is not a string.
487
+ */
488
+ save(path: string) {
489
+ if (typeof path !== "string")
490
+ throw new Error(
491
+ "`path` must be a string representing the file path to write."
492
+ );
493
+ this.#compiledIndex.save(path);
494
+ }
495
+
496
+ /**
497
+ * Load index from a file.
498
+ * @param {string} path File path to read.
499
+ * @throws Will throw an error if `path` is not a string.
500
+ */
501
+ load(path: string) {
502
+ if (typeof path !== "string")
503
+ throw new Error(
504
+ "`path` must be a string representing the file path to read."
505
+ );
506
+ this.#compiledIndex.load(path);
507
+ }
508
+
509
+ /**
510
+ * View index from a file, without loading into RAM.
511
+ * @param {string} path File path to read.
512
+ * @throws Will throw an error if `path` is not a string.
513
+ */
514
+ view(path: string) {
515
+ if (typeof path !== "string")
516
+ throw new Error(
517
+ "`path` must be a string representing the file path to read."
518
+ );
519
+ this.#compiledIndex.view(path);
520
+ }
521
+ }
522
+
523
+ type NumberArrayConstructor =
524
+ | Float64ArrayConstructor
525
+ | Float32ArrayConstructor
526
+ | Int8ArrayConstructor;
527
+ /**
528
+ * Performs an exact search on the given dataset to find the best matching vectors for each query.
529
+ *
530
+ * @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.
531
+ * @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.
532
+ * @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
533
+ * @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
534
+ * @param {MetricKind} metric - The distance metric to be used for the search.
535
+ * @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
536
+ * @throws Will throw an error if `dimensions` and `count` are not positive integers.
537
+ * @throws Will throw an error if `metric` is not a valid MetricKind.
538
+ * @throws Will throw an error if `dataset` and `queries` are not valid input types (TypedArray or an array of arrays).
539
+ * @throws Will throw an error if the sizes of the flattened `dataset` and `queries` are not multiples of `dimensions`.
540
+ * @throws Will throw an error if `count` is greater than the number of vectors in the `dataset`.
541
+ *
542
+ * @example
543
+ * const dataset = [[1.0, 2.0], [3.0, 4.0]]; // Two vectors: [1.0, 2.0] and [3.0, 4.0]
544
+ * const queries = [[1.5, 2.5]]; // One vector: [1.5, 2.5]
545
+ * const dimensions = 2; // The number of elements in each vector.
546
+ * const count = 1; // The number of nearest neighbors to return for each query.
547
+ * const metric = MetricKind.IP; // Using the Inner Product distance metric.
548
+ *
549
+ * const result = exactSearch(dataset, queries, dimensions, count, metric);
550
+ * // result might be:
551
+ * // {
552
+ * // keys: BigUint64Array [ 1n ],
553
+ * // distances: Float32Array [ some_value ],
554
+ * // }
555
+ */
556
+ function exactSearch(
557
+ dataset: VectorOrMatrix,
558
+ queries: VectorOrMatrix,
559
+ dimensions: number,
560
+ count: number,
561
+ metric: MetricKind
562
+ ): Matches | BatchMatches {
563
+ // Validate and normalize the dimensions and count
564
+ dimensions = Number(dimensions);
565
+ count = Number(count);
566
+ if (count <= 0 || dimensions <= 0) {
567
+ throw new Error("Dimensions and count must be positive integers.");
568
+ }
569
+
570
+ // Validate metric
571
+ if (!Object.values(MetricKind).includes(metric)) {
572
+ throw new Error(
573
+ `Invalid metric: ${metric}. It must be one of: ${Object.values(
574
+ MetricKind
575
+ ).join(", ")}`
576
+ );
577
+ }
578
+
579
+ // Flatten and normalize dataset and queries if they are arrays of arrays
580
+ let targetType: NumberArrayConstructor;
581
+ if (dataset instanceof Float64Array) targetType = Float64Array;
582
+ else if (dataset instanceof Int8Array) targetType = Int8Array;
583
+ else targetType = Float32Array; // default to Float32Array if dataset is not Float64Array or Int8Array
584
+
585
+ dataset = normalizeVectors(dataset, dimensions, targetType);
586
+ queries = normalizeVectors(queries, dimensions, targetType);
587
+ const countInDataset = dataset.length / dimensions;
588
+ const countInQueries = queries.length / dimensions;
589
+ if (count > countInDataset) {
590
+ throw new Error(
591
+ "Count must be equal or smaller than the number of vectors in the dataset."
592
+ );
593
+ }
594
+
595
+ // Call the compiled function with the normalized input
596
+ const result = compiled.exactSearch(
597
+ dataset,
598
+ queries,
599
+ dimensions,
600
+ count,
601
+ metric
602
+ );
603
+
604
+ // Create and return a Matches or BatchMatches object with the result
605
+ if (countInQueries == 1) {
606
+ return new Matches(result[0], result[1]);
607
+ } else {
608
+ return new BatchMatches(...result, count);
609
+ }
610
+ }
611
+
612
+ module.exports = {
613
+ Index,
614
+ MetricKind,
615
+ ScalarKind,
616
+ Matches,
617
+ BatchMatches,
618
+ exactSearch,
619
+ };
620
+
621
+ // utility functions to help find native builds
622
+
623
+ function getBuildDir(dir: string) {
624
+ if (existsSync(path.join(dir, "build"))) return dir;
625
+ if (existsSync(path.join(dir, "prebuilds"))) return dir;
626
+ if (path.basename(dir) === ".next") {
627
+ // special case for next.js on custom node (not vercel)
628
+ const sideways = path.join(dir, "..", "node_modules", "usearch");
629
+ if (existsSync(sideways)) return getBuildDir(sideways);
630
+ }
631
+ if (dir === "/") throw new Error("Could not find native build for usearch");
632
+ return getBuildDir(path.join(dir, ".."));
633
+ }
634
+
635
+ function getDirName() {
636
+ try {
637
+ if (__dirname) return __dirname;
638
+ } catch (e) { }
639
+ return getRoot(getFileName());
640
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.10.2",
3
+ "version": "2.10.5",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
@@ -17,7 +17,14 @@
17
17
  "node": "~10 >=10.20 || >=12.17"
18
18
  },
19
19
  "files": [
20
- "javascript/dist",
20
+ "binding.gyp",
21
+ "c",
22
+ "cpp",
23
+ "cmake",
24
+ "fp16",
25
+ "simsimd",
26
+ "include",
27
+ "javascript",
21
28
  "prebuilds"
22
29
  ],
23
30
  "dependencies": {
@@ -29,7 +36,7 @@
29
36
  "test": "node --test ./javascript/usearch.test.js",
30
37
  "install": "node-gyp-build",
31
38
  "prebuild-single": "prebuildify --napi --strip",
32
- "prebuild-arm64": "prebuildify --arch arm64 --napi --strip",
39
+ "prebuild-arm64": "prebuildify --arch arm64 --napi",
33
40
  "prebuild-darwin-x64+arm64": "prebuildify --arch arm64+x64 --napi --strip",
34
41
  "build-js": "rm -fr javascript/dist/* && tsc -p javascript/tsconfig-esm.json && tsc -p javascript/tsconfig-cjs.json && cp javascript/dist-package-esm.json javascript/dist/esm/package.json && cp javascript/dist-package-cjs.json javascript/dist/cjs/package.json"
35
42
  },
Binary file
Binary file
Binary file
Binary file