usearch 0.21.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 +19 -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 +94 -605
- 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/README.md +88 -0
- package/cpp/bench.cpp +32 -41
- package/cpp/test.cpp +13 -7
- 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 +204 -78
- package/include/usearch/index_punned_dense.hpp +239 -85
- 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 +61 -11
- package/python/scripts/join.py +7 -3
- package/python/scripts/test.py +28 -243
- package/python/scripts/test_jit.py +244 -0
- package/python/usearch/index.py +97 -25
- 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
|
@@ -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,8 +185,12 @@ 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;
|
|
@@ -172,6 +198,29 @@ class index_punned_dense_gt {
|
|
|
172
198
|
using serialization_result_t = typename index_t::serialization_result_t;
|
|
173
199
|
using join_result_t = typename index_t::join_result_t;
|
|
174
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
|
+
};
|
|
175
224
|
|
|
176
225
|
index_punned_dense_gt() = default;
|
|
177
226
|
index_punned_dense_gt(index_punned_dense_gt&& other) { swap(other); }
|
|
@@ -193,7 +242,9 @@ class index_punned_dense_gt {
|
|
|
193
242
|
std::swap(casts_, other.casts_);
|
|
194
243
|
std::swap(root_metric_, other.root_metric_);
|
|
195
244
|
std::swap(available_threads_, other.available_threads_);
|
|
196
|
-
std::swap(
|
|
245
|
+
std::swap(labeled_lookup_, other.labeled_lookup_);
|
|
246
|
+
std::swap(free_ids_, other.free_ids_);
|
|
247
|
+
std::swap(free_label_, other.free_label_);
|
|
197
248
|
}
|
|
198
249
|
|
|
199
250
|
static index_config_t optimize(index_config_t config) { return index_t::optimize(config); }
|
|
@@ -201,12 +252,11 @@ class index_punned_dense_gt {
|
|
|
201
252
|
std::size_t dimensions() const { return dimensions_; }
|
|
202
253
|
std::size_t scalar_words() const { return scalar_words_; }
|
|
203
254
|
std::size_t connectivity() const { return typed_->connectivity(); }
|
|
204
|
-
std::size_t size() const { return typed_->size(); }
|
|
255
|
+
std::size_t size() const { return typed_->size() - free_ids_.size(); }
|
|
205
256
|
std::size_t capacity() const { return typed_->capacity(); }
|
|
206
257
|
std::size_t max_level() const noexcept { return typed_->max_level(); }
|
|
207
258
|
index_config_t const& config() const { return typed_->config(); }
|
|
208
259
|
index_limits_t const& limits() const { return typed_->limits(); }
|
|
209
|
-
void clear() { return typed_->clear(); }
|
|
210
260
|
|
|
211
261
|
metric_t const& metric() const { return root_metric_; }
|
|
212
262
|
std::size_t expansion_add() const { return expansion_add_; }
|
|
@@ -224,20 +274,35 @@ class index_punned_dense_gt {
|
|
|
224
274
|
stats_t stats() const { return typed_->stats(); }
|
|
225
275
|
stats_t stats(std::size_t level) const { return typed_->stats(level); }
|
|
226
276
|
|
|
227
|
-
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
|
+
|
|
228
293
|
serialization_result_t save(char const* path) const { return typed_->save(path); }
|
|
229
294
|
|
|
230
295
|
serialization_result_t load(char const* path) {
|
|
231
296
|
serialization_result_t result = typed_->load(path);
|
|
232
297
|
if (result)
|
|
233
|
-
|
|
298
|
+
reindex_labels_();
|
|
234
299
|
return result;
|
|
235
300
|
}
|
|
236
301
|
|
|
237
302
|
serialization_result_t view(char const* path) {
|
|
238
303
|
serialization_result_t result = typed_->view(path);
|
|
239
304
|
if (result)
|
|
240
|
-
|
|
305
|
+
reindex_labels_();
|
|
241
306
|
return result;
|
|
242
307
|
}
|
|
243
308
|
|
|
@@ -246,13 +311,16 @@ class index_punned_dense_gt {
|
|
|
246
311
|
}
|
|
247
312
|
|
|
248
313
|
bool contains(label_t label) const {
|
|
249
|
-
shared_lock_t lock(
|
|
250
|
-
return
|
|
314
|
+
shared_lock_t lock(labeled_lookup_mutex_);
|
|
315
|
+
return labeled_lookup_.contains(label);
|
|
251
316
|
}
|
|
252
317
|
|
|
253
|
-
void export_labels(label_t* labels, std::size_t limit) const {
|
|
254
|
-
shared_lock_t lock(
|
|
255
|
-
|
|
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)
|
|
256
324
|
*labels = it->first;
|
|
257
325
|
}
|
|
258
326
|
|
|
@@ -281,18 +349,6 @@ class index_punned_dense_gt {
|
|
|
281
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); }
|
|
282
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); }
|
|
283
351
|
|
|
284
|
-
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); }
|
|
285
|
-
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); }
|
|
286
|
-
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); }
|
|
287
|
-
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); }
|
|
288
|
-
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); }
|
|
289
|
-
|
|
290
|
-
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); }
|
|
291
|
-
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); }
|
|
292
|
-
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); }
|
|
293
|
-
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); }
|
|
294
|
-
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); }
|
|
295
|
-
|
|
296
352
|
search_result_t empty_search_result() const { return search_result_t{*typed_}; }
|
|
297
353
|
|
|
298
354
|
bool get(label_t label, b1x8_t* vector) const { return get_(label, vector, casts_.to_b1x8); }
|
|
@@ -302,30 +358,121 @@ class index_punned_dense_gt {
|
|
|
302
358
|
bool get(label_t label, f64_t* vector) const { return get_(label, vector, casts_.to_f64); }
|
|
303
359
|
// clang-format on
|
|
304
360
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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>()) {
|
|
311
457
|
|
|
312
458
|
return make_( //
|
|
313
459
|
dimensions, accuracy, //
|
|
314
460
|
config, expansion_add, expansion_search, //
|
|
315
|
-
make_metric_(metric, dimensions, accuracy), make_casts_(accuracy));
|
|
461
|
+
make_metric_(metric, dimensions, accuracy), make_casts_(accuracy), free_label);
|
|
316
462
|
}
|
|
317
463
|
|
|
318
|
-
static index_punned_dense_gt make(
|
|
319
|
-
std::size_t dimensions, metric_t metric,
|
|
320
|
-
index_config_t config = {},
|
|
321
|
-
scalar_kind_t accuracy = scalar_kind_t::f32_k,
|
|
322
|
-
std::size_t expansion_add = default_expansion_add(),
|
|
323
|
-
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>()) {
|
|
324
471
|
|
|
325
472
|
return make_( //
|
|
326
473
|
dimensions, accuracy, //
|
|
327
474
|
config, expansion_add, expansion_search, //
|
|
328
|
-
metric_t(metric), make_casts_(accuracy));
|
|
475
|
+
metric_t(metric), make_casts_(accuracy), free_label);
|
|
329
476
|
}
|
|
330
477
|
|
|
331
478
|
struct copy_result_t {
|
|
@@ -346,8 +493,13 @@ class index_punned_dense_gt {
|
|
|
346
493
|
auto typed_result = typed_->copy(config);
|
|
347
494
|
if (!typed_result)
|
|
348
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_;
|
|
349
502
|
*result.index.typed_ = std::move(typed_result.index);
|
|
350
|
-
result.index.lookup_table_ = lookup_table_;
|
|
351
503
|
return result;
|
|
352
504
|
}
|
|
353
505
|
|
|
@@ -430,10 +582,21 @@ class index_punned_dense_gt {
|
|
|
430
582
|
if (casted)
|
|
431
583
|
vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
|
|
432
584
|
|
|
433
|
-
|
|
585
|
+
// Check if there are some removed entries, whose nodes we can reuse
|
|
586
|
+
id_t free_id = default_free_value<id_t>();
|
|
434
587
|
{
|
|
435
|
-
|
|
436
|
-
|
|
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);
|
|
437
600
|
}
|
|
438
601
|
return result;
|
|
439
602
|
}
|
|
@@ -451,51 +614,51 @@ class index_punned_dense_gt {
|
|
|
451
614
|
if (casted)
|
|
452
615
|
vector_data = casted_data, vector_bytes = casted_vector_bytes_;
|
|
453
616
|
|
|
454
|
-
|
|
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);
|
|
455
619
|
}
|
|
456
620
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
463
|
-
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
|
+
}
|
|
464
625
|
|
|
465
|
-
|
|
466
|
-
bool casted = cast(vector_data, dimensions_, casted_data);
|
|
467
|
-
if (casted)
|
|
468
|
-
vector_data = casted_data, vector_bytes = casted_vector_bytes_;
|
|
626
|
+
void reindex_labels_() {
|
|
469
627
|
|
|
470
|
-
|
|
471
|
-
|
|
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
|
+
}
|
|
472
635
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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);
|
|
476
641
|
for (std::size_t i = 0; i != typed_->size(); ++i) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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));
|
|
480
647
|
}
|
|
481
648
|
}
|
|
482
649
|
|
|
483
|
-
id_t lookup_id_(label_t label) const {
|
|
484
|
-
shared_lock_t lock(lookup_table_mutex_);
|
|
485
|
-
return lookup_table_.at(label);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
650
|
template <typename scalar_at> bool get_(label_t label, scalar_at* reconstructed, cast_t const& cast) const {
|
|
489
651
|
id_t id;
|
|
652
|
+
// Find the matching ID
|
|
490
653
|
{
|
|
491
|
-
shared_lock_t lock(
|
|
492
|
-
auto it =
|
|
493
|
-
if (it ==
|
|
654
|
+
shared_lock_t lock(labeled_lookup_mutex_);
|
|
655
|
+
auto it = labeled_lookup_.find(label);
|
|
656
|
+
if (it == labeled_lookup_.end())
|
|
494
657
|
return false;
|
|
495
658
|
id = it->second;
|
|
496
659
|
}
|
|
497
|
-
|
|
498
|
-
member_cref_t member =
|
|
660
|
+
// Export the entry
|
|
661
|
+
member_cref_t member = typed_->at(id);
|
|
499
662
|
byte_t const* punned_vector = reinterpret_cast<byte_t const*>(member.vector.data());
|
|
500
663
|
bool casted = cast(punned_vector, dimensions_, (byte_t*)reconstructed);
|
|
501
664
|
if (!casted)
|
|
@@ -520,20 +683,10 @@ class index_punned_dense_gt {
|
|
|
520
683
|
return search_(vector, wanted, search_config, cast);
|
|
521
684
|
}
|
|
522
685
|
|
|
523
|
-
template <typename scalar_at>
|
|
524
|
-
search_result_t search_around_( //
|
|
525
|
-
label_t hint, scalar_at const* vector, std::size_t wanted, //
|
|
526
|
-
cast_t const& cast) const {
|
|
527
|
-
thread_lock_t lock = thread_lock_();
|
|
528
|
-
search_config_t search_config;
|
|
529
|
-
search_config.thread = lock.thread_id;
|
|
530
|
-
return search_around_(hint, vector, wanted, search_config, cast);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
686
|
static index_punned_dense_gt make_( //
|
|
534
687
|
std::size_t dimensions, scalar_kind_t scalar_kind, //
|
|
535
688
|
index_config_t config, std::size_t expansion_add, std::size_t expansion_search, //
|
|
536
|
-
metric_t metric, casts_t casts) {
|
|
689
|
+
metric_t metric, casts_t casts, label_t free_label) {
|
|
537
690
|
|
|
538
691
|
std::size_t hardware_threads = std::thread::hardware_concurrency();
|
|
539
692
|
index_punned_dense_gt result;
|
|
@@ -545,6 +698,7 @@ class index_punned_dense_gt {
|
|
|
545
698
|
result.cast_buffer_.resize(hardware_threads * result.casted_vector_bytes_);
|
|
546
699
|
result.casts_ = casts;
|
|
547
700
|
result.root_metric_ = metric;
|
|
701
|
+
result.free_label_ = free_label;
|
|
548
702
|
|
|
549
703
|
// Fill the thread IDs.
|
|
550
704
|
result.available_threads_.resize(hardware_threads);
|
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"))
|