usearch 2.8.7 → 2.8.8
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/CITATION.cff +1 -1
- package/CMakeLists.txt +3 -3
- package/CONTRIBUTING.md +3 -3
- package/Cargo.toml +1 -1
- package/Dockerfile +16 -0
- package/README.md +41 -38
- package/VERSION +1 -0
- package/binding.gyp +57 -8
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +27 -23
- package/package.json +1 -2
- package/setup.py +76 -15
- package/wasmer.toml +1 -1
- package/. npmignore +0 -14
- package/.gitignore +0 -95
- package/.vscode/launch.json +0 -181
- package/.vscode/settings.json +0 -180
- package/.vscode/tasks.json +0 -71
- package/Package.swift +0 -41
- package/csharp/.editorconfig +0 -361
- package/csharp/Cloud.Unum.USearch.sln +0 -34
- package/csharp/DEV_SETUP.md +0 -51
- package/csharp/Directory.Build.props +0 -29
- package/csharp/README.md +0 -74
- package/csharp/build_and_test.cmd +0 -31
- package/csharp/build_and_test.sh +0 -30
- package/csharp/nuget/nuget-package.props +0 -33
- package/csharp/pack.sh +0 -56
- package/csharp/src/Cloud.Unum.USearch.Tests/Cloud.Unum.USearch.Tests.csproj +0 -39
- package/csharp/src/Cloud.Unum.USearch.Tests/USearchIndexTests.cs +0 -1013
- package/csharp/src/Cloud.Unum.USearch.Tests/Usings.cs +0 -1
- package/docs/_static/custom.css +0 -26
- package/docs/_static/custom.js +0 -7
- package/docs/benchmarks.md +0 -245
- package/docs/benchmarks.rst +0 -5
- package/docs/c/index.rst +0 -11
- package/docs/c/reference.rst +0 -7
- package/docs/conf.dox +0 -2668
- package/docs/conf.py +0 -53
- package/docs/contributing.rst +0 -5
- package/docs/cpp/index.rst +0 -11
- package/docs/cpp/reference.rst +0 -17
- package/docs/csharp/index.rst +0 -6
- package/docs/format.md +0 -86
- package/docs/format.rst +0 -5
- package/docs/golang/index.rst +0 -6
- package/docs/index.rst +0 -33
- package/docs/java/index.rst +0 -11
- package/docs/java/reference.rst +0 -7
- package/docs/javascript/index.rst +0 -11
- package/docs/javascript/reference.rst +0 -12
- package/docs/python/index.rst +0 -11
- package/docs/python/reference.rst +0 -42
- package/docs/rust/index.rst +0 -6
- package/docs/swift/index.rst +0 -6
- package/docs/wolfram/index.rst +0 -6
- package/golang/README.md +0 -95
- package/golang/go.mod +0 -3
- package/golang/lib.go +0 -379
- package/golang/lib_test.go +0 -108
- package/java/README.md +0 -22
- package/java/cloud/unum/usearch/Index.java +0 -337
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +0 -197
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.h +0 -125
- package/java/test/IndexTest.java +0 -30
- package/objc/USearchObjective.mm +0 -299
- package/objc/include/USearchObjective.h +0 -154
- package/python/README.md +0 -292
- package/python/lib.cpp +0 -1216
- package/python/scripts/bench.ipynb +0 -193
- package/python/scripts/bench.py +0 -137
- package/python/scripts/bench_exact.py +0 -44
- package/python/scripts/cluster.ipynb +0 -160
- package/python/scripts/index_faiss.py +0 -100
- package/python/scripts/join.py +0 -155
- package/python/scripts/test_index.py +0 -247
- package/python/scripts/test_jit.py +0 -231
- package/python/scripts/test_sparse.py +0 -32
- package/python/scripts/test_tooling.py +0 -133
- package/python/usearch/__init__.py +0 -0
- package/python/usearch/client.py +0 -124
- package/python/usearch/eval.py +0 -470
- package/python/usearch/index.py +0 -1401
- package/python/usearch/io.py +0 -113
- package/python/usearch/numba.py +0 -121
- package/python/usearch/server.py +0 -137
- package/rust/README.md +0 -66
- package/rust/lib.cpp +0 -136
- package/rust/lib.hpp +0 -69
- package/rust/lib.rs +0 -610
- package/swift/Index+Sugar.swift +0 -153
- package/swift/README.md +0 -48
- package/swift/Test.swift +0 -37
- package/swift/USearch.swift +0 -8
- package/wasm/CMakeLists.txt +0 -7
- package/wasm/README.md +0 -13
- package/wasm/lib.cpp +0 -5
- package/wolfram/CMakeLists.txt +0 -18
- package/wolfram/README.md +0 -13
- package/wolfram/lib.cpp +0 -160
- package/wolfram/test.wls +0 -27
package/python/usearch/io.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import struct
|
|
3
|
-
import typing
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
|
|
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
|
-
|
|
39
|
-
def load_matrix(
|
|
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]:
|
|
46
|
-
"""Read *.ibin, *.bbib, *.hbin, *.fbin, *.dbin files with matrices.
|
|
47
|
-
|
|
48
|
-
:param filename: path to the matrix file
|
|
49
|
-
:param start_row: start reading vectors from this index
|
|
50
|
-
:param count_rows: number of vectors to read. If None, read all vectors
|
|
51
|
-
:param view: set to `True` to memory-map the file instead of loading to RAM
|
|
52
|
-
|
|
53
|
-
:return: parsed matrix
|
|
54
|
-
:rtype: numpy.ndarray
|
|
55
|
-
"""
|
|
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
|
|
64
|
-
|
|
65
|
-
with open(filename, "rb") as f:
|
|
66
|
-
rows, cols = np.fromfile(f, count=2, dtype=np.int32)
|
|
67
|
-
rows = (rows - start_row) if count_rows is None else count_rows
|
|
68
|
-
row_offset = start_row * scalar_size * cols
|
|
69
|
-
|
|
70
|
-
if view:
|
|
71
|
-
return np.memmap(
|
|
72
|
-
f,
|
|
73
|
-
dtype=dtype,
|
|
74
|
-
mode="r",
|
|
75
|
-
offset=8 + row_offset,
|
|
76
|
-
shape=(rows, cols),
|
|
77
|
-
)
|
|
78
|
-
else:
|
|
79
|
-
return np.fromfile(
|
|
80
|
-
f,
|
|
81
|
-
count=rows * cols,
|
|
82
|
-
dtype=dtype,
|
|
83
|
-
offset=row_offset,
|
|
84
|
-
).reshape(rows, cols)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def save_matrix(vectors: np.ndarray, filename: str):
|
|
88
|
-
"""Write *.ibin, *.bbib, *.hbin, *.fbin, *.dbin files with matrices.
|
|
89
|
-
|
|
90
|
-
:param vectors: the matrix to serialize
|
|
91
|
-
:type vectors: numpy.ndarray
|
|
92
|
-
:param filename: path to the matrix file
|
|
93
|
-
:type filename: str
|
|
94
|
-
"""
|
|
95
|
-
if filename.endswith(".fbin"):
|
|
96
|
-
dtype = np.float32
|
|
97
|
-
elif filename.endswith(".dbin"):
|
|
98
|
-
dtype = np.float64
|
|
99
|
-
elif filename.endswith(".hbin"):
|
|
100
|
-
dtype = np.float16
|
|
101
|
-
elif filename.endswith(".ibin"):
|
|
102
|
-
dtype = np.int32
|
|
103
|
-
elif filename.endswith(".bbin"):
|
|
104
|
-
dtype = np.uint8
|
|
105
|
-
else:
|
|
106
|
-
dtype = vectors.dtype
|
|
107
|
-
|
|
108
|
-
assert len(vectors.shape) == 2, "Input array must have 2 dimensions"
|
|
109
|
-
with open(filename, "wb") as f:
|
|
110
|
-
count, dim = vectors.shape
|
|
111
|
-
f.write(struct.pack("<i", count))
|
|
112
|
-
f.write(struct.pack("<i", dim))
|
|
113
|
-
vectors.astype(dtype).flatten().tofile(f)
|
package/python/usearch/numba.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# The purpose of this file is to provide Pythonic wrapper on top
|
|
2
|
-
# the native precompiled CPython module. It improves compatibility
|
|
3
|
-
# Python tooling, linters, and static analyzers. It also embeds JIT
|
|
4
|
-
# into the primary `Index` class, connecting USearch with Numba.
|
|
5
|
-
from math import sqrt
|
|
6
|
-
|
|
7
|
-
from usearch.index import MetricKind, ScalarKind, MetricSignature, CompiledMetric
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def jit(
|
|
11
|
-
ndim: int,
|
|
12
|
-
metric: MetricKind = MetricKind.Cos,
|
|
13
|
-
dtype: ScalarKind = ScalarKind.F32,
|
|
14
|
-
) -> CompiledMetric:
|
|
15
|
-
"""JIT-compiles the metric for target hardware and number of dimensions.
|
|
16
|
-
|
|
17
|
-
This can result in up-to 3x performance difference on very large vectors
|
|
18
|
-
and very recent hardware, as the Python module is compiled with high
|
|
19
|
-
compatibility in mind and avoids very fancy assembly instructions.
|
|
20
|
-
|
|
21
|
-
Uses Numba `cfunc` functionality, annotating it with Numba `types` instead
|
|
22
|
-
of `ctypes` to support half-precision.
|
|
23
|
-
https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
|
|
24
|
-
"""
|
|
25
|
-
assert isinstance(metric, MetricKind)
|
|
26
|
-
assert isinstance(dtype, ScalarKind)
|
|
27
|
-
|
|
28
|
-
from numba import cfunc, types, carray
|
|
29
|
-
|
|
30
|
-
signature_i8args = types.float32(
|
|
31
|
-
types.CPointer(types.int8), types.CPointer(types.int8)
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
signature_f16args = types.float32(
|
|
35
|
-
types.CPointer(types.float16), types.CPointer(types.float16)
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
signature_f32args = types.float32(
|
|
39
|
-
types.CPointer(types.float32), types.CPointer(types.float32)
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
signature_f64args = types.float32(
|
|
43
|
-
types.CPointer(types.float64), types.CPointer(types.float64)
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
numba_supported_types = (
|
|
47
|
-
ScalarKind.I8,
|
|
48
|
-
# Half-precision is still unsupported
|
|
49
|
-
# https://github.com/numba/numba/issues/4402
|
|
50
|
-
# ScalarKind.F16: np.float16,
|
|
51
|
-
ScalarKind.F32,
|
|
52
|
-
ScalarKind.F64,
|
|
53
|
-
)
|
|
54
|
-
if dtype not in numba_supported_types:
|
|
55
|
-
return metric
|
|
56
|
-
|
|
57
|
-
scalar_kind_to_accumulator_type = {
|
|
58
|
-
ScalarKind.I8: types.int32,
|
|
59
|
-
ScalarKind.F16: types.float16,
|
|
60
|
-
ScalarKind.F32: types.float32,
|
|
61
|
-
ScalarKind.F64: types.float64,
|
|
62
|
-
}
|
|
63
|
-
accumulator = scalar_kind_to_accumulator_type[dtype]
|
|
64
|
-
|
|
65
|
-
def numba_ip(a, b):
|
|
66
|
-
a_array = carray(a, ndim)
|
|
67
|
-
b_array = carray(b, ndim)
|
|
68
|
-
ab = accumulator(0)
|
|
69
|
-
for i in range(ndim):
|
|
70
|
-
ab += a_array[i] * b_array[i]
|
|
71
|
-
return types.float32(1 - ab)
|
|
72
|
-
|
|
73
|
-
def numba_cos(a, b):
|
|
74
|
-
a_array = carray(a, ndim)
|
|
75
|
-
b_array = carray(b, ndim)
|
|
76
|
-
ab = accumulator(0)
|
|
77
|
-
a_sq = accumulator(0)
|
|
78
|
-
b_sq = accumulator(0)
|
|
79
|
-
for i in range(ndim):
|
|
80
|
-
ab += a_array[i] * b_array[i]
|
|
81
|
-
a_sq += a_array[i] * a_array[i]
|
|
82
|
-
b_sq += b_array[i] * b_array[i]
|
|
83
|
-
a_norm = sqrt(a_sq)
|
|
84
|
-
b_norm = sqrt(b_sq)
|
|
85
|
-
if a_norm == 0 and b_norm == 0:
|
|
86
|
-
return types.float32(0)
|
|
87
|
-
elif a_norm == 0 or b_norm == 0 or ab == 0:
|
|
88
|
-
return types.float32(1)
|
|
89
|
-
else:
|
|
90
|
-
return types.float32(1 - ab / (a_norm * b_norm))
|
|
91
|
-
|
|
92
|
-
def numba_l2sq(a, b):
|
|
93
|
-
a_array = carray(a, ndim)
|
|
94
|
-
b_array = carray(b, ndim)
|
|
95
|
-
ab_delta_sq = accumulator(0)
|
|
96
|
-
for i in range(ndim):
|
|
97
|
-
ab_delta_sq += (a_array[i] - b_array[i]) * (a_array[i] - b_array[i])
|
|
98
|
-
return types.float32(ab_delta_sq)
|
|
99
|
-
|
|
100
|
-
scalar_kind_to_signature = {
|
|
101
|
-
ScalarKind.I8: signature_i8args,
|
|
102
|
-
ScalarKind.F16: signature_f16args,
|
|
103
|
-
ScalarKind.F32: signature_f32args,
|
|
104
|
-
ScalarKind.F64: signature_f64args,
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
metric_kind_to_function = {
|
|
108
|
-
MetricKind.IP: numba_ip,
|
|
109
|
-
MetricKind.Cos: numba_cos,
|
|
110
|
-
MetricKind.L2sq: numba_l2sq,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if dtype == ScalarKind.I8 and metric == MetricKind.IP:
|
|
114
|
-
metric = MetricKind.Cos
|
|
115
|
-
|
|
116
|
-
pointer = cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
|
|
117
|
-
return CompiledMetric(
|
|
118
|
-
pointer=pointer.address,
|
|
119
|
-
kind=metric,
|
|
120
|
-
signature=MetricSignature.ArrayArray,
|
|
121
|
-
)
|
package/python/usearch/server.py
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
import os
|
|
5
|
-
import argparse
|
|
6
|
-
import numpy as np
|
|
7
|
-
from typing import List
|
|
8
|
-
|
|
9
|
-
from ucall.rich_posix import Server
|
|
10
|
-
from usearch.index import Index, Matches, Key
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _ascii_to_vector(string: str) -> np.ndarray:
|
|
14
|
-
"""
|
|
15
|
-
WARNING: A dirty performance hack!
|
|
16
|
-
Assuming the `i8` vectors in our implementations are just integers,
|
|
17
|
-
and generally contain scalars in the [0, 100] range, we can transmit
|
|
18
|
-
them as JSON-embedded strings. The only symbols we must avoid are
|
|
19
|
-
the double-quote '"' (code 22) and backslash '\' (code 60).
|
|
20
|
-
Printable ASCII characters are in [20, 126].
|
|
21
|
-
"""
|
|
22
|
-
vector = np.array(string, dtype=np.int8)
|
|
23
|
-
vector[vector == 124] = 60
|
|
24
|
-
vector -= 23
|
|
25
|
-
return vector
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def serve(
|
|
29
|
-
ndim_: int,
|
|
30
|
-
metric: str = "ip",
|
|
31
|
-
port: int = 8545,
|
|
32
|
-
threads: int = 1,
|
|
33
|
-
path: str = "index.usearch",
|
|
34
|
-
immutable: bool = False,
|
|
35
|
-
):
|
|
36
|
-
server = Server(port=port)
|
|
37
|
-
index = Index(ndim=ndim_, metric=metric)
|
|
38
|
-
|
|
39
|
-
if os.path.exists(path):
|
|
40
|
-
if immutable:
|
|
41
|
-
index.view(path)
|
|
42
|
-
else:
|
|
43
|
-
index.load(path)
|
|
44
|
-
|
|
45
|
-
@server
|
|
46
|
-
def size() -> int:
|
|
47
|
-
return len(index)
|
|
48
|
-
|
|
49
|
-
@server
|
|
50
|
-
def ndim() -> int:
|
|
51
|
-
return index.ndim
|
|
52
|
-
|
|
53
|
-
@server
|
|
54
|
-
def capacity() -> int:
|
|
55
|
-
return index.capacity()
|
|
56
|
-
|
|
57
|
-
@server
|
|
58
|
-
def connectivity() -> int:
|
|
59
|
-
return index.connectivity()
|
|
60
|
-
|
|
61
|
-
@server
|
|
62
|
-
def add_one(key: int, vector: np.ndarray):
|
|
63
|
-
print("adding", key, vector)
|
|
64
|
-
keys = np.array([key], dtype=Key)
|
|
65
|
-
vectors = vector.flatten().reshape(vector.shape[0], 1)
|
|
66
|
-
index.add(keys, vectors)
|
|
67
|
-
|
|
68
|
-
@server
|
|
69
|
-
def add_many(keys: np.ndarray, vectors: np.ndarray):
|
|
70
|
-
index.add(keys, vectors, threads=threads)
|
|
71
|
-
|
|
72
|
-
@server
|
|
73
|
-
def search_one(vector: np.ndarray, count: int) -> List[dict]:
|
|
74
|
-
print("search", vector, count)
|
|
75
|
-
vectors = vector.reshape(vector.shape[0], 1)
|
|
76
|
-
results: Matches = index.search(vectors, count)
|
|
77
|
-
return results.to_list()
|
|
78
|
-
|
|
79
|
-
@server
|
|
80
|
-
def search_many(vectors: np.ndarray, count: int) -> List[List[dict]]:
|
|
81
|
-
results: Matches = index.search(vectors, count)
|
|
82
|
-
return results.to_list()
|
|
83
|
-
|
|
84
|
-
@server
|
|
85
|
-
def add_ascii(key: int, string: str):
|
|
86
|
-
return add_one(key, _ascii_to_vector(string))
|
|
87
|
-
|
|
88
|
-
@server
|
|
89
|
-
def search_ascii(string: str, count: int):
|
|
90
|
-
return search_one(_ascii_to_vector(string), count)
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
server.run()
|
|
94
|
-
except KeyboardInterrupt:
|
|
95
|
-
if not immutable:
|
|
96
|
-
index.save(path)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if __name__ == "__main__":
|
|
100
|
-
parser = argparse.ArgumentParser()
|
|
101
|
-
parser.add_argument("-v", "--verbose", help="log server activity")
|
|
102
|
-
parser.add_argument("--ndim", type=int, help="dimensionality of the vectors")
|
|
103
|
-
parser.add_argument(
|
|
104
|
-
"--immutable", type=bool, default=False, help="the index can not be updated"
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
parser.add_argument(
|
|
108
|
-
"--metric",
|
|
109
|
-
type=str,
|
|
110
|
-
default="ip",
|
|
111
|
-
choices=["ip", "cos", "l2sq", "haversine"],
|
|
112
|
-
help="distance function to compare vectors",
|
|
113
|
-
)
|
|
114
|
-
parser.add_argument(
|
|
115
|
-
"-p",
|
|
116
|
-
"--port",
|
|
117
|
-
type=int,
|
|
118
|
-
default=8545,
|
|
119
|
-
help="port to open for client connections",
|
|
120
|
-
)
|
|
121
|
-
parser.add_argument(
|
|
122
|
-
"-j", "--threads", type=int, default=1, help="number of CPU threads to use"
|
|
123
|
-
)
|
|
124
|
-
parser.add_argument(
|
|
125
|
-
"--path", type=str, default="index.usearch", help="where to store the index"
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
args = parser.parse_args()
|
|
129
|
-
assert args.ndim is not None, "Define the number of dimensions!"
|
|
130
|
-
serve(
|
|
131
|
-
ndim_=args.ndim,
|
|
132
|
-
metric=args.metric,
|
|
133
|
-
threads=args.threads,
|
|
134
|
-
port=args.port,
|
|
135
|
-
path=args.path,
|
|
136
|
-
immutable=args.immutable,
|
|
137
|
-
)
|
package/rust/README.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# USearch for Rust
|
|
2
|
-
|
|
3
|
-
## Installation
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
cargo add usearch
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
You can find [interface documentation on docs.rs](https://docs.rs/usearch/2.3.0/usearch/ffi/struct.Index.html).
|
|
10
|
-
|
|
11
|
-
## Quickstart
|
|
12
|
-
|
|
13
|
-
```rust
|
|
14
|
-
|
|
15
|
-
let options = IndexOptions {
|
|
16
|
-
dimensions: 3,
|
|
17
|
-
metric: MetricKind::IP,
|
|
18
|
-
quantization: ScalarKind::F16,
|
|
19
|
-
connectivity: 0,
|
|
20
|
-
expansion_add: 0,
|
|
21
|
-
expansion_search: 0
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
let index = new_index(&options).unwrap();
|
|
25
|
-
|
|
26
|
-
assert!(index.reserve(10).is_ok());
|
|
27
|
-
assert!(index.capacity() >= 10);
|
|
28
|
-
assert!(index.connectivity() != 0);
|
|
29
|
-
assert_eq!(index.dimensions(), 3);
|
|
30
|
-
assert_eq!(index.size(), 0);
|
|
31
|
-
|
|
32
|
-
let first: [f32; 3] = [0.2, 0.1, 0.2];
|
|
33
|
-
let second: [f32; 3] = [0.2, 0.1, 0.2];
|
|
34
|
-
|
|
35
|
-
assert!(index.add(42, &first).is_ok());
|
|
36
|
-
assert!(index.add(43, &second).is_ok());
|
|
37
|
-
assert_eq!(index.size(), 2);
|
|
38
|
-
|
|
39
|
-
// Read back the tags
|
|
40
|
-
let results = index.search(&first, 10).unwrap();
|
|
41
|
-
assert_eq!(results.keys.len(), 2);
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Serialization
|
|
45
|
-
|
|
46
|
-
```rust
|
|
47
|
-
assert!(index.save("index.usearch").is_ok());
|
|
48
|
-
assert!(index.load("index.usearch").is_ok());
|
|
49
|
-
assert!(index.view("index.usearch").is_ok());
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Similarly, serializing to/from buffers is supported.
|
|
53
|
-
|
|
54
|
-
```rust
|
|
55
|
-
assert!(index.save_to_buffer(&mut serialization_buffer).is_ok());
|
|
56
|
-
assert!(index.load_from_buffer(&serialization_buffer).is_ok());
|
|
57
|
-
assert!(index.view_from_buffer(&serialization_buffer).is_ok());
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Metrics
|
|
61
|
-
|
|
62
|
-
```rust
|
|
63
|
-
assert!(new_l2sq(3, &quant, 0, 0, 0).is_ok());
|
|
64
|
-
assert!(new_cos(3, &quant, 0, 0, 0).is_ok());
|
|
65
|
-
assert!(new_haversine(&quant, 0, 0, 0).is_ok());
|
|
66
|
-
```
|
package/rust/lib.cpp
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
#include "lib.hpp"
|
|
2
|
-
#include "usearch/rust/lib.rs.h"
|
|
3
|
-
|
|
4
|
-
using namespace unum::usearch;
|
|
5
|
-
using namespace unum;
|
|
6
|
-
|
|
7
|
-
using index_t = index_dense_t;
|
|
8
|
-
using add_result_t = typename index_t::add_result_t;
|
|
9
|
-
using search_result_t = typename index_t::search_result_t;
|
|
10
|
-
using labeling_result_t = typename index_t::labeling_result_t;
|
|
11
|
-
using vector_key_t = typename index_dense_t::vector_key_t;
|
|
12
|
-
|
|
13
|
-
template <typename scalar_at> Matches search_(index_dense_t& index, scalar_at const* vec, size_t count) {
|
|
14
|
-
Matches matches;
|
|
15
|
-
matches.keys.reserve(count);
|
|
16
|
-
matches.distances.reserve(count);
|
|
17
|
-
for (size_t i = 0; i != count; ++i)
|
|
18
|
-
matches.keys.push_back(0), matches.distances.push_back(0);
|
|
19
|
-
search_result_t result = index.search(vec, count);
|
|
20
|
-
result.error.raise();
|
|
21
|
-
count = result.dump_to(matches.keys.data(), matches.distances.data());
|
|
22
|
-
matches.keys.truncate(count);
|
|
23
|
-
matches.distances.truncate(count);
|
|
24
|
-
return matches;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
NativeIndex::NativeIndex(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
|
|
28
|
-
|
|
29
|
-
// clang-format off
|
|
30
|
-
void NativeIndex::add_i8(vector_key_t key, rust::Slice<int8_t const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
31
|
-
void NativeIndex::add_f16(vector_key_t key, rust::Slice<uint16_t const> vec) const { index_->add(key, (f16_t const*)vec.data()).error.raise(); }
|
|
32
|
-
void NativeIndex::add_f32(vector_key_t key, rust::Slice<float const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
33
|
-
void NativeIndex::add_f64(vector_key_t key, rust::Slice<double const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
34
|
-
|
|
35
|
-
Matches NativeIndex::search_i8(rust::Slice<int8_t const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
36
|
-
Matches NativeIndex::search_f16(rust::Slice<uint16_t const> vec, size_t count) const { return search_(*index_, (f16_t const*)vec.data(), count); }
|
|
37
|
-
Matches NativeIndex::search_f32(rust::Slice<float const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
38
|
-
Matches NativeIndex::search_f64(rust::Slice<double const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
39
|
-
|
|
40
|
-
size_t NativeIndex::get_i8(vector_key_t key, rust::Slice<int8_t> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
41
|
-
size_t NativeIndex::get_f16(vector_key_t key, rust::Slice<uint16_t> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, (f16_t*)vec.data(), vec.size() / dimensions()); }
|
|
42
|
-
size_t NativeIndex::get_f32(vector_key_t key, rust::Slice<float> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
43
|
-
size_t NativeIndex::get_f64(vector_key_t key, rust::Slice<double> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
44
|
-
// clang-format on
|
|
45
|
-
|
|
46
|
-
size_t NativeIndex::expansion_add() const { return index_->expansion_add(); }
|
|
47
|
-
size_t NativeIndex::expansion_search() const { return index_->expansion_search(); }
|
|
48
|
-
void NativeIndex::change_expansion_add(size_t n) const { index_->change_expansion_add(n); }
|
|
49
|
-
void NativeIndex::change_expansion_search(size_t n) const { index_->change_expansion_search(n); }
|
|
50
|
-
|
|
51
|
-
size_t NativeIndex::remove(vector_key_t key) const {
|
|
52
|
-
labeling_result_t result = index_->remove(key);
|
|
53
|
-
result.error.raise();
|
|
54
|
-
return result.completed;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
size_t NativeIndex::rename(vector_key_t from, vector_key_t to) const {
|
|
58
|
-
labeling_result_t result = index_->rename(from, to);
|
|
59
|
-
result.error.raise();
|
|
60
|
-
return result.completed;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
size_t NativeIndex::count(vector_key_t key) const { return index_->count(key); }
|
|
64
|
-
bool NativeIndex::contains(vector_key_t key) const { return index_->contains(key); }
|
|
65
|
-
|
|
66
|
-
void NativeIndex::reserve(size_t capacity) const { index_->reserve(capacity); }
|
|
67
|
-
|
|
68
|
-
size_t NativeIndex::dimensions() const { return index_->dimensions(); }
|
|
69
|
-
size_t NativeIndex::connectivity() const { return index_->connectivity(); }
|
|
70
|
-
size_t NativeIndex::size() const { return index_->size(); }
|
|
71
|
-
size_t NativeIndex::capacity() const { return index_->capacity(); }
|
|
72
|
-
size_t NativeIndex::serialized_length() const { return index_->serialized_length(); }
|
|
73
|
-
|
|
74
|
-
void NativeIndex::save(rust::Str path) const { index_->save(output_file_t(std::string(path).c_str())).error.raise(); }
|
|
75
|
-
void NativeIndex::load(rust::Str path) const { index_->load(input_file_t(std::string(path).c_str())).error.raise(); }
|
|
76
|
-
void NativeIndex::view(rust::Str path) const {
|
|
77
|
-
index_->view(memory_mapped_file_t(std::string(path).c_str())).error.raise();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
void NativeIndex::reset() const { index_->reset(); }
|
|
81
|
-
size_t NativeIndex::memory_usage() const { return index_->memory_usage(); }
|
|
82
|
-
|
|
83
|
-
void NativeIndex::save_to_buffer(rust::Slice<uint8_t> buffer) const {
|
|
84
|
-
index_->save(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
void NativeIndex::load_from_buffer(rust::Slice<uint8_t const> buffer) const {
|
|
88
|
-
index_->load(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
void NativeIndex::view_from_buffer(rust::Slice<uint8_t const> buffer) const {
|
|
92
|
-
index_->view(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
std::unique_ptr<NativeIndex> wrap(index_t&& index) {
|
|
96
|
-
std::unique_ptr<index_t> punned_ptr;
|
|
97
|
-
punned_ptr.reset(new index_t(std::move(index)));
|
|
98
|
-
std::unique_ptr<NativeIndex> result;
|
|
99
|
-
result.reset(new NativeIndex(std::move(punned_ptr)));
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
metric_kind_t rust_to_cpp_metric(MetricKind value) {
|
|
104
|
-
switch (value) {
|
|
105
|
-
case MetricKind::IP: return metric_kind_t::ip_k;
|
|
106
|
-
case MetricKind::L2sq: return metric_kind_t::l2sq_k;
|
|
107
|
-
case MetricKind::Cos: return metric_kind_t::cos_k;
|
|
108
|
-
case MetricKind::Pearson: return metric_kind_t::pearson_k;
|
|
109
|
-
case MetricKind::Haversine: return metric_kind_t::haversine_k;
|
|
110
|
-
case MetricKind::Divergence: return metric_kind_t::divergence_k;
|
|
111
|
-
case MetricKind::Hamming: return metric_kind_t::hamming_k;
|
|
112
|
-
case MetricKind::Tanimoto: return metric_kind_t::tanimoto_k;
|
|
113
|
-
case MetricKind::Sorensen: return metric_kind_t::sorensen_k;
|
|
114
|
-
default: return metric_kind_t::unknown_k;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
scalar_kind_t rust_to_cpp_scalar(ScalarKind value) {
|
|
119
|
-
switch (value) {
|
|
120
|
-
case ScalarKind::I8: return scalar_kind_t::i8_k;
|
|
121
|
-
case ScalarKind::F16: return scalar_kind_t::f16_k;
|
|
122
|
-
case ScalarKind::F32: return scalar_kind_t::f32_k;
|
|
123
|
-
case ScalarKind::F64: return scalar_kind_t::f64_k;
|
|
124
|
-
case ScalarKind::B1: return scalar_kind_t::b1x8_k;
|
|
125
|
-
default: return scalar_kind_t::unknown_k;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
std::unique_ptr<NativeIndex> new_native_index(IndexOptions const& options) {
|
|
130
|
-
metric_kind_t metric_kind = rust_to_cpp_metric(options.metric);
|
|
131
|
-
scalar_kind_t scalar_kind = rust_to_cpp_scalar(options.quantization);
|
|
132
|
-
metric_punned_t metric(options.dimensions, metric_kind, scalar_kind);
|
|
133
|
-
index_dense_config_t config(options.connectivity, options.expansion_add, options.expansion_search);
|
|
134
|
-
config.multi = options.multi;
|
|
135
|
-
return wrap(index_t::make(metric, config));
|
|
136
|
-
}
|
package/rust/lib.hpp
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
#include "rust/cxx.h"
|
|
3
|
-
|
|
4
|
-
#include <memory> // `std::shared_ptr`
|
|
5
|
-
|
|
6
|
-
#include <usearch/index_dense.hpp>
|
|
7
|
-
|
|
8
|
-
struct Matches;
|
|
9
|
-
struct IndexOptions;
|
|
10
|
-
|
|
11
|
-
class NativeIndex {
|
|
12
|
-
public:
|
|
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 vector_key_t = typename index_dense_t::vector_key_t;
|
|
19
|
-
|
|
20
|
-
NativeIndex(std::unique_ptr<index_dense_t> index);
|
|
21
|
-
|
|
22
|
-
void reserve(size_t) const;
|
|
23
|
-
|
|
24
|
-
void add_i8(vector_key_t key, rust::Slice<int8_t const> vector) const;
|
|
25
|
-
void add_f16(vector_key_t key, rust::Slice<uint16_t const> vector) const;
|
|
26
|
-
void add_f32(vector_key_t key, rust::Slice<float const> vector) const;
|
|
27
|
-
void add_f64(vector_key_t key, rust::Slice<double const> vector) const;
|
|
28
|
-
|
|
29
|
-
Matches search_i8(rust::Slice<int8_t const> vector, size_t count) const;
|
|
30
|
-
Matches search_f16(rust::Slice<uint16_t const> vector, size_t count) const;
|
|
31
|
-
Matches search_f32(rust::Slice<float const> vector, size_t count) const;
|
|
32
|
-
Matches search_f64(rust::Slice<double const> vector, size_t count) const;
|
|
33
|
-
|
|
34
|
-
size_t get_i8(vector_key_t key, rust::Slice<int8_t> vector) const;
|
|
35
|
-
size_t get_f16(vector_key_t key, rust::Slice<uint16_t> vector) const;
|
|
36
|
-
size_t get_f32(vector_key_t key, rust::Slice<float> vector) const;
|
|
37
|
-
size_t get_f64(vector_key_t key, rust::Slice<double> vector) const;
|
|
38
|
-
|
|
39
|
-
size_t expansion_add() const;
|
|
40
|
-
size_t expansion_search() const;
|
|
41
|
-
void change_expansion_add(size_t n) const;
|
|
42
|
-
void change_expansion_search(size_t n) const;
|
|
43
|
-
|
|
44
|
-
size_t count(vector_key_t key) const;
|
|
45
|
-
size_t remove(vector_key_t key) const;
|
|
46
|
-
size_t rename(vector_key_t from, vector_key_t to) const;
|
|
47
|
-
bool contains(vector_key_t key) const;
|
|
48
|
-
|
|
49
|
-
size_t dimensions() const;
|
|
50
|
-
size_t connectivity() const;
|
|
51
|
-
size_t size() const;
|
|
52
|
-
size_t capacity() const;
|
|
53
|
-
size_t serialized_length() const;
|
|
54
|
-
|
|
55
|
-
void save(rust::Str path) const;
|
|
56
|
-
void load(rust::Str path) const;
|
|
57
|
-
void view(rust::Str path) const;
|
|
58
|
-
void reset() const;
|
|
59
|
-
size_t memory_usage() const;
|
|
60
|
-
|
|
61
|
-
void save_to_buffer(rust::Slice<uint8_t> buffer) const;
|
|
62
|
-
void load_from_buffer(rust::Slice<uint8_t const> buffer) const;
|
|
63
|
-
void view_from_buffer(rust::Slice<uint8_t const> buffer) const;
|
|
64
|
-
|
|
65
|
-
private:
|
|
66
|
-
std::unique_ptr<index_dense_t> index_;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
std::unique_ptr<NativeIndex> new_native_index(IndexOptions const& options);
|