usearch 1.2.2 → 2.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 +71 -0
- package/.github/workflows/release.yml +77 -2
- package/.github/workflows/update_version.sh +1 -0
- package/.gitignore +11 -1
- package/.vscode/settings.json +5 -1
- package/CMakeLists.txt +1 -0
- package/Cargo.toml +1 -1
- package/README.md +31 -18
- package/c/lib.cpp +38 -4
- package/c/test.c +117 -114
- package/c/usearch.h +28 -1
- package/conanfile.py +1 -1
- package/cpp/test.cpp +2 -2
- package/csharp/.editorconfig +361 -0
- package/csharp/Cloud.Unum.USearch.sln +34 -0
- package/csharp/DEV_SETUP.md +51 -0
- package/csharp/Directory.Build.props +29 -0
- package/csharp/README.md +77 -0
- package/csharp/build_and_test.cmd +31 -0
- package/csharp/build_and_test.sh +30 -0
- package/csharp/eng/build_lib.sh +65 -0
- package/csharp/eng/clean.sh +12 -0
- package/csharp/nuget/NUGET.md +11 -0
- package/csharp/nuget/nuget-package.props +33 -0
- package/csharp/pack.sh +56 -0
- package/csharp/src/Cloud.Unum.USearch.Tests/Cloud.Unum.USearch.Tests.csproj +39 -0
- package/csharp/src/Cloud.Unum.USearch.Tests/InitTests.cs +306 -0
- package/csharp/src/Cloud.Unum.USearch.Tests/Usings.cs +1 -0
- package/docs/benchmarks.md +6 -6
- package/docs/compilation.md +1 -1
- package/include/usearch/index.hpp +172 -52
- package/include/usearch/index_dense.hpp +201 -64
- package/include/usearch/index_plugins.hpp +90 -69
- package/package.json +2 -2
- package/python/lib.cpp +99 -37
- package/python/scripts/test_index.py +38 -10
- package/python/usearch/index.py +87 -37
- package/rust/README.md +2 -2
- package/rust/lib.cpp +42 -19
- package/rust/lib.hpp +14 -0
- package/rust/lib.rs +85 -43
- package/wasmer.toml +1 -1
package/python/usearch/index.py
CHANGED
|
@@ -21,14 +21,21 @@ from typing import (
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
from tqdm import tqdm
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
from usearch.compiled import
|
|
26
|
-
|
|
24
|
+
# Precompiled symbols that won't be exposed directly:
|
|
25
|
+
from usearch.compiled import (
|
|
26
|
+
Index as _CompiledIndex,
|
|
27
|
+
Indexes as _CompiledIndexes,
|
|
28
|
+
IndexStats as _CompiledIndexStats,
|
|
29
|
+
index_dense_metadata_from_path as _index_dense_metadata_from_path,
|
|
30
|
+
index_dense_metadata_from_buffer as _index_dense_metadata_from_buffer,
|
|
31
|
+
exact_search as _exact_search,
|
|
32
|
+
)
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
from usearch.compiled import exact_search as _exact_search
|
|
30
|
-
from usearch.compiled import MetricKind, ScalarKind, MetricSignature
|
|
34
|
+
# Precompiled symbols that will be exposed
|
|
31
35
|
from usearch.compiled import (
|
|
36
|
+
MetricKind,
|
|
37
|
+
ScalarKind,
|
|
38
|
+
MetricSignature,
|
|
32
39
|
DEFAULT_CONNECTIVITY,
|
|
33
40
|
DEFAULT_EXPANSION_ADD,
|
|
34
41
|
DEFAULT_EXPANSION_SEARCH,
|
|
@@ -60,6 +67,10 @@ DTypeLike = Union[str, ScalarKind]
|
|
|
60
67
|
|
|
61
68
|
MetricLike = Union[str, MetricKind, CompiledMetric]
|
|
62
69
|
|
|
70
|
+
NoneType = type(None)
|
|
71
|
+
|
|
72
|
+
PathOrBuffer = Union[str, os.PathLike, bytes]
|
|
73
|
+
|
|
63
74
|
|
|
64
75
|
def _normalize_dtype(dtype, metric: MetricKind = MetricKind.Cos) -> ScalarKind:
|
|
65
76
|
if dtype is None or dtype == "":
|
|
@@ -559,29 +570,38 @@ class Index:
|
|
|
559
570
|
self._compiled.load(path)
|
|
560
571
|
|
|
561
572
|
@staticmethod
|
|
562
|
-
def metadata(
|
|
563
|
-
path = os.fspath(path)
|
|
564
|
-
if not os.path.exists(path):
|
|
565
|
-
return None
|
|
573
|
+
def metadata(path_or_buffer: PathOrBuffer) -> Optional[dict]:
|
|
566
574
|
try:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
575
|
+
if isinstance(path_or_buffer, bytearray):
|
|
576
|
+
path_or_buffer = bytes(path_or_buffer)
|
|
577
|
+
if isinstance(path_or_buffer, bytes):
|
|
578
|
+
return _index_dense_metadata_from_buffer(path_or_buffer)
|
|
579
|
+
else:
|
|
580
|
+
path_or_buffer = os.fspath(path_or_buffer)
|
|
581
|
+
if not os.path.exists(path_or_buffer):
|
|
582
|
+
return None
|
|
583
|
+
return _index_dense_metadata_from_path(path_or_buffer)
|
|
584
|
+
except Exception as e:
|
|
585
|
+
raise e
|
|
570
586
|
|
|
571
587
|
@staticmethod
|
|
572
|
-
def restore(
|
|
573
|
-
|
|
574
|
-
meta = Index.metadata(path)
|
|
588
|
+
def restore(path_or_buffer: PathOrBuffer, view: bool = False) -> Optional[Index]:
|
|
589
|
+
meta = Index.metadata(path_or_buffer)
|
|
575
590
|
if not meta:
|
|
576
591
|
return None
|
|
577
|
-
|
|
592
|
+
|
|
593
|
+
index = Index(
|
|
578
594
|
ndim=meta["dimensions"],
|
|
579
595
|
dtype=meta["kind_scalar"],
|
|
580
596
|
metric=meta["kind_metric"],
|
|
581
|
-
path=path,
|
|
582
|
-
view=view,
|
|
583
597
|
)
|
|
584
598
|
|
|
599
|
+
if view:
|
|
600
|
+
index.view(path_or_buffer)
|
|
601
|
+
else:
|
|
602
|
+
index.load(path_or_buffer)
|
|
603
|
+
return index
|
|
604
|
+
|
|
585
605
|
def __len__(self) -> int:
|
|
586
606
|
return self._compiled.__len__()
|
|
587
607
|
|
|
@@ -837,6 +857,10 @@ class Index:
|
|
|
837
857
|
def ndim(self) -> int:
|
|
838
858
|
return self._compiled.ndim
|
|
839
859
|
|
|
860
|
+
@property
|
|
861
|
+
def serialized_length(self) -> int:
|
|
862
|
+
return self._compiled.serialized_length
|
|
863
|
+
|
|
840
864
|
@property
|
|
841
865
|
def metric(self) -> Union[MetricKind, CompiledMetric]:
|
|
842
866
|
return self._metric_jit if self._metric_jit else self._metric_kind
|
|
@@ -895,23 +919,34 @@ class Index:
|
|
|
895
919
|
def expansion_search(self, v: int):
|
|
896
920
|
self._compiled.expansion_search = v
|
|
897
921
|
|
|
898
|
-
def save(self,
|
|
899
|
-
|
|
900
|
-
if
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
922
|
+
def save(self, path_or_buffer: Union[str, os.PathLike, NoneType] = None) -> Optional[bytes]:
|
|
923
|
+
path_or_buffer = path_or_buffer if path_or_buffer else self.path
|
|
924
|
+
if path_or_buffer is None:
|
|
925
|
+
return self._compiled.save_index_to_buffer()
|
|
926
|
+
else:
|
|
927
|
+
self._compiled.save_index_to_path(os.fspath(path_or_buffer))
|
|
928
|
+
|
|
929
|
+
def load(self, path_or_buffer: Union[str, os.PathLike, bytes, NoneType] = None):
|
|
930
|
+
path_or_buffer = path_or_buffer if path_or_buffer else self.path
|
|
931
|
+
if path_or_buffer is None:
|
|
932
|
+
raise Exception("Define the source")
|
|
933
|
+
if isinstance(path_or_buffer, bytearray):
|
|
934
|
+
path_or_buffer = bytes(path_or_buffer)
|
|
935
|
+
if isinstance(path_or_buffer, bytes):
|
|
936
|
+
self._compiled.load_index_from_buffer(path_or_buffer)
|
|
937
|
+
else:
|
|
938
|
+
self._compiled.load_index_from_path(os.fspath(path_or_buffer))
|
|
939
|
+
|
|
940
|
+
def view(self, path_or_buffer: Union[str, os.PathLike, bytes, bytearray, NoneType] = None):
|
|
941
|
+
path_or_buffer = path_or_buffer if path_or_buffer else self.path
|
|
942
|
+
if path_or_buffer is None:
|
|
943
|
+
raise Exception("Define the source")
|
|
944
|
+
if isinstance(path_or_buffer, bytearray):
|
|
945
|
+
path_or_buffer = bytes(path_or_buffer)
|
|
946
|
+
if isinstance(path_or_buffer, bytes):
|
|
947
|
+
self._compiled.view_index_from_buffer(path_or_buffer)
|
|
948
|
+
else:
|
|
949
|
+
self._compiled.view_index_from_path(os.fspath(path_or_buffer))
|
|
915
950
|
|
|
916
951
|
def clear(self):
|
|
917
952
|
"""Erases all the vectors from the index, preserving the space for future insertions."""
|
|
@@ -1049,12 +1084,27 @@ class Index:
|
|
|
1049
1084
|
return self._compiled.max_level + 1
|
|
1050
1085
|
|
|
1051
1086
|
@property
|
|
1052
|
-
def
|
|
1087
|
+
def stats(self) -> _CompiledIndexStats:
|
|
1053
1088
|
"""Get the accumulated statistics for the entire multi-level graph.
|
|
1054
1089
|
|
|
1055
1090
|
:return: Statistics for the entire multi-level graph.
|
|
1056
1091
|
:rtype: _CompiledIndexStats
|
|
1057
1092
|
|
|
1093
|
+
Statistics:
|
|
1094
|
+
- ``nodes`` (int): The number of nodes in that level.
|
|
1095
|
+
- ``edges`` (int): The number of edges in that level.
|
|
1096
|
+
- ``max_edges`` (int): The maximum possible number of edges in that level.
|
|
1097
|
+
- ``allocated_bytes`` (int): The amount of allocated memory for that level.
|
|
1098
|
+
"""
|
|
1099
|
+
return self._compiled.stats
|
|
1100
|
+
|
|
1101
|
+
@property
|
|
1102
|
+
def levels_stats(self) -> List[_CompiledIndexStats]:
|
|
1103
|
+
"""Get the accumulated statistics for every level graph.
|
|
1104
|
+
|
|
1105
|
+
:return: Statistics for every level graph.
|
|
1106
|
+
:rtype: List[_CompiledIndexStats]
|
|
1107
|
+
|
|
1058
1108
|
Statistics:
|
|
1059
1109
|
- ``nodes`` (int): The number of nodes in that level.
|
|
1060
1110
|
- ``edges`` (int): The number of edges in that level.
|
package/rust/README.md
CHANGED
|
@@ -11,7 +11,7 @@ cargo add usearch
|
|
|
11
11
|
```rust
|
|
12
12
|
|
|
13
13
|
let options = IndexOptions {
|
|
14
|
-
dimensions:
|
|
14
|
+
dimensions: 3,
|
|
15
15
|
metric: MetricKind::IP,
|
|
16
16
|
quantization: ScalarKind::F16,
|
|
17
17
|
connectivity: 0,
|
|
@@ -36,7 +36,7 @@ assert_eq!(index.size(), 2);
|
|
|
36
36
|
|
|
37
37
|
// Read back the tags
|
|
38
38
|
let results = index.search(&first, 10).unwrap();
|
|
39
|
-
assert_eq!(results.
|
|
39
|
+
assert_eq!(results.keys.len(), 2);
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Multi-Threading
|
package/rust/lib.cpp
CHANGED
|
@@ -9,9 +9,35 @@ using add_result_t = typename index_t::add_result_t;
|
|
|
9
9
|
using search_result_t = typename index_t::search_result_t;
|
|
10
10
|
using labeling_result_t = typename index_t::labeling_result_t;
|
|
11
11
|
|
|
12
|
+
template <typename scalar_at> Matches search_(index_dense_t& index, scalar_at const* vec, size_t count) {
|
|
13
|
+
Matches matches;
|
|
14
|
+
matches.keys.reserve(count);
|
|
15
|
+
matches.distances.reserve(count);
|
|
16
|
+
for (size_t i = 0; i != count; ++i)
|
|
17
|
+
matches.keys.push_back(0), matches.distances.push_back(0);
|
|
18
|
+
search_result_t result = index.search(vec, count);
|
|
19
|
+
result.error.raise();
|
|
20
|
+
count = result.dump_to(matches.keys.data(), matches.distances.data());
|
|
21
|
+
matches.keys.truncate(count);
|
|
22
|
+
matches.distances.truncate(count);
|
|
23
|
+
return matches;
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
Index::Index(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
// clang-format off
|
|
29
|
+
void Index::add(key_t key, rust::Slice<float const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
30
|
+
void Index::add_i8(key_t key, rust::Slice<int8_t const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
31
|
+
void Index::add_f16(key_t key, rust::Slice<uint16_t const> vec) const { index_->add(key, (f16_t const*)vec.data()).error.raise(); }
|
|
32
|
+
void Index::add_f32(key_t key, rust::Slice<float const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
33
|
+
void Index::add_f64(key_t key, rust::Slice<double const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
34
|
+
|
|
35
|
+
Matches Index::search(rust::Slice<float const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
36
|
+
Matches Index::search_i8(rust::Slice<int8_t const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
37
|
+
Matches Index::search_f16(rust::Slice<uint16_t const> vec, size_t count) const { return search_(*index_, (f16_t const*)vec.data(), count); }
|
|
38
|
+
Matches Index::search_f32(rust::Slice<float const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
39
|
+
Matches Index::search_f64(rust::Slice<double const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
40
|
+
// clang-format on
|
|
15
41
|
|
|
16
42
|
bool Index::remove(key_t key) const {
|
|
17
43
|
labeling_result_t result = index_->remove(key);
|
|
@@ -27,32 +53,29 @@ bool Index::rename(key_t from, key_t to) const {
|
|
|
27
53
|
|
|
28
54
|
bool Index::contains(key_t key) const { return index_->contains(key); }
|
|
29
55
|
|
|
30
|
-
Matches Index::search(rust::Slice<float const> vector, size_t count) const {
|
|
31
|
-
Matches matches;
|
|
32
|
-
matches.keys.reserve(count);
|
|
33
|
-
matches.distances.reserve(count);
|
|
34
|
-
for (size_t i = 0; i != count; ++i)
|
|
35
|
-
matches.keys.push_back(0), matches.distances.push_back(0);
|
|
36
|
-
search_result_t result = index_->search(vector.data(), count);
|
|
37
|
-
result.error.raise();
|
|
38
|
-
count = result.dump_to(matches.keys.data(), matches.distances.data());
|
|
39
|
-
matches.keys.truncate(count);
|
|
40
|
-
matches.distances.truncate(count);
|
|
41
|
-
return matches;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
56
|
void Index::reserve(size_t capacity) const { index_->reserve(capacity); }
|
|
45
57
|
|
|
46
58
|
size_t Index::dimensions() const { return index_->dimensions(); }
|
|
47
59
|
size_t Index::connectivity() const { return index_->connectivity(); }
|
|
48
60
|
size_t Index::size() const { return index_->size(); }
|
|
49
61
|
size_t Index::capacity() const { return index_->capacity(); }
|
|
62
|
+
size_t Index::serialized_length() const { return index_->serialized_length(); }
|
|
50
63
|
|
|
51
|
-
void Index::save(rust::Str path) const { index_->save(std::string(path).c_str()); }
|
|
52
|
-
void Index::load(rust::Str path) const { index_->load(std::string(path).c_str()); }
|
|
53
|
-
void Index::view(rust::Str path) const { index_->view(std::string(path).c_str()); }
|
|
64
|
+
void Index::save(rust::Str path) const { index_->save(output_file_t(std::string(path).c_str())).error.raise(); }
|
|
65
|
+
void Index::load(rust::Str path) const { index_->load(input_file_t(std::string(path).c_str())).error.raise(); }
|
|
66
|
+
void Index::view(rust::Str path) const { index_->view(memory_mapped_file_t(std::string(path).c_str())).error.raise(); }
|
|
67
|
+
|
|
68
|
+
void Index::save_to_buffer(rust::Slice<uint8_t> buffer) const {
|
|
69
|
+
index_->save(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
70
|
+
}
|
|
54
71
|
|
|
55
|
-
|
|
72
|
+
void Index::load_from_buffer(rust::Slice<uint8_t const> buffer) const {
|
|
73
|
+
index_->load(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void Index::view_from_buffer(rust::Slice<uint8_t const> buffer) const {
|
|
77
|
+
index_->view(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
78
|
+
}
|
|
56
79
|
|
|
57
80
|
std::unique_ptr<Index> wrap(index_t&& index) {
|
|
58
81
|
std::unique_ptr<index_t> punned_ptr;
|
package/rust/lib.hpp
CHANGED
|
@@ -22,7 +22,16 @@ class Index {
|
|
|
22
22
|
void reserve(size_t) const;
|
|
23
23
|
|
|
24
24
|
void add(key_t key, rust::Slice<float const> vector) const;
|
|
25
|
+
void add_i8(key_t key, rust::Slice<int8_t const> vector) const;
|
|
26
|
+
void add_f16(key_t key, rust::Slice<uint16_t const> vector) const;
|
|
27
|
+
void add_f32(key_t key, rust::Slice<float const> vector) const;
|
|
28
|
+
void add_f64(key_t key, rust::Slice<double const> vector) const;
|
|
29
|
+
|
|
25
30
|
Matches search(rust::Slice<float const> vector, size_t count) const;
|
|
31
|
+
Matches search_i8(rust::Slice<int8_t const> vector, size_t count) const;
|
|
32
|
+
Matches search_f16(rust::Slice<uint16_t const> vector, size_t count) const;
|
|
33
|
+
Matches search_f32(rust::Slice<float const> vector, size_t count) const;
|
|
34
|
+
Matches search_f64(rust::Slice<double const> vector, size_t count) const;
|
|
26
35
|
|
|
27
36
|
bool remove(key_t key) const;
|
|
28
37
|
bool contains(key_t key) const;
|
|
@@ -32,11 +41,16 @@ class Index {
|
|
|
32
41
|
size_t connectivity() const;
|
|
33
42
|
size_t size() const;
|
|
34
43
|
size_t capacity() const;
|
|
44
|
+
size_t serialized_length() const;
|
|
35
45
|
|
|
36
46
|
void save(rust::Str path) const;
|
|
37
47
|
void load(rust::Str path) const;
|
|
38
48
|
void view(rust::Str path) const;
|
|
39
49
|
|
|
50
|
+
void save_to_buffer(rust::Slice<uint8_t> buffer) const;
|
|
51
|
+
void load_from_buffer(rust::Slice<uint8_t const> buffer) const;
|
|
52
|
+
void view_from_buffer(rust::Slice<uint8_t const> buffer) const;
|
|
53
|
+
|
|
40
54
|
private:
|
|
41
55
|
std::unique_ptr<index_dense_t> index_;
|
|
42
56
|
};
|
package/rust/lib.rs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#[cxx::bridge]
|
|
2
2
|
pub mod ffi {
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
// Shared structs with fields visible to both languages.
|
|
5
5
|
struct Matches {
|
|
6
6
|
keys: Vec<u64>,
|
|
@@ -43,20 +43,20 @@ pub mod ffi {
|
|
|
43
43
|
type Index;
|
|
44
44
|
|
|
45
45
|
/// Initializes a new instance of the index with the provided options.
|
|
46
|
-
///
|
|
46
|
+
///
|
|
47
47
|
/// # Arguments
|
|
48
|
-
///
|
|
48
|
+
///
|
|
49
49
|
/// * `options` - A reference to the `IndexOptions` structure containing initialization options.
|
|
50
|
-
///
|
|
50
|
+
///
|
|
51
51
|
/// # Returns
|
|
52
|
-
///
|
|
52
|
+
///
|
|
53
53
|
/// A `Result` which is `Ok` if the index is successfully initialized, or `Err` if an error occurs.
|
|
54
54
|
pub fn new_index(options: &IndexOptions) -> Result<UniquePtr<Index>>;
|
|
55
55
|
|
|
56
56
|
/// Reserves memory for a specified number of incoming vectors.
|
|
57
|
-
///
|
|
57
|
+
///
|
|
58
58
|
/// # Arguments
|
|
59
|
-
///
|
|
59
|
+
///
|
|
60
60
|
/// * `capacity` - The desired total capacity including the current size.
|
|
61
61
|
pub fn reserve(self: &Index, capacity: usize) -> Result<()>;
|
|
62
62
|
|
|
@@ -72,128 +72,160 @@ pub mod ffi {
|
|
|
72
72
|
/// Retrieves the total capacity of the index, including reserved space.
|
|
73
73
|
pub fn capacity(self: &Index) -> usize;
|
|
74
74
|
|
|
75
|
+
/// Reports expected file size after serialization.
|
|
76
|
+
pub fn serialized_length(self: &Index) -> usize;
|
|
77
|
+
|
|
75
78
|
/// Adds a vector with a specified key to the index.
|
|
76
|
-
///
|
|
79
|
+
///
|
|
77
80
|
/// # Arguments
|
|
78
|
-
///
|
|
81
|
+
///
|
|
79
82
|
/// * `key` - The key associated with the vector.
|
|
80
83
|
/// * `vector` - A slice containing the vector data.
|
|
81
84
|
pub fn add(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
|
|
82
85
|
|
|
86
|
+
pub fn add_i8(self: &Index, key: u64, vector: &[i8]) -> Result<()>;
|
|
87
|
+
pub fn add_f16(self: &Index, key: u64, vector: &[u16]) -> Result<()>;
|
|
88
|
+
pub fn add_f32(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
|
|
89
|
+
pub fn add_f64(self: &Index, key: u64, vector: &[f64]) -> Result<()>;
|
|
90
|
+
|
|
83
91
|
/// Performs k-Approximate Nearest Neighbors (kANN) Search for closest vectors to the provided query.
|
|
84
|
-
///
|
|
92
|
+
///
|
|
85
93
|
/// # Arguments
|
|
86
|
-
///
|
|
94
|
+
///
|
|
87
95
|
/// * `query` - A slice containing the query vector data.
|
|
88
96
|
/// * `count` - The maximum number of neighbors to search for.
|
|
89
|
-
///
|
|
97
|
+
///
|
|
90
98
|
/// # Returns
|
|
91
|
-
///
|
|
99
|
+
///
|
|
92
100
|
/// A `Result` containing the matches found.
|
|
93
101
|
pub fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
|
|
94
102
|
|
|
103
|
+
pub fn search_i8(self: &Index, query: &[i8], count: usize) -> Result<Matches>;
|
|
104
|
+
pub fn search_f16(self: &Index, query: &[u16], count: usize) -> Result<Matches>;
|
|
105
|
+
pub fn search_f32(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
|
|
106
|
+
pub fn search_f64(self: &Index, query: &[f64], count: usize) -> Result<Matches>;
|
|
107
|
+
|
|
95
108
|
/// Removes the vector associated with the given key from the index.
|
|
96
|
-
///
|
|
109
|
+
///
|
|
97
110
|
/// # Arguments
|
|
98
|
-
///
|
|
111
|
+
///
|
|
99
112
|
/// * `key` - The key of the vector to be removed.
|
|
100
|
-
///
|
|
113
|
+
///
|
|
101
114
|
/// # Returns
|
|
102
|
-
///
|
|
115
|
+
///
|
|
103
116
|
/// `true` if the vector is successfully removed, `false` otherwise.
|
|
104
117
|
pub fn remove(self: &Index, key: u64) -> Result<bool>;
|
|
105
118
|
|
|
106
119
|
/// Renames the vector under a certain key.
|
|
107
|
-
///
|
|
120
|
+
///
|
|
108
121
|
/// # Arguments
|
|
109
|
-
///
|
|
122
|
+
///
|
|
110
123
|
/// * `from` - The key of the vector to be renamed.
|
|
111
124
|
/// * `to` - The new name.
|
|
112
|
-
///
|
|
125
|
+
///
|
|
113
126
|
/// # Returns
|
|
114
|
-
///
|
|
127
|
+
///
|
|
115
128
|
/// `true` if the vector is successfully renamed, `false` otherwise.
|
|
116
129
|
pub fn rename(self: &Index, from: u64, to: u64) -> Result<bool>;
|
|
117
130
|
|
|
118
131
|
/// Checks if the index contains a vector with a specified key.
|
|
119
|
-
///
|
|
132
|
+
///
|
|
120
133
|
/// # Arguments
|
|
121
|
-
///
|
|
134
|
+
///
|
|
122
135
|
/// * `key` - The key to be checked.
|
|
123
|
-
///
|
|
136
|
+
///
|
|
124
137
|
/// # Returns
|
|
125
|
-
///
|
|
138
|
+
///
|
|
126
139
|
/// `true` if the index contains the vector with the given key, `false` otherwise.
|
|
127
140
|
pub fn contains(self: &Index, key: u64) -> bool;
|
|
128
141
|
|
|
129
142
|
/// Saves the index to a specified file.
|
|
130
|
-
///
|
|
143
|
+
///
|
|
131
144
|
/// # Arguments
|
|
132
|
-
///
|
|
145
|
+
///
|
|
133
146
|
/// * `path` - The file path where the index will be saved.
|
|
134
147
|
pub fn save(self: &Index, path: &str) -> Result<()>;
|
|
135
148
|
|
|
136
149
|
/// Loads the index from a specified file.
|
|
137
|
-
///
|
|
150
|
+
///
|
|
138
151
|
/// # Arguments
|
|
139
|
-
///
|
|
152
|
+
///
|
|
140
153
|
/// * `path` - The file path from where the index will be loaded.
|
|
141
154
|
pub fn load(self: &Index, path: &str) -> Result<()>;
|
|
142
155
|
|
|
143
156
|
/// Creates a view of the index from a file without loading it into memory.
|
|
144
|
-
///
|
|
157
|
+
///
|
|
145
158
|
/// # Arguments
|
|
146
|
-
///
|
|
159
|
+
///
|
|
147
160
|
/// * `path` - The file path from where the view will be created.
|
|
148
161
|
pub fn view(self: &Index, path: &str) -> Result<()>;
|
|
149
162
|
|
|
163
|
+
/// Saves the index to a specified file.
|
|
164
|
+
///
|
|
165
|
+
/// # Arguments
|
|
166
|
+
///
|
|
167
|
+
/// * `path` - The file path where the index will be saved.
|
|
168
|
+
pub fn save_to_buffer(self: &Index, buffer: &mut [u8]) -> Result<()>;
|
|
169
|
+
|
|
170
|
+
/// Loads the index from a specified file.
|
|
171
|
+
///
|
|
172
|
+
/// # Arguments
|
|
173
|
+
///
|
|
174
|
+
/// * `path` - The file path from where the index will be loaded.
|
|
175
|
+
pub fn load_from_buffer(self: &Index, buffer: &[u8]) -> Result<()>;
|
|
176
|
+
|
|
177
|
+
/// Creates a view of the index from a file without loading it into memory.
|
|
178
|
+
///
|
|
179
|
+
/// # Arguments
|
|
180
|
+
///
|
|
181
|
+
/// * `path` - The file path from where the view will be created.
|
|
182
|
+
pub fn view_from_buffer(self: &Index, buffer: &[u8]) -> Result<()>;
|
|
183
|
+
|
|
150
184
|
}
|
|
151
185
|
}
|
|
152
186
|
|
|
153
|
-
|
|
154
187
|
#[cfg(test)]
|
|
155
188
|
mod tests {
|
|
189
|
+
use crate::ffi::new_index;
|
|
190
|
+
use crate::ffi::IndexOptions;
|
|
156
191
|
use crate::ffi::MetricKind;
|
|
157
192
|
use crate::ffi::ScalarKind;
|
|
158
|
-
use crate::ffi::IndexOptions;
|
|
159
|
-
use crate::ffi::new_index;
|
|
160
193
|
|
|
161
194
|
#[test]
|
|
162
195
|
fn integration() {
|
|
163
|
-
|
|
164
196
|
let mut options = IndexOptions {
|
|
165
197
|
dimensions: 5,
|
|
166
198
|
metric: MetricKind::IP,
|
|
167
199
|
quantization: ScalarKind::F16,
|
|
168
200
|
connectivity: 0,
|
|
169
201
|
expansion_add: 0,
|
|
170
|
-
expansion_search: 0
|
|
202
|
+
expansion_search: 0,
|
|
171
203
|
};
|
|
172
204
|
|
|
173
205
|
let index = new_index(&options).unwrap();
|
|
174
|
-
|
|
206
|
+
|
|
175
207
|
assert!(index.reserve(10).is_ok());
|
|
176
208
|
assert!(index.capacity() >= 10);
|
|
177
209
|
assert!(index.connectivity() != 0);
|
|
178
210
|
assert_eq!(index.dimensions(), 5);
|
|
179
211
|
assert_eq!(index.size(), 0);
|
|
180
|
-
|
|
212
|
+
|
|
181
213
|
let first: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
|
|
182
214
|
let second: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
|
|
183
|
-
|
|
215
|
+
|
|
184
216
|
assert!(index.add(42, &first).is_ok());
|
|
185
217
|
assert!(index.add(43, &second).is_ok());
|
|
186
218
|
assert_eq!(index.size(), 2);
|
|
187
|
-
|
|
219
|
+
|
|
188
220
|
// Read back the tags
|
|
189
221
|
let results = index.search(&first, 10).unwrap();
|
|
190
222
|
assert_eq!(results.keys.len(), 2);
|
|
191
|
-
|
|
223
|
+
|
|
192
224
|
// Validate serialization
|
|
193
225
|
assert!(index.save("index.rust.usearch").is_ok());
|
|
194
226
|
assert!(index.load("index.rust.usearch").is_ok());
|
|
195
227
|
assert!(index.view("index.rust.usearch").is_ok());
|
|
196
|
-
|
|
228
|
+
|
|
197
229
|
// Make sure every function is called at least once
|
|
198
230
|
assert!(new_index(&options).is_ok());
|
|
199
231
|
options.metric = MetricKind::L2sq;
|
|
@@ -202,5 +234,15 @@ mod tests {
|
|
|
202
234
|
assert!(new_index(&options).is_ok());
|
|
203
235
|
options.metric = MetricKind::Haversine;
|
|
204
236
|
assert!(new_index(&options).is_ok());
|
|
237
|
+
|
|
238
|
+
let mut serialization_buffer = Vec::new();
|
|
239
|
+
serialization_buffer.resize(index.serialized_length(), 0);
|
|
240
|
+
assert!(index.save_to_buffer(&mut serialization_buffer).is_ok());
|
|
241
|
+
|
|
242
|
+
let deserialized_index = new_index(&options).unwrap();
|
|
243
|
+
assert!(deserialized_index
|
|
244
|
+
.load_from_buffer(&serialization_buffer)
|
|
245
|
+
.is_ok());
|
|
246
|
+
assert_eq!(index.size(), deserialized_index.size());
|
|
205
247
|
}
|
|
206
|
-
}
|
|
248
|
+
}
|