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.
- package/.github/workflows/prerelease.yml +3 -3
- package/.releaserc +10 -2
- package/.vscode/launch.json +10 -10
- package/.vscode/settings.json +9 -1
- package/.vscode/tasks.json +4 -4
- package/CMakeLists.txt +6 -1
- package/Cargo.toml +2 -2
- package/README.md +35 -41
- package/build.rs +2 -2
- package/c/CMakeLists.txt +48 -0
- package/c/README.md +34 -0
- package/c/lib.cpp +83 -72
- package/c/test.c +20 -20
- package/c/usearch.h +132 -32
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +3 -1
- package/cpp/README.md +16 -16
- package/cpp/bench.cpp +50 -126
- package/cpp/test.cpp +164 -80
- package/docs/benchmarks.md +26 -5
- package/docs/c/reference.rst +3 -3
- package/docs/compilation.md +31 -49
- package/docs/cpp/index.rst +1 -1
- package/docs/cpp/reference.rst +4 -4
- package/golang/lib.go +275 -141
- package/golang/lib_test.go +105 -0
- package/include/usearch/index.hpp +1681 -1656
- package/include/usearch/index_dense.hpp +1400 -0
- package/include/usearch/index_plugins.hpp +1263 -0
- package/java/README.md +1 -1
- package/java/cloud/unum/usearch/Index.java +10 -10
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +34 -37
- package/java/test/IndexTest.java +1 -1
- package/javascript/README.md +6 -6
- package/javascript/lib.cpp +156 -55
- package/javascript/test.js +26 -11
- package/javascript/usearch.d.ts +102 -0
- package/javascript/usearch.js +2 -118
- package/objc/USearchObjective.mm +57 -31
- package/objc/include/USearchObjective.h +32 -22
- package/package.json +5 -3
- package/python/README.md +23 -14
- package/python/lib.cpp +468 -444
- package/python/scripts/bench.ipynb +3 -2
- package/python/scripts/bench.py +4 -4
- package/python/scripts/index_faiss.py +5 -5
- package/python/scripts/join.py +4 -4
- package/python/scripts/test.py +152 -61
- package/python/scripts/test_jit.py +10 -12
- package/python/scripts/test_sparse.py +32 -0
- package/python/usearch/client.py +26 -28
- package/python/usearch/eval.py +73 -23
- package/python/usearch/index.py +520 -228
- package/python/usearch/io.py +57 -25
- package/python/usearch/numba.py +4 -4
- package/python/usearch/server.py +10 -10
- package/rust/lib.cpp +34 -30
- package/rust/lib.hpp +14 -12
- package/rust/lib.rs +8 -7
- package/setup.py +5 -2
- package/simsimd/.github/workflows/prerelease.yml +73 -0
- package/simsimd/.github/workflows/release.yml +110 -0
- package/simsimd/.github/workflows/update_version.sh +3 -0
- package/simsimd/.releaserc +77 -0
- package/simsimd/.vscode/launch.json +27 -1
- package/simsimd/.vscode/settings.json +22 -1
- package/simsimd/.vscode/tasks.json +6 -0
- package/simsimd/CMakeLists.txt +3 -2
- package/simsimd/README.md +82 -46
- package/simsimd/bench.cxx +63 -24
- package/simsimd/include/simsimd/simsimd.h +88 -66
- package/simsimd/include/simsimd/simsimd_chem.h +126 -0
- package/simsimd/package.json +8 -0
- package/simsimd/pyproject.toml +42 -0
- package/simsimd/python/python.c +81 -0
- package/simsimd/python/run.sh +1 -0
- package/simsimd/python/test.c +109 -0
- package/simsimd/python/test.py +23 -0
- package/simsimd/setup.py +44 -0
- package/swift/Index+Sugar.swift +75 -23
- package/swift/README.md +26 -2
- package/swift/Test.swift +19 -7
- package/swift/USearch.swift +2 -2
- package/tsconfig.json +8 -0
- package/wasm/CMakeLists.txt +1 -0
- package/wasmer.toml +1 -1
- package/wolfram/CMakeLists.txt +1 -0
- package/wolfram/lib.cpp +11 -13
- package/golang/usearch.h +0 -9
- package/golang/usearch_par_test.go +0 -40
- package/golang/usearch_test.go +0 -82
- package/include/usearch/index_punned_dense.hpp +0 -1001
- package/include/usearch/index_punned_helpers.hpp +0 -676
package/java/README.md
CHANGED
|
@@ -16,12 +16,12 @@ public class Index {
|
|
|
16
16
|
|
|
17
17
|
private long c_ptr = 0;
|
|
18
18
|
|
|
19
|
-
public Index(String metric, String
|
|
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
|
-
|
|
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
|
|
53
|
-
c_add(c_ptr,
|
|
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
|
|
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
|
-
|
|
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
|
|
101
|
-
this.
|
|
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
|
|
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
|
|
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/
|
|
7
|
+
#include <usearch/index_dense.hpp>
|
|
8
8
|
|
|
9
9
|
using namespace unum::usearch;
|
|
10
10
|
using namespace unum;
|
|
11
11
|
|
|
12
|
-
using
|
|
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
|
|
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*
|
|
22
|
+
char const* quantization_cstr{};
|
|
32
23
|
try {
|
|
33
24
|
|
|
34
|
-
|
|
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
|
-
|
|
42
|
-
std::size_t
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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(
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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
|
|
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
|
-
|
|
122
|
+
float_span_t vector_span = float_span_t{vector_data, static_cast<std::size_t>(vector_dims)};
|
|
132
123
|
|
|
133
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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<
|
|
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");
|
package/java/test/IndexTest.java
CHANGED
|
@@ -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[]
|
|
26
|
+
// int[] keys = index.search(vec, 5);
|
|
27
27
|
|
|
28
28
|
System.out.println("Java Tests Passed!");
|
|
29
29
|
}
|
package/javascript/README.md
CHANGED
|
@@ -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:
|
|
22
|
-
index.add(
|
|
23
|
-
var results = index.search(new Float32Array([0.2, 0.6, 0.4]),
|
|
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(),
|
|
26
|
-
assert.deepEqual(results.
|
|
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
|
|
package/javascript/lib.cpp
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file javascript.cpp
|
|
3
|
-
* @author
|
|
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/
|
|
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<
|
|
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 =
|
|
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::
|
|
92
|
+
limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
|
|
95
93
|
if (params.Has("connectivity"))
|
|
96
|
-
|
|
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::
|
|
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::
|
|
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
|
|
103
|
-
if (params.Has("
|
|
104
|
-
std::string
|
|
105
|
-
expected_gt<scalar_kind_t> expected = scalar_kind_from_name(
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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::
|
|
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::
|
|
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::
|
|
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
|
-
|
|
198
|
-
|
|
200
|
+
if (ctx.Length() < 2)
|
|
201
|
+
return Napi::TypeError::New(env, "Expects at least two arguments").ThrowAsJavaScriptException();
|
|
199
202
|
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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].
|
|
221
|
-
Napi::TypeError::New(env, "Expects a
|
|
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::
|
|
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
|
-
|
|
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::
|
|
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("
|
|
285
|
+
result_js.Set("keys", matches_js);
|
|
243
286
|
result_js.Set("distances", distances_js);
|
|
244
|
-
result_js.Set("count", Napi::
|
|
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)
|
package/javascript/test.js
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
var assert = require('assert');
|
|
2
2
|
var usearch = require('bindings')('usearch');
|
|
3
3
|
|
|
4
|
-
|
|
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.
|
|
10
|
-
index.
|
|
11
|
-
assert.equal(index.
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
assert.
|
|
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
|
-
|
|
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!');
|