usearch 1.0.0 → 1.1.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/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "1.0.0"
3
+ version = "1.1.0"
4
4
  authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
package/conanfile.py CHANGED
@@ -4,7 +4,7 @@ from conans import ConanFile
4
4
  class USearchConan(ConanFile):
5
5
 
6
6
  name = 'USearch'
7
- version = '1.0.0'
7
+ version = '1.1.0'
8
8
  license = 'Apache 2.0'
9
9
  url = 'https://github.com/unum-cloud/usearch'
10
10
  description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
@@ -37,6 +37,8 @@ class Index : public Napi::ObjectWrap<Index> {
37
37
 
38
38
  void Add(Napi::CallbackInfo const& ctx);
39
39
  Napi::Value Search(Napi::CallbackInfo const& ctx);
40
+ Napi::Value Remove(Napi::CallbackInfo const& ctx);
41
+ Napi::Value Contains(Napi::CallbackInfo const& ctx);
40
42
 
41
43
  std::unique_ptr<index_dense_t> native_;
42
44
  };
@@ -51,6 +53,8 @@ Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
51
53
  InstanceMethod("connectivity", &Index::GetConnectivity),
52
54
  InstanceMethod("add", &Index::Add),
53
55
  InstanceMethod("search", &Index::Search),
56
+ InstanceMethod("remove", &Index::Remove),
57
+ InstanceMethod("contains", &Index::Contains),
54
58
  InstanceMethod("save", &Index::Save),
55
59
  InstanceMethod("load", &Index::Load),
56
60
  InstanceMethod("view", &Index::View),
@@ -291,6 +295,64 @@ Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
291
295
  }
292
296
  }
293
297
 
298
+ Napi::Value Index::Remove(Napi::CallbackInfo const& ctx) {
299
+ Napi::Env env = ctx.Env();
300
+ if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
301
+ Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
302
+ return {};
303
+ }
304
+
305
+ Napi::BigInt key_js = ctx[0].As<Napi::BigInt>();
306
+ bool lossless = true;
307
+ std::uint64_t key = key_js.Uint64Value(&lossless);
308
+ if (!lossless) {
309
+ Napi::TypeError::New(env, "Identifier must be an unsigned integer").ThrowAsJavaScriptException();
310
+ return {};
311
+ }
312
+
313
+ try {
314
+ auto result = native_->remove(key);
315
+ if (!result) {
316
+ Napi::TypeError::New(env, "Removal has failed").ThrowAsJavaScriptException();
317
+ return {};
318
+ }
319
+ return Napi::Boolean::New(env, result.completed);
320
+ } catch (std::bad_alloc const&) {
321
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
322
+ return {};
323
+ } catch (...) {
324
+ Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
325
+ return {};
326
+ }
327
+ }
328
+
329
+ Napi::Value Index::Contains(Napi::CallbackInfo const& ctx) {
330
+ Napi::Env env = ctx.Env();
331
+ if (ctx.Length() < 1 || !ctx[0].IsBigInt()) {
332
+ Napi::TypeError::New(env, "Expects an entry identifier").ThrowAsJavaScriptException();
333
+ return {};
334
+ }
335
+
336
+ Napi::BigInt key_js = ctx[0].As<Napi::BigInt>();
337
+ bool lossless = true;
338
+ std::uint64_t key = key_js.Uint64Value(&lossless);
339
+ if (!lossless) {
340
+ Napi::TypeError::New(env, "Identifier must be an unsigned integer").ThrowAsJavaScriptException();
341
+ return {};
342
+ }
343
+
344
+ try {
345
+ bool result = native_->contains(key);
346
+ return Napi::Boolean::New(env, result);
347
+ } catch (std::bad_alloc const&) {
348
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
349
+ return {};
350
+ } catch (...) {
351
+ Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
352
+ return {};
353
+ }
354
+ }
355
+
294
356
  Napi::Object InitAll(Napi::Env env, Napi::Object exports) { return Index::Init(env, exports); }
295
357
 
296
358
  NODE_API_MODULE(usearch, InitAll)
@@ -11,6 +11,7 @@ assert.equal(index.size(), 0n, 'initial size should be 0');
11
11
  index.add(15n, new Float32Array([10, 20]));
12
12
  index.add(16n, new Float32Array([10, 25]));
13
13
  assert.equal(index.size(), 2n, 'size after adding elements should be 2');
14
+ assert.equal(index.contains(15n), true, 'entry must be present after insertion');
14
15
 
15
16
  var results = index.search(new Float32Array([13, 14]), 2n);
16
17
  assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
