usearch 0.9.7 → 0.11.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 (41) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/.gitmodules +3 -0
  3. package/.vscode/settings.json +2 -0
  4. package/Cargo.toml +1 -1
  5. package/Package.swift +1 -0
  6. package/README.md +1 -1
  7. package/VERSION +1 -1
  8. package/binding.gyp +4 -3
  9. package/build.gradle +1 -1
  10. package/build.rs +1 -0
  11. package/conanfile.py +1 -1
  12. package/docs/compilation.md +6 -3
  13. package/include/usearch/usearch.hpp +168 -45
  14. package/package.json +1 -1
  15. package/python/lib.cpp +42 -23
  16. package/python/scripts/bench.py +2 -2
  17. package/python/scripts/test.py +1 -1
  18. package/python/usearch/index.py +298 -0
  19. package/robin-map/.codecov.yml +5 -0
  20. package/robin-map/.github/workflows/ci.yml +136 -0
  21. package/robin-map/CMakeLists.txt +86 -0
  22. package/robin-map/LICENSE +21 -0
  23. package/robin-map/README.md +483 -0
  24. package/robin-map/cmake/tsl-robin-mapConfig.cmake.in +9 -0
  25. package/robin-map/doxygen.conf +2483 -0
  26. package/robin-map/include/tsl/robin_growth_policy.h +406 -0
  27. package/robin-map/include/tsl/robin_hash.h +1639 -0
  28. package/robin-map/include/tsl/robin_map.h +807 -0
  29. package/robin-map/include/tsl/robin_set.h +660 -0
  30. package/robin-map/tests/CMakeLists.txt +26 -0
  31. package/robin-map/tests/custom_allocator_tests.cpp +138 -0
  32. package/robin-map/tests/main.cpp +26 -0
  33. package/robin-map/tests/policy_tests.cpp +97 -0
  34. package/robin-map/tests/robin_map_tests.cpp +1451 -0
  35. package/robin-map/tests/robin_set_tests.cpp +164 -0
  36. package/robin-map/tests/utils.h +443 -0
  37. package/robin-map/tsl-robin-map.natvis +78 -0
  38. package/setup.py +4 -2
  39. package/src/CMakeLists.txt +1 -0
  40. package/src/punned.hpp +56 -6
  41. package/src/test.cpp +32 -8
package/python/lib.cpp CHANGED
@@ -169,7 +169,7 @@ static std::unique_ptr<hash_index_py_t> make_hash_index( //
169
169
  return std::unique_ptr<hash_index_py_t>(new hash_index_py_t(config, bits));
170
170
  }
171
171
 
