usearch 0.22.3 → 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.
Files changed (93) hide show
  1. package/.github/workflows/prerelease.yml +3 -3
  2. package/.releaserc +10 -2
  3. package/.vscode/launch.json +10 -10
  4. package/.vscode/settings.json +9 -1
  5. package/.vscode/tasks.json +4 -4
  6. package/CMakeLists.txt +6 -1
  7. package/Cargo.toml +2 -2
  8. package/README.md +35 -41
  9. package/build.rs +2 -2
  10. package/c/CMakeLists.txt +48 -0
  11. package/c/README.md +34 -0
  12. package/c/lib.cpp +83 -72
  13. package/c/test.c +20 -20
  14. package/c/usearch.h +132 -32
  15. package/conanfile.py +1 -1
  16. package/cpp/CMakeLists.txt +3 -1
  17. package/cpp/README.md +16 -16
  18. package/cpp/bench.cpp +50 -126
  19. package/cpp/test.cpp +164 -80
  20. package/docs/benchmarks.md +26 -5
  21. package/docs/c/reference.rst +3 -3
  22. package/docs/compilation.md +31 -49
  23. package/docs/cpp/index.rst +1 -1
  24. package/docs/cpp/reference.rst +4 -4
  25. package/golang/lib.go +275 -141
  26. package/golang/lib_test.go +105 -0
  27. package/include/usearch/index.hpp +1681 -1656
  28. package/include/usearch/index_dense.hpp +1400 -0
  29. package/include/usearch/index_plugins.hpp +1263 -0
  30. package/java/README.md +1 -1
  31. package/java/cloud/unum/usearch/Index.java +10 -10
  32. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +34 -37
  33. package/java/test/IndexTest.java +1 -1
  34. package/javascript/README.md +6 -6
  35. package/javascript/lib.cpp +156 -55
  36. package/javascript/test.js +26 -11
  37. package/javascript/usearch.d.ts +102 -0
  38. package/javascript/usearch.js +2 -118
  39. package/objc/USearchObjective.mm +57 -31
  40. package/objc/include/USearchObjective.h +32 -22
  41. package/package.json +5 -3
  42. package/python/README.md +23 -14
  43. package/python/lib.cpp +468 -444
  44. package/python/scripts/bench.ipynb +3 -2
  45. package/python/scripts/bench.py +4 -4
  46. package/python/scripts/index_faiss.py +5 -5
  47. package/python/scripts/join.py +4 -4
  48. package/python/scripts/test.py +152 -61
  49. package/python/scripts/test_jit.py +10 -12
  50. package/python/scripts/test_sparse.py +32 -0
  51. package/python/usearch/client.py +26 -28
  52. package/python/usearch/eval.py +73 -23
  53. package/python/usearch/index.py +520 -228
  54. package/python/usearch/io.py +57 -25
  55. package/python/usearch/numba.py +4 -4
  56. package/python/usearch/server.py +10 -10
  57. package/rust/lib.cpp +34 -30
  58. package/rust/lib.hpp +14 -12
  59. package/rust/lib.rs +8 -7
  60. package/setup.py +5 -2
  61. package/simsimd/.github/workflows/prerelease.yml +73 -0
  62. package/simsimd/.github/workflows/release.yml +110 -0
  63. package/simsimd/.github/workflows/update_version.sh +3 -0
  64. package/simsimd/.releaserc +77 -0
  65. package/simsimd/.vscode/launch.json +27 -1
  66. package/simsimd/.vscode/settings.json +22 -1
  67. package/simsimd/.vscode/tasks.json +6 -0
  68. package/simsimd/CMakeLists.txt +3 -2
  69. package/simsimd/README.md +82 -46
  70. package/simsimd/bench.cxx +63 -24
  71. package/simsimd/include/simsimd/simsimd.h +88 -66
  72. package/simsimd/include/simsimd/simsimd_chem.h +126 -0
  73. package/simsimd/package.json +8 -0
  74. package/simsimd/pyproject.toml +42 -0
  75. package/simsimd/python/python.c +81 -0
  76. package/simsimd/python/run.sh +1 -0
  77. package/simsimd/python/test.c +109 -0
  78. package/simsimd/python/test.py +23 -0
  79. package/simsimd/setup.py +44 -0
  80. package/swift/Index+Sugar.swift +75 -23
  81. package/swift/README.md +26 -2
  82. package/swift/Test.swift +19 -7
  83. package/swift/USearch.swift +2 -2
  84. package/tsconfig.json +8 -0
  85. package/wasm/CMakeLists.txt +1 -0
  86. package/wasmer.toml +1 -1
  87. package/wolfram/CMakeLists.txt +1 -0
  88. package/wolfram/lib.cpp +11 -13
  89. package/golang/usearch.h +0 -9
  90. package/golang/usearch_par_test.go +0 -40
  91. package/golang/usearch_test.go +0 -82
  92. package/include/usearch/index_punned_dense.hpp +0 -1001
  93. package/include/usearch/index_punned_helpers.hpp +0 -676
