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.
@@ -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,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 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;
@@ -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(lookup_table_, other.lookup_table_);
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) { 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
+
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
- reindex_labels();
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
- reindex_labels();
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(lookup_table_mutex_);
250
- return lookup_table_.contains(label);
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(lookup_table_mutex_);
255
- 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)
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
- static index_punned_dense_gt make( //
306
- std::size_t dimensions, metric_kind_t metric, //
307
- index_config_t config = {}, //
308
- scalar_kind_t accuracy = scalar_kind_t::f32_k, //
309
- std::size_t expansion_add = default_expansion_add(), //
310
- 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>()) {
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
- 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>();
434
587
  {
435
- unique_lock_t lock(lookup_table_mutex_);
436
- 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);
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
- 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);
455
619
  }
456
620
 
457
- template <typename scalar_at>
458
- search_result_t search_around_( //
459
- label_t hint, scalar_at const* vector, std::size_t wanted, //
460
- search_config_t config, cast_t const& cast) const {
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
- byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
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
- return typed_->search_around(static_cast<id_t>(hint), {vector_data, vector_bytes}, wanted, config);
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
- void reindex_labels() {
474
- shared_lock_t lock(lookup_table_mutex_);
475
- 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);
476
641
  for (std::size_t i = 0; i != typed_->size(); ++i) {
477
- member_citerator_t iterator = typed_->cbegin() + i;
478
- member_cref_t member = *iterator;
479
- 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));
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(lookup_table_mutex_);
492
- auto it = lookup_table_.find(label);
493
- 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())
494
657
  return false;
495
658
  id = it->second;
496
659
  }
497
- member_citerator_t iterator = typed_->cbegin() + id;
498
- member_cref_t member = *iterator;
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
+ ```
@@ -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"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",