usearch 0.22.3 → 1.0.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 +94 -55
  36. package/javascript/test.js +25 -11
  37. package/javascript/usearch.d.ts +87 -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 +22 -27
  58. package/rust/lib.hpp +11 -12
  59. package/rust/lib.rs +3 -3
  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);
@@ -46,7 +38,7 @@ class Index : public Napi::ObjectWrap<Index> {
46
38
  void Add(Napi::CallbackInfo const& ctx);
47
39
  Napi::Value Search(Napi::CallbackInfo const& ctx);
48
40
 
49
- std::unique_ptr<index_t> native_;
41
+ std::unique_ptr<index_dense_t> native_;
50
42
  };
51
43
 
52
44
  Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
@@ -82,32 +74,38 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
82
74
  return;
83
75
  }
84
76
 
77
+ bool lossless = true;
85
78
  Napi::Object params = ctx[0].As<Napi::Object>();
86
- std::size_t dimensions = params.Has("dimensions") ? params.Get("dimensions").As<Napi::Number>().Uint32Value() : 0;
79
+ std::size_t dimensions =
80
+ params.Has("dimensions") ? params.Get("dimensions").As<Napi::BigInt>().Uint64Value(&lossless) : 0;
87
81
 
88
- index_config_t config;
89
82
  index_limits_t limits;
83
+ std::size_t connectivity = default_connectivity();
90
84
  std::size_t expansion_add = default_expansion_add();
91
85
  std::size_t expansion_search = default_expansion_search();
92
86
 
93
87
  if (params.Has("capacity"))
94
- limits.members = params.Get("capacity").As<Napi::Number>().Uint32Value();
88
+ limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
95
89
  if (params.Has("connectivity"))
96
- config.connectivity = params.Get("connectivity").As<Napi::Number>().Uint32Value();
90
+ connectivity = params.Get("connectivity").As<Napi::BigInt>().Uint64Value(&lossless);
97
91
  if (params.Has("expansion_add"))
98
- expansion_add = params.Get("expansion_add").As<Napi::Number>().Uint32Value();
92
+ expansion_add = params.Get("expansion_add").As<Napi::BigInt>().Uint64Value(&lossless);
99
93
  if (params.Has("expansion_search"))
100
- expansion_search = params.Get("expansion_search").As<Napi::Number>().Uint32Value();
94
+ expansion_search = params.Get("expansion_search").As<Napi::BigInt>().Uint64Value(&lossless);
95
+ if (!lossless) {
96
+ Napi::TypeError::New(env, "Arguments must be unsigned integers").ThrowAsJavaScriptException();
97
+ return;
98
+ }
101
99
 
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());
100
+ scalar_kind_t quantization = scalar_kind_t::f32_k;
101
+ if (params.Has("quantization")) {
102
+ std::string quantization_str = params.Get("quantization").As<Napi::String>().Utf8Value();
103
+ expected_gt<scalar_kind_t> expected = scalar_kind_from_name(quantization_str.c_str(), quantization_str.size());
106
104
  if (!expected) {
107
105
  Napi::TypeError::New(env, expected.error.what()).ThrowAsJavaScriptException();
108
106
  return;
109
107
  }
110
- accuracy = *expected;
108
+ quantization = *expected;
111
109
  }
112
110
 
113
111
  // By default we use the Inner Product similarity
@@ -122,20 +120,23 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
122
120
  metric_kind = *expected;
123
121
  }
124
122
 
125
- native_.reset(
126
- new index_t(index_t::make(dimensions, metric_kind, config, accuracy, expansion_add, expansion_search)));
123
+ metric_punned_t metric(dimensions, metric_kind, quantization);
124
+ index_dense_config_t config(connectivity, expansion_add, expansion_search);
125
+ native_.reset(new index_dense_t(index_dense_t::make(metric, config)));
127
126
  native_->reserve(limits);
128
127
  }
129
128
 
130
129
  Napi::Value Index::GetDimensions(Napi::CallbackInfo const& ctx) {
131
- return Napi::Number::New(ctx.Env(), native_->dimensions());
130
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->dimensions()));
131
+ }
132
+ Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) {
133
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->size()));
132
134
  }
133
- Napi::Value Index::GetSize(Napi::CallbackInfo const& ctx) { return Napi::Number::New(ctx.Env(), native_->size()); }
134
135
  Napi::Value Index::GetConnectivity(Napi::CallbackInfo const& ctx) {
135
- return Napi::Number::New(ctx.Env(), native_->connectivity());
136
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->connectivity()));
136
137
  }
137
138
  Napi::Value Index::GetCapacity(Napi::CallbackInfo const& ctx) {
138
- return Napi::Number::New(ctx.Env(), native_->capacity());
139
+ return Napi::BigInt::New(ctx.Env(), static_cast<std::uint64_t>(native_->capacity()));
139
140
  }
