usearch 1.1.1 → 1.2.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 (70) hide show
  1. package/.github/workflows/prerelease.yml +3 -3
  2. package/.github/workflows/release.yml +3 -3
  3. package/.github/workflows/update_version.sh +1 -1
  4. package/.gitmodules +1 -4
  5. package/Cargo.toml +1 -1
  6. package/Package.swift +2 -1
  7. package/README.md +86 -42
  8. package/binding.gyp +16 -12
  9. package/build.gradle +1 -1
  10. package/build.rs +0 -1
  11. package/c/CMakeLists.txt +11 -7
  12. package/c/lib.cpp +36 -24
  13. package/c/usearch.h +42 -12
  14. package/cluster.ipynb +167 -0
  15. package/conanfile.py +1 -1
  16. package/cpp/CMakeLists.txt +8 -3
  17. package/cpp/bench.cpp +2 -2
  18. package/cpp/test.cpp +42 -25
  19. package/docs/benchmarks.md +8 -0
  20. package/docs/compilation.md +7 -7
  21. package/docs/conf.py +27 -24
  22. package/docs/javascript/reference.rst +3 -0
  23. package/golang/README.md +31 -7
  24. package/golang/lib.go +1 -1
  25. package/include/usearch/index.hpp +348 -69
  26. package/include/usearch/index_dense.hpp +587 -179
  27. package/include/usearch/index_plugins.hpp +194 -16
  28. package/java/cloud/unum/usearch/Index.java +13 -0
  29. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +40 -9
  30. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.h +16 -0
  31. package/javascript/docs.js +111 -0
  32. package/javascript/lib.cpp +29 -9
  33. package/javascript/usearch.d.ts +9 -1
  34. package/objc/USearchObjective.mm +11 -11
  35. package/package.json +2 -2
  36. package/pyproject.toml +1 -1
  37. package/python/lib.cpp +488 -158
  38. package/python/scripts/index_faiss.py +3 -3
  39. package/python/scripts/join.py +6 -6
  40. package/python/scripts/test_index.py +219 -0
  41. package/python/scripts/test_sparse.py +21 -21
  42. package/python/scripts/test_tooling.py +111 -0
  43. package/python/usearch/eval.py +9 -5
  44. package/python/usearch/index.py +491 -216
  45. package/rust/lib.cpp +6 -24
  46. package/rust/lib.hpp +1 -3
  47. package/rust/lib.rs +89 -0
  48. package/setup.py +5 -6
  49. package/wasm/CMakeLists.txt +0 -1
  50. package/wasmer.toml +1 -1
  51. package/python/scripts/test.py +0 -364
  52. package/robin-map/.codecov.yml +0 -5
  53. package/robin-map/.github/workflows/ci.yml +0 -136
  54. package/robin-map/CMakeLists.txt +0 -86
  55. package/robin-map/LICENSE +0 -21
  56. package/robin-map/README.md +0 -483
  57. package/robin-map/cmake/tsl-robin-mapConfig.cmake.in +0 -9
  58. package/robin-map/doxygen.conf +0 -2483
  59. package/robin-map/include/tsl/robin_growth_policy.h +0 -406
  60. package/robin-map/include/tsl/robin_hash.h +0 -1639
  61. package/robin-map/include/tsl/robin_map.h +0 -807
  62. package/robin-map/include/tsl/robin_set.h +0 -660
  63. package/robin-map/tests/CMakeLists.txt +0 -26
  64. package/robin-map/tests/custom_allocator_tests.cpp +0 -138
  65. package/robin-map/tests/main.cpp +0 -26
  66. package/robin-map/tests/policy_tests.cpp +0 -97
  67. package/robin-map/tests/robin_map_tests.cpp +0 -1451
  68. package/robin-map/tests/robin_set_tests.cpp +0 -164
  69. package/robin-map/tests/utils.h +0 -443
  70. package/robin-map/tsl-robin-map.natvis +0 -78
package/rust/lib.cpp CHANGED
@@ -11,30 +11,6 @@ using labeling_result_t = typename index_t::labeling_result_t;
11
11
 