172
- static void add_one_to_index(punned_py_t& index, label_t label, py::buffer vector, bool copy) {
172
+ static void add_one_to_index(punned_py_t& index, label_t label, py::buffer vector, bool copy, std::size_t) {
173
173
 
174
174
  py::buffer_info vector_info = vector.request();
175
175
  if (vector_info.ndim != 1)
@@ -191,9 +191,9 @@ static void add_one_to_index(punned_py_t& index, label_t label, py::buffer vecto
191
191
  index.add(label, reinterpret_cast<f8_bits_t const*>(vector_data), config);
192
192
  else if (vector_info.format == "e")
193
193
  index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), config);
194
- else if (vector_info.format == "f")
194
+ else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4")
195
195
  index.add(label, reinterpret_cast<float const*>(vector_data), config);
196
- else if (vector_info.format == "d")
196
+ else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8")
197
197
  index.add(label, reinterpret_cast<double const*>(vector_data), config);
198
198
  else
199
199
  throw std::invalid_argument("Incompatible scalars in the vector!");
@@ -201,7 +201,10 @@ static void add_one_to_index(punned_py_t& index, label_t label, py::buffer vecto
201
201
 
202
202
  static void add_many_to_index( //
203
203
  punned_py_t& index, py::buffer labels, py::buffer vectors, //
204
- bool copy, std::size_t threads = 0) {
204
+ bool copy, std::size_t threads) {
205
+
206
+ if (index.config().max_threads_add < threads)
207
+ throw std::invalid_argument("Can't use that many threads!");
205
208
 
206
209
  py::buffer_info labels_info = labels.request();
207
210
  py::buffer_info vectors_info = vectors.request();
@@ -232,7 +235,7 @@ static void add_many_to_index( //
232
235
 
233
236
  // https://docs.python.org/3/library/struct.html#format-characters
234
237
  if (vectors_info.format == "c" || vectors_info.format == "b")
235
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
238
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
236
239
  add_config_t config;
237
240
  config.store_vector = copy;
238
241
  config.thread = thread_idx;
@@ -242,7 +245,7 @@ static void add_many_to_index( //
242
245
  index.add(label, vector, config);
243
246
  });
244
247
  else if (vectors_info.format == "e")
245
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
248
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
246
249
  add_config_t config;
247
250
  config.store_vector = copy;
248
251
  config.thread = thread_idx;
@@ -251,8 +254,8 @@ static void add_many_to_index( //
251
254
  reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
252
255
  index.add(label, vector, config);
253
256
  });
254
- else if (vectors_info.format == "f")
255
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
257
+ else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
258
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
256
259
  add_config_t config;
257
260
  config.store_vector = copy;
258
261
  config.thread = thread_idx;
@@ -260,8 +263,8 @@ static void add_many_to_index( //
260
263
  float const* vector = reinterpret_cast<float const*>(vectors_data + task_idx * vectors_info.strides[0]);
261
264
  index.add(label, vector, config);
262
265
  });
263
- else if (vectors_info.format == "d")
264
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
266
+ else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
267
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
265
268
  add_config_t config;
266
269
  config.store_vector = copy;
267
270
  config.thread = thread_idx;
@@ -299,11 +302,11 @@ static py::tuple search_one_in_index(punned_py_t& index, py::buffer vector, std:
299
302
  count = index //
300
303
  .search(reinterpret_cast<f16_bits_t const*>(vector_data), wanted, config)
301
304
  .dump_to(&labels_py1d(0), &distances_py1d(0));
302
- else if (vector_info.format == "f")
305
+ else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4")
303
306
  count = index //
304
307
  .search(reinterpret_cast<float const*>(vector_data), wanted, config)
305
308
  .dump_to(&labels_py1d(0), &distances_py1d(0));
306
- else if (vector_info.format == "d")
309
+ else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8")
307
310
  count = index //
308
311
  .search(reinterpret_cast<double const*>(vector_data), wanted, config)
309
312
  .dump_to(&labels_py1d(0), &distances_py1d(0));
@@ -329,11 +332,15 @@ static py::tuple search_one_in_index(punned_py_t& index, py::buffer vector, std:
329
332
  * 2. matrix of distances,
330
333
  * 3. array with match counts.
331
334
  */
332
- static py::tuple search_many_in_index(punned_py_t& index, py::buffer vectors, std::size_t wanted, bool exact) {
335
+ static py::tuple search_many_in_index( //
336
+ punned_py_t& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads) {
333
337
 
334
338
  if (wanted == 0)
335
339
  return py::tuple(3);
336
340
 
341
+ if (index.config().max_threads_add < threads)
342
+ throw std::invalid_argument("Can't use that many threads!");
343
+
337
344
  py::buffer_info vectors_info = vectors.request();
338
345
  if (vectors_info.ndim == 1)
339
346
  return search_one_in_index(index, vectors, wanted, exact);
@@ -355,7 +362,7 @@ static py::tuple search_many_in_index(punned_py_t& index, py::buffer vectors, st
355
362
 
356
363
  // https://docs.python.org/3/library/struct.html#format-characters
357
364
  if (vectors_info.format == "c" || vectors_info.format == "b")
358
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
365
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
359
366
  search_config_t config;
360
367
  config.thread = thread_idx;
361
368
  config.exact = exact;
@@ -364,7 +371,7 @@ static py::tuple search_many_in_index(punned_py_t& index, py::buffer vectors, st
364
371
  index.search(vector, wanted, config).dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
365
372
  });
366
373
  else if (vectors_info.format == "e")
367
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
374
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
368
375
  search_config_t config;
369
376
  config.thread = thread_idx;
370
377
  config.exact = exact;
@@ -372,8 +379,8 @@ static py::tuple search_many_in_index(punned_py_t& index, py::buffer vectors, st
372
379
  counts_py1d(task_idx) = static_cast<Py_ssize_t>(
373
380
  index.search(vector, wanted, config).dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
374
381
  });
375
- else if (vectors_info.format == "f")
376
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
382
+ else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
383
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
377
384
  search_config_t config;
378
385
  config.thread = thread_idx;
379
386
  config.exact = exact;
@@ -381,8 +388,8 @@ static py::tuple search_many_in_index(punned_py_t& index, py::buffer vectors, st
381
388
  counts_py1d(task_idx) = static_cast<Py_ssize_t>(
382
389
  index.search(vector, wanted, config).dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
383
390
  });
384
- else if (vectors_info.format == "d")
385
- multithreaded(index.config().concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
391
+ else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
392
+ multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
386
393
  search_config_t config;
387
394
  config.thread = thread_idx;
388
395
  config.exact = exact;
@@ -405,6 +412,8 @@ template <typename index_at> void save_index(index_at const& index, std::string
405
412
  template <typename index_at> void load_index(index_at& index, std::string const& path) { index.load(path.c_str()); }
406
413
  template <typename index_at> void view_index(index_at& index, std::string const& path) { index.view(path.c_str()); }
407
414
  template <typename index_at> void clear_index(index_at& index) { index.clear(); }
415
+ template <typename index_at> std::size_t get_expansion_add(index_at const &index) { return index.config().expansion_add; }
416
+ template <typename index_at> std::size_t get_expansion_search(index_at const &index) { return index.config().expansion_search; }
408
417
  // clang-format on
409
418
 
410
419
  template <typename element_at> bool has_duplicates(element_at const* begin, element_at const* end) {
@@ -473,8 +482,12 @@ void hash_buffer(hash_index_py_t& index, py::buffer vector) {
473
482
  throw std::invalid_argument("Array elements must be 16, 32, or 64 bit hashable integers!");
474
483
  }
475
484
 
476
- PYBIND11_MODULE(index, m) {
477
- m.doc() = "Unum USearch Python bindings";
485
+ PYBIND11_MODULE(compiled, m) {
486
+ m.doc() = "Smaller & Faster Single-File Vector Search Engine from Unum";
487
+
488
+ m.attr("DEFAULT_CONNECTIVITY") = py::int_(default_connectivity());
489
+ m.attr("DEFAULT_EXPANSION_ADD") = py::int_(default_expansion_add());
490
+ m.attr("DEFAULT_EXPANSION_SEARCH") = py::int_(default_expansion_search());
478
491
 
479
492
  auto i = py::class_<punned_py_t>(m, "Index");
480
493
 
@@ -505,14 +518,16 @@ PYBIND11_MODULE(index, m) {
505
518
  py::arg("label"), //
506
519
  py::arg("vector"), //
507
520
  py::kw_only(), //
508
- py::arg("copy") = true //
521
+ py::arg("copy") = true, //
522
+ py::arg("threads") = 0 //
509
523
  );
510
524
 
511
525
  i.def( //
512
526
  "search", &search_many_in_index, //
513
527
  py::arg("query"), //
514
528
  py::arg("count") = 10, //
515
- py::arg("exact") = false //
529
+ py::arg("exact") = false, //
530
+ py::arg("threads") = 0 //
516
531
  );
517
532
 
518
533
  i.def("__len__", &punned_py_t::size);
@@ -522,6 +537,10 @@ PYBIND11_MODULE(index, m) {
522
537
  i.def_property_readonly("capacity", &punned_py_t::capacity);
523
538
  i.def_property_readonly( //
524
539
  "dtype", [](punned_py_t const& index) -> std::string { return accuracy_name(index.accuracy()); });
540
+ i.def_property_readonly("memory_usage", &punned_py_t::memory_usage);
541
+
542
+ i.def_property("expansion_add", &get_expansion_add<punned_py_t>, &punned_py_t::change_expansion_add);
543
+ i.def_property("expansion_search", &get_expansion_search<punned_py_t>, &punned_py_t::change_expansion_search);
525
544
 
526
545
  i.def("save", &save_index<punned_py_t>, py::arg("path"));
527
546
  i.def("load", &load_index<punned_py_t>, py::arg("path"));
@@ -142,7 +142,7 @@ def main(
142
142
  expansion_add=expansion_add,
143
143
  expansion_search=expansion_search,
144
144
  connectivity=connectivity,
145
- metric_pointer=inner_product_f32.address,
145
+ metric=inner_product_f32.address,
146
146
  )
147
147
  bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
148
148
 
@@ -153,7 +153,7 @@ def main(
153
153
  expansion_add=expansion_add,
154
154
  expansion_search=expansion_search,
155
155
  connectivity=connectivity,
156
- metric_pointer=cos_f8.address,
156
+ metric=cos_f8.address,
157
157
  )
158
158
  bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
159
159
  except ImportError:
@@ -86,7 +86,7 @@ def test_user_defined_function(ndim: int, size: int):
86
86
  c += a_array[i] * b_array[i]
87
87
  return c
88
88
 
89
- index = Index(ndim=ndim, metric_pointer=python_dot.address)
89
+ index = Index(ndim=ndim, metric=python_dot.address)
90
90
 
91
91
  vectors = np.random.uniform(
92
92
  0, 0.3, (size, index.ndim)).astype(np.float32)
@@ -0,0 +1,298 @@
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
+ import os
6
+ from math import sqrt
7
+ from typing import Optional, Callable, Union
8
+
9
+ import numpy as np
10
+
11
+ from usearch.compiled import Index as _CompiledIndex
12
+ from usearch.compiled import SetsIndex as _CompiledSetsIndex
13
+ from usearch.compiled import HashIndex as _CompiledHashIndex
14
+ from usearch.compiled import DEFAULT_CONNECTIVITY, DEFAULT_EXPANSION_ADD, DEFAULT_EXPANSION_SEARCH
15
+
16
+ Triplet = tuple[np.ndarray, np.ndarray, np.ndarray]
17
+ SetsIndex = _CompiledSetsIndex
18
+ HashIndex = _CompiledHashIndex
19
+
20
+
21
+ def results_to_list(results: Triplet, row: int) -> list[dict]:
22
+
23
+ count = results[2][row]
24
+ labels = results[0][row, :count]
25
+ distances = results[1][row, :count]
26
+ return [
27
+ {'label': int(label), 'distance': float(distance)}
28
+ for label, distance in zip(labels, distances)
29
+ ]
30
+
31
+
32
+ def jit_metric(ndim: int, metric_name: str, accuracy: str = 'f32') -> Callable:
33
+
34
+ try:
35
+ from numba import cfunc, types, carray
36
+ except ImportError:
37
+ raise ModuleNotFoundError(
38
+ 'To use JIT install Numba with `pip install numba`.'
39
+ 'Alternatively, reinstall usearch with `pip install usearch[jit]`')
40
+
41
+ # Showcases how to use Numba to JIT-compile similarity measures for USearch.
42
+ # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
43
+
44
+ if accuracy == 'f32':
45
+ signature = types.float32(
46
+ types.CPointer(types.float32),
47
+ types.CPointer(types.float32),
48
+ types.size_t, types.size_t)
49
+
50
+ if metric_name == 'ip':
51
+
52
+ def numba_ip(a, b, _n, _m):
53
+ a_array = carray(a, ndim)
54
+ b_array = carray(b, ndim)
55
+ ab = 0.0
56
+ for i in range(ndim):
57
+ ab += a_array[i] * b_array[i]
58
+ return ab
59
+
60
+ return cfunc(numba_ip, signature)
61
+
62
+ if metric_name == 'cos':
63
+
64
+ def numba_cos(a, b, _n, _m):
65
+ a_array = carray(a, ndim)
66
+ b_array = carray(b, ndim)
67
+ ab = 0.0
68
+ a_sq = 0.0
69
+ b_sq = 0.0
70
+ for i in range(ndim):
71
+ ab += a_array[i] * b_array[i]
72
+ a_sq += a_array[i] * a_array[i]
73
+ b_sq += b_array[i] * b_array[i]
74
+ return ab / (sqrt(a_sq) * sqrt(b_sq))
75
+
76
+ return cfunc(numba_cos, signature)
77
+
78
+ if metric_name == 'l2sq':
79
+
80
+ def numba_l2sq(a, b, _n, _m):
81
+ a_array = carray(a, ndim)
82
+ b_array = carray(b, ndim)
83
+ ab_delta_sq = 0.0
84
+ for i in range(ndim):
85
+ ab_delta_sq += (a_array[i] - b_array[i]) * \
86
+ (a_array[i] - b_array[i])
87
+ return ab_delta_sq
88
+
89
+ return cfunc(numba_l2sq, signature)
90
+
91
+ return None
92
+
93
+
94
+ class Index:
95
+ """Fast JIT-compiled index for equi-dimensional embeddings.
96
+
97
+ Vector labels must be integers.
98
+ Vectors must have the same number of dimensions within the index.
99
+ Supports Inner Product, Cosine Distance, Ln measures
100
+ like the Euclidean metric, as well as automatic downcasting
101
+ and quantization.
102
+ """
103
+
104
+ def __init__(
105
+ self,
106
+ ndim: int,
107
+ dtype: str = 'f32',
108
+ metric: Union[str, int] = 'ip',
109
+ jit: bool = False,
110
+
111
+ connectivity: int = DEFAULT_CONNECTIVITY,
112
+ expansion_add: int = DEFAULT_EXPANSION_ADD,
113
+ expansion_search: int = DEFAULT_EXPANSION_SEARCH,
114
+ tune: bool = False,
115
+
116
+ path: Optional[os.PathLike] = None,
117
+ view: bool = False,
118
+ ) -> None:
119
+ """Construct the index and compiles the functions, if requested (expensive).
120
+
121
+ :param ndim: Number of vector dimensions
122
+ :type ndim: int
123
+ :param dtype: Scalar type for vectors, defaults to 'f32'
124
+ :type dtype: str, optional
125
+ Example: you can use the `f16` index with `f32` vectors,
126
+ which will be automatically downcasted.
127
+
128
+ :param metric: Distance function, defaults to 'ip'
129
+ :type metric: Union[str, int], optional
130
+ Name of distance function, or the address of the
131
+ Numba `cfunc` JIT-compiled object.
132
+
133
+ :param jit: Enable Numba to JIT compile the metric, defaults to False
134
+ :type jit: bool, optional
135
+ This can result in up-to 3x performance difference on very large vectors
136
+ and very recent hardware, as the Python module is compiled with high
137
+ compatibility in mind and avoids very fancy assembly instructions.
138
+
139
+ :param connectivity: Connections per node in HNSW, defaults to None
140
+ :type connectivity: Optional[int], optional
141
+ Hyper-parameter for the number of Graph connections
142
+ per layer of HNSW. The original paper calls it "M".
143
+ Optional, but can't be changed after construction.
144
+
145
+ :param expansion_add: Traversal depth on insertions, defaults to None
146
+ :type expansion_add: Optional[int], optional
147
+ Hyper-parameter for the search depth when inserting new
148
+ vectors. The original paper calls it "efConstruction".
149
+ Can be changed afterwards, as the `.expansion_add`.
150
+
151
+ :param expansion_search: Traversal depth on queries, defaults to None
152
+ :type expansion_search: Optional[int], optional
153
+ Hyper-parameter for the search depth when querying
154
+ nearest neighbors. The original paper calls it "ef".
155
+ Can be changed afterwards, as the `.expansion_search`.
156
+
157
+ :param tune: Automatically adjusts hyper-parameters, defaults to False
158
+ :type tune: bool, optional
159
+
160
+ :param path: Where to store the index, defaults to None
161
+ :type path: Optional[os.PathLike], optional
162
+ :param view: Are we simply viewing an immutable index, defaults to False
163
+ :type view: bool, optional
164
+ """
165
+ if jit:
166
+ assert isinstance(metric, str), 'Name the metric to JIT'
167
+ self._metric_name = metric
168
+ self._metric_jit = jit_metric(
169
+ ndim=ndim,
170
+ metric_name=metric,
171
+ dtype=dtype,
172
+ )
173
+ self._metric_pointer = self._metric_jit.address if \
174
+ self._metric_jit else 0
175
+
176
+ elif isinstance(metric, int):
177
+ self._metric_name = ''
178
+ self._metric_jit = None
179
+ self._metric_pointer = metric
180
+
181
+ else:
182
+ if metric is None:
183
+ metric = 'ip'
184
+ assert isinstance(metric, str)
185
+ self._metric_name = metric
186
+ self._metric_jit = None
187
+ self._metric_pointer = 0
188
+
189
+ self._compiled = _CompiledIndex(
190
+ ndim=ndim,
191
+ metric=self._metric_name,
192
+ metric_pointer=self._metric_pointer,
193
+ dtype=dtype,
194
+ connectivity=connectivity,
195
+ expansion_add=expansion_add,
196
+ expansion_search=expansion_search,
197
+ tune=tune,
198
+ )
199
+
200
+ self.path = path
201
+ if path and os.path.exists(path):
202
+ if view:
203
+ self._compiled.view(path)
204
+ else:
205
+ self._compiled.load(path)
206
+
207
+ def add(
208
+ self, labels, vectors, *,
209
+ copy: bool = True, threads: int = 0):
210
+ """Inserts one or move vectors into the index.
211
+
212
+ For maximal performance the `labels` and `vectors`
213
+ should conform to the Python's "buffer protocol" spec.
214
+
215
+ To index a single entry:
216
+ labels: int, vectors: np.ndarray.
217
+ To index many entries:
218
+ labels: np.ndarray, vectors: np.ndarray.
219
+
220
+ When working with extremely large indexes, you may want to
221
+ pass `copy=False`, if you can guarantee the lifetime of the
222
+ primary vectors store during the process of construction.
223
+
224
+ :param labels: Unique identifier for passed vectors.
225
+ :type labels: Buffer
226
+ :param vectors: Collection of vectors.
227
+ :type vectors: Buffer
228
+ :param copy: Should the index store a copy of vectors, defaults to True
229
+ :type copy: bool, optional
230
+ :param threads: Optimal number of cores to use, defaults to 0
231
+ :type threads: int, optional
232
+ """
233
+ if isinstance(labels, np.ndarray):
234
+ labels = labels.astype(np.longlong)
235
+ self._compiled.add(labels, vectors, copy=copy, threads=threads)
236
+
237
+ def search(
238
+ self, vectors, k: int = 10, *,
239
+ threads: int = 0, exact: bool = False) -> Triplet:
240
+ return self._compiled.search(vectors, k, exact=exact, threads=threads)
241
+
242
+ def __len__(self) -> int:
243
+ return len(self._compiled)
244
+
245
+ @property
246
+ def size(self) -> int:
247
+ return self._compiled.size
248
+
249
+ @property
250
+ def ndim(self) -> int:
251
+ return self._compiled.ndim
252
+
253
+ @property
254
+ def dtype(self) -> str:
255
+ return self._compiled.dtype
256
+
257
+ @property
258
+ def connectivity(self) -> int:
259
+ return self._compiled.connectivity
260
+
261
+ @property
262
+ def capacity(self) -> int:
263
+ return self._compiled.capacity
264
+
265
+ @property
266
+ def memory_usage(self) -> int:
267
+ return self._compiled.memory_usage
268
+
269
+ @property
270
+ def expansion_add(self) -> int:
271
+ return self._compiled.expansion_add
272
+
273
+ @property
274
+ def expansion_search(self) -> int:
275
+ return self._compiled.expansion_search
276
+
277
+ @expansion_add.setter
278
+ def change_expansion_add(self, v: int):
279
+ self._compiled.expansion_add = v
280
+
281
+ @expansion_search.setter
282
+ def change_expansion_search(self, v: int):
283
+ self._compiled.expansion_search = v
284
+
285
+ def save(self, path: os.PathLike):
286
+ self._compiled.save(path)
287
+
288
+ def load(self, path: os.PathLike):
289
+ self._compiled.load(path)
290
+
291
+ def view(self, path: os.PathLike):
292
+ self._compiled.view(path)
293
+
294
+ def clear(self):
295
+ self._compiled.clear()
296
+
297
+ def remove(self, label: int):
298
+ pass
@@ -0,0 +1,5 @@
1
+ comment: off
2
+ coverage:
3
+ status:
4
+ project: off
5
+ patch: off
@@ -0,0 +1,136 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request, release]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ config:
11
+ - {
12
+ name: linux-x64-gcc-7,
13
+ os: ubuntu-18.04,
14
+ cxx: g++-7,
15
+ cmake-build-type: Release
16
+ }
17
+ - {
18
+ name: linux-x64-gcc-7-no-exceptions,
19
+ os: ubuntu-18.04,
20
+ cxx: g++-7,
21
+ cxx-flags: -fno-exceptions,
22
+ cmake-build-type: Release
23
+ }
24
+ - {
25
+ name: linux-x64-clang-9,
26
+ os: ubuntu-18.04,
27
+ cxx: clang++-9,
28
+ cmake-build-type: Release
29
+ }
30
+ - {
31
+ name: macos-x64-gcc,
32
+ os: macos-10.15,
33
+ cxx: g++,
34
+ cmake-build-type: Release
35
+ }
36
+ - {
37
+ name: macos-x64-clang,
38
+ os: macos-10.15,
39
+ cxx: clang++,
40
+ cmake-build-type: Release
41
+ }
42
+ - {
43
+ name: linux-x64-clang-12-sanitize,
44
+ os: ubuntu-20.04,
45
+ cxx: clang++-12,
46
+ cxx-flags: "-fsanitize=address,undefined",
47
+ cmake-build-type: Release
48
+ }
49
+ - {
50
+ name: linux-x64-gcc-10-coverage,
51
+ os: ubuntu-20.04,
52
+ cxx: g++-10,
53
+ cxx-flags: --coverage,
54
+ gcov-tool: gcov-10,
55
+ cmake-build-type: Debug
56
+ }
57
+ - {
58
+ name: windows-x64-vs-2019,
59
+ os: windows-2019,
60
+ cmake-build-type: Release,
61
+ cmake-generator: Visual Studio 16 2019,
62
+ cmake-platform: x64,
63
+ vcpkg-triplet: x64-windows-static-md
64
+ }
65
+ - {
66
+ name: windows-x86-vs-2019,
67
+ os: windows-2019,
68
+ cmake-build-type: Release,
69
+ cmake-generator: Visual Studio 16 2019,
70
+ cmake-platform: Win32,
71
+ vcpkg-triplet: x86-windows-static-md
72
+ }
73
+ - {
74
+ name: windows-x64-vs-2022,
75
+ os: windows-2022,
76
+ cmake-build-type: Release,
77
+ cmake-generator: Visual Studio 17 2022,
78
+ cmake-platform: x64,
79
+ vcpkg-triplet: x64-windows-static-md
80
+ }
81
+ - {
82
+ name: windows-x86-vs-2022,
83
+ os: windows-2022,
84
+ cmake-build-type: Release,
85
+ cmake-generator: Visual Studio 17 2022,
86
+ cmake-platform: Win32,
87
+ vcpkg-triplet: x86-windows-static-md
88
+ }
89
+ name: ${{matrix.config.name}}
90
+ runs-on: ${{matrix.config.os}}
91
+ steps:
92
+ - uses: actions/checkout@v2
93
+
94
+ # Windows
95
+ - name: Install boost (Windows)
96
+ run: vcpkg install boost-test:${{matrix.config.vcpkg-triplet}}
97
+ if: runner.os == 'Windows'
98
+
99
+ - name: Configure CMake (Windows)
100
+ run: cmake -G "${{matrix.config.cmake-generator}}" -A ${{matrix.config.cmake-platform}} -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=${{matrix.config.vcpkg-triplet}} -S ${{github.workspace}}/tests -B ${{github.workspace}}/build
101
+ if: runner.os == 'Windows'
102
+
103
+ - name: Build (Windows)
104
+ run: cmake --build ${{github.workspace}}/build --config ${{matrix.config.cmake-build-type}} --verbose
105
+ if: runner.os == 'Windows'
106
+
107
+ - name: Test (Windows)
108
+ run: ${{github.workspace}}/build/${{matrix.config.cmake-build-type}}/tsl_robin_map_tests.exe
109
+ if: runner.os == 'Windows'
110
+
111
+ # Linux or macOS
112
+ - name: Install boost (Linux or macOS)
113
+ run: vcpkg install boost-test
114
+ if: runner.os == 'Linux' || runner.os == 'macOS'
115
+
116
+ - name: Configure CMake (Linux or macOS)
117
+ run: cmake -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -S ${{github.workspace}}/tests -B ${{github.workspace}}/build
118
+ env:
119
+ CXX: ${{matrix.config.cxx}}
120
+ CXXFLAGS: ${{matrix.config.cxx-flags}}
121
+ if: runner.os == 'Linux' || runner.os == 'macOS'
122
+
123
+ - name: Build (Linux or macOS)
124
+ run: cmake --build ${{github.workspace}}/build --verbose
125
+ if: runner.os == 'Linux' || runner.os == 'macOS'
126
+
127
+ - name: Test (Linux or macOS)
128
+ run: ${{github.workspace}}/build/tsl_robin_map_tests
129
+ if: runner.os == 'Linux' || runner.os == 'macOS'
130
+
131
+ - name: Coverage
132
+ run: |
133
+ sudo apt-get install -y lcov
134
+ lcov -c -b ${{github.workspace}}/include -d ${{github.workspace}}/build -o ${{github.workspace}}/coverage.info --no-external --gcov-tool ${{matrix.config.gcov-tool}}
135
+ bash <(curl -s https://codecov.io/bash) -f ${{github.workspace}}/coverage.info
136
+ if: ${{matrix.config.name == 'linux-x64-gcc-10-coverage'}}