140
141
 
141
142
  void Index::Save(Napi::CallbackInfo const& ctx) {
@@ -191,57 +192,95 @@ void Index::View(Napi::CallbackInfo const& ctx) {
191
192
 
192
193
  void Index::Add(Napi::CallbackInfo const& ctx) {
193
194
  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
195
 
197
- Napi::Number label_js = ctx[0].As<Napi::Number>();
198
- Napi::Float32Array vector_js = ctx[1].As<Napi::Float32Array>();
196
+ if (ctx.Length() < 2)
197
+ return Napi::TypeError::New(env, "Expects at least two arguments").ThrowAsJavaScriptException();
199
198
 
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();
199
+ using key_t = typename index_dense_t::key_t;
200
+ std::size_t index_dimensions = native_->dimensions();
205
201
 
206
- if (native_->size() + 1 >= native_->capacity())
207
- native_->reserve(ceil2(native_->size() + 1));
202
+ auto add = [&](Napi::BigInt key_js, Napi::Float32Array vector_js) {
203
+ bool lossless = true;
204
+ key_t key = static_cast<key_t>(key_js.Uint64Value(&lossless));
205
+ if (!lossless)
206
+ return Napi::TypeError::New(env, "Keys must be unsigned integers").ThrowAsJavaScriptException();
208
207
 
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
- }
208
+ float const* vector = vector_js.Data();
209
+ std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
210
+
211
+ if (dimensions != index_dimensions)
212
+ return Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
213
+
214
+ try {
215
+ native_->add(key, vector);
216
+ } catch (std::bad_alloc const&) {
217
+ return Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
218
+ } catch (...) {
219
+ return Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
220
+ }
221
+ };
222
+
223
+ if (ctx[0].IsArray() && ctx[1].IsArray()) {
224
+ Napi::Array keys_js = ctx[0].As<Napi::Array>();
225
+ Napi::Array vectors_js = ctx[1].As<Napi::Array>();
226
+ auto length = keys_js.Length();
227
+
228
+ if (length != vectors_js.Length())
229
+ return Napi::TypeError::New(env, "The number of keys must match the number of vectors")
230
+ .ThrowAsJavaScriptException();
231
+
232
+ if (native_->size() + length >= native_->capacity())
233
+ native_->reserve(ceil2(native_->size() + length));
234
+
235
+ for (std::size_t i = 0; i < length; i++) {
236
+ Napi::Value key_js = keys_js[i];
237
+ Napi::Value vector_js = vectors_js[i];
238
+ add(key_js.As<Napi::BigInt>(), vector_js.As<Napi::Float32Array>());
239
+ }
240
+
241
+ } else if (ctx[0].IsBigInt() && ctx[1].IsTypedArray()) {
242
+ if (native_->size() + 1 >= native_->capacity())
243
+ native_->reserve(ceil2(native_->size() + 1));
244
+ add(ctx[0].As<Napi::BigInt>(), ctx[1].As<Napi::Float32Array>());
245
+ } else
246
+ return Napi::TypeError::New(env, "Invalid argument type, expects integral key(s) and float vector(s)")
247
+ .ThrowAsJavaScriptException();
216
248
  }
217
249
 
218
250
  Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
219
251
  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();
252
+ if (ctx.Length() < 2 || !ctx[0].IsTypedArray() || !ctx[1].IsBigInt()) {
253
+ Napi::TypeError::New(env, "Expects a and the number of wanted results").ThrowAsJavaScriptException();
223
254
  return {};
224
255
  }
225
256
 
226
257
  Napi::Float32Array vector_js = ctx[0].As<Napi::Float32Array>();
227
- Napi::Number wanted_js = ctx[1].As<Napi::Number>();
258
+ Napi::BigInt wanted_js = ctx[1].As<Napi::BigInt>();
228
259
 
229
260
  float const* vector = vector_js.Data();
230
261
  std::size_t dimensions = static_cast<std::size_t>(vector_js.ElementLength());
231
- std::uint32_t wanted = wanted_js.Uint32Value();
232
262
  if (dimensions != native_->dimensions()) {
233
263
  Napi::TypeError::New(env, "Wrong number of dimensions").ThrowAsJavaScriptException();
234
264
  return {};
235
265
  }
236
266
 
237
- Napi::TypedArrayOf<label_t> matches_js = Napi::TypedArrayOf<label_t>::New(env, wanted);
267
+ bool lossless = true;
268
+ std::uint64_t wanted = wanted_js.Uint64Value(&lossless);
269
+ if (!lossless) {
270
+ Napi::TypeError::New(env, "Wanted number of matches must be an unsigned integer").ThrowAsJavaScriptException();
271
+ return {};
272
+ }
273
+
274
+ using key_t = typename index_dense_t::key_t;
275
+ Napi::TypedArrayOf<key_t> matches_js = Napi::TypedArrayOf<key_t>::New(env, wanted);
276
+ static_assert(std::is_same<std::uint64_t, key_t>::value, "Matches.key interface expects BigUint64Array");
238
277
  Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
239
278
  try {
240
- std::size_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());
279
+ std::uint64_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());
241
280
  Napi::Object result_js = Napi::Object::New(env);
