usearch 0.20.0 → 0.22.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/.github/workflows/prerelease.yml +46 -0
- package/.github/workflows/release.yml +28 -0
- package/.github/workflows/update_version.sh +2 -1
- package/CMakeLists.txt +4 -0
- package/Cargo.toml +1 -1
- package/README.md +93 -585
- package/c/lib.cpp +19 -15
- package/c/usearch.h +26 -23
- package/cmake/emscripten.cmake +8 -0
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +6 -2
- package/cpp/README.md +88 -0
- package/cpp/bench.cpp +72 -44
- package/cpp/test.cpp +13 -7
- package/docs/benchmarks.md +14 -12
- package/docs/golang/index.rst +6 -0
- package/docs/index.rst +5 -2
- package/docs/python/reference.rst +9 -2
- package/docs/rust/index.rst +6 -0
- package/docs/swift/index.rst +6 -0
- package/golang/README.md +28 -0
- package/include/usearch/index.hpp +668 -123
- package/include/usearch/index_punned_dense.hpp +305 -93
- package/include/usearch/index_punned_helpers.hpp +28 -3
- package/java/README.md +22 -0
- package/javascript/README.md +27 -0
- package/javascript/lib.cpp +1 -1
- package/package.json +1 -1
- package/python/README.md +283 -0
- package/python/lib.cpp +118 -16
- package/python/scripts/join.py +155 -0
- package/python/scripts/test.py +45 -241
- package/python/scripts/test_jit.py +244 -0
- package/python/usearch/eval.py +11 -3
- package/python/usearch/index.py +207 -26
- package/rust/README.md +67 -0
- package/swift/README.md +20 -0
- package/wasm/CMakeLists.txt +15 -0
- package/wasm/lib.cpp +5 -0
- package/wasmer.toml +8 -0
- package/python/usearch/join.py +0 -98
|
@@ -105,9 +105,31 @@ struct index_punned_dense_metric_t {
|
|
|
105
105
|
inline result_t operator()(view_t a, view_t b) const { return func_(a, b); }
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
constexpr std::size_t default_removals_cycle() { return 64; }
|
|
109
|
+
|
|
110
|
+
template <typename label_at, typename std::enable_if<std::is_integral<label_at>::value>::type* = nullptr>
|
|
111
|
+
label_at default_free_value() {
|
|
112
|
+
return std::numeric_limits<label_at>::max();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
template <typename label_at, typename std::enable_if<std::is_same<label_at, uint40_t>::value>::type* = nullptr>
|
|
116
|
+
uint40_t default_free_value() {
|
|
117
|
+
return uint40_t::max();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
template < //
|
|
121
|
+
typename label_at, //
|
|
122
|
+
typename std::enable_if<!std::is_integral<label_at>::value && //
|
|
123
|
+
!std::is_same<label_at, uint40_t>::value>::type* = nullptr>
|
|
124
|
+
label_at default_free_value() {
|
|
125
|
+
return label_at();
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
/**
|
|
109
129
|
* @brief Oversimplified type-punned index for equidimensional vectors
|
|
110
|
-
* with automatic down-casting
|
|
130
|
+
* with automatic @b down-casting, hardware-specific @b SIMD metrics,
|
|
131
|
+
* and ability to @b remove existing vectors, common in Semantic Caching
|
|
132
|
+
* applications.
|
|
111
133
|
*/
|
|
112
134
|
template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
|
|
113
135
|
class index_punned_dense_gt {
|
|
@@ -163,14 +185,42 @@ class index_punned_dense_gt {
|
|
|
163
185
|
using shared_lock_t = std::unique_lock<shared_mutex_t>;
|
|
164
186
|
using unique_lock_t = std::unique_lock<shared_mutex_t>;
|
|
165
187
|
|
|
166
|
-
mutable shared_mutex_t
|
|
167
|
-
tsl::robin_map<label_t, id_t>
|
|
188
|
+
mutable shared_mutex_t labeled_lookup_mutex_;
|
|
189
|
+
tsl::robin_map<label_t, id_t> labeled_lookup_;
|
|
190
|
+
|
|
191
|
+
mutable std::mutex free_ids_mutex_;
|
|
192
|
+
ring_gt<id_t> free_ids_;
|
|
193
|
+
label_t free_label_;
|
|
168
194
|
|
|
169
195
|
public:
|
|
170
196
|
using search_result_t = typename index_t::search_result_t;
|
|
171
197
|
using add_result_t = typename index_t::add_result_t;
|
|
172
198
|
using serialization_result_t = typename index_t::serialization_result_t;
|
|
199
|
+
using join_result_t = typename index_t::join_result_t;
|
|
173
200
|
using stats_t = typename index_t::stats_t;
|
|
201
|
+
using match_t = typename index_t::match_t;
|
|
202
|
+
|
|
203
|
+
struct labeling_result_t {
|
|
204
|
+
error_t error{};
|
|
205
|
+
std::size_t completed{};
|
|
206
|
+
|
|
207
|
+
explicit operator bool() const noexcept { return !error; }
|
|
208
|
+
labeling_result_t failed(error_t message) noexcept {
|
|
209
|
+
error = std::move(message);
|
|
210
|
+
return std::move(*this);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
struct compaction_result_t {
|
|
215
|
+
error_t error{};
|
|
216
|
+
std::size_t pruned_edges{};
|
|
217
|
+
|
|
218
|
+
explicit operator bool() const noexcept { return !error; }
|
|
219
|
+
compaction_result_t failed(error_t message) noexcept {
|
|
220
|
+
error = std::move(message);
|
|
221
|
+
return std::move(*this);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
174
224
|
|
|
175
225
|
index_punned_dense_gt() = default;
|
|
176
226
|
index_punned_dense_gt(index_punned_dense_gt&& other) { swap(other); }
|
|
@@ -184,12 +234,17 @@ class index_punned_dense_gt {
|
|
|
184
234
|
void swap(index_punned_dense_gt& other) {
|
|
185
235
|
std::swap(dimensions_, other.dimensions_);
|
|
186
236
|
std::swap(scalar_words_, other.scalar_words_);
|
|
237
|
+
std::swap(expansion_add_, other.expansion_add_);
|
|
238
|
+
std::swap(expansion_search_, other.expansion_search_);
|
|
187
239
|
std::swap(casted_vector_bytes_, other.casted_vector_bytes_);
|
|
188
240
|
std::swap(typed_, other.typed_);
|
|
189
241
|
std::swap(cast_buffer_, other.cast_buffer_);
|
|
190
242
|
std::swap(casts_, other.casts_);
|
|
191
243
|
std::swap(root_metric_, other.root_metric_);
|
|
192
244
|
std::swap(available_threads_, other.available_threads_);
|
|
245
|
+
std::swap(labeled_lookup_, other.labeled_lookup_);
|
|
246
|
+
std::swap(free_ids_, other.free_ids_);
|
|
247
|
+
std::swap(free_label_, other.free_label_);
|
|
193
248
|
}
|
|
194
249
|
|
|
195
250
|
static index_config_t optimize(index_config_t config) { return index_t::optimize(config); }
|
|
@@ -197,11 +252,11 @@ class index_punned_dense_gt {
|
|
|
197
252
|
std::size_t dimensions() const { return dimensions_; }
|
|
198
253
|
std::size_t scalar_words() const { return scalar_words_; }
|
|
199
254
|
std::size_t connectivity() const { return typed_->connectivity(); }
|
|
200
|
-
std::size_t size() const { return typed_->size(); }
|
|
255
|
+
std::size_t size() const { return typed_->size() - free_ids_.size(); }
|
|
201
256
|
std::size_t capacity() const { return typed_->capacity(); }
|
|
257
|
+
std::size_t max_level() const noexcept { return typed_->max_level(); }
|
|
202
258
|
index_config_t const& config() const { return typed_->config(); }
|
|
203
259
|
index_limits_t const& limits() const { return typed_->limits(); }
|
|
204
|
-
void clear() { return typed_->clear(); }
|
|
205
260
|
|
|
206
261
|
metric_t const& metric() const { return root_metric_; }
|
|
207
262
|
std::size_t expansion_add() const { return expansion_add_; }
|
|
@@ -219,20 +274,35 @@ class index_punned_dense_gt {
|
|
|
219
274
|
stats_t stats() const { return typed_->stats(); }
|
|
220
275
|
stats_t stats(std::size_t level) const { return typed_->stats(level); }
|
|
221
276
|
|
|
222
|
-
bool reserve(index_limits_t limits) {
|
|
277
|
+
bool reserve(index_limits_t limits) {
|
|
278
|
+
{
|
|
279
|
+
unique_lock_t lock(labeled_lookup_mutex_);
|
|
280
|
+
labeled_lookup_.reserve(limits.members);
|
|
281
|
+
}
|
|
282
|
+
return typed_->reserve(limits);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
void clear() {
|
|
286
|
+
unique_lock_t lookup_lock(labeled_lookup_mutex_);
|
|
287
|
+
std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
|
|
288
|
+
typed_->clear();
|
|
289
|
+
labeled_lookup_.clear();
|
|
290
|
+
free_ids_.clear();
|
|
291
|
+
}
|
|
292
|
+
|
|
223
293
|
serialization_result_t save(char const* path) const { return typed_->save(path); }
|
|
224
294
|
|
|
225
295
|
serialization_result_t load(char const* path) {
|
|
226
296
|
serialization_result_t result = typed_->load(path);
|
|
227
297
|
if (result)
|
|
228
|
-
|
|
298
|
+
reindex_labels_();
|
|
229
299
|
return result;
|
|
230
300
|
}
|
|
231
301
|
|
|
232
302
|
serialization_result_t view(char const* path) {
|
|
233
303
|
serialization_result_t result = typed_->view(path);
|
|
234
304
|
if (result)
|
|
235
|
-
|
|
305
|
+
reindex_labels_();
|
|
236
306
|
return result;
|
|
237
307
|
}
|
|
238
308
|
|
|
@@ -241,13 +311,16 @@ class index_punned_dense_gt {
|
|
|
241
311
|
}
|
|
242
312
|
|
|
243
313
|
bool contains(label_t label) const {
|
|
244
|
-
shared_lock_t lock(
|
|
245
|
-
return
|
|
314
|
+
shared_lock_t lock(labeled_lookup_mutex_);
|
|
315
|
+
return labeled_lookup_.contains(label);
|
|
246
316
|
}
|
|
247
317
|
|
|
248
|
-
void export_labels(label_t* labels, std::size_t limit) const {
|
|
249
|
-
shared_lock_t lock(
|
|
250
|
-
|
|
318
|
+
void export_labels(label_t* labels, std::size_t offset, std::size_t limit) const {
|
|
319
|
+
shared_lock_t lock(labeled_lookup_mutex_);
|
|
320
|
+
auto it = labeled_lookup_.begin();
|
|
321
|
+
offset = (std::min)(offset, labeled_lookup_.size());
|
|
322
|
+
std::advance(it, offset);
|
|
323
|
+
for (; it != labeled_lookup_.end() && limit; ++it, ++labels, --limit)
|
|
251
324
|
*labels = it->first;
|
|
252
325
|
}
|
|
253
326
|
|
|
@@ -276,18 +349,6 @@ class index_punned_dense_gt {
|
|
|
276
349
|
search_result_t search(f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f32); }
|
|
277
350
|
search_result_t search(f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f64); }
|
|
278
351
|
|
|
279
|
-
search_result_t search_around(label_t hint, b1x8_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_b1x8); }
|
|
280
|
-
search_result_t search_around(label_t hint, f8_bits_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f8); }
|
|
281
|
-
search_result_t search_around(label_t hint, f16_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f16); }
|
|
282
|
-
search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f32); }
|
|
283
|
-
search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f64); }
|
|
284
|
-
|
|
285
|
-
search_result_t search_around(label_t hint, b1x8_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_b1x8); }
|
|
286
|
-
search_result_t search_around(label_t hint, f8_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f8); }
|
|
287
|
-
search_result_t search_around(label_t hint, f16_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f16); }
|
|
288
|
-
search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f32); }
|
|
289
|
-
search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f64); }
|
|
290
|
-
|
|
291
352
|
search_result_t empty_search_result() const { return search_result_t{*typed_}; }
|
|
292
353
|
|
|
293
354
|
bool get(label_t label, b1x8_t* vector) const { return get_(label, vector, casts_.to_b1x8); }
|
|
@@ -297,49 +358,198 @@ class index_punned_dense_gt {
|
|
|
297
358
|
bool get(label_t label, f64_t* vector) const { return get_(label, vector, casts_.to_f64); }
|
|
298
359
|
// clang-format on
|
|
299
360
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
361
|
+
labeling_result_t remove(label_t label) {
|
|
362
|
+
labeling_result_t result;
|
|
363
|
+
|
|
364
|
+
unique_lock_t lookup_lock(labeled_lookup_mutex_);
|
|
365
|
+
auto id_terator = labeled_lookup_.find(label);
|
|
366
|
+
if (id_terator == labeled_lookup_.end())
|
|
367
|
+
return result;
|
|
368
|
+
|
|
369
|
+
// Grow the removed entries ring, if needed
|
|
370
|
+
std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
|
|
371
|
+
if (free_ids_.size() == free_ids_.capacity())
|
|
372
|
+
if (!free_ids_.reserve((std::max<std::size_t>)(free_ids_.capacity() * 2, 64ul)))
|
|
373
|
+
return result.failed("Can't allocate memory for a free-list");
|
|
374
|
+
|
|
375
|
+
// A removed entry would be:
|
|
376
|
+
// - present in `free_ids_`
|
|
377
|
+
// - missing in the `labeled_lookup_`
|
|
378
|
+
// - marked in the `typed_` index with a `free_label_`
|
|
379
|
+
id_t id = id_terator->second;
|
|
380
|
+
free_ids_.push(id);
|
|
381
|
+
labeled_lookup_.erase(id_terator);
|
|
382
|
+
typed_->at(id).label = free_label_;
|
|
383
|
+
result.completed = true;
|
|
384
|
+
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
template <typename labels_iterator_at>
|
|
389
|
+
labeling_result_t remove(labels_iterator_at&& labels_begin, labels_iterator_at&& labels_end) {
|
|
390
|
+
|
|
391
|
+
labeling_result_t result;
|
|
392
|
+
unique_lock_t lookup_lock(labeled_lookup_mutex_);
|
|
393
|
+
std::unique_lock<std::mutex> free_lock(free_ids_mutex_);
|
|
394
|
+
|
|
395
|
+
// Grow the removed entries ring, if needed
|
|
396
|
+
std::size_t count_requests = std::distance(labels_begin, labels_end);
|
|
397
|
+
if (!free_ids_.reserve(free_ids_.size() + count_requests))
|
|
398
|
+
return result.failed("Can't allocate memory for a free-list");
|
|
399
|
+
|
|
400
|
+
// Remove them one-by-one
|
|
401
|
+
for (auto label_it = labels_begin; label_it != labels_end; ++label_it) {
|
|
402
|
+
label_t label = *label_it;
|
|
403
|
+
auto id_terator = labeled_lookup_.find(label);
|
|
404
|
+
if (id_terator == labeled_lookup_.end())
|
|
405
|
+
continue;
|
|
406
|
+
|
|
407
|
+
// A removed entry would be:
|
|
408
|
+
// - present in `free_ids_`
|
|
409
|
+
// - missing in the `labeled_lookup_`
|
|
410
|
+
// - marked in the `typed_` index with a `free_label_`
|
|
411
|
+
id_t id = id_terator->second;
|
|
412
|
+
free_ids_.push(id);
|
|
413
|
+
labeled_lookup_.erase(id_terator);
|
|
414
|
+
typed_->at(id).label = free_label_;
|
|
415
|
+
result.completed += 1;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
template <typename executor_at = dummy_executor_t, typename progress_at = dummy_progress_t>
|
|
422
|
+
compaction_result_t compact(executor_at&& executor = executor_at{}, progress_at&& progress = progress_at{}) {
|
|
423
|
+
compaction_result_t result;
|
|
424
|
+
std::atomic<std::size_t> pruned_edges;
|
|
425
|
+
auto disallow = [&](member_cref_t const& member) noexcept {
|
|
426
|
+
bool freed = member.label == free_label_;
|
|
427
|
+
pruned_edges += freed;
|
|
428
|
+
return freed;
|
|
429
|
+
};
|
|
430
|
+
typed_->isolate(disallow, std::forward<executor_at>(executor), std::forward<progress_at>(progress));
|
|
431
|
+
result.pruned_edges = pruned_edges;
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
labeling_result_t rename(label_t from, label_t to) {
|
|
436
|
+
labeling_result_t result;
|
|
437
|
+
unique_lock_t lookup_lock(labeled_lookup_mutex_);
|
|
438
|
+
auto id_terator = labeled_lookup_.find(from);
|
|
439
|
+
if (id_terator == labeled_lookup_.end())
|
|
440
|
+
return result;
|
|
441
|
+
|
|
442
|
+
id_t id = id_terator->second;
|
|
443
|
+
labeled_lookup_.erase(id_terator);
|
|
444
|
+
labeled_lookup_.emplace(to, id);
|
|
445
|
+
typed_->at(id).label = to;
|
|
446
|
+
result.completed = true;
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
static index_punned_dense_gt make( //
|
|
451
|
+
std::size_t dimensions, metric_kind_t metric, //
|
|
452
|
+
index_config_t config = {}, //
|
|
453
|
+
scalar_kind_t accuracy = scalar_kind_t::f32_k, //
|
|
454
|
+
std::size_t expansion_add = default_expansion_add(), //
|
|
455
|
+
std::size_t expansion_search = default_expansion_search(), //
|
|
456
|
+
label_t free_label = default_free_value<label_t>()) {
|
|
306
457
|
|
|
307
458
|
return make_( //
|
|
308
459
|
dimensions, accuracy, //
|
|
309
460
|
config, expansion_add, expansion_search, //
|
|
310
|
-
make_metric_(metric, dimensions, accuracy), make_casts_(accuracy));
|
|
461
|
+
make_metric_(metric, dimensions, accuracy), make_casts_(accuracy), free_label);
|
|
311
462
|
}
|
|
312
463
|
|
|
313
|
-
static index_punned_dense_gt make(
|
|
314
|
-
std::size_t dimensions, metric_t metric,
|
|
315
|
-
index_config_t config = {},
|
|
316
|
-
scalar_kind_t accuracy = scalar_kind_t::f32_k,
|
|
317
|
-
std::size_t expansion_add = default_expansion_add(),
|
|
318
|
-
std::size_t expansion_search = default_expansion_search()
|
|
464
|
+
static index_punned_dense_gt make( //
|
|
465
|
+
std::size_t dimensions, metric_t metric, //
|
|
466
|
+
index_config_t config = {}, //
|
|
467
|
+
scalar_kind_t accuracy = scalar_kind_t::f32_k, //
|
|
468
|
+
std::size_t expansion_add = default_expansion_add(), //
|
|
469
|
+
std::size_t expansion_search = default_expansion_search(), //
|
|
470
|
+
label_t free_label = default_free_value<label_t>()) {
|
|
319
471
|
|
|
320
472
|
return make_( //
|
|
321
473
|
dimensions, accuracy, //
|
|
322
474
|
config, expansion_add, expansion_search, //
|
|
323
|
-
metric_t(metric), make_casts_(accuracy));
|
|
475
|
+
metric_t(metric), make_casts_(accuracy), free_label);
|
|
324
476
|
}
|
|
325
477
|
|
|
326
|
-
|
|
327
|
-
index_punned_dense_gt
|
|
478
|
+
struct copy_result_t {
|
|
479
|
+
index_punned_dense_gt index;
|
|
480
|
+
error_t error;
|
|
328
481
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
482
|
+
explicit operator bool() const noexcept { return !error; }
|
|
483
|
+
copy_result_t failed(error_t message) noexcept {
|
|
484
|
+
error = std::move(message);
|
|
485
|
+
return std::move(*this);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
copy_result_t copy(copy_config_t config = {}) const {
|
|
490
|
+
copy_result_t result = fork();
|
|
491
|
+
if (!result)
|
|
492
|
+
return result;
|
|
493
|
+
auto typed_result = typed_->copy(config);
|
|
494
|
+
if (!typed_result)
|
|
495
|
+
return result.failed(std::move(typed_result.error));
|
|
496
|
+
if (!result.index.free_ids_.reserve(free_ids_.size()))
|
|
497
|
+
return result.failed(std::move(typed_result.error));
|
|
498
|
+
for (std::size_t i = 0; i != free_ids_.size(); ++i)
|
|
499
|
+
result.index.free_ids_.push(free_ids_[i]);
|
|
500
|
+
|
|
501
|
+
result.index.labeled_lookup_ = labeled_lookup_;
|
|
502
|
+
*result.index.typed_ = std::move(typed_result.index);
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
copy_result_t fork() const {
|
|
507
|
+
copy_result_t result;
|
|
508
|
+
index_punned_dense_gt& other = result.index;
|
|
509
|
+
|
|
510
|
+
other.dimensions_ = dimensions_;
|
|
511
|
+
other.scalar_words_ = scalar_words_;
|
|
512
|
+
other.expansion_add_ = expansion_add_;
|
|
513
|
+
other.expansion_search_ = expansion_search_;
|
|
514
|
+
other.casted_vector_bytes_ = casted_vector_bytes_;
|
|
515
|
+
other.cast_buffer_ = cast_buffer_;
|
|
516
|
+
other.casts_ = casts_;
|
|
517
|
+
|
|
518
|
+
other.root_metric_ = root_metric_;
|
|
519
|
+
other.available_threads_ = available_threads_;
|
|
334
520
|
|
|
335
|
-
result.root_metric_ = root_metric_;
|
|
336
521
|
index_t* raw = index_allocator_t{}.allocate(1);
|
|
337
|
-
|
|
338
|
-
|
|
522
|
+
if (!raw)
|
|
523
|
+
return result.failed("Can't allocate the index");
|
|
339
524
|
|
|
525
|
+
new (raw) index_t(config(), root_metric_);
|
|
526
|
+
other.typed_ = raw;
|
|
340
527
|
return result;
|
|
341
528
|
}
|
|
342
529
|
|
|
530
|
+
template < //
|
|
531
|
+
typename first_to_second_at = dummy_label_to_label_mapping_t, //
|
|
532
|
+
typename second_to_first_at = dummy_label_to_label_mapping_t, //
|
|
533
|
+
typename executor_at = dummy_executor_t, //
|
|
534
|
+
typename progress_at = dummy_progress_t //
|
|
535
|
+
>
|
|
536
|
+
static join_result_t join( //
|
|
537
|
+
index_punned_dense_gt const& first, //
|
|
538
|
+
index_punned_dense_gt const& second, //
|
|
539
|
+
join_config_t config = {}, //
|
|
540
|
+
first_to_second_at&& first_to_second = first_to_second_at{}, //
|
|
541
|
+
second_to_first_at&& second_to_first = second_to_first_at{}, //
|
|
542
|
+
executor_at&& executor = executor_at{}, //
|
|
543
|
+
progress_at&& progress = progress_at{}) noexcept {
|
|
544
|
+
|
|
545
|
+
return index_t::join( //
|
|
546
|
+
*first.typed_, *second.typed_, config, //
|
|
547
|
+
std::forward<first_to_second_at>(first_to_second), //
|
|
548
|
+
std::forward<second_to_first_at>(second_to_first), //
|
|
549
|
+
std::forward<executor_at>(executor), //
|
|
550
|
+
std::forward<progress_at>(progress));
|
|
551
|
+
}
|
|
552
|
+
|
|
343
553
|
private:
|
|
344
554
|
struct thread_lock_t {
|
|
345
555
|
index_punned_dense_gt const& parent;
|
|
@@ -372,10 +582,21 @@ class index_punned_dense_gt {
|
|
|
372
582
|
if (casted)
|
|
373
583
|
vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
|
|
374
584
|
|
|
375
|
-
|
|
585
|
+
// Check if there are some removed entries, whose nodes we can reuse
|
|
586
|
+
id_t free_id = default_free_value<id_t>();
|
|
376
587
|
{
|
|
377
|
-
|
|
378
|
-
|
|
588
|
+
std::unique_lock<std::mutex> lock(free_ids_mutex_);
|
|
589
|
+
free_ids_.try_pop(free_id);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Perform the insertion or the update
|
|
593
|
+
add_result_t result = //
|
|
594
|
+
free_id != default_free_value<id_t>() //
|
|
595
|
+
? typed_->update(free_id, label, {vector_data, vector_bytes}, config)
|
|
596
|
+
: typed_->add(label, {vector_data, vector_bytes}, config);
|
|
597
|
+
{
|
|
598
|
+
unique_lock_t lock(labeled_lookup_mutex_);
|
|
599
|
+
labeled_lookup_.emplace(label, result.id);
|
|
379
600
|
}
|
|
380
601
|
return result;
|
|
381
602
|
}
|
|
@@ -393,51 +614,51 @@ class index_punned_dense_gt {
|
|
|
393
614
|
if (casted)
|
|
394
615
|
vector_data = casted_data, vector_bytes = casted_vector_bytes_;
|
|
395
616
|
|
|
396
|
-
|
|
617
|
+
auto allow = [=](match_t const& match) noexcept { return match.member.label != free_label_; };
|
|
618
|
+
return typed_->search({vector_data, vector_bytes}, wanted, config, allow);
|
|
397
619
|
}
|
|
398
620
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
405
|
-
std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
|
|
621
|
+
id_t lookup_id_(label_t label) const {
|
|
622
|
+
shared_lock_t lock(labeled_lookup_mutex_);
|
|
623
|
+
return labeled_lookup_.at(label);
|
|
624
|
+
}
|
|
406
625
|
|
|
407
|
-
|
|
408
|
-
bool casted = cast(vector_data, dimensions_, casted_data);
|
|
409
|
-
if (casted)
|
|
410
|
-
vector_data = casted_data, vector_bytes = casted_vector_bytes_;
|
|
626
|
+
void reindex_labels_() {
|
|
411
627
|
|
|
412
|
-
|
|
413
|
-
|
|
628
|
+
// Estimate number of entries first
|
|
629
|
+
std::size_t count_total = typed_->size();
|
|
630
|
+
std::size_t count_removed = 0;
|
|
631
|
+
for (std::size_t i = 0; i != count_total; ++i) {
|
|
632
|
+
member_cref_t member = typed_->at(i);
|
|
633
|
+
count_removed += member.label == free_label_;
|
|
634
|
+
}
|
|
414
635
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
636
|
+
unique_lock_t lock(labeled_lookup_mutex_);
|
|
637
|
+
labeled_lookup_.clear();
|
|
638
|
+
labeled_lookup_.reserve(count_total - count_removed);
|
|
639
|
+
free_ids_.clear();
|
|
640
|
+
free_ids_.reserve(count_removed);
|
|
418
641
|
for (std::size_t i = 0; i != typed_->size(); ++i) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
642
|
+
member_cref_t member = typed_->at(i);
|
|
643
|
+
if (member.label == free_label_)
|
|
644
|
+
free_ids_.push(static_cast<id_t>(i));
|
|
645
|
+
else
|
|
646
|
+
labeled_lookup_.emplace(member.label, static_cast<id_t>(i));
|
|
422
647
|
}
|
|
423
648
|
}
|
|
424
649
|
|
|
425
|
-
id_t lookup_id_(label_t label) const {
|
|
426
|
-
shared_lock_t lock(lookup_table_mutex_);
|
|
427
|
-
return lookup_table_.at(label);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
650
|
template <typename scalar_at> bool get_(label_t label, scalar_at* reconstructed, cast_t const& cast) const {
|
|
431
651
|
id_t id;
|
|
652
|
+
// Find the matching ID
|
|
432
653
|
{
|
|
433
|
-
shared_lock_t lock(
|
|
434
|
-
auto it =
|
|
435
|
-
if (it ==
|
|
654
|
+
shared_lock_t lock(labeled_lookup_mutex_);
|
|
655
|
+
auto it = labeled_lookup_.find(label);
|
|
656
|
+
if (it == labeled_lookup_.end())
|
|
436
657
|
return false;
|
|
437
658
|
id = it->second;
|
|
438
659
|
}
|
|
439
|
-
|
|
440
|
-
member_cref_t member =
|
|
660
|
+
// Export the entry
|
|
661
|
+
member_cref_t member = typed_->at(id);
|
|
441
662
|
byte_t const* punned_vector = reinterpret_cast<byte_t const*>(member.vector.data());
|
|
442
663
|
bool casted = cast(punned_vector, dimensions_, (byte_t*)reconstructed);
|
|
443
664
|
if (!casted)
|
|
@@ -462,20 +683,10 @@ class index_punned_dense_gt {
|
|
|
462
683
|
return search_(vector, wanted, search_config, cast);
|
|
463
684
|
}
|
|
464
685
|
|
|
465
|
-
template <typename scalar_at>
|
|
466
|
-
search_result_t search_around_( //
|
|
467
|
-
label_t hint, scalar_at const* vector, std::size_t wanted, //
|
|
468
|
-
cast_t const& cast) const {
|
|
469
|
-
thread_lock_t lock = thread_lock_();
|
|
470
|
-
search_config_t search_config;
|
|
471
|
-
search_config.thread = lock.thread_id;
|
|
472
|
-
return search_around_(hint, vector, wanted, search_config, cast);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
686
|
static index_punned_dense_gt make_( //
|
|
476
687
|
std::size_t dimensions, scalar_kind_t scalar_kind, //
|
|
477
688
|
index_config_t config, std::size_t expansion_add, std::size_t expansion_search, //
|
|
478
|
-
metric_t metric, casts_t casts) {
|
|
689
|
+
metric_t metric, casts_t casts, label_t free_label) {
|
|
479
690
|
|
|
480
691
|
std::size_t hardware_threads = std::thread::hardware_concurrency();
|
|
481
692
|
index_punned_dense_gt result;
|
|
@@ -487,6 +698,7 @@ class index_punned_dense_gt {
|
|
|
487
698
|
result.cast_buffer_.resize(hardware_threads * result.casted_vector_bytes_);
|
|
488
699
|
result.casts_ = casts;
|
|
489
700
|
result.root_metric_ = metric;
|
|
701
|
+
result.free_label_ = free_label;
|
|
490
702
|
|
|
491
703
|
// Fill the thread IDs.
|
|
492
704
|
result.available_threads_.resize(hardware_threads);
|
|
@@ -313,9 +313,11 @@ class executor_stl_t {
|
|
|
313
313
|
std::size_t threads_count_{};
|
|
314
314
|
|
|
315
315
|
public:
|
|
316
|
-
executor_stl_t(std::size_t threads_count) noexcept
|
|
316
|
+
executor_stl_t(std::size_t threads_count = 0) noexcept
|
|
317
317
|
: threads_count_(threads_count ? threads_count : std::thread::hardware_concurrency()) {}
|
|
318
318
|
|
|
319
|
+
std::size_t size() const noexcept { return threads_count_; }
|
|
320
|
+
|
|
319
321
|
template <typename thread_aware_function_at>
|
|
320
322
|
void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
321
323
|
std::vector<std::thread> threads_pool;
|
|
@@ -330,22 +332,39 @@ class executor_stl_t {
|
|
|
330
332
|
for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
|
|
331
333
|
threads_pool[thread_idx].join();
|
|
332
334
|
}
|
|
335
|
+
|
|
336
|
+
template <typename thread_aware_function_at>
|
|
337
|
+
void execute_bulk(thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
338
|
+
std::vector<std::thread> threads_pool;
|
|
339
|
+
for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
|
|
340
|
+
threads_pool.emplace_back([=]() { thread_aware_function(thread_idx); });
|
|
341
|
+
for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
|
|
342
|
+
threads_pool[thread_idx].join();
|
|
343
|
+
}
|
|
333
344
|
};
|
|
334
345
|
|
|
335
346
|
#if USEARCH_USE_OPENMP
|
|
336
347
|
|
|
337
348
|
class executor_openmp_t {
|
|
338
349
|
public:
|
|
339
|
-
executor_openmp_t(std::size_t threads_count) noexcept {
|
|
350
|
+
executor_openmp_t(std::size_t threads_count = 0) noexcept {
|
|
340
351
|
omp_set_num_threads(threads_count ? threads_count : std::thread::hardware_concurrency());
|
|
341
352
|
}
|
|
342
353
|
|
|
354
|
+
std::size_t size() const noexcept { return omp_get_num_threads(); }
|
|
355
|
+
|
|
343
356
|
template <typename thread_aware_function_at>
|
|
344
357
|
void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
345
358
|
#pragma omp parallel for schedule(dynamic)
|
|
346
|
-
for (std::size_t i = 0; i
|
|
359
|
+
for (std::size_t i = 0; i != tasks; ++i)
|
|
347
360
|
thread_aware_function(omp_get_thread_num(), i);
|
|
348
361
|
}
|
|
362
|
+
|
|
363
|
+
template <typename thread_aware_function_at>
|
|
364
|
+
void execute_bulk(thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
365
|
+
#pragma omp parallel
|
|
366
|
+
{ thread_aware_function(omp_get_thread_num()); }
|
|
367
|
+
}
|
|
349
368
|
};
|
|
350
369
|
|
|
351
370
|
using executor_default_t = executor_openmp_t;
|
|
@@ -451,6 +470,12 @@ template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
|
|
|
451
470
|
return *this;
|
|
452
471
|
}
|
|
453
472
|
|
|
473
|
+
memory_mapping_allocator_gt(memory_mapping_allocator_gt const&) noexcept {}
|
|
474
|
+
memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt const&) noexcept {
|
|
475
|
+
reset();
|
|
476
|
+
return *this;
|
|
477
|
+
}
|
|
478
|
+
|
|
454
479
|
~memory_mapping_allocator_gt() noexcept { reset(); }
|
|
455
480
|
|
|
456
481
|
inline byte_t* allocate(std::size_t count_bytes) noexcept {
|
package/java/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# USearch for Java
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```xml
|
|
6
|
+
<dependency>
|
|
7
|
+
<groupId>cloud.unum</groupId>
|
|
8
|
+
<artifactId>usearch</artifactId>
|
|
9
|
+
<version>0.2.3</version>
|
|
10
|
+
</dependency>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Add that snippet to your `pom.xml` and hit `mvn install`.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```java
|
|
18
|
+
Index index = new Index.Config().metric("cos").dimensions(2).build();
|
|
19
|
+
float vec[] = {10, 20};
|
|
20
|
+
index.add(42, vec);
|
|
21
|
+
int[] labels = index.search(vec, 5);
|
|
22
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# USearch for JavaScript
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install usearch
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
var index = new usearch.Index({ metric: 'cos', connectivity: 16, dimensions: 3 })
|
|
13
|
+
index.add(42, new Float32Array([0.2, 0.6, 0.4]))
|
|
14
|
+
var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10)
|
|
15
|
+
|
|
16
|
+
assert.equal(index.size(), 1)
|
|
17
|
+
assert.deepEqual(results.labels, new BigUint64Array([42n]))
|
|
18
|
+
assert.deepEqual(results.distances, new Float32Array([0]))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Serialization
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
index.save('index.usearch')
|
|
25
|
+
index.load('index.usearch')
|
|
26
|
+
index.view('index.usearch')
|
|
27
|
+
```
|
package/javascript/lib.cpp
CHANGED
|
@@ -91,7 +91,7 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
|
|
|
91
91
|
std::size_t expansion_search = default_expansion_search();
|
|
92
92
|
|
|
93
93
|
if (params.Has("capacity"))
|
|
94
|
-
limits.
|
|
94
|
+
limits.members = params.Get("capacity").As<Napi::Number>().Uint32Value();
|
|
95
95
|
if (params.Has("connectivity"))
|
|
96
96
|
config.connectivity = params.Get("connectivity").As<Napi::Number>().Uint32Value();
|
|
97
97
|
if (params.Has("expansion_add"))
|