package/java/README.md CHANGED
@@ -18,5 +18,5 @@ Add that snippet to your `pom.xml` and hit `mvn install`.
18
18
  Index index = new Index.Config().metric("cos").dimensions(2).build();
19
19
  float vec[] = {10, 20};
20
20
  index.add(42, vec);
21
- int[] labels = index.search(vec, 5);
21
+ int[] keys = index.search(vec, 5);
22
22
  ```
@@ -16,12 +16,12 @@ public class Index {
16
16
 
17
17
  private long c_ptr = 0;
18
18
 
19
- public Index(String metric, String accuracy, //
19
+ public Index(String metric, String quantization, //
20
20
  long dimensions, long capacity, long connectivity, //
21
21
  long expansion_add, long expansion_search) {
22
22
  c_ptr = c_create(
23
23
  metric,
24
- accuracy,
24
+ quantization,
25
25
  dimensions,
26
26
  capacity,
27
27
  connectivity,
@@ -49,8 +49,8 @@ public class Index {
49
49
  c_reserve(c_ptr, capacity);
50
50
  }
51
51
 
52
- public void add(int label, float vector[]) {
53
- c_add(c_ptr, label, vector);
52
+ public void add(int key, float vector[]) {
53
+ c_add(c_ptr, key, vector);
54
54
  }
55
55
 
56
56
  public int[] search(float vector[], long count) {
@@ -71,7 +71,7 @@ public class Index {
71
71
 
72
72
  public static class Config {
73
73
  private String _metric = "ip";
74
- private String _accuracy = "f32";
74
+ private String _quantization = "f32";
75
75
  private long _dimensions = 0;
76
76
  private long _capacity = 0;
77
77
  private long _connectivity = 0;
@@ -84,7 +84,7 @@ public class Index {
84
84
  public Index build() {
85
85
  return new Index(
86
86
  _metric,
87
- _accuracy,
87
+ _quantization,
88
88
  _dimensions,
89
89
  _capacity,
90
90
  _connectivity,
@@ -97,8 +97,8 @@ public class Index {
97
97
  return this;
98
98
  }
99
99
 
100
- public Config accuracy(String _accuracy) {
101
- this._accuracy = _accuracy;
100
+ public Config quantization(String _quantization) {
101
+ this._quantization = _quantization;
102
102
  return this;
103
103
  }
104
104
 
@@ -138,7 +138,7 @@ public class Index {
138
138
  }
139
139
 
140
140
  private static native long c_create(//
141
- String metric, String accuracy, //
141
+ String metric, String quantization, //
142
142
  long dimensions, long capacity, long connectivity, //
143
143
  long expansion_add, long expansion_search);
144
144
 
@@ -154,7 +154,7 @@ public class Index {
154
154
 
155
155
  private static native void c_reserve(long ptr, long capacity);
156
156
 
157
- private static native void c_add(long ptr, int label, float vector[]);
157
+ private static native void c_add(long ptr, int key, float vector[]);
158
158
 
159
159
  private static native int[] c_search(long ptr, float vector[], long count);
160
160
 
@@ -4,53 +4,44 @@
4
4
 
5
5
  #include <thread>
6
6
 
7
- #include <usearch/index_punned_dense.hpp>
7
+ #include <usearch/index_dense.hpp>
8
8
 
9
9
  using namespace unum::usearch;
10
10
  using namespace unum;
11
11
 
12
- using metric_t = index_punned_dense_metric_t;
13
- using distance_t = punned_distance_t;
14
- using index_t = punned_small_t;
15
-
16
- using add_result_t = typename index_t::add_result_t;
17
- using search_result_t = typename index_t::search_result_t;
18
- using serialization_result_t = typename index_t::serialization_result_t;
19
- using label_t = typename index_t::label_t;
20
- using id_t = typename index_t::id_t;
21
- using vector_view_t = unum::usearch::span_gt<float>;
12
+ using float_span_t = unum::usearch::span_gt<float>;
22
13
 
23
14
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1create( //
24
15
  JNIEnv* env, jclass, //
25
- jstring metric, jstring accuracy, //
16
+ jstring metric, jstring quantization, //
26
17
  jlong dimensions, jlong capacity, jlong connectivity, //
27
18
  jlong expansion_add, jlong expansion_search) {
28
19
 
29
20
  jlong result{};
30
21
  char const* metric_cstr{};
31
- char const* accuracy_cstr{};
22
+ char const* quantization_cstr{};
32
23
  try {
33
24
 
34
- index_config_t config;
35
- config.connectivity = static_cast<std::size_t>(connectivity);
25
+ std::size_t connectivity = static_cast<std::size_t>(connectivity);
36
26
  std::size_t expansion_add = static_cast<std::size_t>(expansion_add);
37
27
  std::size_t expansion_search = static_cast<std::size_t>(expansion_search);
38
28
 
39
29
  metric_cstr = (*env).GetStringUTFChars(metric, 0);
40
30
  std::size_t metric_length = (*env).GetStringUTFLength(metric);
41
- accuracy_cstr = (*env).GetStringUTFChars(accuracy, 0);
42
- std::size_t accuracy_length = (*env).GetStringUTFLength(accuracy);
31
+ quantization_cstr = (*env).GetStringUTFChars(quantization, 0);
32
+ std::size_t quantization_length = (*env).GetStringUTFLength(quantization);
43
33
 
44
- scalar_kind_t accuracy = scalar_kind_from_name(accuracy_cstr, accuracy_length);
45
34
  metric_kind_t metric_kind = metric_from_name(metric_cstr, metric_length);
46
- index_t index = index_t::make( //
47
- static_cast<std::size_t>(dimensions), metric_kind, config, accuracy, expansion_add, expansion_search);
35
+ scalar_kind_t quantization = scalar_kind_from_name(quantization_cstr, quantization_length);
36
+ index_dense_config_t config(connectivity, expansion_add, expansion_search);
37
+ metric_punned_t metric(static_cast<std::size_t>(dimensions), metric_kind, quantization);
38
+ index_dense_t index = index_dense_t::make(metric, config);
48
39
  if (!index.reserve(static_cast<std::size_t>(capacity))) {
49
40
  jclass jc = (*env).FindClass("java/lang/Error");
50
41
  if (jc)
51
42
  (*env).ThrowNew(jc, "Failed to reserve desired capacity!");
52
43
  } else {
53
- index_t* result_ptr = new index_t(std::move(index));
44
+ index_dense_t* result_ptr = new index_dense_t(std::move(index));
54
45
  std::memcpy(&result, &result_ptr, sizeof(jlong));
55
46
  }
56
47
 
@@ -61,13 +52,13 @@ JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1create( //
61
52
  }
62
53
 
63
54
  (*env).ReleaseStringUTFChars(metric, metric_cstr);
64
- (*env).ReleaseStringUTFChars(accuracy, accuracy_cstr);
55
+ (*env).ReleaseStringUTFChars(quantization, quantization_cstr);
65
56
  return result;
66
57
  }
67
58
 
68
59
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1save(JNIEnv* env, jclass, jlong c_ptr, jstring path) {
69
60
  char const* path_cstr = (*env).GetStringUTFChars(path, 0);
70
- if (!reinterpret_cast<index_t*>(c_ptr)->save(path_cstr)) {
61
+ if (!reinterpret_cast<index_dense_t*>(c_ptr)->save(path_cstr)) {
71
62
  jclass jc = (*env).FindClass("java/lang/Error");
72
63
  if (jc)
73
64
  (*env).ThrowNew(jc, "Failed to dump vector index to path!");
@@ -77,7 +68,7 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1save(JNIEnv* env, jclass
77
68
 
78
69
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1load(JNIEnv* env, jclass, jlong c_ptr, jstring path) {
79
70
  char const* path_cstr = (*env).GetStringUTFChars(path, 0);
80
- if (!reinterpret_cast<index_t*>(c_ptr)->load(path_cstr)) {
71
+ if (!reinterpret_cast<index_dense_t*>(c_ptr)->load(path_cstr)) {
81
72
  jclass jc = (*env).FindClass("java/lang/Error");
82
73
  if (jc)
83
74
  (*env).ThrowNew(jc, "Failed to load vector index from path!");
@@ -87,7 +78,7 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1load(JNIEnv* env, jclass
87
78
 
88
79
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1view(JNIEnv* env, jclass, jlong c_ptr, jstring path) {
89
80
  char const* path_cstr = (*env).GetStringUTFChars(path, 0);
90
- if (!reinterpret_cast<index_t*>(c_ptr)->view(path_cstr)) {
81
+ if (!reinterpret_cast<index_dense_t*>(c_ptr)->view(path_cstr)) {
91
82
  jclass jc = (*env).FindClass("java/lang/Error");
92
83
  if (jc)
93
84
  (*env).ThrowNew(jc, "Failed to view vector index from path!");
@@ -96,27 +87,27 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1view(JNIEnv* env, jclass
96
87
  }
97
88
 
98
89
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1destroy(JNIEnv*, jclass, jlong c_ptr) {
99
- delete reinterpret_cast<index_t*>(c_ptr);
90
+ delete reinterpret_cast<index_dense_t*>(c_ptr);
100
91
  }
101
92
 
102
93
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1size(JNIEnv*, jclass, jlong c_ptr) {
103
- return reinterpret_cast<index_t*>(c_ptr)->size();
94
+ return reinterpret_cast<index_dense_t*>(c_ptr)->size();
104
95
  }
105
96
 
106
97
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1connectivity(JNIEnv*, jclass, jlong c_ptr) {
107
- return reinterpret_cast<index_t*>(c_ptr)->connectivity();
98
+ return reinterpret_cast<index_dense_t*>(c_ptr)->connectivity();
108
99
  }
109
100
 
110
101
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1dimensions(JNIEnv*, jclass, jlong c_ptr) {
111
- return reinterpret_cast<index_t*>(c_ptr)->dimensions();
102
+ return reinterpret_cast<index_dense_t*>(c_ptr)->dimensions();
112
103
  }
113
104
 
114
105
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1capacity(JNIEnv*, jclass, jlong c_ptr) {
115
- return reinterpret_cast<index_t*>(c_ptr)->capacity();
106
+ return reinterpret_cast<index_dense_t*>(c_ptr)->capacity();
116
107
  }
117
108
 
118
109
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1reserve(JNIEnv* env, jclass, jlong c_ptr, jlong capacity) {
119
- if (!reinterpret_cast<index_t*>(c_ptr)->reserve(static_cast<std::size_t>(capacity))) {
110
+ if (!reinterpret_cast<index_dense_t*>(c_ptr)->reserve(static_cast<std::size_t>(capacity))) {
120
111
  jclass jc = (*env).FindClass("java/lang/Error");
121
112
  if (jc)
122
113
  (*env).ThrowNew(jc, "Failed to grow vector index!");
@@ -124,13 +115,14 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1reserve(JNIEnv* env, jcl
124
115
  }
125
116
 
126
117
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1add( //
127
- JNIEnv* env, jclass, jlong c_ptr, jint label, jfloatArray vector) {
118
+ JNIEnv* env, jclass, jlong c_ptr, jint key, jfloatArray vector) {
128
119
 
129
120
  jfloat* vector_data = (*env).GetFloatArrayElements(vector, 0);
130
121
  jsize vector_dims = (*env).GetArrayLength(vector);
131
- vector_view_t vector_span = vector_view_t{vector_data, static_cast<std::size_t>(vector_dims)};
122
+ float_span_t vector_span = float_span_t{vector_data, static_cast<std::size_t>(vector_dims)};
132
123
 
133
- if (!reinterpret_cast<index_t*>(c_ptr)->add(static_cast<label_t>(label), vector_span)) {
124
+ using key_t = typename index_dense_t::key_t;
125
+ if (!reinterpret_cast<index_dense_t*>(c_ptr)->add(static_cast<key_t>(key), vector_span)) {
134
126
  jclass jc = (*env).FindClass("java/lang/Error");
135
127
  if (jc)
136
128
  (*env).ThrowNew(jc, "Failed to insert a new point in vector index!");
@@ -152,10 +144,15 @@ JNIEXPORT jintArray JNICALL Java_cloud_unum_usearch_Index_c_1search( //
152
144
 
153
145
  jfloat* vector_data = (*env).GetFloatArrayElements(vector, 0);
154
146
  jsize vector_dims = (*env).GetArrayLength(vector);
155
- vector_view_t vector_span = vector_view_t{vector_data, static_cast<std::size_t>(vector_dims)};
156
- search_result_t result = reinterpret_cast<index_t*>(c_ptr)->search(vector_span, static_cast<std::size_t>(wanted));
147
+ float_span_t vector_span = float_span_t{vector_data, static_cast<std::size_t>(vector_dims)};
148
+
149
+ using key_t = typename index_dense_t::key_t;
150
+ using search_result_t = typename index_dense_t::search_result_t;
151
+
152
+ search_result_t result =
153
+ reinterpret_cast<index_dense_t*>(c_ptr)->search(vector_span, static_cast<std::size_t>(wanted));
157
154
  if (result) {
158
- std::size_t found = result.dump_to(reinterpret_cast<label_t*>(matches_data), NULL);
155
+ std::size_t found = result.dump_to(reinterpret_cast<key_t*>(matches_data), NULL);
159
156
  (*env).SetIntArrayRegion(matches, 0, found, matches_data);
160
157
  } else {
161
158
  jclass jc = (*env).FindClass("java/lang/Error");
@@ -23,7 +23,7 @@ public class IndexTest {
23
23
  // Index index = new Index.Config().metric("cos").dimensions(2).build();
24
24
  // float vec[] = {10, 20};
25
25
  // index.add(42, vec);
26
- // int[] labels = index.search(vec, 5);
26
+ // int[] keys = index.search(vec, 5);
27
27
 
28
28
  System.out.println("Java Tests Passed!");
29
29
  }
@@ -3,7 +3,7 @@
3
3
  ## Installation
4
4
 
5
5
  USearch is available both for Node.js backend runtime and WASM frontend runtime.
6
- For first use the conventional `npm install`:
6
+ For the first option, use the conventional `npm install`:
7
7
 
8
8
  ```sh
