usearch 2.17.1 → 2.19.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/c/lib.cpp +5 -0
- package/c/module.modulemap +4 -0
- package/c/usearch.h +12 -4
- package/cpp/README.md +1 -1
- package/cpp/bench.cpp +1 -1
- package/cpp/test.cpp +110 -16
- package/include/usearch/index.hpp +48 -35
- package/include/usearch/index_dense.hpp +34 -19
- package/include/usearch/index_plugins.hpp +29 -7
- package/javascript/README.md +8 -6
- package/javascript/dist/cjs/usearch.d.ts +9 -4
- package/javascript/dist/cjs/usearch.js +23 -8
- package/javascript/dist/esm/usearch.d.ts +9 -4
- package/javascript/dist/esm/usearch.js +21 -7
- package/javascript/lib.cpp +137 -59
- package/javascript/usearch.test.js +359 -192
- package/javascript/usearch.ts +35 -11
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/CONTRIBUTING.md +23 -8
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +1 -1
- package/simsimd/README.md +49 -9
- package/simsimd/build.rs +7 -3
- package/simsimd/c/lib.c +6 -1
- package/simsimd/include/simsimd/binary.h +3 -3
- package/simsimd/include/simsimd/dot.h +2 -2
- package/simsimd/include/simsimd/elementwise.h +26 -17
- package/simsimd/include/simsimd/simsimd.h +125 -49
- package/simsimd/include/simsimd/sparse.h +30 -29
- package/simsimd/include/simsimd/spatial.h +4 -3
- package/simsimd/include/simsimd/types.h +19 -8
- package/simsimd/javascript/lib.c +0 -1
- package/simsimd/package-lock.json +411 -362
- package/simsimd/package.json +9 -9
- package/simsimd/rust/lib.rs +63 -29
- package/simsimd/scripts/bench.cxx +13 -0
- package/simsimd/scripts/bench_cosine.rs +8 -0
- package/simsimd/scripts/bench_native.rs +4 -0
- package/simsimd/scripts/bench_sqeuclidean.rs +8 -0
- package/simsimd/scripts/test.c +22 -0
- package/simsimd/scripts/test.py +80 -4
- package/simsimd/swift/SimSIMD.swift +17 -0
- package/simsimd/swift/Test.swift +36 -0
|
@@ -229,10 +229,12 @@ export class Index {
|
|
|
229
229
|
* If a single key is provided, it is associated with all provided vectors.
|
|
230
230
|
* @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
|
|
231
231
|
* matrix of size n * d, where n is the number of vectors, and d is their dimensionality.
|
|
232
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for indexing.
|
|
233
|
+
* If set to 0, the number of threads is determined automatically.
|
|
232
234
|
* @throws Will throw an error if the length of keys doesn't match the number of vectors
|
|
233
235
|
* or if it's not a single key.
|
|
234
236
|
*/
|
|
235
|
-
add(keys, vectors) {
|
|
237
|
+
add(keys, vectors, threads = 0) {
|
|
236
238
|
let normalizedKeys = normalizeKeys(keys);
|
|
237
239
|
let normalizedVectors = normalizeVectors(vectors, this.#compiledIndex.dimensions());
|
|
238
240
|
let countVectors = normalizedVectors.length / this.#compiledIndex.dimensions();
|
|
@@ -244,8 +246,11 @@ export class Index {
|
|
|
244
246
|
else if (normalizedKeys.length !== countVectors) {
|
|
245
247
|
throw new Error(`The length of keys (${normalizedKeys.length}) must match the number of vectors (${countVectors}) or be a single key.`);
|
|
246
248
|
}
|
|
249
|
+
if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
|
|
250
|
+
throw new Error("`threads` must be a non-negative integer representing the number of threads to use for searching.");
|
|
251
|
+
}
|
|
247
252
|
// Call the compiled method
|
|
248
|
-
this.#compiledIndex.add(normalizedKeys, normalizedVectors);
|
|
253
|
+
this.#compiledIndex.add(normalizedKeys, normalizedVectors, threads);
|
|
249
254
|
}
|
|
250
255
|
/**
|
|
251
256
|
* Perform a k-nearest neighbor search on the index.
|
|
@@ -264,17 +269,21 @@ export class Index {
|
|
|
264
269
|
*
|
|
265
270
|
* @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
|
|
266
271
|
* @param {number} k - The number of nearest neighbors to search for each query vector.
|
|
272
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for searching. If set to 0, the number of threads is determined automatically.
|
|
267
273
|
* @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
|
|
268
274
|
* @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.
|
|
269
275
|
* @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.
|
|
270
276
|
*/
|
|
271
|
-
search(vectors, k) {
|
|
277
|
+
search(vectors, k, threads = 0) {
|
|
272
278
|
if ((!Number.isNaN(k) && typeof k !== "number") || k <= 0) {
|
|
273
279
|
throw new Error("`k` must be a positive integer representing the number of nearest neighbors to search for.");
|
|
274
280
|
}
|
|
281
|
+
if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
|
|
282
|
+
throw new Error("`threads` must be a non-negative integer representing the number of threads to use for searching.");
|
|
283
|
+
}
|
|
275
284
|
const normalizedVectors = normalizeVectors(vectors, this.#compiledIndex.dimensions());
|
|
276
285
|
// Call the compiled method and create Matches or BatchMatches object with the result
|
|
277
|
-
const result = this.#compiledIndex.search(normalizedVectors, k);
|
|
286
|
+
const result = this.#compiledIndex.search(normalizedVectors, k, threads);
|
|
278
287
|
const countInQueries = normalizedVectors.length / Number(this.#compiledIndex.dimensions());
|
|
279
288
|
const batchMatches = new BatchMatches(...result, k);
|
|
280
289
|
if (countInQueries === 1) {
|
|
@@ -402,6 +411,7 @@ export class Index {
|
|
|
402
411
|
* @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
|
|
403
412
|
* @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.
|
|
404
413
|
* @param {MetricKind} metric - The distance metric to be used for the search.
|
|
414
|
+
* @param {number} [threads=0] - Optional, default is 0. The number of threads to use for the search. If set to 0, the number of threads is determined automatically.
|
|
405
415
|
* @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
|
|
406
416
|
* @throws Will throw an error if `dimensions` and `count` are not positive integers.
|
|
407
417
|
* @throws Will throw an error if `metric` is not a valid MetricKind.
|
|
@@ -415,21 +425,25 @@ export class Index {
|
|
|
415
425
|
* const dimensions = 2; // The number of elements in each vector.
|
|
416
426
|
* const count = 1; // The number of nearest neighbors to return for each query.
|
|
417
427
|
* const metric = MetricKind.IP; // Using the Inner Product distance metric.
|
|
428
|
+
* const threads = 0; // How many threads to use to perform the search.
|
|
418
429
|
*
|
|
419
|
-
* const result = exactSearch(dataset, queries, dimensions, count, metric);
|
|
430
|
+
* const result = exactSearch(dataset, queries, dimensions, count, metric, threads);
|
|
420
431
|
* // result might be:
|
|
421
432
|
* // {
|
|
422
433
|
* // keys: BigUint64Array [ 1n ],
|
|
423
434
|
* // distances: Float32Array [ some_value ],
|
|
424
435
|
* // }
|
|
425
436
|
*/
|
|
426
|
-
function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
437
|
+
export function exactSearch(dataset, queries, dimensions, count, metric, threads = 0) {
|
|
427
438
|
// Validate and normalize the dimensions and count
|
|
428
439
|
dimensions = Number(dimensions);
|
|
429
440
|
count = Number(count);
|
|
430
441
|
if (count <= 0 || dimensions <= 0) {
|
|
431
442
|
throw new Error("Dimensions and count must be positive integers.");
|
|
432
443
|
}
|
|
444
|
+
if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
|
|
445
|
+
throw new Error("`threads` must be a non-negative integer representing the number of threads to use for searching.");
|
|
446
|
+
}
|
|
433
447
|
// Validate metric
|
|
434
448
|
if (!Object.values(MetricKind).includes(metric)) {
|
|
435
449
|
throw new Error(`Invalid metric: ${metric}. It must be one of: ${Object.values(MetricKind).join(", ")}`);
|
|
@@ -450,7 +464,7 @@ function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
|
450
464
|
throw new Error("Count must be equal or smaller than the number of vectors in the dataset.");
|
|
451
465
|
}
|
|
452
466
|
// Call the compiled function with the normalized input
|
|
453
|
-
const result = compiled.exactSearch(dataset, queries, dimensions, count, metric);
|
|
467
|
+
const result = compiled.exactSearch(dataset, queries, dimensions, count, metric, threads);
|
|
454
468
|
// Create and return a Matches or BatchMatches object with the result
|
|
455
469
|
if (countInQueries == 1) {
|
|
456
470
|
return new Matches(result[0], result[1]);
|
package/javascript/lib.cpp
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* @see NodeJS docs: https://nodejs.org/api/addons.html#hello-world
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
|
-
#include <new>
|
|
12
|
+
#include <new> // `std::bad_alloc`
|
|
13
|
+
#include <thread> // `std::thread::hardware_concurrency()`
|
|
13
14
|
|
|
14
15
|
#define NAPI_CPP_EXCEPTIONS
|
|
15
16
|
#include <napi.h>
|
|
@@ -20,7 +21,10 @@
|
|
|
20
21
|
using namespace unum::usearch;
|
|
21
22
|
using namespace unum;
|
|
22
23
|
|
|
24
|
+
using index_error_t = usearch::error_t;
|
|
23
25
|
using add_result_t = typename index_dense_t::add_result_t;
|
|
26
|
+
using search_result_t = typename index_dense_t::search_result_t;
|
|
27
|
+
using state_result_t = typename index_dense_t::state_result_t;
|
|
24
28
|
|
|
25
29
|
class CompiledIndex : public Napi::ObjectWrap<CompiledIndex> {
|
|
26
30
|
public:
|
|
@@ -44,6 +48,7 @@ class CompiledIndex : public Napi::ObjectWrap<CompiledIndex> {
|
|
|
44
48
|
Napi::Value Count(Napi::CallbackInfo const& ctx);
|
|
45
49
|
|
|
46
50
|
std::unique_ptr<index_dense_t> native_;
|
|
51
|
+
std::mutex mtx;
|
|
47
52
|
};
|
|
48
53
|
|
|
49
54
|
Napi::Object CompiledIndex::Init(Napi::Env env, Napi::Object exports) {
|
|
@@ -77,7 +82,6 @@ std::size_t napi_argument_to_size(Napi::Value v) {
|
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
CompiledIndex::CompiledIndex(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<CompiledIndex>(ctx) {
|
|
80
|
-
|
|
81
85
|
// Directly assign the parameters without checks
|
|
82
86
|
std::size_t dimensions = napi_argument_to_size(ctx[0]);
|
|
83
87
|
metric_kind_t metric_kind = metric_from_name(ctx[1].As<Napi::String>().Utf8Value().c_str());
|
|
@@ -95,7 +99,12 @@ CompiledIndex::CompiledIndex(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<C
|
|
|
95
99
|
|
|
96
100
|
index_dense_config_t config(connectivity, expansion_add, expansion_search);
|
|
97
101
|
config.multi = multi;
|
|
98
|
-
|
|
102
|
+
state_result_t result = index_dense_t::make(metric, config);
|
|
103
|
+
if (!result) {
|
|
104
|
+
Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
native_.reset(new index_dense_t(std::move(result.index)));
|
|
99
108
|
if (!native_)
|
|
100
109
|
Napi::Error::New(ctx.Env(), "Out of memory!").ThrowAsJavaScriptException();
|
|
101
110
|
}
|
|
@@ -155,45 +164,92 @@ void CompiledIndex::View(Napi::CallbackInfo const& ctx) {
|
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
void CompiledIndex::Add(Napi::CallbackInfo const& ctx) {
|
|
167
|
+
Napi::Env env = ctx.Env();
|
|
168
|
+
|
|
169
|
+
// Check the number of arguments
|
|
170
|
+
if (ctx.Length() != 3) {
|
|
171
|
+
Napi::TypeError::New(env, "`Add` expects 3 arguments: keys, vectors[, threads]").ThrowAsJavaScriptException();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
158
175
|
// Extract keys and vectors from arguments
|
|
159
176
|
Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
|
|
160
|
-
|
|
177
|
+
Napi::TypedArray vectors = ctx[1].As<Napi::TypedArray>();
|
|
178
|
+
|
|
179
|
+
// Optional arguments
|
|
180
|
+
std::size_t threads = napi_argument_to_size(ctx[2]);
|
|
181
|
+
if (threads == 0)
|
|
182
|
+
threads = std::thread::hardware_concurrency();
|
|
161
183
|
|
|
162
|
-
// Ensure there is enough capacity
|
|
184
|
+
// Ensure there is enough capacity and memory
|
|
185
|
+
std::size_t tasks = keys.ElementLength();
|
|
163
186
|
if (native_->size() + tasks >= native_->capacity())
|
|
164
|
-
native_->
|
|
187
|
+
if (!native_->try_reserve({ceil2(native_->size() + tasks), threads})) {
|
|
188
|
+
Napi::TypeError::New(env, "Failed to reserve memory").ThrowAsJavaScriptException();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
165
191
|
|
|
166
|
-
//
|
|
192
|
+
// Run insertions concurrently
|
|
167
193
|
auto run_parallel = [&](auto vectors) {
|
|
168
|
-
|
|
194
|
+
// Errors can be set only from the main thread, so before spawning workers
|
|
195
|
+
// we need temporary space to keep the message
|
|
196
|
+
index_error_t first_error{};
|
|
197
|
+
std::atomic<bool> failed{false};
|
|
198
|
+
executor_default_t executor{threads};
|
|
169
199
|
executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
200
|
+
if (failed.load())
|
|
201
|
+
return;
|
|
202
|
+
auto key = static_cast<default_key_t>(keys[task_idx]);
|
|
203
|
+
auto vector = vectors + task_idx * native_->dimensions();
|
|
204
|
+
add_result_t result = native_->add(key, vector);
|
|
205
|
+
if (!result) {
|
|
206
|
+
if (!failed.exchange(true)) {
|
|
207
|
+
first_error = std::move(result.error);
|
|
208
|
+
} else {
|
|
209
|
+
result.error.release();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
174
212
|
});
|
|
213
|
+
if (failed)
|
|
214
|
+
Napi::TypeError::New(env, first_error.release()).ThrowAsJavaScriptException();
|
|
175
215
|
};
|
|
176
216
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
217
|
+
// Dispatch the parallel tasks based on the `TypedArray` type
|
|
218
|
+
try {
|
|
219
|
+
if (vectors.TypedArrayType() == napi_float32_array) {
|
|
220
|
+
run_parallel(vectors.As<Napi::Float32Array>().Data());
|
|
221
|
+
} else if (vectors.TypedArrayType() == napi_float64_array) {
|
|
222
|
+
run_parallel(vectors.As<Napi::Float64Array>().Data());
|
|
223
|
+
} else if (vectors.TypedArrayType() == napi_int8_array) {
|
|
224
|
+
run_parallel(vectors.As<Napi::Int8Array>().Data());
|
|
225
|
+
} else {
|
|
226
|
+
Napi::TypeError::New(
|
|
227
|
+
env, "Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
|
|
228
|
+
.ThrowAsJavaScriptException();
|
|
229
|
+
}
|
|
230
|
+
} catch (...) {
|
|
231
|
+
Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
|
|
188
232
|
}
|
|
189
233
|
}
|
|
190
234
|
|
|
191
235
|
Napi::Value CompiledIndex::Search(Napi::CallbackInfo const& ctx) {
|
|
192
236
|
Napi::Env env = ctx.Env();
|
|
237
|
+
|
|
238
|
+
// Check the number of arguments
|
|
239
|
+
if (ctx.Length() != 3) {
|
|
240
|
+
Napi::TypeError::New(env, "`Search` expects 3 arguments: queries, k[, threads]").ThrowAsJavaScriptException();
|
|
241
|
+
return env.Null();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Extract mandatory arguments
|
|
193
245
|
Napi::TypedArray queries = ctx[0].As<Napi::TypedArray>();
|
|
194
|
-
std::size_t tasks = queries.ElementLength() / native_->dimensions();
|
|
195
246
|
std::size_t wanted = napi_argument_to_size(ctx[1]);
|
|
247
|
+
std::size_t threads = napi_argument_to_size(ctx[2]);
|
|
248
|
+
if (threads == 0)
|
|
249
|
+
threads = std::thread::hardware_concurrency();
|
|
196
250
|
|
|
251
|
+
// Run queries concurrently
|
|
252
|
+
std::size_t tasks = queries.ElementLength() / native_->dimensions();
|
|
197
253
|
auto run_parallel = [&](auto vectors) -> Napi::Value {
|
|
198
254
|
Napi::Array result_js = Napi::Array::New(env, 3);
|
|
199
255
|
Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, tasks * wanted);
|
|
@@ -204,29 +260,30 @@ Napi::Value CompiledIndex::Search(Napi::CallbackInfo const& ctx) {
|
|
|
204
260
|
auto distances_data = distances_js.Data();
|
|
205
261
|
auto counts_data = counts_js.Data();
|
|
206
262
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
263
|
+
// Errors can be set only from the main thread, so before spawning workers
|
|
264
|
+
// we need temporary space to keep the message
|
|
265
|
+
index_error_t first_error{};
|
|
266
|
+
std::atomic<bool> failed{false};
|
|
267
|
+
executor_default_t executor{threads};
|
|
268
|
+
executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
|
|
269
|
+
if (failed.load())
|
|
270
|
+
return;
|
|
271
|
+
auto vector = vectors + task_idx * native_->dimensions();
|
|
272
|
+
search_result_t result = native_->search(vector, wanted);
|
|
273
|
+
if (!result) {
|
|
274
|
+
if (!failed.exchange(true)) {
|
|
275
|
+
first_error = std::move(result.error);
|
|
215
276
|
} else {
|
|
216
|
-
|
|
217
|
-
distances_data + task_idx * native_->dimensions());
|
|
277
|
+
result.error.release();
|
|
218
278
|
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
} catch (...) {
|
|
229
|
-
Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
|
|
279
|
+
} else {
|
|
280
|
+
auto matches = matches_data + task_idx * wanted;
|
|
281
|
+
auto distances = distances_data + task_idx * wanted;
|
|
282
|
+
counts_data[task_idx] = result.dump_to(matches, distances);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
if (failed) {
|
|
286
|
+
Napi::TypeError::New(env, first_error.release()).ThrowAsJavaScriptException();
|
|
230
287
|
return env.Null();
|
|
231
288
|
}
|
|
232
289
|
|
|
@@ -236,16 +293,22 @@ Napi::Value CompiledIndex::Search(Napi::CallbackInfo const& ctx) {
|
|
|
236
293
|
return result_js;
|
|
237
294
|
};
|
|
238
295
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
296
|
+
// Dispatch the parallel tasks based on the `TypedArray` type
|
|
297
|
+
try {
|
|
298
|
+
if (queries.TypedArrayType() == napi_float32_array) {
|
|
299
|
+
return run_parallel(queries.As<Napi::Float32Array>().Data());
|
|
300
|
+
} else if (queries.TypedArrayType() == napi_float64_array) {
|
|
301
|
+
return run_parallel(queries.As<Napi::Float64Array>().Data());
|
|
302
|
+
} else if (queries.TypedArrayType() == napi_int8_array) {
|
|
303
|
+
return run_parallel(queries.As<Napi::Int8Array>().Data());
|
|
304
|
+
} else {
|
|
305
|
+
Napi::TypeError::New(
|
|
306
|
+
env, "Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
|
|
307
|
+
.ThrowAsJavaScriptException();
|
|
308
|
+
return env.Null();
|
|
309
|
+
}
|
|
310
|
+
} catch (...) {
|
|
311
|
+
Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
|
|
249
312
|
return env.Null();
|
|
250
313
|
}
|
|
251
314
|
}
|
|
@@ -287,21 +350,36 @@ Napi::Value CompiledIndex::Count(Napi::CallbackInfo const& ctx) {
|
|
|
287
350
|
Napi::Value exactSearch(Napi::CallbackInfo const& ctx) {
|
|
288
351
|
Napi::Env env = ctx.Env();
|
|
289
352
|
|
|
353
|
+
// Check the number of arguments
|
|
354
|
+
if (ctx.Length() != 6) {
|
|
355
|
+
Napi::TypeError::New(env,
|
|
356
|
+
"`exactSearch` expects 6 arguments: dataset, queries, dimensions, k, metric[, threads].")
|
|
357
|
+
.ThrowAsJavaScriptException();
|
|
358
|
+
return env.Null();
|
|
359
|
+
}
|
|
360
|
+
|
|
290
361
|
// Extracting parameters directly without additional type checks.
|
|
291
362
|
Napi::TypedArray dataset = ctx[0].As<Napi::TypedArray>();
|
|
292
363
|
Napi::ArrayBuffer datasetBuffer = dataset.ArrayBuffer();
|
|
293
364
|
Napi::TypedArray queries = ctx[1].As<Napi::TypedArray>();
|
|
294
365
|
Napi::ArrayBuffer queriesBuffer = queries.ArrayBuffer();
|
|
295
|
-
std::
|
|
296
|
-
std::
|
|
366
|
+
std::size_t dimensions = napi_argument_to_size(ctx[2]);
|
|
367
|
+
std::size_t wanted = napi_argument_to_size(ctx[3]);
|
|
297
368
|
metric_kind_t metric_kind = metric_from_name(ctx[4].As<Napi::String>().Utf8Value().c_str());
|
|
369
|
+
std::size_t threads = napi_argument_to_size(ctx[5]);
|
|
370
|
+
if (threads == 0)
|
|
371
|
+
threads = std::thread::hardware_concurrency();
|
|
298
372
|
|
|
373
|
+
// Check the types used
|
|
299
374
|
scalar_kind_t quantization;
|
|
300
375
|
std::size_t bytes_per_scalar;
|
|
301
376
|
switch (queries.TypedArrayType()) {
|
|
302
377
|
case napi_float64_array: quantization = scalar_kind_t::f64_k, bytes_per_scalar = 8; break;
|
|
378
|
+
case napi_float32_array: quantization = scalar_kind_t::f32_k, bytes_per_scalar = 4; break;
|
|
303
379
|
case napi_int8_array: quantization = scalar_kind_t::i8_k, bytes_per_scalar = 1; break;
|
|
304
|
-
default:
|
|
380
|
+
default:
|
|
381
|
+
Napi::TypeError::New(env, "Unsupported TypedArray for queries.").ThrowAsJavaScriptException();
|
|
382
|
+
return env.Null();
|
|
305
383
|
}
|
|
306
384
|
|
|
307
385
|
metric_punned_t metric(dimensions, metric_kind, quantization);
|
|
@@ -310,7 +388,7 @@ Napi::Value exactSearch(Napi::CallbackInfo const& ctx) {
|
|
|
310
388
|
return env.Null();
|
|
311
389
|
}
|
|
312
390
|
|
|
313
|
-
executor_default_t executor;
|
|
391
|
+
executor_default_t executor{threads};
|
|
314
392
|
exact_search_t search;
|
|
315
393
|
|
|
316
394
|
// Performing the exact search.
|