242
- result_js.Set("labels", matches_js);
281
+ result_js.Set("keys", matches_js);
243
282
  result_js.Set("distances", distances_js);
244
- result_js.Set("count", Napi::Number::New(env, count));
283
+ result_js.Set("count", Napi::BigInt::New(env, count));
245
284
  return result_js;
246
285
  } catch (std::bad_alloc const&) {
247
286
  Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
@@ -1,17 +1,31 @@
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');
16
14
 
17
- console.log('JavaScript tests passed!')
15
+ var results = index.search(new Float32Array([13, 14]), 2n);
16
+ assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
17
+ assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
18
+
19
+ // Batch operations
20
+
21
+ var indexBatch = new usearch.Index({ metric: 'l2sq', connectivity: 16n, dimensions: 2n });
22
+ const keys = [15n, 16n];
23
+ const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
24
+ indexBatch.add(keys, vectors);
25
+ assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
26
+
27
+ results = indexBatch.search(new Float32Array([13, 14]), 2n);
28
+ assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
29
+ assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
30
+
31
+ console.log('JavaScript tests passed!');
@@ -0,0 +1,87 @@
1
+
2
+ /** Search result object. */
3
+ export interface Matches {
4
+ /** The keys of the nearest neighbors found, size n*k. */
5
+ keys: BigUint64Array,
6
+ /** The distances of the nearest neighbors found, size n*k. */
7
+ distances: Float32Array,
8
+ /** The distances of the nearest neighbors found, size n*k. */
9
+ count: bigint
10
+ }
11
+
12
+ /** K-Approximate Nearest Neighbors search index. */
13
+ export class Index {
14
+
15
+ /**
16
+ * Constructs a new index.
17
+ *
18
+ * @param {bigint} dimensions
19
+ * @param {string} metric
20
+ * @param {string} quantization
21
+ * @param {bigint} capacity
22
+ * @param {bigint} connectivity
23
+ * @param {bigint} expansion_add
24
+ * @param {bigint} expansion_search
25
+ */
26
+ constructor(...args);
27
+
28
+ /**
29
+ * Returns the dimensionality of vectors.
30
+ * @return {bigint} The dimensionality of vectors.
31
+ */
32
+ dimensions(): bigint;
33
+
34
+ /**
35
+ * Returns the bigint of vectors currently indexed.
36
+ * @return {bigint} The bigint of vectors currently indexed.
37
+ */
38
+ size(): bigint;
39
+
40
+ /**
41
+ * Returns index capacity.
42
+ * @return {bigints} The capacity of index.
43
+ */
44
+ capacity(): bigint;
45
+
46
+ /**
47
+ * Returns connectivity.
48
+ * @return {bigint} The connectivity of index.
49
+ */
50
+ connectivity(): bigint;
51
+
52
+ /**
53
+ * Write index to a file.
54
+ * @param {string} path File path to write.
55
+ */
56
+ save(path: string): void;
57
+
58
+ /**
59
+ * Load index from a file.
60
+ * @param {string} path File path to read.
61
+ */
62
+ load(path: string): void;
63
+
64
+ /**
65
+ * View index from a file, without loading into RAM.
66
+ * @param {string} path File path to read.
67
+ */
68
+ load(path: string): void;
69
+
70
+ /**
71
+ * Add n vectors of dimension d to the index.
72
+ *
73
+ * @param {bigint | bigint[]} keys Input identifiers for every vector.
74
+ * @param {Float32Array | Float32Array[]} mat Input matrix, matrix of size n * d.
75
+ */
76
+ add(keys: bigint | bigint[], mat: Float32Array | Float32Array[]): void;
77
+
78
+ /**
79
+ * Query n vectors of dimension d to the index. Return at most k vectors for each.
80
+ * If there are not enough results for a query, the result array is padded with -1s.
81
+ *
82
+ * @param {Float32Array} mat Input vectors to search, matrix of size n * d.
83
+ * @param {bigint} k The bigint of nearest neighbors to search for.
84
+ * @return {Matches} Output of the search result.
85
+ */
86
+ search(mat: Float32Array, k: bigint): Matches;
87
+ }