9
9
  npm install usearch
@@ -18,12 +18,12 @@ wasmer install unum/usearch
18
18
  ## Quickstart
19
19
 
20
20
  ```js
21
- var index = new usearch.Index({ metric: 'cos', connectivity: 16, dimensions: 3 })
22
- index.add(42, new Float32Array([0.2, 0.6, 0.4]))
23
- var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10)
21
+ var index = new usearch.Index({ metric: 'cos', connectivity: 16n, dimensions: 3n })
22
+ index.add(42n, new Float32Array([0.2, 0.6, 0.4]))
23
+ var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10n)
24
24
 
25
- assert.equal(index.size(), 1)
26
- assert.deepEqual(results.labels, new BigUint64Array([42n]))
25
+ assert.equal(index.size(), 1n)
26
+ assert.deepEqual(results.keys, new BigUint64Array([42n]))
27
27
  assert.deepEqual(results.distances, new Float32Array([0]))
28
28
  ```
29
29
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file javascript.cpp
3
- * @author Ashot Vardanian
3
+ * @author Ash Vardanian
4
4
  * @brief JavaScript bindings for Unum USearch.
5
5
  * @date 2023-04-26
6
6
  *
@@ -15,19 +15,11 @@
15
15
  #include <napi.h>
16
16
  #include <node_api.h>
