usearch 0.22.2 → 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.
- package/.github/workflows/prerelease.yml +3 -3
- package/.releaserc +9 -0
- package/.vscode/launch.json +15 -11
- package/.vscode/settings.json +12 -2
- package/.vscode/tasks.json +4 -4
- package/CMakeLists.txt +6 -1
- package/Cargo.toml +2 -2
- package/README.md +39 -46
- 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 -77
- package/docs/benchmarks.md +26 -5
- package/docs/c/reference.rst +3 -3
- package/docs/compilation.md +49 -45
- package/docs/cpp/index.rst +2 -2
- package/docs/cpp/reference.rst +13 -3
- package/docs/index.rst +8 -8
- package/docs/java/reference.rst +1 -1
- package/golang/lib.go +275 -141
- package/golang/lib_test.go +105 -0
- package/include/usearch/index.hpp +1733 -1659
- 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 +7 -7
- package/javascript/lib.cpp +94 -55
- package/javascript/test.js +25 -11
- package/javascript/usearch.d.ts +87 -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/pyproject.toml +3 -3
- package/python/README.md +23 -14
- package/python/lib.cpp +472 -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 +22 -27
- package/rust/lib.hpp +11 -12
- package/rust/lib.rs +3 -3
- 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 +5 -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 -871
- package/include/usearch/index_punned_helpers.hpp +0 -566
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
|
|
@@ -12,18 +12,18 @@ npm install usearch
|
|
|
12
12
|
For latter:
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
|
|
15
|
+
wasmer install unum/usearch
|
|
16
16
|
```
|
|
17
17
|
|
|
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);
|
|
@@ -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<
|
|
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 =
|
|
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::
|
|
88
|
+
limits.members = params.Get("capacity").As<Napi::BigInt>().Uint64Value(&lossless);
|
|
95
89
|
if (params.Has("connectivity"))
|
|
96
|
-
|
|
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::
|
|
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::
|
|
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
|
|
103
|
-
if (params.Has("
|
|
104
|
-
std::string
|
|
105
|
-
expected_gt<scalar_kind_t> expected = scalar_kind_from_name(
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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::
|
|
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::
|
|
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::
|
|
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
|
-
|
|
198
|
-
|
|
196
|
+
if (ctx.Length() < 2)
|
|
197
|
+
return Napi::TypeError::New(env, "Expects at least two arguments").ThrowAsJavaScriptException();
|
|
199
198
|
|
|
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();
|
|
199
|
+
using key_t = typename index_dense_t::key_t;
|
|
200
|
+
std::size_t index_dimensions = native_->dimensions();
|
|
205
201
|
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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].
|
|
221
|
-
Napi::TypeError::New(env, "Expects a
|
|
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::
|
|
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
|
-
|
|
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::
|
|
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("
|
|
281
|
+
result_js.Set("keys", matches_js);
|
|
243
282
|
result_js.Set("distances", distances_js);
|
|
244
|
-
result_js.Set("count", Napi::
|
|
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();
|
package/javascript/test.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
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');
|
|
16
14
|
|
|
17
|
-
|
|
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
|
+
}
|