usearch 2.6.1 → 2.7.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/. npmignore +14 -0
- package/.cmake-format.py +19 -0
- package/.github/workflows/Dockerfile.libs +2 -2
- package/.github/workflows/prerelease.yml +85 -64
- package/.github/workflows/release.yml +17 -15
- package/.gitignore +1 -1
- package/.vscode/launch.json +47 -14
- package/.vscode/settings.json +7 -0
- package/.vscode/tasks.json +44 -8
- package/CITATION.cff +1 -1
- package/CMakeLists.txt +218 -67
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/binding.gyp +3 -2
- package/build.gradle +19 -10
- package/c/CMakeLists.txt +21 -48
- package/c/README.md +5 -5
- package/c/lib.cpp +52 -7
- package/c/test.c +137 -94
- package/c/usearch.h +42 -0
- package/cmake/clipp.cmake +0 -1
- package/cmake/jemalloc.cmake +21 -37
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +14 -123
- package/cpp/bench.cpp +0 -12
- package/cpp/test.cpp +29 -1
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +23 -16
- package/docs/conf.py +1 -1
- package/golang/README.md +52 -51
- package/golang/lib.go +1 -1
- package/golang/lib_test.go +4 -1
- package/include/usearch/index.hpp +3 -3
- package/include/usearch/index_plugins.hpp +345 -160
- package/javascript/lib.cpp +204 -250
- package/javascript/usearch.js +440 -2
- package/javascript/usearch.test.js +37 -0
- package/package.json +10 -5
- package/python/lib.cpp +95 -224
- package/python/scripts/bench_exact.py +2 -2
- package/python/scripts/test_jit.py +2 -13
- package/python/scripts/test_sparse.py +1 -1
- package/python/scripts/test_tooling.py +15 -5
- package/rust/lib.cpp +7 -4
- package/rust/lib.hpp +2 -0
- package/rust/lib.rs +128 -8
- package/setup.py +14 -4
- package/wasm/CMakeLists.txt +4 -12
- package/wasm/lib.cpp +1 -1
- package/wasmer.toml +1 -1
- package/javascript/docs.js +0 -111
- package/javascript/test.js +0 -32
- package/javascript/usearch.d.ts +0 -110
- package/tsconfig.json +0 -8
package/javascript/lib.cpp
CHANGED
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
using namespace unum::usearch;
|
|
21
21
|
using namespace unum;
|
|
22
22
|
|
|
23
|
-
class
|
|
23
|
+
class CompiledIndex : public Napi::ObjectWrap<CompiledIndex> {
|
|
24
24
|
public:
|
|
25
25
|
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
26
|
-
|
|
26
|
+
CompiledIndex(Napi::CallbackInfo const& ctx);
|
|
27
27
|
|
|
28
28
|
private:
|
|
29
29
|
Napi::Value GetDimensions(Napi::CallbackInfo const& ctx);
|
|
@@ -39,340 +39,294 @@ class Index : public Napi::ObjectWrap<Index> {
|
|
|
39
39
|
Napi::Value Search(Napi::CallbackInfo const& ctx);
|
|
40
40
|
Napi::Value Remove(Napi::CallbackInfo const& ctx);
|
|
41
41
|
Napi::Value Contains(Napi::CallbackInfo const& ctx);
|
|
42
|
+
Napi::Value Count(Napi::CallbackInfo const& ctx);
|
|
42
43
|
|
|
43
44
|
std::unique_ptr<index_dense_t> native_;
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
Napi::Object
|
|
47
|
+
Napi::Object CompiledIndex::Init(Napi::Env env, Napi::Object exports) {
|
|
47
48
|
Napi::Function func = DefineClass( //
|
|
48
|
-
env, "
|
|
49
|
+
env, "CompiledIndex",
|
|
49
50
|
{
|
|
50
|
-
InstanceMethod("dimensions", &
|
|
51
|
-
InstanceMethod("size", &
|
|
52
|
-
InstanceMethod("capacity", &
|
|
53
|
-
InstanceMethod("connectivity", &
|
|
54
|
-
InstanceMethod("add", &
|
|
55
|
-
InstanceMethod("search", &
|
|
56
|
-
InstanceMethod("remove", &
|
|
57
|
-
InstanceMethod("contains", &
|
|
58
|
-
InstanceMethod("
|
|
59
|
-
InstanceMethod("
|
|
60
|
-
InstanceMethod("
|
|
51
|
+
InstanceMethod("dimensions", &CompiledIndex::GetDimensions),
|
|
52
|
+
InstanceMethod("size", &CompiledIndex::GetSize),
|
|
53
|
+
InstanceMethod("capacity", &CompiledIndex::GetCapacity),
|
|
54
|
+
InstanceMethod("connectivity", &CompiledIndex::GetConnectivity),
|
|
55
|
+
InstanceMethod("add", &CompiledIndex::Add),
|
|
56
|
+
InstanceMethod("search", &CompiledIndex::Search),
|
|
57
|
+
InstanceMethod("remove", &CompiledIndex::Remove),
|
|
58
|
+
InstanceMethod("contains", &CompiledIndex::Contains),
|
|
59
|
+
InstanceMethod("count", &CompiledIndex::Count),
|
|
60
|
+
InstanceMethod("save", &CompiledIndex::Save),
|
|
61
|
+
InstanceMethod("load", &CompiledIndex::Load),
|
|
62
|
+
InstanceMethod("view", &CompiledIndex::View),
|
|
61
63
|
});
|
|
62
64
|
|
|
63
65
|
Napi::FunctionReference* constructor = new Napi::FunctionReference();
|
|
64
66
|
*constructor = Napi::Persistent(func);
|
|
65
67
|
env.SetInstanceData(constructor);
|
|
66
68
|
|
|
67
|
-
exports.Set("
|
|
69
|
+
exports.Set("CompiledIndex", func);
|
|
68
70
|
return exports;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
Napi::
|
|
73
|
-
|
|
74
|
-
int length = ctx.Length();
|
|
75
|
-
if (length == 0 || length >= 2 || !ctx[0].IsObject()) {
|
|
76
|
-
Napi::TypeError::New(env, "Pass args as named objects: dimensions: uint, capacity: uint, metric: str")
|
|
77
|
-
.ThrowAsJavaScriptException();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
bool lossless = true;
|
|
82
|
-
Napi::Object params = ctx[0].As<Napi::Object>();
|
|
83
|
-
std::size_t dimensions =
|
|
84
|
-
params.Has("dimensions") ? params.Get("dimensions").As<Napi::BigInt>().Uint64Value(&lossless) : 0;
|
|
85
|
-
|
|
86
|
-
index_limits_t limits;
|
|
87
|
-
std::size_t connectivity = default_connectivity();
|
|
88
|
-
std::size_t expansion_add = default_expansion_add();
|
|
89
|
-
std::size_t expansion_search = default_expansion_search();
|
|
90
|
-
|
|
91
|
-
if (params.Has("capacity"))
|
|
92
|
-
limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
|
|
93
|
-
if (params.Has("connectivity"))
|
|
94
|
-
connectivity = params.Get("connectivity").As<Napi::BigInt>().Uint64Value(&lossless);
|
|
95
|
-
if (params.Has("expansion_add"))
|
|
96
|
-
expansion_add = params.Get("expansion_add").As<Napi::BigInt>().Uint64Value(&lossless);
|
|
97
|
-
if (params.Has("expansion_search"))
|
|
98
|
-
expansion_search = params.Get("expansion_search").As<Napi::BigInt>().Uint64Value(&lossless);
|
|
99
|
-
if (!lossless) {
|
|
100
|
-
Napi::TypeError::New(env, "Arguments must be unsigned integers").ThrowAsJavaScriptException();
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
73
|
+
std::size_t napi_argument_to_size(Napi::Value v) {
|
|
74
|
+
return static_cast<std::size_t>(v.As<Napi::Number>().DoubleValue());
|
|
75
|
+
}
|
|
103
76
|
|
|
104
|
-
|
|
105
|
-
if (params.Has("quantization")) {
|
|
106
|
-
std::string quantization_str = params.Get("quantization").As<Napi::String>().Utf8Value();
|
|
107
|
-
expected_gt<scalar_kind_t> expected = scalar_kind_from_name(quantization_str.c_str(), quantization_str.size());
|
|
108
|
-
if (!expected) {
|
|
109
|
-
Napi::TypeError::New(env, expected.error.release()).ThrowAsJavaScriptException();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
quantization = *expected;
|
|
113
|
-
}
|
|
77
|
+
CompiledIndex::CompiledIndex(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<CompiledIndex>(ctx) {
|
|
114
78
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
metric_kind = *expected;
|
|
125
|
-
}
|
|
79
|
+
// Directly assign the parameters without checks
|
|
80
|
+
std::size_t dimensions = napi_argument_to_size(ctx[0]);
|
|
81
|
+
metric_kind_t metric_kind = metric_from_name(ctx[1].As<Napi::String>().Utf8Value().c_str());
|
|
82
|
+
scalar_kind_t quantization = scalar_kind_from_name(ctx[2].As<Napi::String>().Utf8Value().c_str());
|
|
83
|
+
std::size_t connectivity = napi_argument_to_size(ctx[3]);
|
|
84
|
+
std::size_t expansion_add = napi_argument_to_size(ctx[4]);
|
|
85
|
+
std::size_t expansion_search = napi_argument_to_size(ctx[5]);
|
|
86
|
+
bool multi = ctx[6].As<Napi::Boolean>().Value();
|
|
126
87
|
|
|
127
88
|
metric_punned_t metric(dimensions, metric_kind, quantization);
|
|
128
89
|
index_dense_config_t config(connectivity, expansion_add, expansion_search);
|
|
90
|
+
config.multi = multi;
|
|
91
|
+
|
|
129
92
|
native_.reset(new index_dense_t(index_dense_t::make(metric, config)));
|
|
130
|
-
native_
|
|
93
|
+
if (!native_)
|
|
94
|
+
Napi::Error::New(ctx.Env(), "Out of memory!").ThrowAsJavaScriptException();
|
|
131
95
|
}
|
|
132
96
|
|
|
133
|
-
Napi::Value
|
|
134
|
-
return Napi::
|
|
97
|
+
Napi::Value CompiledIndex::GetDimensions(Napi::CallbackInfo const& ctx) {
|
|
98
|
+
return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
|
|
135
99
|
}
|
|
136
|
-
Napi::Value
|
|
137
|
-
return Napi::
|
|
100
|
+
Napi::Value CompiledIndex::GetConnectivity(Napi::CallbackInfo const& ctx) {
|
|
101
|
+
return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
|
|
138
102
|
}
|
|
139
|
-
Napi::Value
|
|
140
|
-
return Napi::
|
|
103
|
+
Napi::Value CompiledIndex::GetSize(Napi::CallbackInfo const& ctx) {
|
|
104
|
+
return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
|
|
141
105
|
}
|
|
142
|
-
Napi::Value
|
|
143
|
-
return Napi::
|
|
106
|
+
Napi::Value CompiledIndex::GetCapacity(Napi::CallbackInfo const& ctx) {
|
|
107
|
+
return Napi::Number::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
|
|
144
108
|
}
|
|
145
109
|
|
|
146
|
-
void
|
|
147
|
-
Napi::Env env = ctx.Env();
|
|
148
|
-
|
|
149
|
-
int length = ctx.Length();
|
|
150
|
-
if (length == 0 || !ctx[0].IsString()) {
|
|
151
|
-
Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
110
|
+
void CompiledIndex::Save(Napi::CallbackInfo const& ctx) {
|
|
155
111
|
try {
|
|
156
112
|
std::string path = ctx[0].As<Napi::String>();
|
|
157
113
|
auto result = native_->save(path.c_str());
|
|
158
114
|
if (!result)
|
|
159
|
-
|
|
115
|
+
Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
160
116
|
|
|
161
117
|
} catch (...) {
|
|
162
|
-
Napi::TypeError::New(
|
|
118
|
+
Napi::TypeError::New(ctx.Env(), "Serialization failed").ThrowAsJavaScriptException();
|
|
163
119
|
}
|
|
164
120
|
}
|
|
165
121
|
|
|
166
|
-
void
|
|
167
|
-
Napi::Env env = ctx.Env();
|
|
168
|
-
|
|
169
|
-
int length = ctx.Length();
|
|
170
|
-
if (length == 0 || !ctx[0].IsString()) {
|
|
171
|
-
Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
122
|
+
void CompiledIndex::Load(Napi::CallbackInfo const& ctx) {
|
|
175
123
|
try {
|
|
176
124
|
std::string path = ctx[0].As<Napi::String>();
|
|
177
125
|
auto result = native_->load(path.c_str());
|
|
178
126
|
if (!result)
|
|
179
|
-
|
|
127
|
+
Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
180
128
|
|
|
181
129
|
} catch (...) {
|
|
182
|
-
Napi::TypeError::New(
|
|
130
|
+
Napi::TypeError::New(ctx.Env(), "Loading failed").ThrowAsJavaScriptException();
|
|
183
131
|
}
|
|
184
132
|
}
|
|
185
133
|
|
|
186
|
-
void
|
|
187
|
-
Napi::Env env = ctx.Env();
|
|
188
|
-
|
|
189
|
-
int length = ctx.Length();
|
|
190
|
-
if (length == 0 || !ctx[0].IsString()) {
|
|
191
|
-
Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
134
|
+
void CompiledIndex::View(Napi::CallbackInfo const& ctx) {
|
|
194
135
|
|
|
195
136
|
try {
|
|
196
137
|
std::string path = ctx[0].As<Napi::String>();
|
|
197
138
|
auto result = native_->view(path.c_str());
|
|
198
139
|
if (!result)
|
|
199
|
-
|
|
140
|
+
Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
200
141
|
|
|
201
142
|
} catch (...) {
|
|
202
|
-
Napi::TypeError::New(
|
|
143
|
+
Napi::TypeError::New(ctx.Env(), "Memory-mapping failed").ThrowAsJavaScriptException();
|
|
203
144
|
}
|
|
204
145
|
}
|
|
205
146
|
|
|
206
|
-
void
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return Napi::TypeError::New(env, "Expects at least two arguments").ThrowAsJavaScriptException();
|
|
211
|
-
|
|
212
|
-
using key_t = typename index_dense_t::key_t;
|
|
213
|
-
std::size_t index_dimensions = native_->dimensions();
|
|
214
|
-
|
|
215
|
-
auto add = [&](Napi::BigInt key_js, Napi::Float32Array vector_js) {
|
|
216
|
-
bool lossless = true;
|
|
217
|
-
key_t key = static_cast<key_t>(key_js.Uint64Value(&lossless));
|
|
218
|
-
if (!lossless)
|
|
219
|
-
return Napi::TypeError::New(env, "Keys must be unsigned integers").ThrowAsJavaScriptException();
|
|
147
|
+
void CompiledIndex::Add(Napi::CallbackInfo const& ctx) {
|
|
148
|
+
// Extract keys and vectors from arguments
|
|
149
|
+
Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
|
|
150
|
+
std::size_t tasks = keys.ElementLength();
|
|
220
151
|
|
|
221
|
-
|
|
222
|
-
|
|
152
|
+
// Ensure there is enough capacity
|
|
153
|
+
if (native_->size() + tasks >= native_->capacity())
|
|
154
|
+
native_->reserve(ceil2(native_->size() + tasks));
|
|
223
155
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return Napi::TypeError::New(env, result.error.release()).ThrowAsJavaScriptException();
|
|
231
|
-
|
|
232
|
-
} catch (std::bad_alloc const&) {
|
|
233
|
-
return Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
|
|
234
|
-
} catch (...) {
|
|
235
|
-
return Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
|
|
236
|
-
}
|
|
156
|
+
// Create an instance of the executor with the default number of threads
|
|
157
|
+
auto run_parallel = [&](auto vectors) {
|
|
158
|
+
executor_stl_t executor;
|
|
159
|
+
executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
|
|
160
|
+
native_->add(static_cast<default_key_t>(keys[task_idx]), vectors + task_idx * native_->dimensions());
|
|
161
|
+
});
|
|
237
162
|
};
|
|
238
163
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (!native_->reserve(ceil2(native_->size() + length)))
|
|
250
|
-
return Napi::TypeError::New(env, "Out of memory!").ThrowAsJavaScriptException();
|
|
251
|
-
|
|
252
|
-
for (std::size_t i = 0; i < length; i++) {
|
|
253
|
-
Napi::Value key_js = keys_js[i];
|
|
254
|
-
Napi::Value vector_js = vectors_js[i];
|
|
255
|
-
add(key_js.As<Napi::BigInt>(), vector_js.As<Napi::Float32Array>());
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
} else if (ctx[0].IsBigInt() && ctx[1].IsTypedArray()) {
|
|
259
|
-
if (native_->size() + 1 >= native_->capacity())
|
|
260
|
-
native_->reserve(ceil2(native_->size() + 1));
|
|
261
|
-
add(ctx[0].As<Napi::BigInt>(), ctx[1].As<Napi::Float32Array>());
|
|
262
|
-
} else
|
|
263
|
-
return Napi::TypeError::New(env, "Invalid argument type, expects integral key(s) and float vector(s)")
|
|
164
|
+
Napi::TypedArray vectors = ctx[1].As<Napi::TypedArray>();
|
|
165
|
+
if (vectors.TypedArrayType() == napi_float32_array) {
|
|
166
|
+
run_parallel(vectors.As<Napi::Float32Array>().Data());
|
|
167
|
+
} else if (vectors.TypedArrayType() == napi_float64_array) {
|
|
168
|
+
run_parallel(vectors.As<Napi::Float64Array>().Data());
|
|
169
|
+
} else if (vectors.TypedArrayType() == napi_int8_array) {
|
|
170
|
+
run_parallel(vectors.As<Napi::Int8Array>().Data());
|
|
171
|
+
} else {
|
|
172
|
+
Napi::TypeError::New(ctx.Env(),
|
|
173
|
+
"Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
|
|
264
174
|
.ThrowAsJavaScriptException();
|
|
175
|
+
}
|
|
265
176
|
}
|
|
266
177
|
|
|
267
|
-
Napi::Value
|
|
178
|
+
Napi::Value CompiledIndex::Search(Napi::CallbackInfo const& ctx) {
|
|
268
179
|
Napi::Env env = ctx.Env();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
180
|
+
Napi::TypedArray queries = ctx[0].As<Napi::TypedArray>();
|
|
181
|
+
std::size_t tasks = queries.ElementLength() / native_->dimensions();
|
|
182
|
+
std::size_t wanted = napi_argument_to_size(ctx[1]);
|
|
273
183
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (dimensions != native_->dimensions()) {
|
|
280
|
-
Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
|
|
281
|
-
return {};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
bool lossless = true;
|
|
285
|
-
std::uint64_t wanted = wanted_js.Uint64Value(&lossless);
|
|
286
|
-
if (!lossless) {
|
|
287
|
-
Napi::TypeError::New(env, "Wanted number of matches must be an unsigned integer").ThrowAsJavaScriptException();
|
|
288
|
-
return {};
|
|
289
|
-
}
|
|
184
|
+
auto run_parallel = [&](auto vectors) {
|
|
185
|
+
Napi::Array result_js = Napi::Array::New(env, 3);
|
|
186
|
+
Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, tasks * wanted);
|
|
187
|
+
Napi::Float32Array distances_js = Napi::Float32Array::New(env, tasks * wanted);
|
|
188
|
+
Napi::BigUint64Array counts_js = Napi::BigUint64Array::New(env, tasks);
|
|
290
189
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
|
|
295
|
-
try {
|
|
190
|
+
auto matches_data = matches_js.Data();
|
|
191
|
+
auto distances_data = distances_js.Data();
|
|
192
|
+
auto counts_data = counts_js.Data();
|
|
296
193
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
194
|
+
try {
|
|
195
|
+
executor_stl_t executor;
|
|
196
|
+
executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
|
|
197
|
+
auto result = native_->search(vectors + task_idx * native_->dimensions(), wanted);
|
|
198
|
+
if (!result) {
|
|
199
|
+
// Handle the error appropriately
|
|
200
|
+
// For example, log the error or set some flag in the result_js object
|
|
201
|
+
} else {
|
|
202
|
+
counts_data[task_idx] = result.dump_to(matches_data + task_idx * native_->dimensions(),
|
|
203
|
+
distances_data + task_idx * native_->dimensions());
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
} catch (std::bad_alloc const&) {
|
|
207
|
+
Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
|
|
208
|
+
} catch (...) {
|
|
209
|
+
Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
|
|
301
210
|
}
|
|
302
211
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
result_js.Set(
|
|
306
|
-
result_js.Set("distances", distances_js);
|
|
307
|
-
result_js.Set("count", Napi::BigInt::New(env, count));
|
|
212
|
+
result_js.Set(0u, matches_js);
|
|
213
|
+
result_js.Set(1u, distances_js);
|
|
214
|
+
result_js.Set(2u, counts_js);
|
|
308
215
|
return result_js;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
if (queries.TypedArrayType() == napi_float32_array) {
|
|
219
|
+
return run_parallel(queries.As<Napi::Float32Array>().Data());
|
|
220
|
+
} else if (queries.TypedArrayType() == napi_float64_array) {
|
|
221
|
+
return run_parallel(queries.As<Napi::Float64Array>().Data());
|
|
222
|
+
} else if (queries.TypedArrayType() == napi_int8_array) {
|
|
223
|
+
return run_parallel(queries.As<Napi::Int8Array>().Data());
|
|
224
|
+
} else {
|
|
225
|
+
Napi::TypeError::New(env,
|
|
226
|
+
"Unsupported TypedArray. Supported types are Float32Array, Float64Array, and Int8Array.")
|
|
227
|
+
.ThrowAsJavaScriptException();
|
|
228
|
+
return env.Null();
|
|
315
229
|
}
|
|
316
230
|
}
|
|
317
231
|
|
|
318
|
-
Napi::Value
|
|
232
|
+
Napi::Value CompiledIndex::Remove(Napi::CallbackInfo const& ctx) {
|
|
319
233
|
Napi::Env env = ctx.Env();
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
234
|
+
Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
|
|
235
|
+
std::size_t length = keys.ElementLength();
|
|
236
|
+
Napi::Array result = Napi::Array::New(env, length);
|
|
237
|
+
for (std::size_t i = 0; i < length; ++i) {
|
|
238
|
+
result[i] = Napi::Number::New(env, native_->remove(static_cast<default_key_t>(keys[i])).completed);
|
|
323
239
|
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
324
242
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
243
|
+
Napi::Value CompiledIndex::Contains(Napi::CallbackInfo const& ctx) {
|
|
244
|
+
Napi::Env env = ctx.Env();
|
|
245
|
+
Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
|
|
246
|
+
std::size_t length = keys.ElementLength();
|
|
247
|
+
Napi::Array result = Napi::Array::New(env, length);
|
|
248
|
+
for (std::size_t i = 0; i < length; ++i)
|
|
249
|
+
result[i] = Napi::Boolean::New(env, native_->contains(static_cast<default_key_t>(keys[i])));
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
332
252
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
|
|
342
|
-
return {};
|
|
343
|
-
} catch (...) {
|
|
344
|
-
Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
|
|
345
|
-
return {};
|
|
346
|
-
}
|
|
253
|
+
Napi::Value CompiledIndex::Count(Napi::CallbackInfo const& ctx) {
|
|
254
|
+
Napi::Env env = ctx.Env();
|
|
255
|
+
Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
|
|
256
|
+
std::size_t length = keys.ElementLength();
|
|
257
|
+
Napi::Array result = Napi::Array::New(env, length);
|
|
258
|
+
for (std::size_t i = 0; i < length; ++i)
|
|
259
|
+
result[i] = Napi::Boolean::New(env, native_->count(static_cast<default_key_t>(keys[i])));
|
|
260
|
+
return result;
|
|
347
261
|
}
|
|
348
262
|
|
|
349
|
-
Napi::Value
|
|
263
|
+
Napi::Value compiledExactSearch(Napi::CallbackInfo const& ctx) {
|
|
350
264
|
Napi::Env env = ctx.Env();
|
|
351
|
-
if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
|
|
352
|
-
Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
|
|
353
|
-
return {};
|
|
354
|
-
}
|
|
355
265
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
266
|
+
// Extracting parameters directly without additional type checks.
|
|
267
|
+
Napi::TypedArray dataset = ctx[0].As<Napi::TypedArray>();
|
|
268
|
+
Napi::ArrayBuffer datasetBuffer = dataset.ArrayBuffer();
|
|
269
|
+
Napi::TypedArray queries = ctx[1].As<Napi::TypedArray>();
|
|
270
|
+
Napi::ArrayBuffer queriesBuffer = queries.ArrayBuffer();
|
|
271
|
+
std::uint64_t dimensions = napi_argument_to_size(ctx[2]);
|
|
272
|
+
std::uint64_t wanted = napi_argument_to_size(ctx[3]);
|
|
273
|
+
metric_kind_t metric_kind = metric_from_name(ctx[4].As<Napi::String>().Utf8Value().c_str());
|
|
274
|
+
|
|
275
|
+
scalar_kind_t quantization;
|
|
276
|
+
std::size_t bytes_per_scalar;
|
|
277
|
+
switch (queries.TypedArrayType()) {
|
|
278
|
+
case napi_float64_array: quantization = scalar_kind_t::f64_k, bytes_per_scalar = 8; break;
|
|
279
|
+
case napi_int8_array: quantization = scalar_kind_t::i8_k, bytes_per_scalar = 1; break;
|
|
280
|
+
default: quantization = scalar_kind_t::f32_k, bytes_per_scalar = 4; break;
|
|
362
281
|
}
|
|
363
282
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
283
|
+
metric_punned_t metric(dimensions, metric_kind, quantization);
|
|
284
|
+
exact_search_t search;
|
|
285
|
+
|
|
286
|
+
// Performing the exact search.
|
|
287
|
+
std::size_t dataset_size = dataset.ElementLength() / dimensions;
|
|
288
|
+
std::size_t queries_size = queries.ElementLength() / dimensions;
|
|
289
|
+
auto results = search( //
|
|
290
|
+
reinterpret_cast<byte_t const*>(datasetBuffer.Data()), //
|
|
291
|
+
dataset_size, //
|
|
292
|
+
dimensions * bytes_per_scalar, //
|
|
293
|
+
reinterpret_cast<byte_t const*>(queriesBuffer.Data()), //
|
|
294
|
+
queries_size, //
|
|
295
|
+
dimensions * bytes_per_scalar, //
|
|
296
|
+
wanted, metric);
|
|
297
|
+
|
|
298
|
+
if (!results)
|
|
368
299
|
Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
300
|
+
|
|
301
|
+
// Constructing the result object
|
|
302
|
+
Napi::Array result_js = Napi::Array::New(env, 3);
|
|
303
|
+
Napi::BigUint64Array matches_js = Napi::BigUint64Array::New(env, queries_size * wanted);
|
|
304
|
+
Napi::Float32Array distances_js = Napi::Float32Array::New(env, queries_size * wanted);
|
|
305
|
+
Napi::BigUint64Array counts_js = Napi::BigUint64Array::New(env, queries_size);
|
|
306
|
+
|
|
307
|
+
auto matches_data = matches_js.Data();
|
|
308
|
+
auto distances_data = distances_js.Data();
|
|
309
|
+
auto counts_data = counts_js.Data();
|
|
310
|
+
|
|
311
|
+
// Export into JS buffers
|
|
312
|
+
for (std::size_t task_idx = 0; task_idx != queries_size; ++task_idx) {
|
|
313
|
+
auto result = results.at(task_idx);
|
|
314
|
+
counts_data[task_idx] = wanted;
|
|
315
|
+
for (std::size_t result_idx = 0; result_idx != wanted; ++result_idx) {
|
|
316
|
+
matches_data[task_idx * wanted + result_idx] = result[result_idx].offset;
|
|
317
|
+
distances_data[task_idx * wanted + result_idx] = result[result_idx].distance;
|
|
318
|
+
}
|
|
373
319
|
}
|
|
320
|
+
|
|
321
|
+
result_js.Set(0u, matches_js);
|
|
322
|
+
result_js.Set(1u, distances_js);
|
|
323
|
+
result_js.Set(2u, counts_js);
|
|
324
|
+
return result_js;
|
|
374
325
|
}
|
|
375
326
|
|
|
376
|
-
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
|
|
327
|
+
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
|
|
328
|
+
exports.Set("compiledExactSearch", Napi::Function::New(env, compiledExactSearch));
|
|
329
|
+
return CompiledIndex::Init(env, exports);
|
|
330
|
+
}
|
|
377
331
|
|
|
378
332
|
NODE_API_MODULE(usearch, InitAll)
|