17
17
 
18
- #include <usearch/index_punned_dense.hpp>
18
+ #include <usearch/index_dense.hpp>
19
19
 
20
20
  using namespace unum::usearch;
21
21
  using namespace unum;
22
22
 
23
- using metric_t = index_punned_dense_metric_t;
24
- using distance_t = punned_distance_t;
25
- using index_t = punned_small_t;
26
- using add_result_t = typename index_t::add_result_t;
27
- using search_result_t = typename index_t::search_result_t;
28
- using label_t = typename index_t::label_t;
29
- using id_t = typename index_t::id_t;
30
-
31
23
  class Index : public Napi::ObjectWrap<Index> {
32
24
  public:
33
25
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
@@ -45,8 +37,10 @@ class Index : public Napi::ObjectWrap<Index> {
45
37
 
46
38
  void Add(Napi::CallbackInfo const& ctx);
47
39
  Napi::Value Search(Napi::CallbackInfo const& ctx);
40
+ Napi::Value Remove(Napi::CallbackInfo const& ctx);
41
+ Napi::Value Contains(Napi::CallbackInfo const& ctx);
48
42
 
49
- std::unique_ptr<index_t> native_;
43
+ std::unique_ptr<index_dense_t> native_;
50
44
  };
51
45
 
52
46
  Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
@@ -59,6 +53,8 @@ Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
59
53
  InstanceMethod("connectivity", &Index::GetConnectivity),
60
54
  InstanceMethod("add", &Index::Add),
61
55
  InstanceMethod("search", &Index::Search),
56
+ InstanceMethod("remove", &Index::Remove),
57
+ InstanceMethod("contains", &Index::Contains),
62
58
  InstanceMethod("save", &Index::Save),
63
59
  InstanceMethod("load", &Index::Load),
64
60
  InstanceMethod("view", &Index::View),
@@ -82,32 +78,38 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
82
78
  return;
83
79
  }
