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.
@@ -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 and hardware isa.
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 lookup_table_mutex_;
167
- tsl::robin_map<label_t, id_t> lookup_table_;
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) { return typed_->reserve(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
- reindex_labels();
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
- reindex_labels();
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(lookup_table_mutex_);
245
- return lookup_table_.contains(label);
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(lookup_table_mutex_);
250
- for (auto it = lookup_table_.begin(); it != lookup_table_.end() && limit; ++it, ++labels, --limit)
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
- static index_punned_dense_gt make( //
301
- std::size_t dimensions, metric_kind_t metric, //
302
- index_config_t config = {}, //
303
- scalar_kind_t accuracy = scalar_kind_t::f32_k, //
304
- std::size_t expansion_add = default_expansion_add(), //
305
- std::size_t expansion_search = default_expansion_search()) {
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
- index_punned_dense_gt fork() const {
327
- index_punned_dense_gt result;
478
+ struct copy_result_t {
479
+ index_punned_dense_gt index;
480
+ error_t error;
328
481
 
329
- result.dimensions_ = dimensions_;
330
- result.scalar_words_ = scalar_words_;
331
- result.casted_vector_bytes_ = casted_vector_bytes_;
332
- result.cast_buffer_ = cast_buffer_;
333
- result.casts_ = casts_;
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
- new (raw) index_t(config(), root_metric_);
338
- result.typed_ = raw;
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
- add_result_t result = typed_->add(label, {vector_data, vector_bytes}, config);
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
- unique_lock_t lock(lookup_table_mutex_);
378
- lookup_table_.emplace(label, result.id);
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
- return typed_->search({vector_data, vector_bytes}, wanted, config);
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
- template <typename scalar_at>
400
- search_result_t search_around_( //
401
- label_t hint, scalar_at const* vector, std::size_t wanted, //
402
- search_config_t config, cast_t const& cast) const {
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
- byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
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
- return typed_->search_around(static_cast<id_t>(hint), {vector_data, vector_bytes}, wanted, config);
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
- void reindex_labels() {
416
- shared_lock_t lock(lookup_table_mutex_);
417
- lookup_table_.clear();
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
- member_citerator_t iterator = typed_->cbegin() + i;
420
- member_cref_t member = *iterator;
421
- lookup_table_[member.label] = static_cast<id_t>(i);
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(lookup_table_mutex_);
434
- auto it = lookup_table_.find(label);
435
- if (it == lookup_table_.end())
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
- member_citerator_t iterator = typed_->cbegin() + id;
440
- member_cref_t member = *iterator;
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 < tasks; ++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
+ ```
@@ -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.elements = params.Get("capacity").As<Napi::Number>().Uint32Value();
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"))