12
12
  Index::Index(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
13
13
 
14
- void Index::add_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const {
15
- index_update_config_t config;
16
- config.thread = thread;
17
- config.expansion = index_->expansion_add();
18
- index_->add(key, vector.data(), config).error.raise();
19
- }
20
-
21
- Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
22
- Matches matches;
23
- matches.keys.reserve(count);
24
- matches.distances.reserve(count);
25
- for (size_t i = 0; i != count; ++i)
26
- matches.keys.push_back(0), matches.distances.push_back(0);
27
- index_search_config_t config;
28
- config.thread = thread;
29
- config.expansion = index_->expansion_search();
30
- search_result_t result = index_->search(vector.data(), count, config);
31
- result.error.raise();
32
- count = result.dump_to(matches.keys.data(), matches.distances.data());
33
- matches.keys.truncate(count);
34
- matches.distances.truncate(count);
35
- return matches;
36
- }
37
-
38
14
  void Index::add(key_t key, rust::Slice<float const> vector) const { index_->add(key, vector.data()).error.raise(); }
39
15
 
40
16
  bool Index::remove(key_t key) const {
@@ -43,6 +19,12 @@ bool Index::remove(key_t key) const {
43
19
  return result.completed;
44
20
  }
45
21
 
22
+ bool Index::rename(key_t from, key_t to) const {
23
+ labeling_result_t result = index_->rename(from, to);
24
+ result.error.raise();
25
+ return result.completed;
26
+ }
27
+
46
28
  bool Index::contains(key_t key) const { return index_->contains(key); }
47
29
 
48
30
  Matches Index::search(rust::Slice<float const> vector, size_t count) const {
package/rust/lib.hpp CHANGED
@@ -22,13 +22,11 @@ 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_in_thread(key_t key, rust::Slice<float const> vector, size_t thread) const;
26
-
27
25
  Matches search(rust::Slice<float const> vector, size_t count) const;
28
- Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
29
26
 
30
27
  bool remove(key_t key) const;
31
28
  bool contains(key_t key) const;
29
+ bool rename(key_t from, key_t to) const;
32
30
 
33
31
  size_t dimensions() const;
34
32
  size_t connectivity() const;
package/rust/lib.rs CHANGED
@@ -39,25 +39,114 @@ pub mod ffi {
39
39
  unsafe extern "C++" {
40
40
  include!("lib.hpp");
41
41
 
42
+ /// Represents the USearch index.
42
43
  type Index;
43
44
 
45
+ /// Initializes a new instance of the index with the provided options.
46
+ ///
47
+ /// # Arguments
48
+ ///
49
+ /// * `options` - A reference to the `IndexOptions` structure containing initialization options.
50
+ ///
51
+ /// # Returns
52
+ ///
53
+ /// A `Result` which is `Ok` if the index is successfully initialized, or `Err` if an error occurs.
44
54
  pub fn new_index(options: &IndexOptions) -> Result<UniquePtr<Index>>;
45
55
 
56
+ /// Reserves memory for a specified number of incoming vectors.
57
+ ///
58
+ /// # Arguments
59
+ ///
60
+ /// * `capacity` - The desired total capacity including the current size.
46
61
  pub fn reserve(self: &Index, capacity: usize) -> Result<()>;
47
62
 
63
+ /// Retrieves the number of dimensions in the vectors indexed.
48
64
  pub fn dimensions(self: &Index) -> usize;
65
+
66
+ /// Retrieves the connectivity parameter that limits connections-per-node in the graph.
49
67
  pub fn connectivity(self: &Index) -> usize;
68
+
69
+ /// Retrieves the current number of vectors in the index.
50
70
  pub fn size(self: &Index) -> usize;
71
+
72
+ /// Retrieves the total capacity of the index, including reserved space.
51
73
  pub fn capacity(self: &Index) -> usize;
52
74
 
75
+ /// Adds a vector with a specified key to the index.
76
+ ///
77
+ /// # Arguments
78
+ ///
79
+ /// * `key` - The key associated with the vector.
80
+ /// * `vector` - A slice containing the vector data.
53
81
  pub fn add(self: &Index, key: u64, vector: &[f32]) -> Result<()>;
82
+
83
+ /// Performs k-Approximate Nearest Neighbors (kANN) Search for closest vectors to the provided query.
84
+ ///
85
+ /// # Arguments
86
+ ///
87
+ /// * `query` - A slice containing the query vector data.
88
+ /// * `count` - The maximum number of neighbors to search for.
89
+ ///
90
+ /// # Returns
91
+ ///
92
+ /// A `Result` containing the matches found.
54
93
  pub fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
94
+
95
+ /// Removes the vector associated with the given key from the index.
96
+ ///
97
+ /// # Arguments
98
+ ///
99
+ /// * `key` - The key of the vector to be removed.
100
+ ///
101
+ /// # Returns
102
+ ///
103
+ /// `true` if the vector is successfully removed, `false` otherwise.
55
104
  pub fn remove(self: &Index, key: u64) -> Result<bool>;
105
+
106
+ /// Renames the vector under a certain key.
107
+ ///
108
+ /// # Arguments
109
+ ///
110
+ /// * `from` - The key of the vector to be renamed.
111
+ /// * `to` - The new name.
112
+ ///
113
+ /// # Returns
114
+ ///
115
+ /// `true` if the vector is successfully renamed, `false` otherwise.
116
+ pub fn rename(self: &Index, from: u64, to: u64) -> Result<bool>;
117
+
118
+ /// Checks if the index contains a vector with a specified key.
119
+ ///
120
+ /// # Arguments
121
+ ///
122
+ /// * `key` - The key to be checked.
123
+ ///
124
+ /// # Returns
125
+ ///
126
+ /// `true` if the index contains the vector with the given key, `false` otherwise.
56
127
  pub fn contains(self: &Index, key: u64) -> bool;
57
128
 
129
+ /// Saves the index to a specified file.
130
+ ///
131
+ /// # Arguments
132
+ ///
133
+ /// * `path` - The file path where the index will be saved.
58
134
  pub fn save(self: &Index, path: &str) -> Result<()>;
135
+
136
+ /// Loads the index from a specified file.
137
+ ///
138
+ /// # Arguments
139
+ ///
140
+ /// * `path` - The file path from where the index will be loaded.
59
141
  pub fn load(self: &Index, path: &str) -> Result<()>;
142
+
143
+ /// Creates a view of the index from a file without loading it into memory.
144
+ ///
145
+ /// # Arguments
146
+ ///
147
+ /// * `path` - The file path from where the view will be created.
60
148
  pub fn view(self: &Index, path: &str) -> Result<()>;
149
+
61
150
  }
62
151
  }
63
152
 
package/setup.py CHANGED
@@ -13,7 +13,7 @@ macros_args = [
13
13
  ]
14
14
 
15
15
  if sys.platform == "linux":
16
- compile_args.append("-std=c++11")
16
+ compile_args.append("-std=c++17")
17
17
  compile_args.append("-O3") # Maximize performance
18
18
  compile_args.append("-g") # Simplify debugging
19
19
  compile_args.append("-Wno-unknown-pragmas")
@@ -26,18 +26,18 @@ if sys.platform == "darwin":
26
26
  # MacOS 10.15 or higher is needed for `aligned_alloc` support.
27
27
  # https://github.com/unum-cloud/usearch/actions/runs/4975434891/jobs/8902603392
28
28
  compile_args.append("-mmacosx-version-min=10.15")
29
- compile_args.append("-std=c++11")
29
+ compile_args.append("-std=c++17")
30
30
  compile_args.append("-O3") # Maximize performance
31
31
  compile_args.append("-g") # Simplify debugging
32
32
  compile_args.append("-Wno-unknown-pragmas")
33
33
 
34
- # Linking OpenMP requires additional preparion in CIBuildWheel
34
+ # Linking OpenMP requires additional preparation in CIBuildWheel
35
35
  # macros_args.append(("USEARCH_USE_OPENMP", "1"))
36
36
  # compile_args.append("-Xpreprocessor -fopenmp")
37
37
  # link_args.append("-Xpreprocessor -lomp")
38
38
 
39
39
  if sys.platform == "win32":
40
- compile_args.append("/std:c++14")
40
+ compile_args.append("/std:c++17")
41
41
  compile_args.append("/O2")
42
42
 
43
43
  ext_modules = [
@@ -90,11 +90,10 @@ setup(
90
90
  "Topic :: Database :: Database Engines/Servers",
91
91
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
92
92
  ],
93
- include_dirs=["include", "fp16/include", "robin-map/include", "simsimd/include"],
93
+ include_dirs=["include", "fp16/include", "simsimd/include"],
94
94
  ext_modules=ext_modules,
95
95
  install_requires=[
96
96
  "numpy",
97
- "pandas",
98
97
  "tqdm",
99
98
  'ucall; python_version >= "3.9"',
100
99
  ],
@@ -7,7 +7,6 @@ set(USEARCH_PUNNED_INCLUDE_DIRS
7
7
  "${CMAKE_CURRENT_SOURCE_DIR}/../c"
8
8
  "${CMAKE_CURRENT_SOURCE_DIR}/../include"
9
9
  "${CMAKE_CURRENT_SOURCE_DIR}/../fp16/include"
10
- "${CMAKE_CURRENT_SOURCE_DIR}/../robin-map/include"
11
10
  )
12
11
 
13
12
  add_executable(index lib.cpp)
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="1.1.1"
3
+ version="1.2.0"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"
@@ -1,364 +0,0 @@
1
- import os
2
-
3
- import pytest
4
- import numpy as np
5
-
6
- from usearch.io import load_matrix, save_matrix
7
- from usearch.eval import random_vectors
8
- from usearch.index import search
9
-
10
- from usearch.index import (
11
- Index,
12
- Indexes,
13
- MetricKind,
14
- ScalarKind,
15
- Match,
16
- Matches,
17
- BatchMatches,
18
- )
19
- from usearch.index import (
20
- DEFAULT_CONNECTIVITY,
21
- DEFAULT_EXPANSION_ADD,
22
- DEFAULT_EXPANSION_SEARCH,
23
- )
24
-
25
- # When running the tests concurrently in different VMs, one can get access violation errors.
26
- # To avoid that, we embed the OS name into the temporary files name.
27
- # https://github.com/unum-cloud/usearch/actions/runs/5705359698/job/15459945738
28
- temporary_filename = f"tmp-{os.name}"
29
- temporary_usearch_filename = temporary_filename + ".usearch"
30
-
31
- dimensions = [3, 97, 256]
32
- batch_sizes = [1, 77]
33
- index_types = [
34
- ScalarKind.F32,
35
- ScalarKind.F64,
36
- ScalarKind.F16,
37
- ScalarKind.I8,
38
- ]
39
- numpy_types = [np.float32, np.float64, np.float16]
40
-
41
- connectivity_options = [3, 13, 50, DEFAULT_CONNECTIVITY]
42
- continuous_metrics = [
43
- MetricKind.Cos,
44
- MetricKind.L2sq,
45
- ]
46
- hash_metrics = [
47
- MetricKind.Hamming,
48
- MetricKind.Tanimoto,
49
- MetricKind.Sorensen,
50
- ]
51
-
52
-
53
- @pytest.mark.parametrize("rows", batch_sizes)
54
- @pytest.mark.parametrize("cols", dimensions)
55
- def test_serializing_fbin_matrix(rows: int, cols: int):
56
- """
57
- Test the serialization of floating point binary matrix.
58
-
59
- :param int rows: The number of rows in the matrix.
60
- :param int cols: The number of columns in the matrix.
61
- """
62
- original = np.random.rand(rows, cols).astype(np.float32)
63
- save_matrix(original, temporary_filename + ".fbin")
64
- reconstructed = load_matrix(temporary_filename + ".fbin")
65
- assert np.allclose(original, reconstructed)
66
- os.remove(temporary_filename + ".fbin")
67
-
68
-
69
- @pytest.mark.parametrize("rows", batch_sizes)
70
- @pytest.mark.parametrize("cols", dimensions)
71
- def test_serializing_ibin_matrix(rows: int, cols: int):
72
- """
73
- Test the serialization of integer binary matrix.
74
-
75
- :param int rows: The number of rows in the matrix.
76
- :param int cols: The number of columns in the matrix.
77
- """
78
- original = np.random.randint(0, rows + 1, size=(rows, cols)).astype(np.int32)
79
- save_matrix(original, temporary_filename + ".ibin")
80
- reconstructed = load_matrix(temporary_filename + ".ibin")
81
- assert np.allclose(original, reconstructed)
82
- os.remove(temporary_filename + ".ibin")
83
-
84
-
85
- @pytest.mark.parametrize("rows", batch_sizes)
86
- @pytest.mark.parametrize("cols", dimensions)
87
- def test_exact_search(rows: int, cols: int):
88
- """
89
- Test exact search.
90
-
91
- :param int rows: The number of rows in the matrix.
92
- :param int cols: The number of columns in the matrix.
93
- """
94
- original = np.random.rand(rows, cols)
95
- matches: BatchMatches = search(original, original, 10, exact=True)
96
- top_matches = (
97
- [int(m.keys[0]) for m in matches] if rows > 1 else int(matches.keys[0])
98
- )
99
- assert np.all(top_matches == np.arange(rows))
100
-
101
- matches: Matches = search(original, original[0], 10, exact=True)
102
- top_match = int(matches.keys[0])
103
- assert top_match == 0
104
-
105
-
106
- @pytest.mark.parametrize("ndim", dimensions)
107
- @pytest.mark.parametrize("metric", continuous_metrics)
108
- @pytest.mark.parametrize("index_type", index_types)
109
- @pytest.mark.parametrize("numpy_type", numpy_types)
110
- @pytest.mark.parametrize("connectivity", connectivity_options)
111
- def test_index(
112
- ndim: int,
113
- metric: MetricKind,
114
- index_type: ScalarKind,
115
- numpy_type: str,
116
- connectivity: int,
117
- ):
118
- index = Index(
119
- metric=metric,
120
- ndim=ndim,
121
- dtype=index_type,
122
- connectivity=connectivity,
123
- expansion_add=DEFAULT_EXPANSION_ADD,
124
- expansion_search=DEFAULT_EXPANSION_SEARCH,
125
- )
126
- assert index.ndim == ndim
127
- assert index.connectivity == connectivity
128
-
129
- vector = random_vectors(count=1, ndim=ndim, dtype=numpy_type).flatten()
130
- index.add(42, vector)
131
-
132
- assert len(index) == 1, "Size after addition"
133
- assert 42 in index, "Presence in the index"
134
- assert 42 in index.keys, "Presence among keys"
135
- assert 43 not in index, "Presence in the index, false positive"
136
- assert index[42] is not None, "Vector recovery"
137
- assert index[43] is None, "Vector recovery, false positive"
138
- assert len(index[42]) == ndim
139
- if numpy_type != np.byte:
140
- assert np.allclose(index[42], vector, atol=0.1)
141
-
142
- matches: Matches = index.search(vector, 10)
143
- assert len(matches.keys) == 1, "Number of matches"
144
- assert len(matches.keys) == len(matches.distances), "Symmetric match sub-arrays"
145
- assert len({match.key for match in matches}) == 1, "Iteration over matches"
146
- assert matches[0].key == 42
147
- assert matches[0].distance == pytest.approx(0, abs=1e-3)
148
- assert matches.computed_distances != 0
149
- assert matches.visited_members != 0
150
-
151
- # Validating the index structure and metadata:
152
- assert index.max_level >= 0
153
- assert index.levels_stats.nodes >= 1
154
- assert index.level_stats(0).nodes == 1
155
- assert str(index).startswith("usearch.")
156
-
157
- # Try removals
158
- other_vector = random_vectors(count=1, ndim=ndim, dtype=numpy_type).flatten()
159
- index.add(43, other_vector)
160
- assert len(index) == 2
161
- index.remove(43)
162
- assert len(index) == 1
163
-
164
- # Try inserting back
165
- index.add(43, other_vector)
166
- assert len(index) == 2
167
- index.remove(43)
168
- assert len(index) == 1
169
-
170
- index.save(temporary_usearch_filename)
171
-
172
- # Re-populate cleared index
173
- index.clear()
174
- assert len(index) == 0
175
- index.add(42, vector)
176
- assert len(index) == 1
177
- matches: Matches = index.search(vector, 10)
178
- assert len(matches) == 1
179
-
180
- index_copy = index.copy()
181
- assert len(index_copy) == 1
182
- assert len(index_copy[42]) == ndim
183
- matches_copy: Matches = index_copy.search(vector, 10)
184
- assert np.all(matches_copy.keys == matches.keys)
185
-
186
- index.load(temporary_usearch_filename)
187
- assert len(index) == 1
188
- assert len(index[42]) == ndim
189
-
190
- matches_loaded: Matches = index.search(vector, 10)
191
- assert np.all(matches_loaded.keys == matches.keys)
192
-
193
- index = Index.restore(temporary_usearch_filename, view=True)
194
- assert len(index) == 1
195
- assert len(index[42]) == ndim
196
-
197
- matches_viewed: Matches = index.search(vector, 10)
198
- assert np.all(matches_viewed.keys == matches.keys)
199
-
200
- # Cleanup
201
- index.reset()
202
- os.remove(temporary_usearch_filename)
203
-
204
- # Try opening a missing file
205
- meta = Index.metadata(temporary_usearch_filename)
206
- assert meta is None
207
- index = Index.restore(temporary_usearch_filename)
208
- assert index is None
209
-
210
- # Try opening a corrupt file
211
- with open(temporary_usearch_filename, "w") as file:
212
- file.write("Some random string")
213
- meta = Index.metadata(temporary_usearch_filename)
214
- assert meta is None
215
- index = Index.restore(temporary_usearch_filename)
216
-
217
- assert index is None
218
- os.remove(temporary_usearch_filename)
219
-
220
-
221
- @pytest.mark.parametrize("ndim", dimensions)
222
- @pytest.mark.parametrize("metric", continuous_metrics)
223
- @pytest.mark.parametrize("batch_size", batch_sizes)
224
- @pytest.mark.parametrize("index_type", index_types)
225
- @pytest.mark.parametrize("numpy_type", numpy_types)
226
- def test_index_batch(
227
- ndim: int,
228
- metric: MetricKind,
229
- batch_size: int,
230
- index_type: ScalarKind,
231
- numpy_type: str,
232
- ):
233
- index = Index(ndim=ndim, metric=metric, dtype=index_type)
234
-
235
- keys = np.arange(batch_size)
236
- vectors = random_vectors(count=batch_size, ndim=ndim, dtype=numpy_type)
237
-
238
- index.add(keys, vectors, threads=2)
239
- assert len(index) == batch_size
240
- assert np.allclose(index.get_vectors(keys).astype(numpy_type), vectors, atol=0.1)
241
-
242
- matches: BatchMatches = index.search(vectors, 10, threads=2)
243
- assert matches.keys.shape[0] == matches.distances.shape[0]
244
- assert len(matches) == batch_size
245
- assert np.all(np.sort(index.keys) == np.sort(keys))
246
-
247
- if batch_size > 1:
248
- assert index.max_level >= 1
249
- else:
250
- assert index.max_level >= 0
251
- assert index.levels_stats.nodes >= batch_size
252
- assert index.level_stats(0).nodes == batch_size
253
-
254
- index.save(temporary_usearch_filename)
255
- index.clear()
256
- assert len(index) == 0
257
-
258
- index.load(temporary_usearch_filename)
259
- assert len(index) == batch_size
260
- assert len(index[0]) == ndim
261
-
262
- if batch_size > 1:
263
- matches_loaded: BatchMatches = index.search(vectors, 10, threads=2)
264
- for idx in range(len(matches_loaded)):
265
- assert np.all(matches_loaded[idx].keys == matches[idx].keys)
266
-
267
- index = Index.restore(temporary_usearch_filename, view=True)
268
- assert len(index) == batch_size
269
- assert len(index[0]) == ndim
270
-
271
- if batch_size > 1:
272
- matches_viewed: BatchMatches = index.search(vectors, 10, threads=2)
273
- for idx in range(len(matches_viewed)):
274
- assert np.all(matches_viewed[idx].keys == matches[idx].keys)
275
-
276
- # Cleanup
277
- index.reset()
278
- os.remove(temporary_usearch_filename)
279
-
280
-
281
- @pytest.mark.parametrize("metric", [MetricKind.L2sq])
282
- @pytest.mark.parametrize("batch_size", batch_sizes)
283
- @pytest.mark.parametrize("index_type", index_types)
284
- @pytest.mark.parametrize("numpy_type", numpy_types)
285
- def test_exact_recall(
286
- metric: MetricKind,
287
- batch_size: int,
288
- index_type: ScalarKind,
289
- numpy_type: str,
290
- ):
291
- ndim: int = batch_size
292
- index = Index(ndim=ndim, metric=metric, dtype=index_type)
293
- assert index.ndim == ndim
294
-
295
- vectors = np.zeros((batch_size, ndim), dtype=numpy_type)
296
- for i in range(batch_size):
297
- vectors[i, i] = 1
298
- keys = np.arange(batch_size)
299
- index.add(keys, vectors)
300
- assert len(index) == batch_size
301
-
302
- # Search one at a time
303
- for i in range(batch_size):
304
- matches: Matches = index.search(vectors[i], 10, exact=True)
305
- found_labels = matches.keys
306
- assert found_labels[0] == i
307
- assert matches.computed_distances == len(index)
308
- assert matches.visited_members == 0, "Exact search won't traverse the graph"
309
-
310
- # Search the whole batch
311
- if batch_size > 1:
312
- matches: BatchMatches = index.search(vectors, 10, exact=True)
313
- assert matches.computed_distances == len(index) * len(vectors)
314
- assert matches.visited_members == 0, "Exact search won't traverse the graph"
315
-
316
- found_labels = matches.keys
317
- for i in range(batch_size):
318
- assert found_labels[i, 0] == i
319
-
320
- # Match entries against themselves
321
- index_copy: Index = index.copy()
322
- mapping: dict = index.join(index_copy, exact=True)
323
- for man, woman in mapping.items():
324
- assert man == woman, "Stable marriage failed"
325
-
326
-
327
- def test_indexes():
328
- ndim = 10
329
- index_a = Index(ndim=ndim)
330
- index_b = Index(ndim=ndim)
331
-
332
- vectors = random_vectors(count=3, ndim=ndim)
333
- index_a.add(42, vectors[0])
334
- index_b.add(43, vectors[1])
335
-
336
- indexes = Indexes([index_a, index_b])
337
- matches = indexes.search(vectors[2], 10)
338
- assert len(matches) == 2
339
-
340
-
341
- @pytest.mark.parametrize("bits", dimensions)
342
- @pytest.mark.parametrize("metric", hash_metrics)
343
- @pytest.mark.parametrize("connectivity", connectivity_options)
344
- @pytest.mark.parametrize("batch_size", batch_sizes)
345
- def test_bitwise_index(
346
- bits: int,
347
- metric: MetricKind,
348
- connectivity: int,
349
- batch_size: int,
350
- ):
351
- index = Index(ndim=bits, metric=metric, connectivity=connectivity)
352
-
353
- keys = np.arange(batch_size)
354
- byte_vectors = np.random.randint(2, size=(batch_size, bits))
355
- bit_vectors = np.packbits(byte_vectors, axis=1)
356
-
357
- index.add(keys, bit_vectors)
358
- assert np.all(index.get_vectors(keys, ScalarKind.B1) == bit_vectors)
359
-
360
- index.search(bit_vectors, 10)
361
-
362
-
363
- if __name__ == "__main__":
364
- pytest.main(args=["python/scripts/test.py", "-s", "-x", "-v"])
@@ -1,5 +0,0 @@
1
- comment: off
2
- coverage:
3
- status:
4
- project: off
5
- patch: off