84
80
 
81
+ bool lossless = true;
85
82
  Napi::Object params = ctx[0].As<Napi::Object>();
86
- std::size_t dimensions = params.Has("dimensions") ? params.Get("dimensions").As<Napi::Number>().Uint32Value() : 0;
83
+ std::size_t dimensions =
84
+ params.Has("dimensions") ? params.Get("dimensions").As<Napi::BigInt>().Uint64Value(&lossless) : 0;
87
85
 
88
- index_config_t config;
89
86
  index_limits_t limits;
87
+ std::size_t connectivity = default_connectivity();
90
88
  std::size_t expansion_add = default_expansion_add();
91
89
  std::size_t expansion_search = default_expansion_search();
92
90
 
93
91
  if (params.Has("capacity"))
94
- limits.members = params.Get("capacity").As<Napi::Number>().Uint32Value();
92
+ limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
95
93
  if (params.Has("connectivity"))
96
- config.connectivity = params.Get("connectivity").As<Napi::Number>().Uint32Value();
94
+ connectivity = params.Get("connectivity").As<Napi::BigInt>().Uint64Value(&lossless);
97
95
  if (params.Has("expansion_add"))
98
- expansion_add = params.Get("expansion_add").As<Napi::Number>().Uint32Value();
96
+ expansion_add = params.Get("expansion_add").As<Napi::BigInt>().Uint64Value(&lossless);
99
97
  if (params.Has("expansion_search"))
