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.
- 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 +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/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 +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 +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/python/usearch/io.py
CHANGED
|
@@ -1,11 +1,48 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import struct
|
|
3
|
+
import typing
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
6
|
|
|
5
7
|
|
|
8
|
+
def numpy_scalar_size(dtype) -> int:
|
|
9
|
+
return {
|
|
10
|
+
np.float64: 8,
|
|
11
|
+
np.int64: 8,
|
|
12
|
+
np.uint64: 8,
|
|
13
|
+
np.float32: 4,
|
|
14
|
+
np.int32: 4,
|
|
15
|
+
np.uint32: 4,
|
|
16
|
+
np.float16: 2,
|
|
17
|
+
np.int16: 2,
|
|
18
|
+
np.uint16: 2,
|
|
19
|
+
np.int8: 1,
|
|
20
|
+
np.uint8: 1,
|
|
21
|
+
}[dtype]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def guess_numpy_dtype_from_filename(filename) -> typing.Optional[type]:
|
|
25
|
+
if filename.endswith(".fbin"):
|
|
26
|
+
return np.float32
|
|
27
|
+
elif filename.endswith(".dbin"):
|
|
28
|
+
return np.float64
|
|
29
|
+
elif filename.endswith(".hbin"):
|
|
30
|
+
return np.float16
|
|
31
|
+
elif filename.endswith(".ibin"):
|
|
32
|
+
return np.int32
|
|
33
|
+
elif filename.endswith(".bbin"):
|
|
34
|
+
return np.uint8
|
|
35
|
+
else:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
6
39
|
def load_matrix(
|
|
7
|
-
filename: str,
|
|
8
|
-
|
|
40
|
+
filename: str,
|
|
41
|
+
start_row: int = 0,
|
|
42
|
+
count_rows: int = None,
|
|
43
|
+
view: bool = False,
|
|
44
|
+
dtype: typing.Optional[type] = None,
|
|
45
|
+
) -> typing.Optional[np.ndarray]:
|
|
9
46
|
"""Read *.ibin, *.bbib, *.hbin, *.fbin, *.dbin files with matrices.
|
|
10
47
|
|
|
11
48
|
:param filename: path to the matrix file
|
|
@@ -16,25 +53,14 @@ def load_matrix(
|
|
|
16
53
|
:return: parsed matrix
|
|
17
54
|
:rtype: numpy.ndarray
|
|
18
55
|
"""
|
|
19
|
-
dtype
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
elif filename.endswith(".hbin"):
|
|
28
|
-
dtype = np.float16
|
|
29
|
-
scalar_size = 2
|
|
30
|
-
elif filename.endswith(".ibin"):
|
|
31
|
-
dtype = np.int32
|
|
32
|
-
scalar_size = 4
|
|
33
|
-
elif filename.endswith(".bbin"):
|
|
34
|
-
dtype = np.uint8
|
|
35
|
-
scalar_size = 1
|
|
36
|
-
else:
|
|
37
|
-
raise Exception("Unknown file type")
|
|
56
|
+
if dtype is None:
|
|
57
|
+
dtype = guess_numpy_dtype_from_filename(filename)
|
|
58
|
+
if dtype is None:
|
|
59
|
+
raise Exception("Unknown file type")
|
|
60
|
+
scalar_size = numpy_scalar_size(dtype)
|
|
61
|
+
|
|
62
|
+
if not os.path.exists(filename):
|
|
63
|
+
return None
|
|
38
64
|
|
|
39
65
|
with open(filename, "rb") as f:
|
|
40
66
|
rows, cols = np.fromfile(f, count=2, dtype=np.int32)
|
|
@@ -43,11 +69,18 @@ def load_matrix(
|
|
|
43
69
|
|
|
44
70
|
if view:
|
|
45
71
|
return np.memmap(
|
|
46
|
-
f,
|
|
72
|
+
f,
|
|
73
|
+
dtype=dtype,
|
|
74
|
+
mode="r",
|
|
75
|
+
offset=8 + row_offset,
|
|
76
|
+
shape=(rows, cols),
|
|
47
77
|
)
|
|
48
78
|
else:
|
|
49
79
|
return np.fromfile(
|
|
50
|
-
f,
|
|
80
|
+
f,
|
|
81
|
+
count=rows * cols,
|
|
82
|
+
dtype=dtype,
|
|
83
|
+
offset=row_offset,
|
|
51
84
|
).reshape(rows, cols)
|
|
52
85
|
|
|
53
86
|
|
|
@@ -59,7 +92,6 @@ def save_matrix(vectors: np.ndarray, filename: str):
|
|
|
59
92
|
:param filename: path to the matrix file
|
|
60
93
|
:type filename: str
|
|
61
94
|
"""
|
|
62
|
-
dtype = np.float32
|
|
63
95
|
if filename.endswith(".fbin"):
|
|
64
96
|
dtype = np.float32
|
|
65
97
|
elif filename.endswith(".dbin"):
|
|
@@ -71,7 +103,7 @@ def save_matrix(vectors: np.ndarray, filename: str):
|
|
|
71
103
|
elif filename.endswith(".bbin"):
|
|
72
104
|
dtype = np.uint8
|
|
73
105
|
else:
|
|
74
|
-
|
|
106
|
+
dtype = vectors.dtype
|
|
75
107
|
|
|
76
108
|
assert len(vectors.shape) == 2, "Input array must have 2 dimensions"
|
|
77
109
|
with open(filename, "wb") as f:
|
package/python/usearch/numba.py
CHANGED
|
@@ -44,7 +44,7 @@ def jit(
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
numba_supported_types = (
|
|
47
|
-
ScalarKind.
|
|
47
|
+
ScalarKind.I8,
|
|
48
48
|
# Half-precision is still unsupported
|
|
49
49
|
# https://github.com/numba/numba/issues/4402
|
|
50
50
|
# ScalarKind.F16: np.float16,
|
|
@@ -55,7 +55,7 @@ def jit(
|
|
|
55
55
|
return metric
|
|
56
56
|
|
|
57
57
|
scalar_kind_to_accumulator_type = {
|
|
58
|
-
ScalarKind.
|
|
58
|
+
ScalarKind.I8: types.int32,
|
|
59
59
|
ScalarKind.F16: types.float16,
|
|
60
60
|
ScalarKind.F32: types.float32,
|
|
61
61
|
ScalarKind.F64: types.float64,
|
|
@@ -98,7 +98,7 @@ def jit(
|
|
|
98
98
|
return types.float32(ab_delta_sq)
|
|
99
99
|
|
|
100
100
|
scalar_kind_to_signature = {
|
|
101
|
-
ScalarKind.
|
|
101
|
+
ScalarKind.I8: signature_i8args,
|
|
102
102
|
ScalarKind.F16: signature_f16args,
|
|
103
103
|
ScalarKind.F32: signature_f32args,
|
|
104
104
|
ScalarKind.F64: signature_f64args,
|
|
@@ -110,7 +110,7 @@ def jit(
|
|
|
110
110
|
MetricKind.L2sq: numba_l2sq,
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
if dtype == ScalarKind.
|
|
113
|
+
if dtype == ScalarKind.I8 and metric == MetricKind.IP:
|
|
114
114
|
metric = MetricKind.Cos
|
|
115
115
|
|
|
116
116
|
pointer = cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
|
package/python/usearch/server.py
CHANGED
|
@@ -7,13 +7,13 @@ import numpy as np
|
|
|
7
7
|
from typing import List
|
|
8
8
|
|
|
9
9
|
from ucall.rich_posix import Server
|
|
10
|
-
from usearch.index import Index, Matches,
|
|
10
|
+
from usearch.index import Index, Matches, Key
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def _ascii_to_vector(string: str) -> np.ndarray:
|
|
14
14
|
"""
|
|
15
15
|
WARNING: A dirty performance hack!
|
|
16
|
-
Assuming the `
|
|
16
|
+
Assuming the `i8` vectors in our implementations are just integers,
|
|
17
17
|
and generally contain scalars in the [0, 100] range, we can transmit
|
|
18
18
|
them as JSON-embedded strings. The only symbols we must avoid are
|
|
19
19
|
the double-quote '"' (code 22) and backslash '\' (code 60).
|
|
@@ -59,15 +59,15 @@ def serve(
|
|
|
59
59
|
return index.connectivity()
|
|
60
60
|
|
|
61
61
|
@server
|
|
62
|
-
def add_one(
|
|
63
|
-
print("adding",
|
|
64
|
-
|
|
62
|
+
def add_one(key: int, vector: np.ndarray):
|
|
63
|
+
print("adding", key, vector)
|
|
64
|
+
keys = np.array([key], dtype=Key)
|
|
65
65
|
vectors = vector.flatten().reshape(vector.shape[0], 1)
|
|
66
|
-
index.add(
|
|
66
|
+
index.add(keys, vectors)
|
|
67
67
|
|
|
68
68
|
@server
|
|
69
|
-
def add_many(
|
|
70
|
-
index.add(
|
|
69
|
+
def add_many(keys: np.ndarray, vectors: np.ndarray):
|
|
70
|
+
index.add(keys, vectors, threads=threads)
|
|
71
71
|
|
|
72
72
|
@server
|
|
73
73
|
def search_one(vector: np.ndarray, count: int) -> List[dict]:
|
|
@@ -82,8 +82,8 @@ def serve(
|
|
|
82
82
|
return results.to_list()
|
|
83
83
|
|
|
84
84
|
@server
|
|
85
|
-
def add_ascii(
|
|
86
|
-
return add_one(
|
|
85
|
+
def add_ascii(key: int, string: str):
|
|
86
|
+
return add_one(key, _ascii_to_vector(string))
|
|
87
87
|
|
|
88
88
|
@server
|
|
89
89
|
def search_ascii(string: str, count: int):
|
package/rust/lib.cpp
CHANGED
|
@@ -7,46 +7,45 @@ using namespace unum;
|
|
|
7
7
|
using index_t = typename Index::index_t;
|
|
8
8
|
using add_result_t = typename index_t::add_result_t;
|
|
9
9
|
using search_result_t = typename index_t::search_result_t;
|
|
10
|
-
using serialization_result_t = typename index_t::serialization_result_t;
|
|
11
10
|
|
|
12
11
|
Index::Index(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
|
|
13
12
|
|
|
14
|
-
void Index::add_in_thread(
|
|
15
|
-
|
|
13
|
+
void Index::add_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const {
|
|
14
|
+
index_update_config_t config;
|
|
16
15
|
config.thread = thread;
|
|
17
|
-
index_->
|
|
16
|
+
config.expansion = index_->expansion_add();
|
|
17
|
+
index_->add(key, vector.data(), config).error.raise();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
|
|
21
21
|
Matches matches;
|
|
22
|
-
matches.
|
|
22
|
+
matches.keys.reserve(count);
|
|
23
23
|
matches.distances.reserve(count);
|
|
24
24
|
for (size_t i = 0; i != count; ++i)
|
|
25
|
-
matches.
|
|
26
|
-
|
|
25
|
+
matches.keys.push_back(0), matches.distances.push_back(0);
|
|
26
|
+
index_search_config_t config;
|
|
27
27
|
config.thread = thread;
|
|
28
|
+
config.expansion = index_->expansion_search();
|
|
28
29
|
search_result_t result = index_->search(vector.data(), count, config);
|
|
29
30
|
result.error.raise();
|
|
30
|
-
matches.count = result.dump_to(matches.
|
|
31
|
-
matches.
|
|
31
|
+
matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
|
|
32
|
+
matches.keys.truncate(matches.count);
|
|
32
33
|
matches.distances.truncate(matches.count);
|
|
33
34
|
return matches;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
void Index::add(
|
|
37
|
-
index_->add(label, vector.data()).error.raise();
|
|
38
|
-
}
|
|
37
|
+
void Index::add(key_t key, rust::Slice<float const> vector) const { index_->add(key, vector.data()).error.raise(); }
|
|
39
38
|
|
|
40
39
|
Matches Index::search(rust::Slice<float const> vector, size_t count) const {
|
|
41
40
|
Matches matches;
|
|
42
|
-
matches.
|
|
41
|
+
matches.keys.reserve(count);
|
|
43
42
|
matches.distances.reserve(count);
|
|
44
43
|
for (size_t i = 0; i != count; ++i)
|
|
45
|
-
matches.
|
|
44
|
+
matches.keys.push_back(0), matches.distances.push_back(0);
|
|
46
45
|
search_result_t result = index_->search(vector.data(), count);
|
|
47
46
|
result.error.raise();
|
|
48
|
-
matches.count = result.dump_to(matches.
|
|
49
|
-
matches.
|
|
47
|
+
matches.count = result.dump_to(matches.keys.data(), matches.distances.data());
|
|
48
|
+
matches.keys.truncate(matches.count);
|
|
50
49
|
matches.distances.truncate(matches.count);
|
|
51
50
|
return matches;
|
|
52
51
|
}
|
|
@@ -62,7 +61,7 @@ void Index::save(rust::Str path) const { index_->save(std::string(path).c_str())
|
|
|
62
61
|
void Index::load(rust::Str path) const { index_->load(std::string(path).c_str()); }
|
|
63
62
|
void Index::view(rust::Str path) const { index_->view(std::string(path).c_str()); }
|
|
64
63
|
|
|
65
|
-
scalar_kind_t
|
|
64
|
+
scalar_kind_t quantization(rust::Str quant) { return scalar_kind_from_name(quant.data(), quant.size()); }
|
|
66
65
|
|
|
67
66
|
std::unique_ptr<Index> wrap(index_t&& index) {
|
|
68
67
|
std::unique_ptr<index_t> punned_ptr;
|
|
@@ -72,12 +71,6 @@ std::unique_ptr<Index> wrap(index_t&& index) {
|
|
|
72
71
|
return result;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
index_config_t config(size_t connectivity) {
|
|
76
|
-
index_config_t result;
|
|
77
|
-
result.connectivity = connectivity ? connectivity : default_connectivity();
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
74
|
metric_kind_t rust_to_cpp_metric(MetricKind value) {
|
|
82
75
|
switch (value) {
|
|
83
76
|
case MetricKind::IP: return metric_kind_t::ip_k;
|
|
@@ -94,7 +87,7 @@ metric_kind_t rust_to_cpp_metric(MetricKind value) {
|
|
|
94
87
|
|
|
95
88
|
scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
|
|
96
89
|
switch (value) {
|
|
97
|
-
case ScalarKind::
|
|
90
|
+
case ScalarKind::I8: return scalar_kind_t::i8_k;
|
|
98
91
|
case ScalarKind::F16: return scalar_kind_t::f16_k;
|
|
99
92
|
case ScalarKind::F32: return scalar_kind_t::f32_k;
|
|
100
93
|
case ScalarKind::F64: return scalar_kind_t::f64_k;
|
|
@@ -104,7 +97,9 @@ scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
|
|
|
104
97
|
}
|
|
105
98
|
|
|
106
99
|
std::unique_ptr<Index> new_index(IndexOptions const& options) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
metric_kind_t metric_kind = rust_to_cpp_metric(options.metric);
|
|
101
|
+
scalar_kind_t scalar_kind = rust_to_cpp_scalar(options.quantization);
|
|
102
|
+
metric_punned_t metric(options.dimensions, metric_kind, scalar_kind);
|
|
103
|
+
index_dense_config_t config(options.connectivity, options.expansion_add, options.expansion_search);
|
|
104
|
+
return wrap(index_t::make(metric, config));
|
|
110
105
|
}
|
package/rust/lib.hpp
CHANGED
|
@@ -3,27 +3,26 @@
|
|
|
3
3
|
|
|
4
4
|
#include <memory> // `std::shared_ptr`
|
|
5
5
|
|
|
6
|
-
#include <usearch/
|
|
6
|
+
#include <usearch/index_dense.hpp>
|
|
7
7
|
|
|
8
8
|
struct Matches;
|
|
9
9
|
struct IndexOptions;
|
|
10
10
|
|
|
11
11
|
class Index {
|
|
12
12
|
public:
|
|
13
|
-
using metric_t = unum::usearch::
|
|
14
|
-
using distance_t = unum::usearch::
|
|
15
|
-
using
|
|
16
|
-
using add_result_t = typename
|
|
17
|
-
using search_result_t = typename
|
|
18
|
-
using
|
|
19
|
-
using id_t = typename index_t::id_t;
|
|
13
|
+
using metric_t = unum::usearch::metric_punned_t;
|
|
14
|
+
using distance_t = unum::usearch::distance_punned_t;
|
|
15
|
+
using index_dense_t = unum::usearch::index_dense_t;
|
|
16
|
+
using add_result_t = typename index_dense_t::add_result_t;
|
|
17
|
+
using search_result_t = typename index_dense_t::search_result_t;
|
|
18
|
+
using key_t = typename index_dense_t::key_t;
|
|
20
19
|
|
|
21
|
-
Index(std::unique_ptr<
|
|
20
|
+
Index(std::unique_ptr<index_dense_t> index);
|
|
22
21
|
|
|
23
22
|
void reserve(size_t) const;
|
|
24
23
|
|
|
25
|
-
void add(
|
|
26
|
-
void add_in_thread(
|
|
24
|
+
void add(key_t key, rust::Slice<float const> vector) const;
|
|
25
|
+
void add_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const;
|
|
27
26
|
|
|
28
27
|
Matches search(rust::Slice<float const> vector, size_t count) const;
|
|
29
28
|
Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
|
|
@@ -38,7 +37,7 @@ class Index {
|
|
|
38
37
|
void view(rust::Str path) const;
|
|
39
38
|
|
|
40
39
|
private:
|
|
41
|
-
std::unique_ptr<
|
|
40
|
+
std::unique_ptr<index_dense_t> index_;
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
std::unique_ptr<Index> new_index(IndexOptions const& options);
|
package/rust/lib.rs
CHANGED
|
@@ -4,7 +4,7 @@ pub mod ffi {
|
|
|
4
4
|
// Shared structs with fields visible to both languages.
|
|
5
5
|
struct Matches {
|
|
6
6
|
count: usize,
|
|
7
|
-
|
|
7
|
+
keys: Vec<u64>,
|
|
8
8
|
distances: Vec<f32>,
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ pub mod ffi {
|
|
|
23
23
|
F64,
|
|
24
24
|
F32,
|
|
25
25
|
F16,
|
|
26
|
-
|
|
26
|
+
I8,
|
|
27
27
|
B1,
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -51,7 +51,7 @@ pub mod ffi {
|
|
|
51
51
|
pub fn size(self: &Index) -> usize;
|
|
52
52
|
pub fn capacity(self: &Index) -> usize;
|
|
53
53
|
|
|
54
|
-
pub fn add(self: &Index,
|
|
54
|
+
pub fn add(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
|
|
55
55
|
pub fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
|
|
56
56
|
|
|
57
57
|
pub fn save(self: &Index, path: &str) -> Result<()>;
|
package/setup.py
CHANGED
|
@@ -14,7 +14,8 @@ macros_args = [
|
|
|
14
14
|
|
|
15
15
|
if sys.platform == "linux":
|
|
16
16
|
compile_args.append("-std=c++11")
|
|
17
|
-
compile_args.append("-O3")
|
|
17
|
+
compile_args.append("-O3") # Maximize performance
|
|
18
|
+
compile_args.append("-g") # Simplify debugging
|
|
18
19
|
compile_args.append("-Wno-unknown-pragmas")
|
|
19
20
|
|
|
20
21
|
macros_args.append(("USEARCH_USE_OPENMP", "1"))
|
|
@@ -26,7 +27,8 @@ if sys.platform == "darwin":
|
|
|
26
27
|
# https://github.com/unum-cloud/usearch/actions/runs/4975434891/jobs/8902603392
|
|
27
28
|
compile_args.append("-mmacosx-version-min=10.15")
|
|
28
29
|
compile_args.append("-std=c++11")
|
|
29
|
-
compile_args.append("-O3")
|
|
30
|
+
compile_args.append("-O3") # Maximize performance
|
|
31
|
+
compile_args.append("-g") # Simplify debugging
|
|
30
32
|
compile_args.append("-Wno-unknown-pragmas")
|
|
31
33
|
|
|
32
34
|
# Linking OpenMP requires additional preparion in CIBuildWheel
|
|
@@ -63,6 +65,7 @@ setup(
|
|
|
63
65
|
packages=["usearch"],
|
|
64
66
|
package_dir={"usearch": "python/usearch"},
|
|
65
67
|
description="Smaller & Faster Single-File Vector Search Engine from Unum",
|
|
68
|
+
author="Ash Vardanian",
|
|
66
69
|
long_description=long_description,
|
|
67
70
|
long_description_content_type="text/markdown",
|
|
68
71
|
license="Apache-2.0",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Pre-Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main-dev"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["main-dev"]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
BUILD_TYPE: Release
|
|
11
|
+
GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
|
|
12
|
+
PYTHONUTF8: 1
|
|
13
|
+
|
|
14
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
|
|
20
|
+
test_python_311:
|
|
21
|
+
name: Test Python
|
|
22
|
+
runs-on: ${{ matrix.os }}
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
os: [ubuntu-20.04, macOS-11, windows-2022]
|
|
26
|
+
python-version: ["3.11"]
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v3
|
|
30
|
+
- run: git submodule update --init --recursive
|
|
31
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
32
|
+
uses: actions/setup-python@v4
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ matrix.python-version }}
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: |
|
|
37
|
+
python -m pip install --no-cache-dir --upgrade pip
|
|
38
|
+
pip install --no-cache-dir pytest
|
|
39
|
+
- name: Build locally
|
|
40
|
+
run: python -m pip install .
|
|
41
|
+
- name: Test with PyTest
|
|
42
|
+
run: pytest python/test.py
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
test_python_37:
|
|
46
|
+
name: Test Python 3.7
|
|
47
|
+
runs-on: ${{ matrix.os }}
|
|
48
|
+
strategy:
|
|
49
|
+
matrix:
|
|
50
|
+
os: [ubuntu-20.04]
|
|
51
|
+
python-version: ["3.7"]
|
|
52
|
+
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v3
|
|
55
|
+
- run: git submodule update --init --recursive
|
|
56
|
+
|
|
57
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
58
|
+
uses: actions/setup-python@v4
|
|
59
|
+
with:
|
|
60
|
+
python-version: ${{ matrix.python-version }}
|
|
61
|
+
|
|
62
|
+
- name: Install dependencies
|
|
63
|
+
run: |
|
|
64
|
+
python -m pip install --no-cache-dir --upgrade pip
|
|
65
|
+
pip install --no-cache-dir pytest
|
|
66
|
+
|
|
67
|
+
- name: Build locally
|
|
68
|
+
run: python -m pip install .
|
|
69
|
+
|
|
70
|
+
- name: Test with PyTest
|
|
71
|
+
run: pytest python/test.py
|
|
72
|
+
|
|
73
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
BUILD_TYPE: Release
|
|
9
|
+
GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
|
|
10
|
+
PYTHONUTF8: 1
|
|
11
|
+
|
|
12
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
|
|
20
|
+
versioning:
|
|
21
|
+
name: Semantic Release
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v3
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
- uses: actions/setup-node@v3
|
|
28
|
+
- run: npm install --save-dev @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx semantic-release
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
rebase:
|
|
32
|
+
name: Rebase Development Branch
|
|
33
|
+
needs: versioning
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout the latest code
|
|
37
|
+
uses: actions/checkout@v3
|
|
38
|
+
with:
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
|
|
41
|
+
- name: Perform rebase
|
|
42
|
+
run: |
|
|
43
|
+
git checkout main-dev
|
|
44
|
+
git rebase main
|
|
45
|
+
|
|
46
|
+
- name: Push changes
|
|
47
|
+
uses: CasperWA/push-protected@v2
|
|
48
|
+
with:
|
|
49
|
+
token: ${{ secrets.SEMANTIC_REBASE_TOKEN }}
|
|
50
|
+
branch: main-dev
|
|
51
|
+
unprotect_reviews: True
|
|
52
|
+
force: True
|
|
53
|
+
|
|
54
|
+
build_wheels:
|
|
55
|
+
name: Build Wheels for ${{ matrix.os }}
|
|
56
|
+
runs-on: ${{ matrix.os }}
|
|
57
|
+
needs: versioning
|
|
58
|
+
strategy:
|
|
59
|
+
matrix:
|
|
60
|
+
os: [ubuntu-20.04, macOS-11, windows-2022]
|
|
61
|
+
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v3
|
|
64
|
+
with:
|
|
65
|
+
ref: 'main'
|
|
66
|
+
- uses: actions/setup-python@v3
|
|
67
|
+
|
|
68
|
+
- name: Setup Docker
|
|
69
|
+
if: matrix.os != 'windows-2022'
|
|
70
|
+
uses: crazy-max/ghaction-setup-docker@v1.0.0
|
|
71
|
+
with:
|
|
72
|
+
version: 23.0.1
|
|
73
|
+
|
|
74
|
+
- name: Setup QEMU
|
|
75
|
+
if: matrix.os != 'windows-2022'
|
|
76
|
+
uses: docker/setup-qemu-action@v2.1.0
|
|
77
|
+
|
|
78
|
+
- name: Install CIBuildWheel
|
|
79
|
+
run: python -m pip install --no-cache-dir cibuildwheel
|
|
80
|
+
|
|
81
|
+
- name: Build wheels
|
|
82
|
+
run: python -m cibuildwheel
|
|
83
|
+
|
|
84
|
+
- uses: actions/upload-artifact@v3
|
|
85
|
+
with:
|
|
86
|
+
path: ./wheelhouse/*.whl
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
publish_python:
|
|
90
|
+
name: Publish Python
|
|
91
|
+
needs: build_wheels
|
|
92
|
+
runs-on: ubuntu-20.04
|
|
93
|
+
environment:
|
|
94
|
+
name: pypi
|
|
95
|
+
url: https://pypi.org/p/stringzilla
|
|
96
|
+
permissions:
|
|
97
|
+
id-token: write
|
|
98
|
+
|
|
99
|
+
steps:
|
|
100
|
+
- name: Download artifacts
|
|
101
|
+
uses: actions/download-artifact@v3.0.2
|
|
102
|
+
with:
|
|
103
|
+
path: ./dist/
|
|
104
|
+
|
|
105
|
+
- name: Publish to PyPi
|
|
106
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
107
|
+
with:
|
|
108
|
+
packages-dir: ./dist/artifact
|
|
109
|
+
verbose: true
|
|
110
|
+
print-hash: true
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": [
|
|
3
|
+
"main"
|
|
4
|
+
],
|
|
5
|
+
"debug": true,
|
|
6
|
+
"ci": true,
|
|
7
|
+
"dryRun": false,
|
|
8
|
+
"plugins": [
|
|
9
|
+
[
|
|
10
|
+
"@semantic-release/commit-analyzer",
|
|
11
|
+
{
|
|
12
|
+
"preset": "eslint",
|
|
13
|
+
"releaseRules": [
|
|
14
|
+
{
|
|
15
|
+
"tag": "Add",
|
|
16
|
+
"release": "minor"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"tag": "Improve",
|
|
20
|
+
"release": "patch"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"tag": "Make",
|
|
24
|
+
"release": "patch"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"tag": "Refactor",
|
|
28
|
+
"release": false
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"@semantic-release/release-notes-generator",
|
|
35
|
+
{
|
|
36
|
+
"preset": "eslint",
|
|
37
|
+
"releaseRules": [
|
|
38
|
+
{
|
|
39
|
+
"tag": "Add",
|
|
40
|
+
"release": "minor"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"tag": "Improve",
|
|
44
|
+
"release": "patch"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"tag": "Make",
|
|
48
|
+
"release": "patch"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"tag": "Refactor",
|
|
52
|
+
"release": false
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"@semantic-release/github",
|
|
58
|
+
[
|
|
59
|
+
"@semantic-release/exec",
|
|
60
|
+
{
|
|
61
|
+
"prepareCmd": "bash .github/workflows/update_version.sh '${nextRelease.version}'"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
"@semantic-release/git",
|
|
66
|
+
{
|
|
67
|
+
"assets": [
|
|
68
|
+
"VERSION",
|
|
69
|
+
"conanfile.py",
|
|
70
|
+
"package.json",
|
|
71
|
+
"Cargo.toml",
|
|
72
|
+
],
|
|
73
|
+
"message": "Build: Released ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
]
|
|
77
|
+
}
|