usearch 2.8.7 → 2.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CITATION.cff +1 -1
- package/CMakeLists.txt +3 -3
- package/CONTRIBUTING.md +26 -3
- package/Cargo.toml +1 -1
- package/Dockerfile +16 -0
- package/README.md +41 -38
- package/VERSION +1 -0
- package/binding.gyp +57 -8
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +28 -24
- package/javascript/README.md +64 -12
- package/javascript/lib.cpp +11 -3
- package/javascript/usearch.js +4 -2
- package/javascript/usearch.test.js +15 -0
- package/package.json +1 -2
- package/setup.py +76 -15
- package/simsimd/README.md +14 -13
- package/simsimd/include/simsimd/probability.h +2 -2
- package/simsimd/package.json +1 -1
- package/wasmer.toml +1 -1
- package/. npmignore +0 -14
- package/.gitignore +0 -95
- package/.vscode/launch.json +0 -181
- package/.vscode/settings.json +0 -180
- package/.vscode/tasks.json +0 -71
- package/Package.swift +0 -41
- package/csharp/.editorconfig +0 -361
- package/csharp/Cloud.Unum.USearch.sln +0 -34
- package/csharp/DEV_SETUP.md +0 -51
- package/csharp/Directory.Build.props +0 -29
- package/csharp/README.md +0 -74
- package/csharp/build_and_test.cmd +0 -31
- package/csharp/build_and_test.sh +0 -30
- package/csharp/nuget/nuget-package.props +0 -33
- package/csharp/pack.sh +0 -56
- package/csharp/src/Cloud.Unum.USearch.Tests/Cloud.Unum.USearch.Tests.csproj +0 -39
- package/csharp/src/Cloud.Unum.USearch.Tests/USearchIndexTests.cs +0 -1013
- package/csharp/src/Cloud.Unum.USearch.Tests/Usings.cs +0 -1
- package/docs/_static/custom.css +0 -26
- package/docs/_static/custom.js +0 -7
- package/docs/benchmarks.md +0 -245
- package/docs/benchmarks.rst +0 -5
- package/docs/c/index.rst +0 -11
- package/docs/c/reference.rst +0 -7
- package/docs/conf.dox +0 -2668
- package/docs/conf.py +0 -53
- package/docs/contributing.rst +0 -5
- package/docs/cpp/index.rst +0 -11
- package/docs/cpp/reference.rst +0 -17
- package/docs/csharp/index.rst +0 -6
- package/docs/format.md +0 -86
- package/docs/format.rst +0 -5
- package/docs/golang/index.rst +0 -6
- package/docs/index.rst +0 -33
- package/docs/java/index.rst +0 -11
- package/docs/java/reference.rst +0 -7
- package/docs/javascript/index.rst +0 -11
- package/docs/javascript/reference.rst +0 -12
- package/docs/python/index.rst +0 -11
- package/docs/python/reference.rst +0 -42
- package/docs/rust/index.rst +0 -6
- package/docs/swift/index.rst +0 -6
- package/docs/wolfram/index.rst +0 -6
- package/golang/README.md +0 -95
- package/golang/go.mod +0 -3
- package/golang/lib.go +0 -379
- package/golang/lib_test.go +0 -108
- package/java/README.md +0 -22
- package/java/cloud/unum/usearch/Index.java +0 -337
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +0 -197
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.h +0 -125
- package/java/test/IndexTest.java +0 -30
- package/objc/USearchObjective.mm +0 -299
- package/objc/include/USearchObjective.h +0 -154
- package/python/README.md +0 -292
- package/python/lib.cpp +0 -1216
- package/python/scripts/bench.ipynb +0 -193
- package/python/scripts/bench.py +0 -137
- package/python/scripts/bench_exact.py +0 -44
- package/python/scripts/cluster.ipynb +0 -160
- package/python/scripts/index_faiss.py +0 -100
- package/python/scripts/join.py +0 -155
- package/python/scripts/test_index.py +0 -247
- package/python/scripts/test_jit.py +0 -231
- package/python/scripts/test_sparse.py +0 -32
- package/python/scripts/test_tooling.py +0 -133
- package/python/usearch/__init__.py +0 -0
- package/python/usearch/client.py +0 -124
- package/python/usearch/eval.py +0 -470
- package/python/usearch/index.py +0 -1401
- package/python/usearch/io.py +0 -113
- package/python/usearch/numba.py +0 -121
- package/python/usearch/server.py +0 -137
- package/rust/README.md +0 -66
- package/rust/lib.cpp +0 -136
- package/rust/lib.hpp +0 -69
- package/rust/lib.rs +0 -610
- package/simsimd/CMakeLists.txt +0 -67
- package/simsimd/cpp/bench.cxx +0 -206
- package/simsimd/package-ci.json +0 -10
- package/simsimd/setup.py +0 -86
- package/swift/Index+Sugar.swift +0 -153
- package/swift/README.md +0 -48
- package/swift/Test.swift +0 -37
- package/swift/USearch.swift +0 -8
- package/wasm/CMakeLists.txt +0 -7
- package/wasm/README.md +0 -13
- package/wasm/lib.cpp +0 -5
- package/wolfram/CMakeLists.txt +0 -18
- package/wolfram/README.md +0 -13
- package/wolfram/lib.cpp +0 -160
- package/wolfram/test.wls +0 -27
package/python/lib.cpp
DELETED
|
@@ -1,1216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file python.cpp
|
|
3
|
-
* @author Ash Vardanian
|
|
4
|
-
* @brief Python bindings for Unum USearch.
|
|
5
|
-
* @date 2023-04-26
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* https://pythoncapi.readthedocs.io/type_object.html
|
|
9
|
-
* https://numpy.org/doc/stable/reference/c-api/types-and-structures.html
|
|
10
|
-
* https://pythonextensionpatterns.readthedocs.io/en/latest/refcount.html
|
|
11
|
-
* https://docs.python.org/3/extending/newtypes_tutorial.html#adding-data-and-methods-to-the-basic-example
|
|
12
|
-
*
|
|
13
|
-
* @copyright Copyright (c) 2023
|
|
14
|
-
*/
|
|
15
|
-
#if !defined(__cpp_exceptions)
|
|
16
|
-
#define __cpp_exceptions 1
|
|
17
|
-
#endif
|
|
18
|
-
|
|
19
|
-
#include <limits> // `std::numeric_limits`
|
|
20
|
-
#include <thread> // `std::thread`
|
|
21
|
-
|
|
22
|
-
#define _CRT_SECURE_NO_WARNINGS
|
|
23
|
-
#define PY_SSIZE_T_CLEAN
|
|
24
|
-
#include <pybind11/functional.h>
|
|
25
|
-
#include <pybind11/numpy.h>
|
|
26
|
-
#include <pybind11/pybind11.h>
|
|
27
|
-
#include <pybind11/stl.h>
|
|
28
|
-
|
|
29
|
-
#include <usearch/index_dense.hpp>
|
|
30
|
-
#include <usearch/index_plugins.hpp>
|
|
31
|
-
|
|
32
|
-
using namespace unum::usearch;
|
|
33
|
-
using namespace unum;
|
|
34
|
-
|
|
35
|
-
namespace py = pybind11;
|
|
36
|
-
using py_shape_t = py::array::ShapeContainer;
|
|
37
|
-
|
|
38
|
-
using metric_t = metric_punned_t;
|
|
39
|
-
using distance_t = distance_punned_t;
|
|
40
|
-
|
|
41
|
-
using dense_key_t = typename index_dense_t::vector_key_t;
|
|
42
|
-
using dense_add_result_t = typename index_dense_t::add_result_t;
|
|
43
|
-
using dense_search_result_t = typename index_dense_t::search_result_t;
|
|
44
|
-
using dense_labeling_result_t = typename index_dense_t::labeling_result_t;
|
|
45
|
-
using dense_cluster_result_t = typename index_dense_t::cluster_result_t;
|
|
46
|
-
using dense_clustering_result_t = typename index_dense_t::clustering_result_t;
|
|
47
|
-
|
|
48
|
-
using progress_func_t = std::function<bool(std::size_t /*processed*/, std::size_t /*total*/)>;
|
|
49
|
-
|
|
50
|
-
struct progress_t {
|
|
51
|
-
inline progress_t(std::nullptr_t = nullptr) : func_(&dummy_progress) {}
|
|
52
|
-
inline progress_t(progress_func_t const& func) : func_(func ? func : &dummy_progress) {}
|
|
53
|
-
inline bool operator()(std::size_t processed, std::size_t total) const noexcept { return func_(processed, total); }
|
|
54
|
-
|
|
55
|
-
private:
|
|
56
|
-
static inline bool dummy_progress(std::size_t /*processed*/, std::size_t /*total*/) { return true; }
|
|
57
|
-
|
|
58
|
-
progress_func_t func_;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
struct dense_index_py_t : public index_dense_t {
|
|
62
|
-
using native_t = index_dense_t;
|
|
63
|
-
using native_t::add;
|
|
64
|
-
using native_t::capacity;
|
|
65
|
-
using native_t::reserve;
|
|
66
|
-
using native_t::search;
|
|
67
|
-
using native_t::size;
|
|
68
|
-
|
|
69
|
-
dense_index_py_t(native_t&& base) : index_dense_t(std::move(base)) {}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
struct dense_indexes_py_t {
|
|
73
|
-
std::vector<std::shared_ptr<dense_index_py_t>> shards_;
|
|
74
|
-
|
|
75
|
-
void merge(std::shared_ptr<dense_index_py_t> shard) { shards_.push_back(shard); }
|
|
76
|
-
std::size_t bytes_per_vector() const noexcept { return shards_.empty() ? 0 : shards_[0]->bytes_per_vector(); }
|
|
77
|
-
std::size_t scalar_words() const noexcept { return shards_.empty() ? 0 : shards_[0]->scalar_words(); }
|
|
78
|
-
index_limits_t limits() const noexcept { return {size(), std::numeric_limits<std::size_t>::max()}; }
|
|
79
|
-
|
|
80
|
-
void merge_paths(std::vector<std::string> const& paths, bool view = true, std::size_t threads = 0) {
|
|
81
|
-
if (!threads)
|
|
82
|
-
threads = std::thread::hardware_concurrency();
|
|
83
|
-
|
|
84
|
-
shards_.reserve(shards_.size() + paths.size());
|
|
85
|
-
std::mutex shards_mutex;
|
|
86
|
-
executor_default_t{threads}.dynamic(paths.size(), [&](std::size_t, std::size_t task_idx) {
|
|
87
|
-
index_dense_t index = index_dense_t::make(paths[task_idx].c_str(), view);
|
|
88
|
-
if (!index)
|
|
89
|
-
return false;
|
|
90
|
-
auto shared_index = std::make_shared<dense_index_py_t>(std::move(index));
|
|
91
|
-
std::unique_lock<std::mutex> lock(shards_mutex);
|
|
92
|
-
shards_.push_back(shared_index);
|
|
93
|
-
if (PyErr_CheckSignals() != 0)
|
|
94
|
-
throw py::error_already_set();
|
|
95
|
-
return true;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
std::size_t size() const noexcept {
|
|
100
|
-
std::size_t result = 0;
|
|
101
|
-
for (auto const& shard : shards_)
|
|
102
|
-
result += shard->size();
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
static dense_index_py_t make_index( //
|
|
108
|
-
std::size_t dimensions, //
|
|
109
|
-
scalar_kind_t scalar_kind, //
|
|
110
|
-
std::size_t connectivity, //
|
|
111
|
-
std::size_t expansion_add, //
|
|
112
|
-
std::size_t expansion_search, //
|
|
113
|
-
metric_kind_t metric_kind, //
|
|
114
|
-
metric_punned_signature_t metric_signature, //
|
|
115
|
-
std::uintptr_t metric_uintptr, //
|
|
116
|
-
bool multi, //
|
|
117
|
-
bool enable_key_lookups) {
|
|
118
|
-
|
|
119
|
-
index_dense_config_t config(connectivity, expansion_add, expansion_search);
|
|
120
|
-
config.multi = multi;
|
|
121
|
-
config.enable_key_lookups = enable_key_lookups;
|
|
122
|
-
|
|
123
|
-
metric_t metric = //
|
|
124
|
-
metric_uintptr //
|
|
125
|
-
? metric_t(dimensions, metric_uintptr, metric_signature, metric_kind, scalar_kind)
|
|
126
|
-
: metric_t(dimensions, metric_kind, scalar_kind);
|
|
127
|
-
return index_dense_t::make(metric, config);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
scalar_kind_t numpy_string_to_kind(std::string const& name) {
|
|
131
|
-
// https://docs.python.org/3/library/struct.html#format-characters
|
|
132
|
-
if (name == "B" || name == "<B" || name == "u1" || name == "|u1")
|
|
133
|
-
return scalar_kind_t::b1x8_k;
|
|
134
|
-
else if (name == "b" || name == "<b" || name == "i1" || name == "|i1")
|
|
135
|
-
return scalar_kind_t::i8_k;
|
|
136
|
-
else if (name == "e" || name == "<e" || name == "f2" || name == "<f2")
|
|
137
|
-
return scalar_kind_t::f16_k;
|
|
138
|
-
else if (name == "f" || name == "<f" || name == "f4" || name == "<f4")
|
|
139
|
-
return scalar_kind_t::f32_k;
|
|
140
|
-
else if (name == "d" || name == "<d" || name == "i8" || name == "<i8")
|
|
141
|
-
return scalar_kind_t::f64_k;
|
|
142
|
-
else
|
|
143
|
-
return scalar_kind_t::unknown_k;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
template <typename result_at> void forward_error(result_at&& result) {
|
|
147
|
-
|
|
148
|
-
if (!result)
|
|
149
|
-
throw std::invalid_argument(result.error.release());
|
|
150
|
-
|
|
151
|
-
int signals = PyErr_CheckSignals();
|
|
152
|
-
if (signals != 0)
|
|
153
|
-
throw py::error_already_set();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
using atomic_error_t = std::atomic<char const*>;
|
|
157
|
-
|
|
158
|
-
template <typename scalar_at>
|
|
159
|
-
static void add_typed_to_index( //
|
|
160
|
-
dense_index_py_t& index, //
|
|
161
|
-
py::buffer_info const& keys_info, py::buffer_info const& vectors_info, //
|
|
162
|
-
bool force_copy, std::size_t threads, //
|
|
163
|
-
progress_func_t const& progress) {
|
|
164
|
-
|
|
165
|
-
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
166
|
-
byte_t const* vectors_data = reinterpret_cast<byte_t const*>(vectors_info.ptr);
|
|
167
|
-
byte_t const* keys_data = reinterpret_cast<byte_t const*>(keys_info.ptr);
|
|
168
|
-
atomic_error_t atomic_error{nullptr};
|
|
169
|
-
|
|
170
|
-
// Progress status
|
|
171
|
-
progress_t progress_{progress};
|
|
172
|
-
std::atomic<std::size_t> processed{0};
|
|
173
|
-
|
|
174
|
-
executor_default_t{threads}.dynamic(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
175
|
-
dense_key_t key = *reinterpret_cast<dense_key_t const*>(keys_data + task_idx * keys_info.strides[0]);
|
|
176
|
-
scalar_at const* vector = reinterpret_cast<scalar_at const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
177
|
-
dense_add_result_t result = index.add(key, vector, thread_idx, force_copy);
|
|
178
|
-
if (!result) {
|
|
179
|
-
atomic_error = result.error.release();
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// We don't want to check for signals from multiple threads
|
|
184
|
-
++processed;
|
|
185
|
-
if (thread_idx == 0)
|
|
186
|
-
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), vectors_count)) {
|
|
187
|
-
atomic_error.store("Operation has been terminated");
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
return true;
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
194
|
-
progress_(processed.load(), vectors_count);
|
|
195
|
-
|
|
196
|
-
// Raise the error from a single thread
|
|
197
|
-
auto error = atomic_error.load();
|
|
198
|
-
if (error) {
|
|
199
|
-
PyErr_SetString(PyExc_RuntimeError, error);
|
|
200
|
-
throw py::error_already_set();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
template <typename index_at>
|
|
205
|
-
static void add_many_to_index( //
|
|
206
|
-
index_at& index, py::buffer keys, py::buffer vectors, //
|
|
207
|
-
bool force_copy, std::size_t threads, //
|
|
208
|
-
progress_func_t const& progress) {
|
|
209
|
-
|
|
210
|
-
py::buffer_info keys_info = keys.request();
|
|
211
|
-
py::buffer_info vectors_info = vectors.request();
|
|
212
|
-
|
|
213
|
-
if (keys_info.itemsize != sizeof(dense_key_t))
|
|
214
|
-
throw std::invalid_argument("Incompatible key type!");
|
|
215
|
-
|
|
216
|
-
if (keys_info.ndim != 1)
|
|
217
|
-
throw std::invalid_argument("Keys must be placed in a single-dimensional array!");
|
|
218
|
-
|
|
219
|
-
if (vectors_info.ndim != 2)
|
|
220
|
-
throw std::invalid_argument("Expects a matrix of vectors to add!");
|
|
221
|
-
|
|
222
|
-
Py_ssize_t keys_count = keys_info.shape[0];
|
|
223
|
-
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
224
|
-
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
225
|
-
if (vectors_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
226
|
-
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
227
|
-
|
|
228
|
-
if (keys_count != vectors_count)
|
|
229
|
-
throw std::invalid_argument("Number of keys and vectors must match!");
|
|
230
|
-
|
|
231
|
-
if (!threads)
|
|
232
|
-
threads = std::thread::hardware_concurrency();
|
|
233
|
-
if (!index.reserve(index_limits_t(ceil2(index.size() + vectors_count), threads)))
|
|
234
|
-
throw std::invalid_argument("Out of memory!");
|
|
235
|
-
|
|
236
|
-
// clang-format off
|
|
237
|
-
switch (numpy_string_to_kind(vectors_info.format)) {
|
|
238
|
-
case scalar_kind_t::b1x8_k: add_typed_to_index<b1x8_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
239
|
-
case scalar_kind_t::i8_k: add_typed_to_index<i8_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
240
|
-
case scalar_kind_t::f16_k: add_typed_to_index<f16_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
241
|
-
case scalar_kind_t::f32_k: add_typed_to_index<f32_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
242
|
-
case scalar_kind_t::f64_k: add_typed_to_index<f64_t>(index, keys_info, vectors_info, force_copy, threads, progress); break;
|
|
243
|
-
default: throw std::invalid_argument("Incompatible scalars in the vectors matrix: " + vectors_info.format);
|
|
244
|
-
}
|
|
245
|
-
// clang-format on
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
template <typename scalar_at>
|
|
249
|
-
static void search_typed( //
|
|
250
|
-
dense_index_py_t& index, py::buffer_info& vectors_info, //
|
|
251
|
-
std::size_t wanted, bool exact, std::size_t threads, //
|
|
252
|
-
py::array_t<dense_key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
|
|
253
|
-
std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances,
|
|
254
|
-
progress_func_t const& progress) {
|
|
255
|
-
|
|
256
|
-
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
257
|
-
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
258
|
-
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
259
|
-
|
|
260
|
-
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
261
|
-
byte_t const* vectors_data = reinterpret_cast<byte_t const*>(vectors_info.ptr);
|
|
262
|
-
|
|
263
|
-
if (!threads)
|
|
264
|
-
threads = std::thread::hardware_concurrency();
|
|
265
|
-
if (!index.reserve(index_limits_t(index.size(), threads)))
|
|
266
|
-
throw std::invalid_argument("Out of memory!");
|
|
267
|
-
|
|
268
|
-
// Progress status
|
|
269
|
-
progress_t progress_{progress};
|
|
270
|
-
std::atomic<std::size_t> processed{0};
|
|
271
|
-
|
|
272
|
-
atomic_error_t atomic_error{nullptr};
|
|
273
|
-
executor_default_t{threads}.dynamic(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
274
|
-
scalar_at const* vector = (scalar_at const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
275
|
-
dense_search_result_t result = index.search(vector, wanted, thread_idx, exact);
|
|
276
|
-
if (!result) {
|
|
277
|
-
atomic_error = result.error.release();
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
counts_py1d(task_idx) =
|
|
282
|
-
static_cast<Py_ssize_t>(result.dump_to(&keys_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
283
|
-
|
|
284
|
-
stats_visited_members += result.visited_members;
|
|
285
|
-
stats_computed_distances += result.computed_distances;
|
|
286
|
-
|
|
287
|
-
// We don't want to check for signals from multiple threads
|
|
288
|
-
++processed;
|
|
289
|
-
if (thread_idx == 0)
|
|
290
|
-
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), vectors_count)) {
|
|
291
|
-
atomic_error.store("Operation has been terminated");
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
298
|
-
progress_(processed.load(), vectors_count);
|
|
299
|
-
|
|
300
|
-
// Raise the error from a single thread
|
|
301
|
-
auto error = atomic_error.load();
|
|
302
|
-
if (error) {
|
|
303
|
-
PyErr_SetString(PyExc_RuntimeError, error);
|
|
304
|
-
throw py::error_already_set();
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
template <typename scalar_at>
|
|
309
|
-
static void search_typed( //
|
|
310
|
-
dense_indexes_py_t& indexes, py::buffer_info& vectors_info, //
|
|
311
|
-
std::size_t wanted, bool exact, std::size_t threads, //
|
|
312
|
-
py::array_t<dense_key_t>& keys_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py,
|
|
313
|
-
std::atomic<std::size_t>& stats_visited_members, std::atomic<std::size_t>& stats_computed_distances,
|
|
314
|
-
progress_func_t const& progress) {
|
|
315
|
-
|
|
316
|
-
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
317
|
-
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
318
|
-
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
319
|
-
|
|
320
|
-
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
321
|
-
byte_t const* vectors_data = reinterpret_cast<byte_t const*>(vectors_info.ptr);
|
|
322
|
-
for (std::size_t vector_idx = 0; vector_idx != static_cast<std::size_t>(vectors_count); ++vector_idx)
|
|
323
|
-
counts_py1d(vector_idx) = 0;
|
|
324
|
-
|
|
325
|
-
if (!threads)
|
|
326
|
-
threads = std::thread::hardware_concurrency();
|
|
327
|
-
|
|
328
|
-
bitset_t query_mutexes(static_cast<std::size_t>(vectors_count));
|
|
329
|
-
if (!query_mutexes)
|
|
330
|
-
throw std::bad_alloc();
|
|
331
|
-
|
|
332
|
-
// Progress status
|
|
333
|
-
progress_t progress_{progress};
|
|
334
|
-
std::atomic<std::size_t> processed{0};
|
|
335
|
-
|
|
336
|
-
atomic_error_t atomic_error{nullptr};
|
|
337
|
-
executor_default_t{threads}.dynamic(indexes.shards_.size(), [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
338
|
-
dense_index_py_t& index = *indexes.shards_[task_idx].get();
|
|
339
|
-
|
|
340
|
-
index_limits_t limits;
|
|
341
|
-
limits.members = index.size();
|
|
342
|
-
limits.threads_add = 0;
|
|
343
|
-
limits.threads_search = 1;
|
|
344
|
-
if (!index.reserve(limits)) {
|
|
345
|
-
atomic_error = "Out of memory!";
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
for (std::size_t vector_idx = 0; vector_idx != static_cast<std::size_t>(vectors_count); ++vector_idx) {
|
|
350
|
-
scalar_at const* vector = (scalar_at const*)(vectors_data + vector_idx * vectors_info.strides[0]);
|
|
351
|
-
dense_search_result_t result = index.search(vector, wanted, 0, exact);
|
|
352
|
-
if (!result) {
|
|
353
|
-
atomic_error = result.error.release();
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
{
|
|
358
|
-
auto lock = query_mutexes.lock(vector_idx);
|
|
359
|
-
counts_py1d(vector_idx) = static_cast<Py_ssize_t>(result.merge_into( //
|
|
360
|
-
&keys_py2d(vector_idx, 0), //
|
|
361
|
-
&distances_py2d(vector_idx, 0), //
|
|
362
|
-
static_cast<std::size_t>(counts_py1d(vector_idx)), //
|
|
363
|
-
wanted));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
stats_visited_members += result.visited_members;
|
|
367
|
-
stats_computed_distances += result.computed_distances;
|
|
368
|
-
|
|
369
|
-
// We don't want to check for signals from multiple threads
|
|
370
|
-
++processed;
|
|
371
|
-
if (thread_idx == 0)
|
|
372
|
-
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), indexes.shards_.size())) {
|
|
373
|
-
atomic_error.store("Operation has been terminated");
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return true;
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
381
|
-
progress_(processed.load(), indexes.shards_.size());
|
|
382
|
-
|
|
383
|
-
// Raise the error from a single thread
|
|
384
|
-
auto error = atomic_error.load();
|
|
385
|
-
if (error) {
|
|
386
|
-
PyErr_SetString(PyExc_RuntimeError, error);
|
|
387
|
-
throw py::error_already_set();
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* @param vectors Matrix of vectors to search for.
|
|
393
|
-
* @param wanted Number of matches per request.
|
|
394
|
-
*
|
|
395
|
-
* @return Tuple with:
|
|
396
|
-
* 1. matrix of neighbors,
|
|
397
|
-
* 2. matrix of distances,
|
|
398
|
-
* 3. array with match counts,
|
|
399
|
-
* 4. number of visited nodes,
|
|
400
|
-
* 4. number of computed pairwise distances.
|
|
401
|
-
*/
|
|
402
|
-
template <typename index_at>
|
|
403
|
-
static py::tuple search_many_in_index( //
|
|
404
|
-
index_at& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads,
|
|
405
|
-
progress_func_t const& progress) {
|
|
406
|
-
|
|
407
|
-
if (wanted == 0)
|
|
408
|
-
return py::tuple(5);
|
|
409
|
-
|
|
410
|
-
if (index.limits().threads_search < threads)
|
|
411
|
-
throw std::invalid_argument("Can't use that many threads!");
|
|
412
|
-
|
|
413
|
-
py::buffer_info vectors_info = vectors.request();
|
|
414
|
-
if (vectors_info.ndim != 2)
|
|
415
|
-
throw std::invalid_argument("Expects a matrix of vectors to add!");
|
|
416
|
-
|
|
417
|
-
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
418
|
-
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
419
|
-
if (vectors_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
420
|
-
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
421
|
-
|
|
422
|
-
py::array_t<dense_key_t> keys_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
423
|
-
py::array_t<distance_t> distances_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
424
|
-
py::array_t<Py_ssize_t> counts_py(vectors_count);
|
|
425
|
-
std::atomic<std::size_t> stats_visited_members(0);
|
|
426
|
-
std::atomic<std::size_t> stats_computed_distances(0);
|
|
427
|
-
|
|
428
|
-
// clang-format off
|
|
429
|
-
switch (numpy_string_to_kind(vectors_info.format)) {
|
|
430
|
-
case scalar_kind_t::b1x8_k: search_typed<b1x8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
431
|
-
case scalar_kind_t::i8_k: search_typed<i8_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
432
|
-
case scalar_kind_t::f16_k: search_typed<f16_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
433
|
-
case scalar_kind_t::f32_k: search_typed<f32_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
434
|
-
case scalar_kind_t::f64_k: search_typed<f64_t>(index, vectors_info, wanted, exact, threads, keys_py, distances_py, counts_py, stats_visited_members, stats_computed_distances, progress); break;
|
|
435
|
-
default: throw std::invalid_argument("Incompatible scalars in the query matrix: " + vectors_info.format);
|
|
436
|
-
}
|
|
437
|
-
// clang-format on
|
|
438
|
-
|
|
439
|
-
py::tuple results(5);
|
|
440
|
-
results[0] = keys_py;
|
|
441
|
-
results[1] = distances_py;
|
|
442
|
-
results[2] = counts_py;
|
|
443
|
-
results[3] = stats_visited_members.load();
|
|
444
|
-
results[4] = stats_computed_distances.load();
|
|
445
|
-
return results;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
static py::tuple search_many_brute_force( //
|
|
449
|
-
py::buffer dataset, py::buffer queries, //
|
|
450
|
-
std::size_t wanted, std::size_t threads, //
|
|
451
|
-
metric_kind_t metric_kind, //
|
|
452
|
-
metric_punned_signature_t metric_signature, //
|
|
453
|
-
std::uintptr_t metric_uintptr, //
|
|
454
|
-
progress_func_t const& progress_func) {
|
|
455
|
-
|
|
456
|
-
if (wanted == 0)
|
|
457
|
-
return py::tuple(5);
|
|
458
|
-
|
|
459
|
-
py::buffer_info dataset_info = dataset.request();
|
|
460
|
-
py::buffer_info queries_info = queries.request();
|
|
461
|
-
if (dataset_info.ndim != 2 || queries_info.ndim != 2)
|
|
462
|
-
throw std::invalid_argument("Expects a matrix of dataset to add!");
|
|
463
|
-
|
|
464
|
-
std::size_t dataset_count = static_cast<std::size_t>(dataset_info.shape[0]);
|
|
465
|
-
std::size_t dataset_dimensions = static_cast<std::size_t>(dataset_info.shape[1]);
|
|
466
|
-
std::size_t dataset_stride = static_cast<std::size_t>(dataset_info.strides[0]);
|
|
467
|
-
std::size_t queries_stride = static_cast<std::size_t>(queries_info.strides[0]);
|
|
468
|
-
std::size_t queries_count = static_cast<std::size_t>(queries_info.shape[0]);
|
|
469
|
-
std::size_t queries_dimensions = static_cast<std::size_t>(queries_info.shape[1]);
|
|
470
|
-
|
|
471
|
-
if (dataset_dimensions != queries_dimensions)
|
|
472
|
-
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
473
|
-
if (wanted > dataset_count)
|
|
474
|
-
throw std::invalid_argument("You can't request more matches than in the dataset!");
|
|
475
|
-
|
|
476
|
-
scalar_kind_t dataset_kind = numpy_string_to_kind(dataset_info.format);
|
|
477
|
-
scalar_kind_t queries_kind = numpy_string_to_kind(queries_info.format);
|
|
478
|
-
if (dataset_kind != queries_kind)
|
|
479
|
-
throw std::invalid_argument("The types of vectors don't match!");
|
|
480
|
-
|
|
481
|
-
std::size_t dimensions = static_cast<std::size_t>(queries_dimensions);
|
|
482
|
-
metric_t metric = //
|
|
483
|
-
metric_uintptr //
|
|
484
|
-
? metric_t(dimensions, metric_uintptr, metric_signature, metric_kind, queries_kind)
|
|
485
|
-
: metric_t(dimensions, metric_kind, queries_kind);
|
|
486
|
-
|
|
487
|
-
py::array_t<dense_key_t> keys_py({static_cast<Py_ssize_t>(queries_count), static_cast<Py_ssize_t>(wanted)});
|
|
488
|
-
py::array_t<distance_t> distances_py({static_cast<Py_ssize_t>(queries_count), static_cast<Py_ssize_t>(wanted)});
|
|
489
|
-
py::array_t<Py_ssize_t> counts_py(static_cast<Py_ssize_t>(queries_count));
|
|
490
|
-
|
|
491
|
-
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
492
|
-
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
493
|
-
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
494
|
-
|
|
495
|
-
byte_t const* dataset_data = reinterpret_cast<byte_t const*>(dataset_info.ptr);
|
|
496
|
-
byte_t const* queries_data = reinterpret_cast<byte_t const*>(queries_info.ptr);
|
|
497
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
498
|
-
counts_py1d(query_idx) = wanted;
|
|
499
|
-
|
|
500
|
-
if (!threads)
|
|
501
|
-
threads = std::thread::hardware_concurrency();
|
|
502
|
-
|
|
503
|
-
// Dispatch brute-force search
|
|
504
|
-
progress_t progress{progress_func};
|
|
505
|
-
executor_default_t executor{threads};
|
|
506
|
-
exact_search_t search;
|
|
507
|
-
|
|
508
|
-
exact_search_results_t offsets_and_distances = search( //
|
|
509
|
-
dataset_data, dataset_count, dataset_stride, //
|
|
510
|
-
queries_data, queries_count, queries_stride, //
|
|
511
|
-
wanted, metric, executor,
|
|
512
|
-
[&](std::size_t passed, std::size_t total) { return PyErr_CheckSignals() == 0 && progress(passed, total); });
|
|
513
|
-
|
|
514
|
-
if (!offsets_and_distances)
|
|
515
|
-
throw std::bad_alloc();
|
|
516
|
-
|
|
517
|
-
// Export the results
|
|
518
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx) {
|
|
519
|
-
dense_key_t* query_keys = &keys_py2d(query_idx, 0);
|
|
520
|
-
distance_t* query_distances = &distances_py2d(query_idx, 0);
|
|
521
|
-
auto query_result = offsets_and_distances.at(query_idx);
|
|
522
|
-
for (std::size_t i = 0; i != wanted; ++i)
|
|
523
|
-
query_keys[i] = static_cast<dense_key_t>(query_result[i].offset),
|
|
524
|
-
query_distances[i] = query_result[i].distance;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
py::tuple results(5);
|
|
528
|
-
results[0] = keys_py;
|
|
529
|
-
results[1] = distances_py;
|
|
530
|
-
results[2] = counts_py;
|
|
531
|
-
results[3] = 0;
|
|
532
|
-
results[4] = static_cast<std::size_t>(dataset_count * queries_count);
|
|
533
|
-
return results;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
template <typename scalar_at> struct rows_lookup_gt {
|
|
537
|
-
byte_t* data_;
|
|
538
|
-
std::size_t stride_;
|
|
539
|
-
|
|
540
|
-
rows_lookup_gt(void* data, std::size_t stride) noexcept : data_((byte_t*)data), stride_(stride) {}
|
|
541
|
-
scalar_at* operator[](std::size_t i) const noexcept { return reinterpret_cast<scalar_at*>(data_ + i * stride_); }
|
|
542
|
-
std::ptrdiff_t operator-(rows_lookup_gt const& other) const noexcept { return (data_ - other.data_) / stride_; }
|
|
543
|
-
rows_lookup_gt operator+(std::size_t n) const noexcept { return {data_ + stride_ * n, stride_}; }
|
|
544
|
-
template <typename other_scalar_at> rows_lookup_gt<other_scalar_at> as() const noexcept { return {data_, stride_}; }
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* @param queries Matrix of vectors to search for.
|
|
549
|
-
* @param count Number of clusters to produce.
|
|
550
|
-
*
|
|
551
|
-
* @return Tuple with:
|
|
552
|
-
* 1. vector of cluster IDs,
|
|
553
|
-
* 2. vector of distances to those clusters,
|
|
554
|
-
* 3. array with match counts, set to all ones,
|
|
555
|
-
* 4. number of visited nodes,
|
|
556
|
-
* 4. number of computed pairwise distances.
|
|
557
|
-
*/
|
|
558
|
-
template <typename index_at>
|
|
559
|
-
static py::tuple cluster_vectors( //
|
|
560
|
-
index_at& index, py::buffer queries, //
|
|
561
|
-
std::size_t min_count, std::size_t max_count, std::size_t threads, progress_func_t const& progress) {
|
|
562
|
-
|
|
563
|
-
if (index.limits().threads_search < threads)
|
|
564
|
-
throw std::invalid_argument("Can't use that many threads!");
|
|
565
|
-
|
|
566
|
-
py::buffer_info queries_info = queries.request();
|
|
567
|
-
if (queries_info.ndim != 2)
|
|
568
|
-
throw std::invalid_argument("Expects a matrix of queries to add!");
|
|
569
|
-
|
|
570
|
-
std::size_t queries_count = static_cast<std::size_t>(queries_info.shape[0]);
|
|
571
|
-
std::size_t queries_stride = static_cast<std::size_t>(queries_info.strides[0]);
|
|
572
|
-
std::size_t queries_dimensions = static_cast<std::size_t>(queries_info.shape[1]);
|
|
573
|
-
if (queries_dimensions != index.scalar_words())
|
|
574
|
-
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
575
|
-
|
|
576
|
-
py::array_t<dense_key_t> keys_py({Py_ssize_t(queries_count), Py_ssize_t(1)});
|
|
577
|
-
py::array_t<distance_t> distances_py({Py_ssize_t(queries_count), Py_ssize_t(1)});
|
|
578
|
-
dense_clustering_result_t cluster_result;
|
|
579
|
-
executor_default_t executor{threads};
|
|
580
|
-
|
|
581
|
-
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
582
|
-
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
583
|
-
dense_key_t* keys_ptr = reinterpret_cast<dense_key_t*>(&keys_py2d(0, 0));
|
|
584
|
-
distance_t* distances_ptr = reinterpret_cast<distance_t*>(&distances_py2d(0, 0));
|
|
585
|
-
|
|
586
|
-
index_dense_clustering_config_t config;
|
|
587
|
-
config.min_clusters = min_count;
|
|
588
|
-
config.max_clusters = max_count;
|
|
589
|
-
|
|
590
|
-
rows_lookup_gt<byte_t const> queries_begin(queries_info.ptr, queries_stride);
|
|
591
|
-
rows_lookup_gt<byte_t const> queries_end = queries_begin + queries_count;
|
|
592
|
-
|
|
593
|
-
// clang-format off
|
|
594
|
-
switch (numpy_string_to_kind(queries_info.format)) {
|
|
595
|
-
case scalar_kind_t::b1x8_k: cluster_result = index.cluster(queries_begin.as<b1x8_t const>(), queries_end.as<b1x8_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
596
|
-
case scalar_kind_t::i8_k: cluster_result = index.cluster(queries_begin.as<i8_t const>(), queries_end.as<i8_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
597
|
-
case scalar_kind_t::f16_k: cluster_result = index.cluster(queries_begin.as<f16_t const>(), queries_end.as<f16_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
598
|
-
case scalar_kind_t::f32_k: cluster_result = index.cluster(queries_begin.as<f32_t const>(), queries_end.as<f32_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
599
|
-
case scalar_kind_t::f64_k: cluster_result = index.cluster(queries_begin.as<f64_t const>(), queries_end.as<f64_t const>(), config, keys_ptr, distances_ptr, executor, progress_t{progress}); break;
|
|
600
|
-
default: throw std::invalid_argument("Incompatible scalars in the query matrix: " + queries_info.format);
|
|
601
|
-
}
|
|
602
|
-
// clang-format on
|
|
603
|
-
|
|
604
|
-
cluster_result.error.raise();
|
|
605
|
-
|
|
606
|
-
// Those would be set to 1 for all entries, in case of success
|
|
607
|
-
py::array_t<Py_ssize_t> counts_py(queries_count);
|
|
608
|
-
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
609
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
610
|
-
counts_py1d(static_cast<Py_ssize_t>(query_idx)) = 1;
|
|
611
|
-
|
|
612
|
-
py::tuple results(5);
|
|
613
|
-
results[0] = keys_py;
|
|
614
|
-
results[1] = distances_py;
|
|
615
|
-
results[2] = counts_py;
|
|
616
|
-
results[3] = cluster_result.visited_members;
|
|
617
|
-
results[4] = cluster_result.computed_distances;
|
|
618
|
-
return results;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* @param queries Array of keys to cluster.
|
|
623
|
-
* @param count Number of clusters to produce.
|
|
624
|
-
*
|
|
625
|
-
* @return Tuple with:
|
|
626
|
-
* 1. vector of cluster IDs,
|
|
627
|
-
* 2. vector of distances to those clusters,
|
|
628
|
-
* 3. array with match counts, set to all ones,
|
|
629
|
-
* 4. number of visited nodes,
|
|
630
|
-
* 4. number of computed pairwise distances.
|
|
631
|
-
*/
|
|
632
|
-
template <typename index_at>
|
|
633
|
-
static py::tuple cluster_keys( //
|
|
634
|
-
index_at& index, py::array_t<dense_key_t> queries_py, //
|
|
635
|
-
std::size_t min_count, std::size_t max_count, std::size_t threads, progress_func_t const& progress) {
|
|
636
|
-
|
|
637
|
-
if (index.limits().threads_search < threads)
|
|
638
|
-
throw std::invalid_argument("Can't use that many threads!");
|
|
639
|
-
|
|
640
|
-
std::size_t queries_count = static_cast<std::size_t>(queries_py.size());
|
|
641
|
-
auto queries_py1d = queries_py.template unchecked<1>();
|
|
642
|
-
dense_key_t const* queries_begin = &queries_py1d(0);
|
|
643
|
-
dense_key_t const* queries_end = queries_begin + queries_count;
|
|
644
|
-
|
|
645
|
-
py::array_t<dense_key_t> keys_py({Py_ssize_t(queries_count), Py_ssize_t(1)});
|
|
646
|
-
py::array_t<distance_t> distances_py({Py_ssize_t(queries_count), Py_ssize_t(1)});
|
|
647
|
-
executor_default_t executor{threads};
|
|
648
|
-
|
|
649
|
-
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
650
|
-
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
651
|
-
dense_key_t* keys_ptr = reinterpret_cast<dense_key_t*>(&keys_py2d(0, 0));
|
|
652
|
-
distance_t* distances_ptr = reinterpret_cast<distance_t*>(&distances_py2d(0, 0));
|
|
653
|
-
|
|
654
|
-
index_dense_clustering_config_t config;
|
|
655
|
-
config.min_clusters = min_count;
|
|
656
|
-
config.max_clusters = max_count;
|
|
657
|
-
|
|
658
|
-
dense_clustering_result_t cluster_result =
|
|
659
|
-
index.cluster(queries_begin, queries_end, config, keys_ptr, distances_ptr, executor, progress_t{progress});
|
|
660
|
-
cluster_result.error.raise();
|
|
661
|
-
|
|
662
|
-
// Those would be set to 1 for all entries, in case of success
|
|
663
|
-
py::array_t<Py_ssize_t> counts_py(queries_count);
|
|
664
|
-
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
665
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
666
|
-
counts_py1d(static_cast<Py_ssize_t>(query_idx)) = 1;
|
|
667
|
-
|
|
668
|
-
py::tuple results(5);
|
|
669
|
-
results[0] = keys_py;
|
|
670
|
-
results[1] = distances_py;
|
|
671
|
-
results[2] = counts_py;
|
|
672
|
-
results[3] = cluster_result.visited_members;
|
|
673
|
-
results[4] = cluster_result.computed_distances;
|
|
674
|
-
return results;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
static std::unordered_map<dense_key_t, dense_key_t> join_index( //
|
|
678
|
-
dense_index_py_t const& a, dense_index_py_t const& b, //
|
|
679
|
-
std::size_t max_proposals, bool exact, //
|
|
680
|
-
progress_func_t const& progress) {
|
|
681
|
-
|
|
682
|
-
std::unordered_map<dense_key_t, dense_key_t> a_to_b;
|
|
683
|
-
dummy_key_to_key_mapping_t b_to_a;
|
|
684
|
-
a_to_b.reserve((std::min)(a.size(), b.size()));
|
|
685
|
-
|
|
686
|
-
index_join_config_t config;
|
|
687
|
-
config.max_proposals = max_proposals;
|
|
688
|
-
config.exact = exact;
|
|
689
|
-
config.expansion = (std::max)(a.expansion_search(), b.expansion_search());
|
|
690
|
-
std::size_t threads = (std::min)(a.limits().threads(), b.limits().threads());
|
|
691
|
-
executor_default_t executor{threads};
|
|
692
|
-
join_result_t result = a.join(b, config, a_to_b, b_to_a, executor, progress_t{progress});
|
|
693
|
-
forward_error(result);
|
|
694
|
-
|
|
695
|
-
return a_to_b;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
static dense_index_py_t copy_index(dense_index_py_t const& index, bool force_copy) {
|
|
699
|
-
|
|
700
|
-
using copy_result_t = typename dense_index_py_t::copy_result_t;
|
|
701
|
-
index_dense_copy_config_t config;
|
|
702
|
-
config.force_vector_copy = force_copy;
|
|
703
|
-
copy_result_t result = index.copy(config);
|
|
704
|
-
forward_error(result);
|
|
705
|
-
return std::move(result.index);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
static void compact_index(dense_index_py_t& index, std::size_t threads, progress_func_t const& progress) {
|
|
709
|
-
|
|
710
|
-
if (!threads)
|
|
711
|
-
threads = std::thread::hardware_concurrency();
|
|
712
|
-
if (!index.reserve(index_limits_t(index.size(), threads)))
|
|
713
|
-
throw std::invalid_argument("Out of memory!");
|
|
714
|
-
|
|
715
|
-
index.compact(executor_default_t{threads}, progress_t{progress});
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
static py::dict index_metadata(index_dense_metadata_result_t const& meta) {
|
|
719
|
-
py::dict result;
|
|
720
|
-
result["matrix_included"] = !meta.config.exclude_vectors;
|
|
721
|
-
result["matrix_uses_64_bit_dimensions"] = meta.config.use_64_bit_dimensions;
|
|
722
|
-
|
|
723
|
-
index_dense_head_t const& head = meta.head;
|
|
724
|
-
result["version"] = std::to_string(head.version_major) + "." + //
|
|
725
|
-
std::to_string(head.version_minor) + "." + //
|
|
726
|
-
std::to_string(head.version_patch);
|
|
727
|
-
|
|
728
|
-
result["kind_metric"] = metric_kind_t(head.kind_metric);
|
|
729
|
-
result["kind_scalar"] = scalar_kind_t(head.kind_scalar);
|
|
730
|
-
result["kind_key"] = scalar_kind_t(head.kind_key);
|
|
731
|
-
result["kind_compressed_slot"] = scalar_kind_t(head.kind_compressed_slot);
|
|
732
|
-
|
|
733
|
-
result["count_present"] = std::uint64_t(head.count_present);
|
|
734
|
-
result["count_deleted"] = std::uint64_t(head.count_deleted);
|
|
735
|
-
result["dimensions"] = std::uint64_t(head.dimensions);
|
|
736
|
-
|
|
737
|
-
return result;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// clang-format off
|
|
741
|
-
template <typename index_at> void save_index_to_path(index_at const& index, std::string const& path, progress_func_t const& progress) { index.save(path.c_str(), {}, progress_t{progress}).error.raise(); }
|
|
742
|
-
template <typename index_at> void load_index_from_path(index_at& index, std::string const& path, progress_func_t const& progress) { index.load(path.c_str(), {}, progress_t{progress}).error.raise(); }
|
|
743
|
-
template <typename index_at> void view_index_from_path(index_at& index, std::string const& path, progress_func_t const& progress) { index.view(path.c_str(), 0, {}, progress_t{progress}).error.raise(); }
|
|
744
|
-
template <typename index_at> void reset_index(index_at& index) { index.reset(); }
|
|
745
|
-
template <typename index_at> void clear_index(index_at& index) { index.clear(); }
|
|
746
|
-
template <typename index_at> std::size_t max_level(index_at const &index) { return index.max_level(); }
|
|
747
|
-
template <typename index_at> std::size_t serialized_length(index_at const &index) { return index.serialized_length(); }
|
|
748
|
-
template <typename index_at> typename index_at::stats_t compute_stats(index_at const &index) { return index.stats(); }
|
|
749
|
-
template <typename index_at> typename index_at::stats_t compute_level_stats(index_at const &index, std::size_t level) { return index.stats(level); }
|
|
750
|
-
// clang-format on
|
|
751
|
-
|
|
752
|
-
template <typename py_bytes_at> memory_mapped_file_t memory_map_from_bytes(py_bytes_at&& bytes) {
|
|
753
|
-
py::buffer_info info(py::buffer(bytes).request());
|
|
754
|
-
return {(byte_t*)(info.ptr), static_cast<std::size_t>(info.size)};
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
template <typename index_at> py::object save_index_to_buffer(index_at const& index, progress_func_t const& progress) {
|
|
758
|
-
std::size_t serialized_length = index.serialized_length();
|
|
759
|
-
|
|
760
|
-
// Create an empty bytearray object using CPython API
|
|
761
|
-
PyObject* byte_array = PyByteArray_FromStringAndSize(nullptr, 0);
|
|
762
|
-
if (!byte_array)
|
|
763
|
-
throw std::runtime_error("Could not allocate bytearray object");
|
|
764
|
-
|
|
765
|
-
// Resize the bytearray object to the desired length
|
|
766
|
-
if (PyByteArray_Resize(byte_array, static_cast<Py_ssize_t>(serialized_length)) != 0) {
|
|
767
|
-
Py_XDECREF(byte_array);
|
|
768
|
-
throw std::runtime_error("Could not resize bytearray object");
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
char* buffer = PyByteArray_AS_STRING(byte_array);
|
|
772
|
-
memory_mapped_file_t memory_map((byte_t*)buffer, serialized_length);
|
|
773
|
-
serialization_result_t result = index.save(std::move(memory_map), {}, {}, progress_t{progress});
|
|
774
|
-
|
|
775
|
-
if (!result) {
|
|
776
|
-
Py_XDECREF(byte_array);
|
|
777
|
-
result.error.raise();
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
return py::reinterpret_steal<py::object>(byte_array);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
template <typename index_at>
|
|
784
|
-
void load_index_from_buffer(index_at& index, py::bytes const& buffer, progress_func_t const& progress) {
|
|
785
|
-
index.load(memory_map_from_bytes(buffer), {}, {}, progress_t{progress}).error.raise();
|
|
786
|
-
}
|
|
787
|
-
template <typename index_at>
|
|
788
|
-
void view_index_from_buffer(index_at& index, py::bytes const& buffer, progress_func_t const& progress) {
|
|
789
|
-
index.view(memory_map_from_bytes(buffer), {}, {}, progress_t{progress}).error.raise();
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
template <typename index_at> std::vector<typename index_at::stats_t> compute_levels_stats(index_at const& index) {
|
|
793
|
-
using stats_t = typename index_at::stats_t;
|
|
794
|
-
std::size_t max_level = index.max_level();
|
|
795
|
-
std::vector<stats_t> result(max_level + 1);
|
|
796
|
-
index.stats(result.data(), max_level);
|
|
797
|
-
return result;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
template <typename internal_at, typename external_at = internal_at, typename index_at = void>
|
|
801
|
-
static py::tuple get_typed_vectors_for_keys(index_at const& index, py::buffer keys) {
|
|
802
|
-
|
|
803
|
-
py::buffer_info keys_info = keys.request();
|
|
804
|
-
if (keys_info.ndim != 1)
|
|
805
|
-
throw std::invalid_argument("Keys must be placed in a single-dimensional array!");
|
|
806
|
-
|
|
807
|
-
Py_ssize_t keys_count = keys_info.shape[0];
|
|
808
|
-
byte_t const* keys_data = reinterpret_cast<byte_t const*>(keys_info.ptr);
|
|
809
|
-
py::tuple results(keys_count);
|
|
810
|
-
|
|
811
|
-
for (Py_ssize_t task_idx = 0; task_idx != keys_count; ++task_idx) {
|
|
812
|
-
dense_key_t key = *reinterpret_cast<dense_key_t const*>(keys_data + task_idx * keys_info.strides[0]);
|
|
813
|
-
std::size_t vectors_count = index.count(key);
|
|
814
|
-
if (!vectors_count) {
|
|
815
|
-
results[task_idx] = py::none();
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
py::array_t<external_at> result_py({static_cast<Py_ssize_t>(vectors_count), //
|
|
820
|
-
static_cast<Py_ssize_t>(index.scalar_words())});
|
|
821
|
-
auto result_py2d = result_py.template mutable_unchecked<2>();
|
|
822
|
-
index.get(key, (internal_at*)&result_py2d(0, 0), vectors_count);
|
|
823
|
-
results[task_idx] = result_py;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
return results;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
template <typename index_at> py::tuple get_many(index_at const& index, py::buffer keys, scalar_kind_t scalar_kind) {
|
|
830
|
-
if (scalar_kind == scalar_kind_t::f32_k)
|
|
831
|
-
return get_typed_vectors_for_keys<f32_t>(index, keys);
|
|
832
|
-
else if (scalar_kind == scalar_kind_t::f64_k)
|
|
833
|
-
return get_typed_vectors_for_keys<f64_t>(index, keys);
|
|
834
|
-
else if (scalar_kind == scalar_kind_t::f16_k)
|
|
835
|
-
return get_typed_vectors_for_keys<f16_t, std::uint16_t>(index, keys);
|
|
836
|
-
else if (scalar_kind == scalar_kind_t::i8_k)
|
|
837
|
-
return get_typed_vectors_for_keys<i8_t, std::int8_t>(index, keys);
|
|
838
|
-
else if (scalar_kind == scalar_kind_t::b1x8_k)
|
|
839
|
-
return get_typed_vectors_for_keys<b1x8_t, std::uint8_t>(index, keys);
|
|
840
|
-
else
|
|
841
|
-
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
PYBIND11_MODULE(compiled, m) {
|
|
845
|
-
m.doc() = "Smaller & Faster Single-File Vector Search Engine from Unum";
|
|
846
|
-
|
|
847
|
-
m.attr("DEFAULT_CONNECTIVITY") = py::int_(default_connectivity());
|
|
848
|
-
m.attr("DEFAULT_EXPANSION_ADD") = py::int_(default_expansion_add());
|
|
849
|
-
m.attr("DEFAULT_EXPANSION_SEARCH") = py::int_(default_expansion_search());
|
|
850
|
-
|
|
851
|
-
m.attr("USES_OPENMP") = py::int_(USEARCH_USE_OPENMP);
|
|
852
|
-
m.attr("USES_SIMSIMD") = py::int_(USEARCH_USE_SIMSIMD);
|
|
853
|
-
m.attr("USES_NATIVE_F16") = py::int_(USEARCH_USE_NATIVE_F16);
|
|
854
|
-
|
|
855
|
-
py::enum_<metric_punned_signature_t>(m, "MetricSignature")
|
|
856
|
-
.value("ArrayArray", metric_punned_signature_t::array_array_k)
|
|
857
|
-
.value("ArrayArraySize", metric_punned_signature_t::array_array_size_k);
|
|
858
|
-
|
|
859
|
-
py::enum_<metric_kind_t>(m, "MetricKind")
|
|
860
|
-
.value("Unknown", metric_kind_t::unknown_k)
|
|
861
|
-
|
|
862
|
-
.value("IP", metric_kind_t::ip_k)
|
|
863
|
-
.value("Cos", metric_kind_t::cos_k)
|
|
864
|
-
.value("L2sq", metric_kind_t::l2sq_k)
|
|
865
|
-
|
|
866
|
-
.value("Haversine", metric_kind_t::haversine_k)
|
|
867
|
-
.value("Divergence", metric_kind_t::divergence_k)
|
|
868
|
-
.value("Pearson", metric_kind_t::pearson_k)
|
|
869
|
-
.value("Jaccard", metric_kind_t::jaccard_k)
|
|
870
|
-
.value("Hamming", metric_kind_t::hamming_k)
|
|
871
|
-
.value("Tanimoto", metric_kind_t::tanimoto_k)
|
|
872
|
-
.value("Sorensen", metric_kind_t::sorensen_k)
|
|
873
|
-
|
|
874
|
-
.value("Cosine", metric_kind_t::cos_k)
|
|
875
|
-
.value("InnerProduct", metric_kind_t::ip_k);
|
|
876
|
-
|
|
877
|
-
py::enum_<scalar_kind_t>(m, "ScalarKind")
|
|
878
|
-
.value("Unknown", scalar_kind_t::unknown_k)
|
|
879
|
-
.value("B1", scalar_kind_t::b1x8_k)
|
|
880
|
-
.value("U40", scalar_kind_t::u40_k)
|
|
881
|
-
.value("UUID", scalar_kind_t::uuid_k)
|
|
882
|
-
.value("F64", scalar_kind_t::f64_k)
|
|
883
|
-
.value("F32", scalar_kind_t::f32_k)
|
|
884
|
-
.value("F16", scalar_kind_t::f16_k)
|
|
885
|
-
.value("F8", scalar_kind_t::f8_k)
|
|
886
|
-
.value("U64", scalar_kind_t::u64_k)
|
|
887
|
-
.value("U32", scalar_kind_t::u32_k)
|
|
888
|
-
.value("U16", scalar_kind_t::u16_k)
|
|
889
|
-
.value("U8", scalar_kind_t::u8_k)
|
|
890
|
-
.value("I64", scalar_kind_t::i64_k)
|
|
891
|
-
.value("I32", scalar_kind_t::i32_k)
|
|
892
|
-
.value("I16", scalar_kind_t::i16_k)
|
|
893
|
-
.value("I8", scalar_kind_t::i8_k);
|
|
894
|
-
|
|
895
|
-
m.def("index_dense_metadata_from_path", [](std::string const& path) -> py::dict {
|
|
896
|
-
index_dense_metadata_result_t meta = index_dense_metadata_from_path(path.c_str());
|
|
897
|
-
forward_error(meta);
|
|
898
|
-
return index_metadata(meta);
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
m.def("index_dense_metadata_from_buffer", [](py::bytes const& buffer) -> py::dict {
|
|
902
|
-
index_dense_metadata_result_t meta = index_dense_metadata_from_buffer(memory_map_from_bytes(buffer));
|
|
903
|
-
forward_error(meta);
|
|
904
|
-
return index_metadata(meta);
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
m.def("exact_search", &search_many_brute_force, //
|
|
908
|
-
py::arg("dataset"), //
|
|
909
|
-
py::arg("queries"), //
|
|
910
|
-
py::arg("count") = 10, //
|
|
911
|
-
py::kw_only(), //
|
|
912
|
-
py::arg("threads") = 0, //
|
|
913
|
-
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
914
|
-
py::arg("metric_signature") = metric_punned_signature_t::array_array_k, //
|
|
915
|
-
py::arg("metric_pointer") = 0, //
|
|
916
|
-
py::arg("progress") = nullptr //
|
|
917
|
-
);
|
|
918
|
-
|
|
919
|
-
m.def(
|
|
920
|
-
"hardware_acceleration",
|
|
921
|
-
[](scalar_kind_t scalar_kind, std::size_t dimensions, metric_kind_t metric_kind) -> py::str {
|
|
922
|
-
return metric_t(dimensions, metric_kind, scalar_kind).isa_name();
|
|
923
|
-
},
|
|
924
|
-
py::kw_only(), //
|
|
925
|
-
py::arg("dtype") = scalar_kind_t::f32_k, //
|
|
926
|
-
py::arg("ndim") = 0, //
|
|
927
|
-
py::arg("metric_kind") = metric_kind_t::cos_k //
|
|
928
|
-
);
|
|
929
|
-
|
|
930
|
-
auto i = py::class_<dense_index_py_t, std::shared_ptr<dense_index_py_t>>(m, "Index");
|
|
931
|
-
|
|
932
|
-
i.def(py::init(&make_index), //
|
|
933
|
-
py::kw_only(), //
|
|
934
|
-
py::arg("ndim") = 0, //
|
|
935
|
-
py::arg("dtype") = scalar_kind_t::f32_k, //
|
|
936
|
-
py::arg("connectivity") = default_connectivity(), //
|
|
937
|
-
py::arg("expansion_add") = default_expansion_add(), //
|
|
938
|
-
py::arg("expansion_search") = default_expansion_search(), //
|
|
939
|
-
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
940
|
-
py::arg("metric_signature") = metric_punned_signature_t::array_array_k, //
|
|
941
|
-
py::arg("metric_pointer") = 0, //
|
|
942
|
-
py::arg("multi") = false, //
|
|
943
|
-
py::arg("enable_key_lookups") = true //
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
i.def( //
|
|
947
|
-
"add_many", &add_many_to_index<dense_index_py_t>, //
|
|
948
|
-
py::arg("keys"), //
|
|
949
|
-
py::arg("vectors"), //
|
|
950
|
-
py::kw_only(), //
|
|
951
|
-
py::arg("copy") = true, //
|
|
952
|
-
py::arg("threads") = 0, //
|
|
953
|
-
py::arg("progress") = nullptr //
|
|
954
|
-
);
|
|
955
|
-
|
|
956
|
-
i.def( //
|
|
957
|
-
"search_many", &search_many_in_index<dense_index_py_t>, //
|
|
958
|
-
py::arg("queries"), //
|
|
959
|
-
py::arg("count") = 10, //
|
|
960
|
-
py::arg("exact") = false, //
|
|
961
|
-
py::arg("threads") = 0, //
|
|
962
|
-
py::arg("progress") = nullptr //
|
|
963
|
-
);
|
|
964
|
-
|
|
965
|
-
i.def( //
|
|
966
|
-
"cluster_vectors", &cluster_vectors<dense_index_py_t>, //
|
|
967
|
-
py::arg("queries"), //
|
|
968
|
-
py::arg("min_count") = 0, //
|
|
969
|
-
py::arg("max_count") = 0, //
|
|
970
|
-
py::arg("threads") = 0, //
|
|
971
|
-
py::arg("progress") = nullptr //
|
|
972
|
-
);
|
|
973
|
-
|
|
974
|
-
i.def( //
|
|
975
|
-
"cluster_keys", &cluster_keys<dense_index_py_t>, //
|
|
976
|
-
py::arg("queries"), //
|
|
977
|
-
py::arg("min_count") = 0, //
|
|
978
|
-
py::arg("max_count") = 0, //
|
|
979
|
-
py::arg("threads") = 0, //
|
|
980
|
-
py::arg("progress") = nullptr //
|
|
981
|
-
);
|
|
982
|
-
|
|
983
|
-
i.def(
|
|
984
|
-
"rename_one_to_one",
|
|
985
|
-
[](dense_index_py_t& index, dense_key_t from, dense_key_t to) -> bool {
|
|
986
|
-
dense_labeling_result_t result = index.rename(from, to);
|
|
987
|
-
forward_error(result);
|
|
988
|
-
return result.completed;
|
|
989
|
-
},
|
|
990
|
-
py::arg("from_"), py::arg("to"));
|
|
991
|
-
|
|
992
|
-
i.def(
|
|
993
|
-
"rename_many_to_many",
|
|
994
|
-
[](dense_index_py_t& index, std::vector<dense_key_t> const& from,
|
|
995
|
-
std::vector<dense_key_t> const& to) -> std::vector<bool> {
|
|
996
|
-
if (from.size() != to.size())
|
|
997
|
-
throw std::invalid_argument("Sizes of `from` and `to` arrays don't match!");
|
|
998
|
-
|
|
999
|
-
std::vector<bool> results(from.size(), false);
|
|
1000
|
-
for (std::size_t i = 0; i != from.size(); ++i) {
|
|
1001
|
-
dense_labeling_result_t result = index.rename(from[i], to[i]);
|
|
1002
|
-
results[i] = result.completed;
|
|
1003
|
-
forward_error(result);
|
|
1004
|
-
}
|
|
1005
|
-
return results;
|
|
1006
|
-
},
|
|
1007
|
-
py::arg("from_"), py::arg("to"));
|
|
1008
|
-
|
|
1009
|
-
i.def(
|
|
1010
|
-
"rename_many_to_one",
|
|
1011
|
-
[](dense_index_py_t& index, std::vector<dense_key_t> const& from, dense_key_t to) -> std::vector<bool> {
|
|
1012
|
-
std::vector<bool> results(from.size(), false);
|
|
1013
|
-
for (std::size_t i = 0; i != from.size(); ++i) {
|
|
1014
|
-
dense_labeling_result_t result = index.rename(from[i], to);
|
|
1015
|
-
results[i] = result.completed;
|
|
1016
|
-
forward_error(result);
|
|
1017
|
-
}
|
|
1018
|
-
return results;
|
|
1019
|
-
},
|
|
1020
|
-
py::arg("from_"), py::arg("to"));
|
|
1021
|
-
|
|
1022
|
-
i.def(
|
|
1023
|
-
"remove_one",
|
|
1024
|
-
[](dense_index_py_t& index, dense_key_t key, bool compact, std::size_t threads) -> bool {
|
|
1025
|
-
dense_labeling_result_t result = index.remove(key);
|
|
1026
|
-
forward_error(result);
|
|
1027
|
-
if (!compact)
|
|
1028
|
-
return result.completed;
|
|
1029
|
-
|
|
1030
|
-
if (!threads)
|
|
1031
|
-
threads = std::thread::hardware_concurrency();
|
|
1032
|
-
if (!index.reserve(index_limits_t(index.size(), threads)))
|
|
1033
|
-
throw std::invalid_argument("Out of memory!");
|
|
1034
|
-
|
|
1035
|
-
index.isolate(executor_default_t{threads});
|
|
1036
|
-
return result.completed;
|
|
1037
|
-
},
|
|
1038
|
-
py::arg("key"), py::arg("compact"), py::arg("threads"));
|
|
1039
|
-
|
|
1040
|
-
i.def(
|
|
1041
|
-
"remove_many",
|
|
1042
|
-
[](dense_index_py_t& index, std::vector<dense_key_t> const& keys, bool compact,
|
|
1043
|
-
std::size_t threads) -> std::size_t {
|
|
1044
|
-
dense_labeling_result_t result = index.remove(keys.begin(), keys.end());
|
|
1045
|
-
forward_error(result);
|
|
1046
|
-
if (!compact)
|
|
1047
|
-
return result.completed;
|
|
1048
|
-
|
|
1049
|
-
if (!threads)
|
|
1050
|
-
threads = std::thread::hardware_concurrency();
|
|
1051
|
-
if (!index.reserve(index_limits_t(index.size(), threads)))
|
|
1052
|
-
throw std::invalid_argument("Out of memory!");
|
|
1053
|
-
|
|
1054
|
-
index.isolate(executor_default_t{threads});
|
|
1055
|
-
return result.completed;
|
|
1056
|
-
},
|
|
1057
|
-
py::arg("key"), py::arg("compact"), py::arg("threads"));
|
|
1058
|
-
|
|
1059
|
-
i.def("__len__", &dense_index_py_t::size);
|
|
1060
|
-
i.def_property_readonly("size", &dense_index_py_t::size);
|
|
1061
|
-
i.def_property_readonly("connectivity", &dense_index_py_t::connectivity);
|
|
1062
|
-
i.def_property_readonly("capacity", &dense_index_py_t::capacity);
|
|
1063
|
-
i.def_property_readonly("ndim",
|
|
1064
|
-
[](dense_index_py_t const& index) -> std::size_t { return index.metric().dimensions(); });
|
|
1065
|
-
i.def_property_readonly( //
|
|
1066
|
-
"dtype", [](dense_index_py_t const& index) -> scalar_kind_t { return index.scalar_kind(); });
|
|
1067
|
-
|
|
1068
|
-
i.def_property_readonly("serialized_length", &dense_index_py_t::serialized_length);
|
|
1069
|
-
i.def_property_readonly("memory_usage", &dense_index_py_t::memory_usage);
|
|
1070
|
-
|
|
1071
|
-
i.def_property("expansion_add", &dense_index_py_t::expansion_add, &dense_index_py_t::change_expansion_add);
|
|
1072
|
-
i.def_property("expansion_search", &dense_index_py_t::expansion_search, &dense_index_py_t::change_expansion_search);
|
|
1073
|
-
|
|
1074
|
-
i.def(
|
|
1075
|
-
"change_metric",
|
|
1076
|
-
[](dense_index_py_t& index, metric_kind_t metric_kind, metric_punned_signature_t metric_signature,
|
|
1077
|
-
std::uintptr_t metric_uintptr) {
|
|
1078
|
-
scalar_kind_t scalar_kind = index.scalar_kind();
|
|
1079
|
-
std::size_t dimensions = index.dimensions();
|
|
1080
|
-
metric_t metric = //
|
|
1081
|
-
metric_uintptr //
|
|
1082
|
-
? metric_t(dimensions, metric_uintptr, metric_signature, metric_kind, scalar_kind)
|
|
1083
|
-
: metric_t(dimensions, metric_kind, scalar_kind);
|
|
1084
|
-
index.change_metric(std::move(metric));
|
|
1085
|
-
},
|
|
1086
|
-
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
1087
|
-
py::arg("metric_signature") = metric_punned_signature_t::array_array_k, //
|
|
1088
|
-
py::arg("metric_pointer") = 0 //
|
|
1089
|
-
);
|
|
1090
|
-
|
|
1091
|
-
i.def_property_readonly("hardware_acceleration",
|
|
1092
|
-
[](dense_index_py_t const& index) -> py::str { return index.metric().isa_name(); });
|
|
1093
|
-
|
|
1094
|
-
i.def("contains_one", &dense_index_py_t::contains);
|
|
1095
|
-
i.def("count_one", &dense_index_py_t::count);
|
|
1096
|
-
|
|
1097
|
-
i.def( //
|
|
1098
|
-
"contains_many",
|
|
1099
|
-
[](dense_index_py_t const& index, py::array_t<dense_key_t> const& keys_py) -> py::array_t<bool> {
|
|
1100
|
-
py::array_t<bool> results_py(keys_py.size());
|
|
1101
|
-
auto results_py1d = results_py.template mutable_unchecked<1>();
|
|
1102
|
-
auto keys_py1d = keys_py.template unchecked<1>();
|
|
1103
|
-
for (Py_ssize_t task_idx = 0; task_idx != keys_py.size(); ++task_idx)
|
|
1104
|
-
results_py1d(task_idx) = index.contains(keys_py1d(task_idx));
|
|
1105
|
-
return results_py;
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
i.def( //
|
|
1109
|
-
"count_many",
|
|
1110
|
-
[](dense_index_py_t const& index, py::array_t<dense_key_t> const& keys_py) -> py::array_t<std::size_t> {
|
|
1111
|
-
py::array_t<std::size_t> results_py(keys_py.size());
|
|
1112
|
-
auto results_py1d = results_py.template mutable_unchecked<1>();
|
|
1113
|
-
auto keys_py1d = keys_py.template unchecked<1>();
|
|
1114
|
-
for (Py_ssize_t task_idx = 0; task_idx != keys_py.size(); ++task_idx)
|
|
1115
|
-
results_py1d(task_idx) = index.count(keys_py1d(task_idx));
|
|
1116
|
-
return results_py;
|
|
1117
|
-
});
|
|
1118
|
-
|
|
1119
|
-
i.def( //
|
|
1120
|
-
"pairwise_distances",
|
|
1121
|
-
[](dense_index_py_t const& index, py::array_t<dense_key_t> const& left_py,
|
|
1122
|
-
py::array_t<dense_key_t> const& right_py) -> py::array_t<distance_t> {
|
|
1123
|
-
py::array_t<distance_t> results_py(left_py.size());
|
|
1124
|
-
auto results_py1d = results_py.template mutable_unchecked<1>();
|
|
1125
|
-
auto left_py1d = left_py.template unchecked<1>();
|
|
1126
|
-
auto right_py1d = right_py.template unchecked<1>();
|
|
1127
|
-
for (Py_ssize_t task_idx = 0; task_idx != left_py.size(); ++task_idx)
|
|
1128
|
-
results_py1d(task_idx) = index.distance_between(left_py1d(task_idx), right_py1d(task_idx)).min;
|
|
1129
|
-
return results_py;
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
i.def( //
|
|
1133
|
-
"pairwise_distance", [](dense_index_py_t const& index, dense_key_t left, dense_key_t right) -> distance_t {
|
|
1134
|
-
return index.distance_between(left, right).min;
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
i.def("get_many", &get_many<dense_index_py_t>, py::arg("keys"), py::arg("dtype") = scalar_kind_t::f32_k);
|
|
1138
|
-
|
|
1139
|
-
i.def(
|
|
1140
|
-
"get_keys_in_slice",
|
|
1141
|
-
[](dense_index_py_t const& index, std::size_t offset, std::size_t limit) -> py::array_t<dense_key_t> {
|
|
1142
|
-
limit = std::min(index.size(), limit);
|
|
1143
|
-
py::array_t<dense_key_t> result_py(static_cast<Py_ssize_t>(limit));
|
|
1144
|
-
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
1145
|
-
index.export_keys(&result_py1d(0), offset, limit);
|
|
1146
|
-
return result_py;
|
|
1147
|
-
},
|
|
1148
|
-
py::arg("offset") = 0, py::arg("limit") = std::numeric_limits<std::size_t>::max());
|
|
1149
|
-
|
|
1150
|
-
i.def(
|
|
1151
|
-
"get_keys_at_offsets",
|
|
1152
|
-
[](dense_index_py_t const& index, py::array_t<Py_ssize_t> const& offsets_py) -> py::array_t<dense_key_t> {
|
|
1153
|
-
py::array_t<dense_key_t> result_py(offsets_py.size());
|
|
1154
|
-
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
1155
|
-
auto offsets_py1d = offsets_py.template unchecked<1>();
|
|
1156
|
-
for (Py_ssize_t task_idx = 0; task_idx != offsets_py.size(); ++task_idx)
|
|
1157
|
-
index.export_keys(&result_py1d(task_idx), offsets_py1d(task_idx), 1);
|
|
1158
|
-
return result_py;
|
|
1159
|
-
},
|
|
1160
|
-
py::arg("offsets"));
|
|
1161
|
-
|
|
1162
|
-
i.def(
|
|
1163
|
-
"get_key_at_offset",
|
|
1164
|
-
[](dense_index_py_t const& index, std::size_t offset) -> dense_key_t {
|
|
1165
|
-
dense_key_t result;
|
|
1166
|
-
index.export_keys(&result, offset, 1);
|
|
1167
|
-
return result;
|
|
1168
|
-
},
|
|
1169
|
-
py::arg("offset"));
|
|
1170
|
-
|
|
1171
|
-
i.def("save_index_to_path", &save_index_to_path<dense_index_py_t>, py::arg("path"), py::arg("progress") = nullptr);
|
|
1172
|
-
i.def("load_index_from_path", &load_index_from_path<dense_index_py_t>, py::arg("path"),
|
|
1173
|
-
py::arg("progress") = nullptr);
|
|
1174
|
-
i.def("view_index_from_path", &view_index_from_path<dense_index_py_t>, py::arg("path"),
|
|
1175
|
-
py::arg("progress") = nullptr);
|
|
1176
|
-
|
|
1177
|
-
i.def("save_index_to_buffer", &save_index_to_buffer<dense_index_py_t>, py::arg("progress") = nullptr);
|
|
1178
|
-
i.def("load_index_from_buffer", &load_index_from_buffer<dense_index_py_t>, py::arg("buffer"),
|
|
1179
|
-
py::arg("progress") = nullptr);
|
|
1180
|
-
i.def("view_index_from_buffer", &view_index_from_buffer<dense_index_py_t>, py::arg("buffer"),
|
|
1181
|
-
py::arg("progress") = nullptr);
|
|
1182
|
-
|
|
1183
|
-
i.def("reset", &reset_index<dense_index_py_t>);
|
|
1184
|
-
i.def("clear", &clear_index<dense_index_py_t>);
|
|
1185
|
-
i.def("copy", ©_index, py::kw_only(), py::arg("copy") = true);
|
|
1186
|
-
i.def("compact", &compact_index, py::arg("threads"), py::arg("progress") = nullptr);
|
|
1187
|
-
i.def("join", &join_index, py::arg("other"), py::arg("max_proposals") = 0, py::arg("exact") = false,
|
|
1188
|
-
py::arg("progress") = nullptr);
|
|
1189
|
-
|
|
1190
|
-
using punned_index_stats_t = typename dense_index_py_t::stats_t;
|
|
1191
|
-
auto i_stats = py::class_<punned_index_stats_t>(m, "IndexStats");
|
|
1192
|
-
i_stats.def_readonly("nodes", &punned_index_stats_t::nodes);
|
|
1193
|
-
i_stats.def_readonly("edges", &punned_index_stats_t::edges);
|
|
1194
|
-
i_stats.def_readonly("max_edges", &punned_index_stats_t::max_edges);
|
|
1195
|
-
i_stats.def_readonly("allocated_bytes", &punned_index_stats_t::allocated_bytes);
|
|
1196
|
-
|
|
1197
|
-
i.def_property_readonly("max_level", &max_level<dense_index_py_t>);
|
|
1198
|
-
i.def_property_readonly("stats", &compute_stats<dense_index_py_t>);
|
|
1199
|
-
i.def_property_readonly("levels_stats", &compute_levels_stats<dense_index_py_t>);
|
|
1200
|
-
i.def("level_stats", &compute_level_stats<dense_index_py_t>, py::arg("level"));
|
|
1201
|
-
|
|
1202
|
-
auto is = py::class_<dense_indexes_py_t>(m, "Indexes");
|
|
1203
|
-
is.def(py::init());
|
|
1204
|
-
is.def("__len__", &dense_indexes_py_t::size);
|
|
1205
|
-
is.def("merge", &dense_indexes_py_t::merge);
|
|
1206
|
-
is.def("merge_paths", &dense_indexes_py_t::merge_paths, py::arg("paths"), py::arg("view") = true,
|
|
1207
|
-
py::arg("threads") = 0);
|
|
1208
|
-
is.def( //
|
|
1209
|
-
"search_many", &search_many_in_index<dense_indexes_py_t>, //
|
|
1210
|
-
py::arg("query"), //
|
|
1211
|
-
py::arg("count") = 10, //
|
|
1212
|
-
py::arg("exact") = false, //
|
|
1213
|
-
py::arg("threads") = 0, //
|
|
1214
|
-
py::arg("progress") = nullptr //
|
|
1215
|
-
);
|
|
1216
|
-
}
|