100
- expansion_search = params.Get("expansion_search").As<Napi::Number>().Uint32Value();
98
+ expansion_search = params.Get("expansion_search").As<Napi::BigInt>().Uint64Value(&lossless);
99
+ if (!lossless) {
100
+ Napi::TypeError::New(env, "Arguments must be unsigned integers").ThrowAsJavaScriptException();
101
+ return;
102
+ }
101
103
 
102
- scalar_kind_t accuracy = scalar_kind_t::f32_k;
103
- if (params.Has("accuracy")) {
104
- std::string accuracy_str = params.Get("accuracy").As<Napi::String>().Utf8Value();
105
- expected_gt<scalar_kind_t> expected = scalar_kind_from_name(accuracy_str.c_str(), accuracy_str.size());
104
+ scalar_kind_t quantization = scalar_kind_t::f32_k;
105
+ if (params.Has("quantization")) {
106
+ std::string quantization_str = params.Get("quantization").As<Napi::String>().Utf8Value();
107
+ expected_gt<scalar_kind_t> expected = scalar_kind_from_name(quantization_str.c_str(), quantization_str.size());
106
108
  if (!expected) {
107
109
  Napi::TypeError::New(env, expected.error.what()).ThrowAsJavaScriptException();
108
110
  return;
109
111
  }
110
- accuracy = *expected;
112
+ quantization = *expected;
111
113
  }
112
114
 
113
115
  // By default we use the Inner Product similarity
@@ -122,20 +124,23 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
122
124
  metric_kind = *expected;
123
125
  }
124
126
 
125
- native_.reset(
126
- new index_t(index_t::make(dimensions, metric_kind, config, accuracy, expansion_add, expansion_search)));
127
+ metric_punned_t metric(dimensions, metric_kind, quantization);
128
+ index_dense_config_t config(connectivity, expansion_add, expansion_search);
129
+ native_.reset(new index_dense_t(index_dense_t::make(metric, config)));
127
130
  native_->reserve(limits);
128
131
  }
129
132
 
130
133
  Napi::Value Index::GetDimensions(Napi::CallbackInfo const& ctx) {
131
- return Napi::Number::New(ctx.Env(), native_->dimensions());
134
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
135
+ }
136
+ Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) {
137
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
132
138
  }
133
- Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) { return Napi::Number::New(ctx.Env(), native_->size()); }
134
139
  Napi::Value Index::GetConnectivity(Napi::CallbackInfo const& ctx) {
135
- return Napi::Number::New(ctx.Env(), native_->connectivity());
140
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
136
141
  }