@@ -11,7 +11,7 @@ export interface Matches {
11
11
 
12
12
  /** K-Approximate Nearest Neighbors search index. */
13
13
  export class Index {
14
-
14
+
15
15
  /**
16
16
  * Constructs a new index.
17
17
  *
@@ -42,7 +42,7 @@ export class Index {
42
42
  * @return {bigints} The capacity of index.
43
43
  */
44
44
  capacity(): bigint;
45
-
45
+
46
46
  /**
47
47
  * Returns connectivity.
48
48
  * @return {bigint} The connectivity of index.
@@ -84,4 +84,19 @@ export class Index {
84
84
  * @return {Matches} Output of the search result.
85
85
  */
86
86
  search(mat: Float32Array, k: bigint): Matches;
87
+
88
+ /**
89
+ * Check if an entry is contained in the index.
90
+ *
91
+ * @param {bigint} key Identifier to look up.
92
+ */
93
+ contains(key: bigint): boolean;
94
+
95
+ /**
96
+ * Remove a vector from the index.
97
+ *
98
+ * @param {bigint} key Input identifier for every vector to be removed.
99
+ */
100
+ remove(key: bigint): boolean;
101
+
87
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",
package/rust/lib.cpp CHANGED
@@ -4,9 +4,10 @@
4
4
  using namespace unum::usearch;
5
5
  using namespace unum;
6
6
 
7
- using index_t = typename Index::index_t;
7
+ using index_t = index_dense_t;
8
8
  using add_result_t = typename index_t::add_result_t;
9
9
  using search_result_t = typename index_t::search_result_t;
10
+ using labeling_result_t = typename index_t::labeling_result_t;
10
11
 
11
12
  Index::Index(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
12
13
 
@@ -28,14 +29,22 @@ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, s
28
29
  config.expansion = index_->expansion_search();
29
30
  search_result_t result = index_->search(vector.data(), count, config);
30
31
  result.error.raise();
31
- matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
32
- matches.keys.truncate(matches.count);
33
- matches.distances.truncate(matches.count);
32
+ count = result.dump_to(matches.keys.data(), matches.distances.data());
33
+ matches.keys.truncate(count);
34
+ matches.distances.truncate(count);
34
35
  return matches;
35
36
  }
36
37
 
37
38
  void Index::add(key_t key, rust::Slice<float const> vector) const { index_->add(key, vector.data()).error.raise(); }
38
39
 
40
+ bool Index::remove(key_t key) const {
41
+ labeling_result_t result = index_->remove(key);
42
+ result.error.raise();
43
+ return result.completed;
44
+ }
45
+
46
+ bool Index::contains(key_t key) const { return index_->contains(key); }
47
+
39
48
  Matches Index::search(rust::Slice<float const> vector, size_t count) const {
40
49
  Matches matches;
41
50
  matches.keys.reserve(count);
@@ -44,9 +53,9 @@ Matches Index::search(rust::Slice<float const> vector, size_t count) const {
44
53
  matches.keys.push_back(0), matches.distances.push_back(0);
45
54
  search_result_t result = index_->search(vector.data(), count);
46
55
  result.error.raise();
47
- matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
48
- matches.keys.truncate(matches.count);
49
- matches.distances.truncate(matches.count);
56
+ count = result.dump_to(matches.keys.data(), matches.distances.data());
57
+ matches.keys.truncate(count);
58
+ matches.distances.truncate(count);
50
59
  return matches;
51
60
  }
52
61
 
@@ -74,7 +83,7 @@ std::unique_ptr<Index> wrap(index_t&& index) {
74
83
  metric_kind_t rust_to_cpp_metric(MetricKind value) {
75
84
  switch (value) {
76
85
  case MetricKind::IP: return metric_kind_t::ip_k;
77
- case MetricKind::L2Sq: return metric_kind_t::l2sq_k;
86
+ case MetricKind::L2sq: return metric_kind_t::l2sq_k;
78
87
  case MetricKind::Cos: return metric_kind_t::cos_k;
79
88
  case MetricKind::Pearson: return metric_kind_t::pearson_k;
80
89
  case MetricKind::Haversine: return metric_kind_t::haversine_k;
package/rust/lib.hpp CHANGED
@@ -27,6 +27,9 @@ class Index {
27
27
  Matches search(rust::Slice<float const> vector, size_t count) const;
28
28
  Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
29
29
 
30
+ bool remove(key_t key) const;
31
+ bool contains(key_t key) const;
32
+
30
33
  size_t dimensions() const;
31
34
  size_t connectivity() const;
32
35
  size_t size() const;
package/rust/lib.rs CHANGED
@@ -3,14 +3,13 @@ pub mod ffi {
3
3
 
4
4
  // Shared structs with fields visible to both languages.
5
5
  struct Matches {
6
- count: usize,
7
6
  keys: Vec<u64>,
8
7
  distances: Vec<f32>,
9
8
  }
10
9
 
11
10
  enum MetricKind {
12
11
  IP,
13
- L2Sq,
12
+ L2sq,
14
13
  Cos,
15
14
  Pearson,
16
15
  Haversine,
@@ -53,6 +52,8 @@ pub mod ffi {
53
52
 
54
53
  pub fn add(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
55
54
  pub fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
55
+ pub fn remove(self: &Index, key: u64) -> Result<bool>;
56
+ pub fn contains(self: &Index, key: u64) -> bool;
56
57
 
57
58
  pub fn save(self: &Index, path: &str) -> Result<()>;
58
59
  pub fn load(self: &Index, path: &str) -> Result<()>;
@@ -97,7 +98,7 @@ mod tests {
97
98
 
98
99
  // Read back the tags
99
100
  let results = index.search(&first, 10).unwrap();
100
- assert_eq!(results.count, 2);
101
+ assert_eq!(results.keys.len(), 2);
101
102
 
102
103
  // Validate serialization
103
104
  assert!(index.save("index.rust.usearch").is_ok());
@@ -106,7 +107,7 @@ mod tests {
106
107
 
107
108
  // Make sure every function is called at least once
108
109
  assert!(new_index(&options).is_ok());
109
- options.metric = MetricKind::L2Sq;
110
+ options.metric = MetricKind::L2sq;
110
111
  assert!(new_index(&options).is_ok());
111
112
  options.metric = MetricKind::Cos;
112
113
  assert!(new_index(&options).is_ok());
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="1.0.0"
3
+ version="1.1.0"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"