usearch 2.10.5 → 2.11.1
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 +54 -15
- package/c/README.md +232 -19
- package/c/lib.cpp +134 -37
- package/c/test.c +4 -0
- package/c/usearch.h +99 -4
- package/cpp/README.md +12 -1
- package/cpp/test.cpp +14 -2
- package/include/usearch/index.hpp +9 -9
- package/include/usearch/index_dense.hpp +38 -15
- package/include/usearch/index_plugins.hpp +89 -27
- package/javascript/dist/cjs/usearch.js +5 -5
- package/javascript/dist/esm/usearch.js +2 -2
- package/javascript/usearch.ts +2 -2
- package/package.json +5 -5
- 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/c/lib.cpp
CHANGED
|
@@ -6,6 +6,13 @@ extern "C" {
|
|
|
6
6
|
#include "usearch.h"
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// Check if NDEBUG is defined to determine if it's a release build
|
|
10
|
+
#ifdef NDEBUG
|
|
11
|
+
#define USEARCH_ASSERT(expression) (void)(expression)
|
|
12
|
+
#else
|
|
13
|
+
#define USEARCH_ASSERT(expression) assert(expression)
|
|
14
|
+
#endif
|
|
15
|
+
|
|
9
16
|
using namespace unum::usearch;
|
|
10
17
|
using namespace unum;
|
|
11
18
|
|
|
@@ -91,22 +98,38 @@ std::size_t get_(index_dense_t* index, usearch_key_t key, size_t count, void* ve
|
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
|
|
101
|
+
template <typename predicate_at = dummy_predicate_t>
|
|
102
|
+
search_result_t search_(index_dense_t* index, void const* vector, scalar_kind_t kind, size_t n,
|
|
103
|
+
predicate_at&& predicate = predicate_at{}) {
|
|
95
104
|
switch (kind) {
|
|
96
|
-
case scalar_kind_t::f32_k:
|
|
97
|
-
|
|
98
|
-
case scalar_kind_t::
|
|
99
|
-
|
|
100
|
-
case scalar_kind_t::
|
|
105
|
+
case scalar_kind_t::f32_k:
|
|
106
|
+
return index->filtered_search((f32_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
107
|
+
case scalar_kind_t::f64_k:
|
|
108
|
+
return index->filtered_search((f64_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
109
|
+
case scalar_kind_t::f16_k:
|
|
110
|
+
return index->filtered_search((f16_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
111
|
+
case scalar_kind_t::i8_k:
|
|
112
|
+
return index->filtered_search((i8_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
113
|
+
case scalar_kind_t::b1x8_k:
|
|
114
|
+
return index->filtered_search((b1x8_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
101
115
|
default: return search_result_t().failed("Unknown scalar kind!");
|
|
102
116
|
}
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
extern "C" {
|
|
106
120
|
|
|
121
|
+
USEARCH_EXPORT char const* usearch_version(void) {
|
|
122
|
+
int major = USEARCH_VERSION_MAJOR;
|
|
123
|
+
int minor = USEARCH_VERSION_MINOR;
|
|
124
|
+
int patch = USEARCH_VERSION_PATCH;
|
|
125
|
+
static char version[32];
|
|
126
|
+
sprintf(version, "%d.%d.%d", major, minor, patch);
|
|
127
|
+
return version;
|
|
128
|
+
}
|
|
129
|
+
|
|
107
130
|
USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
USEARCH_ASSERT(options && error && "Missing arguments");
|
|
110
133
|
|
|
111
134
|
index_dense_config_t config(options->connectivity, options->expansion_add, options->expansion_search);
|
|
112
135
|
config.multi = options->multi;
|
|
@@ -114,11 +137,11 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
|
|
|
114
137
|
scalar_kind_t scalar_kind = scalar_kind_to_cpp(options->quantization);
|
|
115
138
|
|
|
116
139
|
metric_punned_t metric = //
|
|
117
|
-
!options->metric ? metric_punned_t(options->dimensions, metric_kind, scalar_kind)
|
|
118
|
-
: metric_punned_t(options->dimensions, //
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
140
|
+
!options->metric ? metric_punned_t::builtin(options->dimensions, metric_kind, scalar_kind)
|
|
141
|
+
: metric_punned_t::stateless(options->dimensions, //
|
|
142
|
+
reinterpret_cast<std::uintptr_t>(options->metric), //
|
|
143
|
+
metric_punned_signature_t::array_array_k, //
|
|
144
|
+
metric_kind, scalar_kind);
|
|
122
145
|
if (metric.missing()) {
|
|
123
146
|
*error = "Unknown metric kind!";
|
|
124
147
|
return NULL;
|
|
@@ -136,13 +159,13 @@ USEARCH_EXPORT void usearch_free(usearch_index_t index, usearch_error_t*) {
|
|
|
136
159
|
}
|
|
137
160
|
|
|
138
161
|
USEARCH_EXPORT size_t usearch_serialized_length(usearch_index_t index, usearch_error_t*) {
|
|
139
|
-
|
|
162
|
+
USEARCH_ASSERT(index && "Missing arguments");
|
|
140
163
|
return reinterpret_cast<index_dense_t*>(index)->serialized_length();
|
|
141
164
|
}
|
|
142
165
|
|
|
143
166
|
USEARCH_EXPORT void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
144
167
|
|
|
145
|
-
|
|
168
|
+
USEARCH_ASSERT(index && path && error && "Missing arguments");
|
|
146
169
|
serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->save(path);
|
|
147
170
|
if (!result)
|
|
148
171
|
*error = result.error.release();
|
|
@@ -150,7 +173,7 @@ USEARCH_EXPORT void usearch_save(usearch_index_t index, char const* path, usearc
|
|
|
150
173
|
|
|
151
174
|
USEARCH_EXPORT void usearch_load(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
152
175
|
|
|
153
|
-
|
|
176
|
+
USEARCH_ASSERT(index && path && error && "Missing arguments");
|
|
154
177
|
serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->load(path);
|
|
155
178
|
if (!result)
|
|
156
179
|
*error = result.error.release();
|
|
@@ -158,7 +181,7 @@ USEARCH_EXPORT void usearch_load(usearch_index_t index, char const* path, usearc
|
|
|
158
181
|
|
|
159
182
|
USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
160
183
|
|
|
161
|
-
|
|
184
|
+
USEARCH_ASSERT(index && path && error && "Missing arguments");
|
|
162
185
|
serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->view(path);
|
|
163
186
|
if (!result)
|
|
164
187
|
*error = result.error.release();
|
|
@@ -166,7 +189,7 @@ USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearc
|
|
|
166
189
|
|
|
167
190
|
USEARCH_EXPORT void usearch_metadata(char const* path, usearch_init_options_t* options, usearch_error_t* error) {
|
|
168
191
|
|
|
169
|
-
|
|
192
|
+
USEARCH_ASSERT(path && options && error && "Missing arguments");
|
|
170
193
|
index_dense_metadata_result_t result = index_dense_metadata_from_path(path);
|
|
171
194
|
if (!result)
|
|
172
195
|
*error = result.error.release();
|
|
@@ -184,7 +207,7 @@ USEARCH_EXPORT void usearch_metadata(char const* path, usearch_init_options_t* o
|
|
|
184
207
|
|
|
185
208
|
USEARCH_EXPORT void usearch_save_buffer(usearch_index_t index, void* buffer, size_t length, usearch_error_t* error) {
|
|
186
209
|
|
|
187
|
-
|
|
210
|
+
USEARCH_ASSERT(index && buffer && length && error && "Missing arguments");
|
|
188
211
|
memory_mapped_file_t memory_map((byte_t*)buffer, length);
|
|
189
212
|
serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->save(std::move(memory_map));
|
|
190
213
|
if (!result)
|
|
@@ -194,7 +217,7 @@ USEARCH_EXPORT void usearch_save_buffer(usearch_index_t index, void* buffer, siz
|
|
|
194
217
|
USEARCH_EXPORT void usearch_load_buffer(usearch_index_t index, void const* buffer, size_t length,
|
|
195
218
|
usearch_error_t* error) {
|
|
196
219
|
|
|
197
|
-
|
|
220
|
+
USEARCH_ASSERT(index && buffer && length && error && "Missing arguments");
|
|
198
221
|
memory_mapped_file_t memory_map((byte_t*)buffer, length);
|
|
199
222
|
serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->load(std::move(memory_map));
|
|
200
223
|
if (!result)
|
|
@@ -204,7 +227,7 @@ USEARCH_EXPORT void usearch_load_buffer(usearch_index_t index, void const* buffe
|
|
|
204
227
|
USEARCH_EXPORT void usearch_view_buffer(usearch_index_t index, void const* buffer, size_t length,
|
|
205
228
|
usearch_error_t* error) {
|
|
206
229
|
|
|
207
|
-
|
|
230
|
+
USEARCH_ASSERT(index && buffer && length && error && "Missing arguments");
|
|
208
231
|
memory_mapped_file_t memory_map((byte_t*)buffer, length);
|
|
209
232
|
serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->view(std::move(memory_map));
|
|
210
233
|
if (!result)
|
|
@@ -214,7 +237,7 @@ USEARCH_EXPORT void usearch_view_buffer(usearch_index_t index, void const* buffe
|
|
|
214
237
|
USEARCH_EXPORT void usearch_metadata_buffer(void const* buffer, size_t length, usearch_init_options_t* options,
|
|
215
238
|
usearch_error_t* error) {
|
|
216
239
|
|
|
217
|
-
|
|
240
|
+
USEARCH_ASSERT(buffer && length && options && error && "Missing arguments");
|
|
218
241
|
index_dense_metadata_result_t result =
|
|
219
242
|
index_dense_metadata_from_buffer(memory_mapped_file_t((byte_t*)(buffer), length));
|
|
220
243
|
if (!result)
|
|
@@ -231,24 +254,80 @@ USEARCH_EXPORT void usearch_metadata_buffer(void const* buffer, size_t length, u
|
|
|
231
254
|
options->metric = NULL;
|
|
232
255
|
}
|
|
233
256
|
|
|
234
|
-
USEARCH_EXPORT size_t usearch_size(usearch_index_t index, usearch_error_t*) {
|
|
257
|
+
USEARCH_EXPORT size_t usearch_size(usearch_index_t index, usearch_error_t* error) {
|
|
258
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
235
259
|
return reinterpret_cast<index_dense_t*>(index)->size();
|
|
236
260
|
}
|
|
237
261
|
|
|
238
|
-
USEARCH_EXPORT size_t usearch_capacity(usearch_index_t index, usearch_error_t*) {
|
|
262
|
+
USEARCH_EXPORT size_t usearch_capacity(usearch_index_t index, usearch_error_t* error) {
|
|
263
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
239
264
|
return reinterpret_cast<index_dense_t*>(index)->capacity();
|
|
240
265
|
}
|
|
241
266
|
|
|
242
|
-
USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t index, usearch_error_t*) {
|
|
267
|
+
USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t index, usearch_error_t* error) {
|
|
268
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
243
269
|
return reinterpret_cast<index_dense_t*>(index)->dimensions();
|
|
244
270
|
}
|
|
245
271
|
|
|
246
|
-
USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t index, usearch_error_t*) {
|
|
272
|
+
USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t index, usearch_error_t* error) {
|
|
273
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
247
274
|
return reinterpret_cast<index_dense_t*>(index)->connectivity();
|
|
248
275
|
}
|
|
249
276
|
|
|
277
|
+
USEARCH_EXPORT size_t usearch_expansion_add(usearch_index_t index, usearch_error_t* error) {
|
|
278
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
279
|
+
return reinterpret_cast<index_dense_t*>(index)->expansion_add();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
USEARCH_EXPORT size_t usearch_expansion_search(usearch_index_t index, usearch_error_t* error) {
|
|
283
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
284
|
+
return reinterpret_cast<index_dense_t*>(index)->expansion_search();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
USEARCH_EXPORT size_t usearch_memory_usage(usearch_index_t index, usearch_error_t* error) {
|
|
288
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
289
|
+
return reinterpret_cast<index_dense_t*>(index)->memory_usage();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
USEARCH_EXPORT char const* usearch_hardware_acceleration(usearch_index_t index, usearch_error_t* error) {
|
|
293
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
294
|
+
return reinterpret_cast<index_dense_t*>(index)->metric().isa_name();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
USEARCH_EXPORT void usearch_change_expansion_add(usearch_index_t index, size_t expansion, usearch_error_t* error) {
|
|
298
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
299
|
+
reinterpret_cast<index_dense_t*>(index)->change_expansion_add(expansion);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
USEARCH_EXPORT void usearch_change_expansion_search(usearch_index_t index, size_t expansion, usearch_error_t* error) {
|
|
303
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
304
|
+
reinterpret_cast<index_dense_t*>(index)->change_expansion_search(expansion);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
USEARCH_EXPORT void usearch_change_metric_kind(usearch_index_t index, usearch_metric_kind_t kind,
|
|
308
|
+
usearch_error_t* error) {
|
|
309
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
310
|
+
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
311
|
+
index_dense.change_metric(
|
|
312
|
+
metric_punned_t::builtin(index_dense.dimensions(), metric_kind_to_cpp(kind), index_dense.scalar_kind()));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_t metric, void* state,
|
|
316
|
+
usearch_metric_kind_t kind, usearch_error_t* error) {
|
|
317
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
318
|
+
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
319
|
+
auto metric_punned =
|
|
320
|
+
state ? metric_punned_t::statefull(reinterpret_cast<std::uintptr_t>(metric),
|
|
321
|
+
reinterpret_cast<std::uintptr_t>(state), metric_kind_to_cpp(kind),
|
|
322
|
+
index_dense.scalar_kind())
|
|
323
|
+
: metric_punned_t::stateless(index_dense.dimensions(), reinterpret_cast<std::uintptr_t>(metric),
|
|
324
|
+
metric_punned_signature_t::array_array_k, metric_kind_to_cpp(kind),
|
|
325
|
+
index_dense.scalar_kind());
|
|
326
|
+
index_dense.change_metric(std::move(metric_punned));
|
|
327
|
+
}
|
|
328
|
+
|
|
250
329
|
USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t* error) {
|
|
251
|
-
|
|
330
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
252
331
|
if (!reinterpret_cast<index_dense_t*>(index)->reserve(capacity))
|
|
253
332
|
*error = "Out of memory!";
|
|
254
333
|
}
|
|
@@ -257,29 +336,47 @@ USEARCH_EXPORT void usearch_add(
|
|
|
257
336
|
usearch_index_t index, usearch_key_t key, void const* vector, usearch_scalar_kind_t kind, //
|
|
258
337
|
usearch_error_t* error) {
|
|
259
338
|
|
|
260
|
-
|
|
339
|
+
USEARCH_ASSERT(index && vector && error && "Missing arguments");
|
|
261
340
|
add_result_t result = add_(reinterpret_cast<index_dense_t*>(index), key, vector, scalar_kind_to_cpp(kind));
|
|
262
341
|
if (!result)
|
|
263
342
|
*error = result.error.release();
|
|
264
343
|
}
|
|
265
344
|
|
|
266
345
|
USEARCH_EXPORT bool usearch_contains(usearch_index_t index, usearch_key_t key, usearch_error_t*) {
|
|
267
|
-
|
|
346
|
+
USEARCH_ASSERT(index && "Missing arguments");
|
|
268
347
|
return reinterpret_cast<index_dense_t*>(index)->contains(key);
|
|
269
348
|
}
|
|
270
349
|
|
|
271
350
|
USEARCH_EXPORT size_t usearch_count(usearch_index_t index, usearch_key_t key, usearch_error_t*) {
|
|
272
|
-
|
|
351
|
+
USEARCH_ASSERT(index && "Missing arguments");
|
|
273
352
|
return reinterpret_cast<index_dense_t*>(index)->count(key);
|
|
274
353
|
}
|
|
275
354
|
|
|
276
|
-
USEARCH_EXPORT size_t usearch_search(
|
|
277
|
-
usearch_index_t index, void const*
|
|
355
|
+
USEARCH_EXPORT size_t usearch_search( //
|
|
356
|
+
usearch_index_t index, void const* query, usearch_scalar_kind_t query_kind, size_t results_limit, //
|
|
357
|
+
usearch_key_t* found_keys, usearch_distance_t* found_distances, usearch_error_t* error) {
|
|
358
|
+
|
|
359
|
+
USEARCH_ASSERT(index && query && error && "Missing arguments");
|
|
360
|
+
search_result_t result =
|
|
361
|
+
search_(reinterpret_cast<index_dense_t*>(index), query, scalar_kind_to_cpp(query_kind), results_limit);
|
|
362
|
+
if (!result) {
|
|
363
|
+
*error = result.error.release();
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return result.dump_to(found_keys, found_distances);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
USEARCH_EXPORT size_t usearch_filtered_search( //
|
|
371
|
+
usearch_index_t index, //
|
|
372
|
+
void const* query, usearch_scalar_kind_t query_kind, size_t results_limit, //
|
|
373
|
+
int (*filter)(usearch_key_t key, void* filter_state), void* filter_state, //
|
|
278
374
|
usearch_key_t* found_keys, usearch_distance_t* found_distances, usearch_error_t* error) {
|
|
279
375
|
|
|
280
|
-
|
|
376
|
+
USEARCH_ASSERT(index && query && filter && error && "Missing arguments");
|
|
281
377
|
search_result_t result =
|
|
282
|
-
search_(reinterpret_cast<index_dense_t*>(index),
|
|
378
|
+
search_(reinterpret_cast<index_dense_t*>(index), query, scalar_kind_to_cpp(query_kind), results_limit,
|
|
379
|
+
[=](usearch_key_t key) noexcept { return filter(key, filter_state); });
|
|
283
380
|
if (!result) {
|
|
284
381
|
*error = result.error.release();
|
|
285
382
|
return 0;
|
|
@@ -292,13 +389,13 @@ USEARCH_EXPORT size_t usearch_get( //
|
|
|
292
389
|
usearch_index_t index, usearch_key_t key, size_t count, //
|
|
293
390
|
void* vectors, usearch_scalar_kind_t kind, usearch_error_t*) {
|
|
294
391
|
|
|
295
|
-
|
|
392
|
+
USEARCH_ASSERT(index && vectors);
|
|
296
393
|
return get_(reinterpret_cast<index_dense_t*>(index), key, count, vectors, scalar_kind_to_cpp(kind));
|
|
297
394
|
}
|
|
298
395
|
|
|
299
396
|
USEARCH_EXPORT size_t usearch_remove(usearch_index_t index, usearch_key_t key, usearch_error_t* error) {
|
|
300
397
|
|
|
301
|
-
|
|
398
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
302
399
|
labeling_result_t result = reinterpret_cast<index_dense_t*>(index)->remove(key);
|
|
303
400
|
if (!result)
|
|
304
401
|
*error = result.error.release();
|
|
@@ -308,7 +405,7 @@ USEARCH_EXPORT size_t usearch_remove(usearch_index_t index, usearch_key_t key, u
|
|
|
308
405
|
USEARCH_EXPORT size_t usearch_rename( //
|
|
309
406
|
usearch_index_t index, usearch_key_t from, usearch_key_t to, usearch_error_t* error) {
|
|
310
407
|
|
|
311
|
-
|
|
408
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
312
409
|
labeling_result_t result = reinterpret_cast<index_dense_t*>(index)->rename(from, to);
|
|
313
410
|
if (!result)
|
|
314
411
|
*error = result.error.release();
|
|
@@ -334,7 +431,7 @@ USEARCH_EXPORT void usearch_exact_search( //
|
|
|
334
431
|
usearch_distance_t* distances, size_t distances_stride, //
|
|
335
432
|
usearch_error_t* error) {
|
|
336
433
|
|
|
337
|
-
|
|
434
|
+
USEARCH_ASSERT(dataset && queries && keys && distances && error && "Missing arguments");
|
|
338
435
|
|
|
339
436
|
metric_punned_t metric(dimensions, metric_kind_to_cpp(metric_kind), scalar_kind_to_cpp(scalar_kind));
|
|
340
437
|
executor_default_t executor(threads);
|
package/c/test.c
CHANGED
|
@@ -72,6 +72,8 @@ void test_init(size_t const collection_size, size_t const dimensions) {
|
|
|
72
72
|
ASSERT(usearch_capacity(index, &error) == collection_size, error);
|
|
73
73
|
ASSERT(usearch_dimensions(index, &error) == dimensions, error);
|
|
74
74
|
ASSERT(usearch_connectivity(index, &error) == opts.connectivity, error);
|
|
75
|
+
ASSERT(usearch_hardware_acceleration(index, &error), error);
|
|
76
|
+
ASSERT(usearch_memory_usage(index, &error), error);
|
|
75
77
|
|
|
76
78
|
usearch_free(index, &error);
|
|
77
79
|
ASSERT(!error, error);
|
|
@@ -361,6 +363,8 @@ void test_view(size_t const collection_size, size_t const dimensions) {
|
|
|
361
363
|
}
|
|
362
364
|
|
|
363
365
|
int main(int argc, char const* argv[]) {
|
|
366
|
+
printf("Running tests...\n");
|
|
367
|
+
printf("USearch version: %s\n", usearch_version());
|
|
364
368
|
|
|
365
369
|
size_t collection_sizes[] = {11, 512};
|
|
366
370
|
size_t dimensions[] = {83, 2}; // Not all distance functions make sense for 1 dimensional data
|
package/c/usearch.h
CHANGED
|
@@ -97,6 +97,12 @@ USEARCH_EXPORT typedef struct usearch_init_options_t {
|
|
|
97
97
|
bool multi;
|
|
98
98
|
} usearch_init_options_t;
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @brief Retrieves the version of the library.
|
|
102
|
+
* @return The version of the library.
|
|
103
|
+
*/
|
|
104
|
+
USEARCH_EXPORT char const* usearch_version(void);
|
|
105
|
+
|
|
100
106
|
/**
|
|
101
107
|
* @brief Initializes a new instance of the index.
|
|
102
108
|
* @param options Pointer to the `usearch_init_options_t` structure containing initialization options.
|
|
@@ -112,6 +118,22 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
|
|
|
112
118
|
*/
|
|
113
119
|
USEARCH_EXPORT void usearch_free(usearch_index_t index, usearch_error_t* error);
|
|
114
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @brief Reports the memory usage of the index.
|
|
123
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
124
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
125
|
+
* @return Number of bytes used by the index.
|
|
126
|
+
*/
|
|
127
|
+
USEARCH_EXPORT size_t usearch_memory_usage(usearch_index_t index, usearch_error_t* error);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @brief Reports the SIMD capabilities used by the index on the current CPU.
|
|
131
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
132
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
133
|
+
* @return The codename of the SIMD instruction set used by the index.
|
|
134
|
+
*/
|
|
135
|
+
USEARCH_EXPORT char const* usearch_hardware_acceleration(usearch_index_t index, usearch_error_t* error);
|
|
136
|
+
|
|
115
137
|
/**
|
|
116
138
|
* @brief Reports expected file size after serialization.
|
|
117
139
|
* @param[in] index The handle to the USearch index to be serialized.
|
|
@@ -227,6 +249,58 @@ USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t index, usearch_error_
|
|
|
227
249
|
*/
|
|
228
250
|
USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t* error);
|
|
229
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @brief Retrieves the expansion value used during index creation.
|
|
254
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
255
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
256
|
+
* @return The expansion value used during index creation.
|
|
257
|
+
*/
|
|
258
|
+
USEARCH_EXPORT size_t usearch_expansion_add(usearch_index_t index, usearch_error_t* error);
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @brief Retrieves the expansion value used during search.
|
|
262
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
263
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
264
|
+
* @return The expansion value used during search.
|
|
265
|
+
*/
|
|
266
|
+
USEARCH_EXPORT size_t usearch_expansion_search(usearch_index_t index, usearch_error_t* error);
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @brief Updates the expansion value used during index creation. Rarely used.
|
|
270
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
271
|
+
* @param[in] expansion The new expansion value.
|
|
272
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
273
|
+
*/
|
|
274
|
+
USEARCH_EXPORT void usearch_change_expansion_add(usearch_index_t index, size_t expansion, usearch_error_t* error);
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @brief Updates the expansion value used during search. Rarely used.
|
|
278
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
279
|
+
* @param[in] expansion The new expansion value.
|
|
280
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
281
|
+
*/
|
|
282
|
+
USEARCH_EXPORT void usearch_change_expansion_search(usearch_index_t index, size_t expansion, usearch_error_t* error);
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @brief Updates the metric kind used for distance calculation between vectors.
|
|
286
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
287
|
+
* @param[in] kind The metric kind used for distance calculation between vectors.
|
|
288
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
289
|
+
*/
|
|
290
|
+
USEARCH_EXPORT void usearch_change_metric_kind(usearch_index_t index, usearch_metric_kind_t kind,
|
|
291
|
+
usearch_error_t* error);
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @brief Updates the custom metric function used for distance calculation between vectors.
|
|
295
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
296
|
+
* @param[in] metric The custom metric function used for distance calculation between vectors.
|
|
297
|
+
* @param[in] state The @b optional state pointer to be passed to the custom metric function.
|
|
298
|
+
* @param[in] kind The metric kind used for distance calculation between vectors. Needed for serialization.
|
|
299
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
300
|
+
*/
|
|
301
|
+
USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_t metric, void* state,
|
|
302
|
+
usearch_metric_kind_t kind, usearch_error_t* error);
|
|
303
|
+
|
|
230
304
|
/**
|
|
231
305
|
* @brief Adds a vector with a key to the index.
|
|
232
306
|
* @param[inout] index The handle to the USearch index to be populated.
|
|
@@ -268,10 +342,31 @@ USEARCH_EXPORT size_t usearch_count(usearch_index_t index, usearch_key_t, usearc
|
|
|
268
342
|
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
269
343
|
* @return Number of found matches.
|
|
270
344
|
*/
|
|
271
|
-
USEARCH_EXPORT size_t usearch_search(
|
|
272
|
-
usearch_index_t index,
|
|
273
|
-
void const* query_vector, usearch_scalar_kind_t query_kind, //
|
|
274
|
-
|
|
345
|
+
USEARCH_EXPORT size_t usearch_search( //
|
|
346
|
+
usearch_index_t index, //
|
|
347
|
+
void const* query_vector, usearch_scalar_kind_t query_kind, size_t count, //
|
|
348
|
+
usearch_key_t* keys, usearch_distance_t* distances, usearch_error_t* error);
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @brief Performs k-Approximate Nearest Neighbors (kANN) Search for closest vectors to query,
|
|
352
|
+
* predicated on a custom function that returns `true` for vectors to be included.
|
|
353
|
+
*
|
|
354
|
+
* @param[in] index The handle to the USearch index to be queried.
|
|
355
|
+
* @param[in] query_vector Pointer to the query vector data.
|
|
356
|
+
* @param[in] query_kind The scalar type used in the query vector data.
|
|
357
|
+
* @param[in] count Upper bound on the number of neighbors to search, the "k" in "kANN".
|
|
358
|
+
* @param[in] filter The custom filter function that returns `true` for vectors to be included.
|
|
359
|
+
* @param[in] filter_state The @b optional state pointer to be passed to the custom filter function.
|
|
360
|
+
* @param[out] keys Output buffer for up to `count` nearest neighbors keys.
|
|
361
|
+
* @param[out] distances Output buffer for up to `count` distances to nearest neighbors.
|
|
362
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
363
|
+
* @return Number of found matches.
|
|
364
|
+
*/
|
|
365
|
+
USEARCH_EXPORT size_t usearch_filtered_search( //
|
|
366
|
+
usearch_index_t index, //
|
|
367
|
+
void const* query_vector, usearch_scalar_kind_t query_kind, size_t count, //
|
|
368
|
+
int (*filter)(usearch_key_t key, void* filter_state), void* filter_state, //
|
|
369
|
+
usearch_key_t* keys, usearch_distance_t* distances, usearch_error_t* error);
|
|
275
370
|
|
|
276
371
|
/**
|
|
277
372
|
* @brief Retrieves the vector associated with the given key from the index.
|
package/cpp/README.md
CHANGED
|
@@ -71,8 +71,19 @@ Instead of spawning additional threads within USearch, we focus on the thread sa
|
|
|
71
71
|
|
|
72
72
|
During initialization, we allocate enough temporary memory for all the cores on the machine.
|
|
73
73
|
On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
|
|
74
|
+
Here is how parallel indexing may look like, when dealing with the low-level engine:
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
```cpp
|
|
77
|
+
std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
|
|
78
|
+
executor_default_t executor(executor_threads);
|
|
79
|
+
|
|
80
|
+
index.reserve(index_limits_t {vectors.size(), executor.size()});
|
|
81
|
+
executor.fixed(vectors.size(), [&](std::size_t thread, std::size_t task) {
|
|
82
|
+
index.add(task, vectors[task + 3].data(), index_update_config_t { .thread = thread });
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Aside from the `executor_default_t`, you can take advantage of one of the provided "executors" to parallelize the search:
|
|
76
87
|
|
|
77
88
|
- `executor_openmp_t`, that would use OpenMP under the hood.
|
|
78
89
|
- `executor_stl_t`, that will spawn `std::thread` instances.
|
package/cpp/test.cpp
CHANGED
|
@@ -65,6 +65,17 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
|
|
|
65
65
|
|
|
66
66
|
// Perform exact search
|
|
67
67
|
matched_count = index.search(vector_first, 5, args...).dump_to(matched_keys, matched_distances);
|
|
68
|
+
expect(matched_count != 0);
|
|
69
|
+
|
|
70
|
+
// Perform filtered exact search, keeping only odd values
|
|
71
|
+
if constexpr (punned_ak) {
|
|
72
|
+
auto is_odd = [](vector_key_t key) -> bool { return (key & 1) != 0; };
|
|
73
|
+
matched_count =
|
|
74
|
+
index.filtered_search(vector_first, 5, is_odd, args...).dump_to(matched_keys, matched_distances);
|
|
75
|
+
expect(matched_count != 0);
|
|
76
|
+
for (std::size_t i = 0; i < matched_count; i++)
|
|
77
|
+
expect(is_odd(matched_keys[i]));
|
|
78
|
+
}
|
|
68
79
|
|
|
69
80
|
// Validate scans
|
|
70
81
|
std::size_t count = 0;
|
|
@@ -103,8 +114,9 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
|
|
|
103
114
|
expect(std::equal(vector_second, vector_second + dimensions, vec_recovered_from_load.data()));
|
|
104
115
|
}
|
|
105
116
|
|
|
106
|
-
// Try batch requests
|
|
107
|
-
|
|
117
|
+
// Try batch requests, heavily obersubscribing the CPU cores
|
|
118
|
+
std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
|
|
119
|
+
executor_default_t executor(executor_threads);
|
|
108
120
|
index.reserve({vectors.size(), executor.size()});
|
|
109
121
|
executor.fixed(vectors.size() - 3, [&](std::size_t thread, std::size_t task) {
|
|
110
122
|
if constexpr (punned_ak) {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
#define UNUM_USEARCH_HPP
|
|
11
11
|
|
|
12
12
|
#define USEARCH_VERSION_MAJOR 2
|
|
13
|
-
#define USEARCH_VERSION_MINOR
|
|
14
|
-
#define USEARCH_VERSION_PATCH
|
|
13
|
+
#define USEARCH_VERSION_MINOR 11
|
|
14
|
+
#define USEARCH_VERSION_PATCH 1
|
|
15
15
|
|
|
16
16
|
// Inferring C++ version
|
|
17
17
|
// https://stackoverflow.com/a/61552074
|
|
@@ -3497,9 +3497,12 @@ class index_gt {
|
|
|
3497
3497
|
|
|
3498
3498
|
distance_t radius = context.measure(query, citerator_at(start_slot), metric);
|
|
3499
3499
|
next.insert_reserved({-radius, static_cast<compressed_slot_t>(start_slot)});
|
|
3500
|
-
top.insert_reserved({radius, static_cast<compressed_slot_t>(start_slot)});
|
|
3501
3500
|
visits.set(static_cast<compressed_slot_t>(start_slot));
|
|
3502
3501
|
|
|
3502
|
+
// Don't populate the top list if the predicate is not satisfied
|
|
3503
|
+
if (is_dummy<predicate_at>() || predicate(member_cref_t{node_at_(start_slot).ckey(), start_slot}))
|
|
3504
|
+
top.insert_reserved({radius, static_cast<compressed_slot_t>(start_slot)});
|
|
3505
|
+
|
|
3503
3506
|
while (!next.empty()) {
|
|
3504
3507
|
|
|
3505
3508
|
candidate_t candidate = next.top();
|
|
@@ -3529,12 +3532,9 @@ class index_gt {
|
|
|
3529
3532
|
if (top.size() < top_limit || successor_dist < radius) {
|
|
3530
3533
|
// This can substantially grow our priority queue:
|
|
3531
3534
|
next.insert({-successor_dist, successor_slot});
|
|
3532
|
-
if (
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
// This will automatically evict poor matches:
|
|
3537
|
-
top.insert({successor_dist, successor_slot}, top_limit);
|
|
3535
|
+
if (is_dummy<predicate_at>() ||
|
|
3536
|
+
predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot}))
|
|
3537
|
+
top.insert({successor_dist, successor_slot}, top_limit);
|
|
3538
3538
|
radius = top.top().distance;
|
|
3539
3539
|
}
|
|
3540
3540
|
}
|