137
142
  Napi::Value Index::GetCapacity(Napi::CallbackInfo const& ctx) {
138
- return Napi::Number::New(ctx.Env(), native_->capacity());
143
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
139
144
  }
140
145
 
141
146
  void Index::Save(Napi::CallbackInfo const& ctx) {
@@ -191,57 +196,95 @@ void Index::View(Napi::CallbackInfo const& ctx) {
191
196
 
192
197
  void Index::Add(Napi::CallbackInfo const& ctx) {
193
198
  Napi::Env env = ctx.Env();
194
- if (ctx.Length() < 2 || !ctx[0].IsNumber() || !ctx[1].IsTypedArray())
195
- return Napi::TypeError::New(env, "Expects an integral label and a float vector").ThrowAsJavaScriptException();
196
199
 
197
- Napi::Number label_js = ctx[0].As<Napi::Number>();
198
- Napi::Float32Array vector_js = ctx[1].As<Napi::Float32Array>();
200
+ if (ctx.Length() < 2)
201
+ return Napi::TypeError::New(env, "Expects at least two arguments").ThrowAsJavaScriptException();
199
202
 
200
- label_t label = label_js.Uint32Value();
201
- float const* vector = vector_js.Data();
202
- std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
203
- if (dimensions != native_->dimensions())
204
- return Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
203
+ using key_t = typename index_dense_t::key_t;
204
+ std::size_t index_dimensions = native_->dimensions();
205
205
 
206
- if (native_->size() + 1 >= native_->capacity())
207
- native_->reserve(ceil2(native_->size() + 1));
206
+ auto add = [&](Napi::BigInt key_js, Napi::Float32Array vector_js) {
207
+ bool lossless = true;
208
+ key_t key = static_cast<key_t>(key_js.Uint64Value(&lossless));
209
+ if (!lossless)
210
+ return Napi::TypeError::New(env, "Keys must be unsigned integers").ThrowAsJavaScriptException();
208
211
 
209
- try {
210
- native_->add(label, vector);
211
- } catch (std::bad_alloc const&) {
212
- Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
213
- } catch (...) {
214
- Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
215
- }
212
+ float const* vector = vector_js.Data();
213
+ std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
214
+
215
+ if (dimensions != index_dimensions)
216
+ return Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
217
+
218
+ try {
219
+ native_->add(key, vector);
220
+ } catch (std::bad_alloc const&) {
221
+ return Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
222
+ } catch (...) {
223
+ return Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
224
+ }
225
+ };
226
+
227
+ if (ctx[0].IsArray() && ctx[1].IsArray()) {
228
+ Napi::Array keys_js = ctx[0].As<Napi::Array>();
229
+ Napi::Array vectors_js = ctx[1].As<Napi::Array>();
230
+ auto length = keys_js.Length();
231
+
232
+ if (length != vectors_js.Length())
233
+ return Napi::TypeError::New(env, "The number of keys must match the number of vectors")
234
+ .ThrowAsJavaScriptException();
235
+
236
+ if (native_->size() + length >= native_->capacity())
237
+ native_->reserve(ceil2(native_->size() + length));
238
+
239
+ for (std::size_t i = 0; i < length; i++) {
240
+ Napi::Value key_js = keys_js[i];
241
+ Napi::Value vector_js = vectors_js[i];
242
+ add(key_js.As<Napi::BigInt>(), vector_js.As<Napi::Float32Array>());
243
+ }
244
+
245
+ } else if (ctx[0].IsBigInt() && ctx[1].IsTypedArray()) {
246
+ if (native_->size() + 1 >= native_->capacity())
247
+ native_->reserve(ceil2(native_->size() + 1));
248
+ add(ctx[0].As<Napi::BigInt>(), ctx[1].As<Napi::Float32Array>());
249
+ } else
250
+ return Napi::TypeError::New(env, "Invalid argument type, expects integral key(s) and float vector(s)")
251
+ .ThrowAsJavaScriptException();
216
252
  }
217
253
 
218
254
  Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
219
255
  Napi::Env env = ctx.Env();
220
- if (ctx.Length() < 2 || !ctx[0].IsTypedArray() || !ctx[1].IsNumber()) {
221
- Napi::TypeError::New(env, "Expects a float vector and the number of wanted results")
222
- .ThrowAsJavaScriptException();
256
+ if (ctx.Length() < 2 || !ctx[0].IsTypedArray() || !ctx[1].IsBigInt()) {
257
+ Napi::TypeError::New(env, "Expects a and the number of wanted results").ThrowAsJavaScriptException();
223
258
  return {};
224
259
  }
225
260
 
226
261
  Napi::Float32Array vector_js = ctx[0].As<Napi::Float32Array>();
227
- Napi::Number wanted_js = ctx[1].As<Napi::Number>();
262
+ Napi::BigInt wanted_js = ctx[1].As<Napi::BigInt>();
228
263
 
229
264
  float const* vector = vector_js.Data();
230
265
  std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
231
- std::uint32_t wanted = wanted_js.Uint32Value();
232
266
  if (dimensions != native_->dimensions()) {
233
267
  Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
234
268
  return {};
235
269
  }
236
270
 
237
- Napi::TypedArrayOf<label_t> matches_js = Napi::TypedArrayOf<label_t>::New(env, wanted);
271
+ bool lossless = true;
272
+ std::uint64_t wanted = wanted_js.Uint64Value(&lossless);
273
+ if (!lossless) {
274
+ Napi::TypeError::New(env, "Wanted number of matches must be an unsigned integer").ThrowAsJavaScriptException();
275
+ return {};
276
+ }
277
+
278
+ using key_t = typename index_dense_t::key_t;
279
+ Napi::TypedArrayOf<key_t> matches_js = Napi::TypedArrayOf<key_t>::New(env, wanted);
280
+ static_assert(std::is_same<std::uint64_t, key_t>::value, "Matches.key interface expects BigUint64Array");
238
281
  Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
239
282
  try {
240
- std::size_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());
283
+ std::uint64_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());
241
284
  Napi::Object result_js = Napi::Object::New(env);
242
- result_js.Set("labels", matches_js);
285
+ result_js.Set("keys", matches_js);
243
286
  result_js.Set("distances", distances_js);
244
- result_js.Set("count", Napi::Number::New(env, count));
287
+ result_js.Set("count", Napi::BigInt::New(env, count));
245
288
  return result_js;
246
289
  } catch (std::bad_alloc const&) {
247
290
  Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
@@ -252,6 +295,64 @@ Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
252
295
  }
253
296
  }
