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.
Files changed (42) hide show
  1. package/.github/workflows/prerelease.yml +71 -0
  2. package/.github/workflows/release.yml +77 -2
  3. package/.github/workflows/update_version.sh +1 -0
  4. package/.gitignore +11 -1
  5. package/.vscode/settings.json +5 -1
  6. package/CMakeLists.txt +1 -0
  7. package/Cargo.toml +1 -1
  8. package/README.md +31 -18
  9. package/c/lib.cpp +38 -4
  10. package/c/test.c +117 -114
  11. package/c/usearch.h +28 -1
  12. package/conanfile.py +1 -1
  13. package/cpp/test.cpp +2 -2
  14. package/csharp/.editorconfig +361 -0
  15. package/csharp/Cloud.Unum.USearch.sln +34 -0
  16. package/csharp/DEV_SETUP.md +51 -0
  17. package/csharp/Directory.Build.props +29 -0
  18. package/csharp/README.md +77 -0
  19. package/csharp/build_and_test.cmd +31 -0
  20. package/csharp/build_and_test.sh +30 -0
  21. package/csharp/eng/build_lib.sh +65 -0
  22. package/csharp/eng/clean.sh +12 -0
  23. package/csharp/nuget/NUGET.md +11 -0
  24. package/csharp/nuget/nuget-package.props +33 -0
  25. package/csharp/pack.sh +56 -0
  26. package/csharp/src/Cloud.Unum.USearch.Tests/Cloud.Unum.USearch.Tests.csproj +39 -0
  27. package/csharp/src/Cloud.Unum.USearch.Tests/InitTests.cs +306 -0
  28. package/csharp/src/Cloud.Unum.USearch.Tests/Usings.cs +1 -0
  29. package/docs/benchmarks.md +6 -6
  30. package/docs/compilation.md +1 -1
  31. package/include/usearch/index.hpp +172 -52
  32. package/include/usearch/index_dense.hpp +201 -64
  33. package/include/usearch/index_plugins.hpp +90 -69
  34. package/package.json +2 -2
  35. package/python/lib.cpp +99 -37
  36. package/python/scripts/test_index.py +38 -10
  37. package/python/usearch/index.py +87 -37
  38. package/rust/README.md +2 -2
  39. package/rust/lib.cpp +42 -19
  40. package/rust/lib.hpp +14 -0
  41. package/rust/lib.rs +85 -43
  42. package/wasmer.toml +1 -1
@@ -21,14 +21,21 @@ from typing import (
21
21
  import numpy as np
22
22
  from tqdm import tqdm
23
23
 
24
- from usearch.compiled import Index as _CompiledIndex
25
- from usearch.compiled import Indexes as _CompiledIndexes
26
- from usearch.compiled import IndexStats as _CompiledIndexStats
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
- from usearch.compiled import index_dense_metadata as _index_dense_metadata
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(path: os.PathLike) -> Optional[dict]:
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
- return _index_dense_metadata(path)
568
- except Exception:
569
- return None
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(path: os.PathLike, view: bool = False) -> Optional[Index]:
573
- path = os.fspath(path)
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
- return Index(
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, path: Optional[os.PathLike] = None):
899
- path = path if path else self.path
900
- if path is None:
901
- raise Exception("Define `path` argument")
902
- self._compiled.save(os.fspath(path))
903
-
904
- def load(self, path: Optional[os.PathLike] = None):
905
- path = path if path else self.path
906
- if path is None:
907
- raise Exception("Define `path` argument")
908
- self._compiled.load(os.fspath(path))
909
-
910
- def view(self, path: Optional[os.PathLike] = None):
911
- path = path if path else self.path
912
- if path is None:
913
- raise Exception("Define `path` argument")
914
- self._compiled.view(os.fspath(path))
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 levels_stats(self) -> _CompiledIndexStats:
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: 5,
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.count, 2);
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
- void Index::add(key_t key, rust::Slice<float const> vector) const { index_->add(key, vector.data()).error.raise(); }
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
- scalar_kind_t quantization(rust::Str quant) { return scalar_kind_from_name(quant.data(), quant.size()); }
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
+ }
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="1.2.2"
3
+ version="2.0.0"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"