254
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
+
255
356
  Napi::Object InitAll(Napi::Env env, Napi::Object exports) { return Index::Init(env, exports); }
256
357
 
257
358
  NODE_API_MODULE(usearch, InitAll)
@@ -1,17 +1,32 @@
1
1
  var assert = require('assert');
2
2
  var usearch = require('bindings')('usearch');
3
3
 
4
- var index = new usearch.Index({ metric: 'l2sq', connectivity: 16, dimensions: 2 })
5
- assert.equal(index.connectivity(), 16)
6
- assert.equal(index.dimensions(), 2)
7
- assert.equal(index.size(), 0)
4
+ // Single-entry operations
8
5
 
9
- index.add(15, new Float32Array([10, 20]))
10
- index.add(16, new Float32Array([10, 25]))
11
- assert.equal(index.size(), 2)
6
+ var index = new usearch.Index({ metric: 'l2sq', connectivity: 16n, dimensions: 2n });
7
+ assert.equal(index.connectivity(), 16n, 'connectivity should be 16');
8
+ assert.equal(index.dimensions(), 2n, 'dimensions should be 2');
9
+ assert.equal(index.size(), 0n, 'initial size should be 0');
12
10
 
13
- var results = index.search(new Float32Array([13, 14]), 2)
14
- assert.deepEqual(results.labels, new BigUint64Array([15n, 16n]))
15
- assert.deepEqual(results.distances, new Float32Array([45, 130]))
11
+ index.add(15n, new Float32Array([10, 20]));
12
+ index.add(16n, new Float32Array([10, 25]));
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');
16
15
 
17
- console.log('JavaScript tests passed!')
16
+ var results = index.search(new Float32Array([13, 14]), 2n);
17
+ assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
18
+ assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
19
+
20
+ // Batch operations
21
+
22
+ var indexBatch = new usearch.Index({ metric: 'l2sq', connectivity: 16n, dimensions: 2n });
23
+ const keys = [15n, 16n];
24
+ const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
25
+ indexBatch.add(keys, vectors);
26
+ assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
27
+
28
+ results = indexBatch.search(new Float32Array([13, 14]), 2n);
29
+ assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
30
+ assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
31
+
32
+ console.log('JavaScript tests passed!');