usearch 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/prerelease.yml +2 -5
- package/.github/workflows/release.yml +22 -2
- package/.gitignore +84 -0
- package/.vscode/launch.json +7 -1
- package/.vscode/settings.json +15 -1
- package/Cargo.toml +1 -1
- package/README.md +43 -8
- package/binding.gyp +2 -2
- package/c/lib.cpp +141 -0
- package/c/usearch.h +43 -0
- package/conanfile.py +1 -1
- package/docs/compilation.md +14 -3
- package/golang/go.mod +3 -0
- package/golang/lib.go +238 -0
- package/golang/usearch.h +9 -0
- package/golang/usearch_par_test.go +40 -0
- package/golang/usearch_test.go +82 -0
- package/include/usearch/usearch.hpp +595 -248
- package/java/cloud/unum/usearch/Index.java +1 -1
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +36 -44
- package/javascript/lib.cpp +10 -6
- package/javascript/usearch.js +3 -0
- package/objc/USearchObjective.mm +3 -3
- package/package.json +4 -3
- package/python/lib.cpp +354 -181
- package/python/scripts/bench.py +2 -2
- package/python/scripts/test.py +88 -29
- package/python/usearch/index.py +132 -57
- package/rust/lib.cpp +33 -24
- package/rust/lib.hpp +2 -2
- package/setup.py +4 -1
- package/src/CMakeLists.txt +4 -4
- package/src/bench.cpp +22 -19
- package/src/punned.hpp +108 -79
- package/src/test.cpp +77 -27
- package/wolfram/lib.cpp +7 -8
- package/Dockerfile +0 -16
- package/VERSION +0 -1
package/python/lib.cpp
CHANGED
|
@@ -29,19 +29,20 @@ using py_shape_t = py::array::ShapeContainer;
|
|
|
29
29
|
using label_t = Py_ssize_t;
|
|
30
30
|
using distance_t = punned_distance_t;
|
|
31
31
|
using id_t = std::uint32_t;
|
|
32
|
-
using punned_t = punned_gt<label_t, id_t>;
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
using punned_index_t = punned_gt<label_t, id_t>;
|
|
34
|
+
using punned_add_result_t = typename punned_index_t::add_result_t;
|
|
35
|
+
using punned_search_result_t = typename punned_index_t::search_result_t;
|
|
36
|
+
|
|
37
|
+
struct punned_index_py_t : public punned_index_t {
|
|
38
|
+
using native_t = punned_index_t;
|
|
36
39
|
using native_t::add;
|
|
37
40
|
using native_t::capacity;
|
|
38
41
|
using native_t::reserve;
|
|
39
42
|
using native_t::search;
|
|
40
43
|
using native_t::size;
|
|
41
44
|
|
|
42
|
-
std::
|
|
43
|
-
|
|
44
|
-
punned_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
45
|
+
punned_index_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
using set_member_t = std::uint32_t;
|
|
@@ -59,33 +60,53 @@ struct sets_index_py_t : public sets_index_t {
|
|
|
59
60
|
sets_index_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
using
|
|
63
|
-
using
|
|
64
|
-
|
|
63
|
+
using bits_numpy_word_t = std::uint8_t;
|
|
64
|
+
using bits_native_word_t = std::uint64_t;
|
|
65
|
+
using bits_distance_t = float;
|
|
66
|
+
using bits_metric_t =
|
|
67
|
+
std::function<bits_distance_t(bits_native_word_t const*, bits_native_word_t const*, std::size_t, std::size_t)>;
|
|
68
|
+
using bits_index_t = index_gt<bits_metric_t, label_t, id_t, bits_native_word_t>;
|
|
69
|
+
using bits_add_result_t = typename bits_index_t::add_result_t;
|
|
70
|
+
using bits_search_result_t = typename bits_index_t::search_result_t;
|
|
71
|
+
|
|
72
|
+
static constexpr std::size_t bits_per_hash_numpy_word_k = sizeof(bits_numpy_word_t) * CHAR_BIT;
|
|
73
|
+
static constexpr std::size_t bits_per_hash_native_word_k = sizeof(bits_native_word_t) * CHAR_BIT;
|
|
65
74
|
|
|
66
|
-
struct
|
|
67
|
-
using native_t =
|
|
75
|
+
struct bits_index_py_t : public bits_index_t {
|
|
76
|
+
using native_t = bits_index_t;
|
|
68
77
|
using native_t::add;
|
|
69
78
|
using native_t::capacity;
|
|
79
|
+
using native_t::limits;
|
|
70
80
|
using native_t::reserve;
|
|
71
81
|
using native_t::search;
|
|
72
82
|
using native_t::size;
|
|
73
83
|
|
|
74
|
-
std::vector<hash_word_t> buffer_;
|
|
75
|
-
std::size_t words_;
|
|
76
84
|
std::size_t bits_;
|
|
85
|
+
std::size_t numpy_words_;
|
|
86
|
+
std::size_t native_words_;
|
|
87
|
+
std::vector<bits_native_word_t> word_aligned_buffer_;
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
hash_index_py_t(config_t config, std::size_t bits) : hash_index_t(config) {
|
|
81
|
-
words_ = divide_round_up<bits_per_hash_word_k>(bits);
|
|
82
|
-
bits_ = words_ * bits_per_hash_word_k;
|
|
83
|
-
buffer_.resize(words_);
|
|
84
|
-
}
|
|
89
|
+
bits_index_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
bits_index_py_t(index_config_t config, std::size_t bits, bits_metric_t metric)
|
|
92
|
+
: bits_index_t(config, metric), bits_(bits), //
|
|
93
|
+
numpy_words_(divide_round_up<bits_per_hash_numpy_word_k>(bits)), //
|
|
94
|
+
native_words_(divide_round_up<bits_per_hash_native_word_k>(bits)), //
|
|
95
|
+
word_aligned_buffer_(native_words_* index_limits_t{}.threads()) {}
|
|
87
96
|
};
|
|
88
97
|
|
|
98
|
+
bits_metric_t bits_metric(common_metric_kind_t kind) {
|
|
99
|
+
switch (kind) {
|
|
100
|
+
case common_metric_kind_t::bitwise_hamming_k:
|
|
101
|
+
return bits_metric_t(bitwise_hamming_gt<bits_native_word_t, bits_distance_t>{});
|
|
102
|
+
case common_metric_kind_t::bitwise_tanimoto_k:
|
|
103
|
+
return bits_metric_t(bitwise_tanimoto_gt<bits_native_word_t, bits_distance_t>{});
|
|
104
|
+
case common_metric_kind_t::bitwise_sorensen_k:
|
|
105
|
+
return bits_metric_t(bitwise_sorensen_gt<bits_native_word_t, bits_distance_t>{});
|
|
106
|
+
default: throw std::invalid_argument("Not a bitwise metric!"); return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
template <typename scalar_at> punned_stateful_metric_t udf(std::size_t metric_uintptr) {
|
|
90
111
|
return [metric_uintptr](byte_t const* a_bytes, byte_t const* b_bytes, std::size_t n, std::size_t m) -> distance_t {
|
|
91
112
|
using metric_t = punned_distance_t (*)(scalar_at const*, scalar_at const*, std::size_t, std::size_t);
|
|
@@ -105,71 +126,59 @@ punned_stateful_metric_t udf(std::size_t metric_uintptr, accuracy_t accuracy) {
|
|
|
105
126
|
}
|
|
106
127
|
}
|
|
107
128
|
|
|
108
|
-
static
|
|
109
|
-
std::size_t dimensions,
|
|
110
|
-
std::
|
|
111
|
-
|
|
112
|
-
std::
|
|
113
|
-
std::size_t
|
|
114
|
-
std::size_t
|
|
115
|
-
std::size_t
|
|
116
|
-
std::size_t metric_uintptr, //
|
|
129
|
+
static punned_index_py_t make_index( //
|
|
130
|
+
std::size_t dimensions, //
|
|
131
|
+
std::string const& scalar_type, //
|
|
132
|
+
common_metric_kind_t metric, //
|
|
133
|
+
std::size_t connectivity, //
|
|
134
|
+
std::size_t expansion_add, //
|
|
135
|
+
std::size_t expansion_search, //
|
|
136
|
+
std::size_t metric_uintptr, //
|
|
117
137
|
bool tune) {
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
index_config_t config;
|
|
120
140
|
config.expansion_add = expansion_add;
|
|
121
141
|
config.expansion_search = expansion_search;
|
|
122
142
|
config.connectivity = connectivity;
|
|
123
|
-
config.max_elements = capacity;
|
|
124
|
-
config.max_threads_add = std::thread::hardware_concurrency();
|
|
125
|
-
config.max_threads_search = std::thread::hardware_concurrency();
|
|
126
143
|
|
|
127
144
|
if (tune)
|
|
128
|
-
config =
|
|
145
|
+
config = punned_index_t::optimize(config);
|
|
129
146
|
|
|
130
147
|
accuracy_t accuracy = accuracy_from_name(scalar_type.c_str(), scalar_type.size());
|
|
131
148
|
if (metric_uintptr)
|
|
132
|
-
return
|
|
149
|
+
return punned_index_t::udf(dimensions, udf(metric_uintptr, accuracy), accuracy, config);
|
|
133
150
|
else
|
|
134
|
-
return
|
|
151
|
+
return punned_index_t(make_punned<punned_index_t>(metric, dimensions, accuracy, config));
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
static std::unique_ptr<sets_index_py_t> make_sets_index( //
|
|
138
|
-
std::size_t capacity, //
|
|
139
155
|
std::size_t connectivity, //
|
|
140
156
|
std::size_t expansion_add, //
|
|
141
157
|
std::size_t expansion_search //
|
|
142
158
|
) {
|
|
143
|
-
|
|
159
|
+
index_config_t config;
|
|
144
160
|
config.expansion_add = expansion_add;
|
|
145
161
|
config.expansion_search = expansion_search;
|
|
146
162
|
config.connectivity = connectivity;
|
|
147
|
-
config.max_elements = capacity;
|
|
148
|
-
config.max_threads_add = 1;
|
|
149
|
-
config.max_threads_search = 1;
|
|
150
163
|
|
|
151
164
|
return std::unique_ptr<sets_index_py_t>(new sets_index_py_t(sets_index_t(config)));
|
|
152
165
|
}
|
|
153
166
|
|
|
154
|
-
static std::unique_ptr<
|
|
167
|
+
static std::unique_ptr<bits_index_py_t> make_hash_index( //
|
|
155
168
|
std::size_t bits, //
|
|
156
|
-
|
|
169
|
+
common_metric_kind_t metric, //
|
|
157
170
|
std::size_t connectivity, //
|
|
158
171
|
std::size_t expansion_add, //
|
|
159
172
|
std::size_t expansion_search //
|
|
160
173
|
) {
|
|
161
|
-
|
|
174
|
+
index_config_t config;
|
|
162
175
|
config.expansion_add = expansion_add;
|
|
163
176
|
config.expansion_search = expansion_search;
|
|
164
177
|
config.connectivity = connectivity;
|
|
165
|
-
config
|
|
166
|
-
config.max_threads_add = 1;
|
|
167
|
-
config.max_threads_search = 1;
|
|
168
|
-
|
|
169
|
-
return std::unique_ptr<hash_index_py_t>(new hash_index_py_t(config, bits));
|
|
178
|
+
return std::unique_ptr<bits_index_py_t>(new bits_index_py_t(config, bits, bits_metric(metric)));
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
static void add_one_to_index(
|
|
181
|
+
static void add_one_to_index(punned_index_py_t& index, label_t label, py::buffer vector, bool copy, std::size_t) {
|
|
173
182
|
|
|
174
183
|
py::buffer_info vector_info = vector.request();
|
|
175
184
|
if (vector_info.ndim != 1)
|
|
@@ -188,22 +197,22 @@ static void add_one_to_index(punned_py_t& index, label_t label, py::buffer vecto
|
|
|
188
197
|
|
|
189
198
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
190
199
|
if (vector_info.format == "c" || vector_info.format == "b")
|
|
191
|
-
index.add(label, reinterpret_cast<f8_bits_t const*>(vector_data), config);
|
|
200
|
+
index.add(label, reinterpret_cast<f8_bits_t const*>(vector_data), config).error.raise();
|
|
192
201
|
else if (vector_info.format == "e")
|
|
193
|
-
index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), config);
|
|
202
|
+
index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), config).error.raise();
|
|
194
203
|
else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4")
|
|
195
|
-
index.add(label, reinterpret_cast<float const*>(vector_data), config);
|
|
204
|
+
index.add(label, reinterpret_cast<float const*>(vector_data), config).error.raise();
|
|
196
205
|
else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8")
|
|
197
|
-
index.add(label, reinterpret_cast<double const*>(vector_data), config);
|
|
206
|
+
index.add(label, reinterpret_cast<double const*>(vector_data), config).error.raise();
|
|
198
207
|
else
|
|
199
208
|
throw std::invalid_argument("Incompatible scalars in the vector!");
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
static void add_many_to_index(
|
|
203
|
-
|
|
211
|
+
static void add_many_to_index( //
|
|
212
|
+
punned_index_py_t& index, py::buffer labels, py::buffer vectors, //
|
|
204
213
|
bool copy, std::size_t threads) {
|
|
205
214
|
|
|
206
|
-
if (index.
|
|
215
|
+
if (index.limits().threads_add < threads)
|
|
207
216
|
throw std::invalid_argument("Can't use that many threads!");
|
|
208
217
|
|
|
209
218
|
py::buffer_info labels_info = labels.request();
|
|
@@ -242,7 +251,7 @@ static void add_many_to_index( //
|
|
|
242
251
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
243
252
|
f8_bits_t const* vector =
|
|
244
253
|
reinterpret_cast<f8_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
245
|
-
index.add(label, vector, config);
|
|
254
|
+
index.add(label, vector, config).error.raise();
|
|
246
255
|
});
|
|
247
256
|
else if (vectors_info.format == "e")
|
|
248
257
|
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
@@ -252,7 +261,7 @@ static void add_many_to_index( //
|
|
|
252
261
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
253
262
|
f16_bits_t const* vector =
|
|
254
263
|
reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
255
|
-
index.add(label, vector, config);
|
|
264
|
+
index.add(label, vector, config).error.raise();
|
|
256
265
|
});
|
|
257
266
|
else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
|
|
258
267
|
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
@@ -261,7 +270,7 @@ static void add_many_to_index( //
|
|
|
261
270
|
config.thread = thread_idx;
|
|
262
271
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
263
272
|
float const* vector = reinterpret_cast<float const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
264
|
-
index.add(label, vector, config);
|
|
273
|
+
index.add(label, vector, config).error.raise();
|
|
265
274
|
});
|
|
266
275
|
else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
|
|
267
276
|
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
@@ -270,13 +279,64 @@ static void add_many_to_index( //
|
|
|
270
279
|
config.thread = thread_idx;
|
|
271
280
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
272
281
|
double const* vector = reinterpret_cast<double const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
273
|
-
index.add(label, vector, config);
|
|
282
|
+
index.add(label, vector, config).error.raise();
|
|
274
283
|
});
|
|
275
284
|
else
|
|
276
285
|
throw std::invalid_argument("Incompatible scalars in the vectors matrix!");
|
|
277
286
|
}
|
|
278
287
|
|
|
279
|
-
static
|
|
288
|
+
static void add_many_to_bits_index( //
|
|
289
|
+
bits_index_py_t& index, py::buffer labels, py::buffer vectors, //
|
|
290
|
+
bool copy, std::size_t threads) {
|
|
291
|
+
|
|
292
|
+
if (index.limits().threads_add < threads)
|
|
293
|
+
throw std::invalid_argument("Can't use that many threads!");
|
|
294
|
+
|
|
295
|
+
py::buffer_info labels_info = labels.request();
|
|
296
|
+
py::buffer_info vectors_info = vectors.request();
|
|
297
|
+
|
|
298
|
+
if (labels_info.format != py::format_descriptor<label_t>::format())
|
|
299
|
+
throw std::invalid_argument("Incompatible label type!");
|
|
300
|
+
|
|
301
|
+
if (labels_info.ndim != 1)
|
|
302
|
+
throw std::invalid_argument("Labels must be placed in a single-dimensional array!");
|
|
303
|
+
|
|
304
|
+
if (vectors_info.ndim != 2)
|
|
305
|
+
throw std::invalid_argument("Expects a matrix of vectors to add!");
|
|
306
|
+
|
|
307
|
+
Py_ssize_t labels_count = labels_info.shape[0];
|
|
308
|
+
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
309
|
+
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
310
|
+
if (vectors_dimensions != static_cast<Py_ssize_t>(index.numpy_words_))
|
|
311
|
+
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
312
|
+
|
|
313
|
+
if (labels_count != vectors_count)
|
|
314
|
+
throw std::invalid_argument("Number of labels and vectors must match!");
|
|
315
|
+
|
|
316
|
+
if (index.size() + vectors_count >= index.capacity())
|
|
317
|
+
index.reserve(ceil2(index.size() + vectors_count));
|
|
318
|
+
|
|
319
|
+
char const* vectors_data = reinterpret_cast<char const*>(vectors_info.ptr);
|
|
320
|
+
char const* labels_data = reinterpret_cast<char const*>(labels_info.ptr);
|
|
321
|
+
|
|
322
|
+
// https://docs.python.org/3/library/struct.html#format-characters
|
|
323
|
+
if (vectors_info.format != "B")
|
|
324
|
+
throw std::invalid_argument("Incompatible scalars in the vectors matrix!");
|
|
325
|
+
|
|
326
|
+
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
327
|
+
add_config_t config;
|
|
328
|
+
config.store_vector = copy;
|
|
329
|
+
config.thread = thread_idx;
|
|
330
|
+
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
331
|
+
bits_numpy_word_t const* vector =
|
|
332
|
+
reinterpret_cast<bits_numpy_word_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
333
|
+
bits_native_word_t* vector_words = index.word_aligned_buffer_.data() + index.native_words_ * thread_idx;
|
|
334
|
+
std::memcpy(vector_words, vector, vectors_dimensions);
|
|
335
|
+
index.add(label, {vector_words, index.native_words_}, config).error.raise();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector, std::size_t wanted, bool exact) {
|
|
280
340
|
|
|
281
341
|
py::buffer_info vector_info = vector.request();
|
|
282
342
|
Py_ssize_t vector_dimensions = vector_info.shape[0];
|
|
@@ -294,23 +354,23 @@ static py::tuple search_one_in_index(punned_py_t& index, py::buffer vector, std:
|
|
|
294
354
|
config.exact = exact;
|
|
295
355
|
|
|
296
356
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
297
|
-
if (vector_info.format == "c" || vector_info.format == "b")
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
else if (vector_info.format == "e")
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4")
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8")
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
else
|
|
357
|
+
if (vector_info.format == "c" || vector_info.format == "b") {
|
|
358
|
+
punned_search_result_t result = index.search(reinterpret_cast<f8_bits_t const*>(vector_data), wanted, config);
|
|
359
|
+
result.error.raise();
|
|
360
|
+
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
361
|
+
} else if (vector_info.format == "e") {
|
|
362
|
+
punned_search_result_t result = index.search(reinterpret_cast<f16_bits_t const*>(vector_data), wanted, config);
|
|
363
|
+
result.error.raise();
|
|
364
|
+
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
365
|
+
} else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4") {
|
|
366
|
+
punned_search_result_t result = index.search(reinterpret_cast<float const*>(vector_data), wanted, config);
|
|
367
|
+
result.error.raise();
|
|
368
|
+
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
369
|
+
} else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8") {
|
|
370
|
+
punned_search_result_t result = index.search(reinterpret_cast<double const*>(vector_data), wanted, config);
|
|
371
|
+
result.error.raise();
|
|
372
|
+
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
373
|
+
} else
|
|
314
374
|
throw std::invalid_argument("Incompatible scalars in the query vector!");
|
|
315
375
|
|
|
316
376
|
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
@@ -333,12 +393,12 @@ static py::tuple search_one_in_index(punned_py_t& index, py::buffer vector, std:
|
|
|
333
393
|
* 3. array with match counts.
|
|
334
394
|
*/
|
|
335
395
|
static py::tuple search_many_in_index( //
|
|
336
|
-
|
|
396
|
+
punned_index_py_t& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads) {
|
|
337
397
|
|
|
338
398
|
if (wanted == 0)
|
|
339
399
|
return py::tuple(3);
|
|
340
400
|
|
|
341
|
-
if (index.
|
|
401
|
+
if (index.limits().threads_add < threads)
|
|
342
402
|
throw std::invalid_argument("Can't use that many threads!");
|
|
343
403
|
|
|
344
404
|
py::buffer_info vectors_info = vectors.request();
|
|
@@ -367,8 +427,10 @@ static py::tuple search_many_in_index( //
|
|
|
367
427
|
config.thread = thread_idx;
|
|
368
428
|
config.exact = exact;
|
|
369
429
|
f8_bits_t const* vector = (f8_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
370
|
-
|
|
371
|
-
|
|
430
|
+
punned_search_result_t result = index.search(vector, wanted, config);
|
|
431
|
+
result.error.raise();
|
|
432
|
+
counts_py1d(task_idx) =
|
|
433
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
372
434
|
});
|
|
373
435
|
else if (vectors_info.format == "e")
|
|
374
436
|
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
@@ -376,8 +438,10 @@ static py::tuple search_many_in_index( //
|
|
|
376
438
|
config.thread = thread_idx;
|
|
377
439
|
config.exact = exact;
|
|
378
440
|
f16_bits_t const* vector = (f16_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
379
|
-
|
|
380
|
-
|
|
441
|
+
punned_search_result_t result = index.search(vector, wanted, config);
|
|
442
|
+
result.error.raise();
|
|
443
|
+
counts_py1d(task_idx) =
|
|
444
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
381
445
|
});
|
|
382
446
|
else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
|
|
383
447
|
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
@@ -385,8 +449,10 @@ static py::tuple search_many_in_index( //
|
|
|
385
449
|
config.thread = thread_idx;
|
|
386
450
|
config.exact = exact;
|
|
387
451
|
float const* vector = (float const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
388
|
-
|
|
389
|
-
|
|
452
|
+
punned_search_result_t result = index.search(vector, wanted, config);
|
|
453
|
+
result.error.raise();
|
|
454
|
+
counts_py1d(task_idx) =
|
|
455
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
390
456
|
});
|
|
391
457
|
else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
|
|
392
458
|
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
@@ -394,8 +460,10 @@ static py::tuple search_many_in_index( //
|
|
|
394
460
|
config.thread = thread_idx;
|
|
395
461
|
config.exact = exact;
|
|
396
462
|
double const* vector = (double const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
397
|
-
|
|
398
|
-
|
|
463
|
+
punned_search_result_t result = index.search(vector, wanted, config);
|
|
464
|
+
result.error.raise();
|
|
465
|
+
counts_py1d(task_idx) =
|
|
466
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
399
467
|
});
|
|
400
468
|
else
|
|
401
469
|
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
@@ -407,10 +475,74 @@ static py::tuple search_many_in_index( //
|
|
|
407
475
|
return results;
|
|
408
476
|
}
|
|
409
477
|
|
|
478
|
+
/**
|
|
479
|
+
* @param vectors Matrix of vectors to search for.
|
|
480
|
+
* @param wanted Number of matches per request.
|
|
481
|
+
*
|
|
482
|
+
* @return Tuple with:
|
|
483
|
+
* 1. matrix of neighbors,
|
|
484
|
+
* 2. matrix of distances,
|
|
485
|
+
* 3. array with match counts.
|
|
486
|
+
*/
|
|
487
|
+
static py::tuple search_many_in_bits_index( //
|
|
488
|
+
bits_index_py_t& index, py::buffer vectors, std::size_t wanted, bool exact, std::size_t threads) {
|
|
489
|
+
|
|
490
|
+
if (wanted == 0)
|
|
491
|
+
return py::tuple(3);
|
|
492
|
+
|
|
493
|
+
if (index.limits().threads_add < threads)
|
|
494
|
+
throw std::invalid_argument("Can't use that many threads!");
|
|
495
|
+
|
|
496
|
+
py::buffer_info vectors_info = vectors.request();
|
|
497
|
+
// if (vectors_info.ndim == 1)
|
|
498
|
+
// return search_one_in_index(index, vectors, wanted, exact);
|
|
499
|
+
if (vectors_info.ndim != 2)
|
|
500
|
+
throw std::invalid_argument("Expects a matrix of vectors to add!");
|
|
501
|
+
|
|
502
|
+
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
503
|
+
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
504
|
+
char const* vectors_data = reinterpret_cast<char const*>(vectors_info.ptr);
|
|
505
|
+
if (vectors_dimensions != static_cast<Py_ssize_t>(index.numpy_words_))
|
|
506
|
+
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
507
|
+
|
|
508
|
+
py::array_t<label_t> labels_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
509
|
+
py::array_t<distance_t> distances_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
510
|
+
py::array_t<Py_ssize_t> counts_py(vectors_count);
|
|
511
|
+
auto labels_py2d = labels_py.mutable_unchecked<2>();
|
|
512
|
+
auto distances_py2d = distances_py.mutable_unchecked<2>();
|
|
513
|
+
auto counts_py1d = counts_py.mutable_unchecked<1>();
|
|
514
|
+
|
|
515
|
+
// https://docs.python.org/3/library/struct.html#format-characters
|
|
516
|
+
if (vectors_info.format != "B")
|
|
517
|
+
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
518
|
+
|
|
519
|
+
multithreaded(threads, vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
520
|
+
search_config_t config;
|
|
521
|
+
config.thread = thread_idx;
|
|
522
|
+
config.exact = exact;
|
|
523
|
+
|
|
524
|
+
bits_numpy_word_t const* vector =
|
|
525
|
+
reinterpret_cast<bits_numpy_word_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
526
|
+
bits_native_word_t* vector_words = index.word_aligned_buffer_.data() + index.native_words_ * thread_idx;
|
|
527
|
+
std::memcpy(vector_words, vector, vectors_dimensions);
|
|
528
|
+
|
|
529
|
+
bits_search_result_t result = index.search({vector_words, index.native_words_}, wanted, config);
|
|
530
|
+
result.error.raise();
|
|
531
|
+
counts_py1d(task_idx) =
|
|
532
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
py::tuple results(3);
|
|
536
|
+
results[0] = labels_py;
|
|
537
|
+
results[1] = distances_py;
|
|
538
|
+
results[2] = counts_py;
|
|
539
|
+
return results;
|
|
540
|
+
}
|
|
541
|
+
|
|
410
542
|
// clang-format off
|
|
411
|
-
template <typename index_at> void save_index(index_at const& index, std::string const& path) { index.save(path.c_str()); }
|
|
412
|
-
template <typename index_at> void load_index(index_at& index, std::string const& path) { index.load(path.c_str()); }
|
|
413
|
-
template <typename index_at> void view_index(index_at& index, std::string const& path) { index.view(path.c_str()); }
|
|
543
|
+
template <typename index_at> void save_index(index_at const& index, std::string const& path) { index.save(path.c_str()).error.raise(); }
|
|
544
|
+
template <typename index_at> void load_index(index_at& index, std::string const& path) { index.load(path.c_str()).error.raise(); }
|
|
545
|
+
template <typename index_at> void view_index(index_at& index, std::string const& path) { index.view(path.c_str()).error.raise(); }
|
|
414
546
|
template <typename index_at> void clear_index(index_at& index) { index.clear(); }
|
|
415
547
|
template <typename index_at> std::size_t get_expansion_add(index_at const &index) { return index.config().expansion_add; }
|
|
416
548
|
template <typename index_at> std::size_t get_expansion_search(index_at const &index) { return index.config().expansion_search; }
|
|
@@ -442,9 +574,9 @@ void validate_set(py::array_t<set_member_t> const& set) {
|
|
|
442
574
|
throw std::invalid_argument("Set must be deduplicated!");
|
|
443
575
|
}
|
|
444
576
|
|
|
445
|
-
inline
|
|
577
|
+
inline bits_native_word_t hash_ror64(bits_native_word_t v, int r) noexcept { return (v >> r) | (v << (64 - r)); }
|
|
446
578
|
|
|
447
|
-
inline
|
|
579
|
+
inline bits_native_word_t hash(bits_native_word_t v) noexcept {
|
|
448
580
|
v ^= hash_ror64(v, 25) ^ hash_ror64(v, 50);
|
|
449
581
|
v *= 0xA24BAED4963EE407UL;
|
|
450
582
|
v ^= hash_ror64(v, 24) ^ hash_ror64(v, 49);
|
|
@@ -453,34 +585,71 @@ inline std::uint64_t hash(std::uint64_t v) noexcept {
|
|
|
453
585
|
}
|
|
454
586
|
|
|
455
587
|
template <typename scalar_at>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
std::
|
|
588
|
+
void hash_typed_row( //
|
|
589
|
+
byte_t const* scalars, std::size_t scalar_stride, std::size_t scalars_count, //
|
|
590
|
+
bits_numpy_word_t* hashes, std::size_t bits) noexcept {
|
|
591
|
+
|
|
592
|
+
std::size_t words = divide_round_up<8>(bits);
|
|
593
|
+
for (std::size_t i = 0; i != scalars_count; ++i) {
|
|
594
|
+
scalar_at scalar;
|
|
595
|
+
std::memcpy(&scalar, scalars + i * scalar_stride, sizeof(scalar_at));
|
|
596
|
+
bits_native_word_t scalar_hash = hash(scalar);
|
|
597
|
+
hashes[scalar_hash & (words - 1)] |= std::uint8_t(1) << (scalar_hash % 8);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
461
600
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
601
|
+
#if 0
|
|
602
|
+
template <typename scalar_at>
|
|
603
|
+
py::array_t<bits_numpy_word_t> hash_typed_buffer(py::buffer_info const& info, std::size_t bits) {
|
|
604
|
+
byte_t const* data = reinterpret_cast<byte_t const*>(info.ptr);
|
|
605
|
+
Py_ssize_t elements_per_row = info.shape[0];
|
|
606
|
+
Py_ssize_t elements_stride = info.strides[0];
|
|
607
|
+
Py_ssize_t words_per_row = divide_round_up<std::size_t>(bits, 8);
|
|
608
|
+
|
|
609
|
+
if (info.ndim == 2) {
|
|
610
|
+
Py_ssize_t rows_count = info.shape[1];
|
|
611
|
+
Py_ssize_t rows_stride = info.strides[1];
|
|
612
|
+
auto hashes_shape = py_shape_t{rows_count, words_per_row};
|
|
613
|
+
auto hashes_py = py::array_t<bits_numpy_word_t>(hashes_shape);
|
|
614
|
+
auto hashes_proxy = hashes_py.mutable_unchecked<2>();
|
|
615
|
+
|
|
616
|
+
for (Py_ssize_t i = 0; i != rows_count; ++i)
|
|
617
|
+
hash_typed_row( //
|
|
618
|
+
data + i * rows_stride, hashes_proxy.stride(1), static_cast<std::size_t>(elements_per_row), //
|
|
619
|
+
&hashes_proxy(i, 0), bits);
|
|
620
|
+
|
|
621
|
+
return hashes_py;
|
|
622
|
+
} else {
|
|
623
|
+
auto hashes_shape = py_shape_t{words_per_row};
|
|
624
|
+
auto hashes_py = py::array_t<bits_numpy_word_t>(hashes_shape);
|
|
625
|
+
auto hashes_proxy = hashes_py.mutable_unchecked<1>();
|
|
626
|
+
|
|
627
|
+
hash_typed_row( //
|
|
628
|
+
data, hashes_info.strides[0], static_cast<std::size_t>(elements_per_row), //
|
|
629
|
+
&hashes_proxy(0), bits);
|
|
630
|
+
|
|
631
|
+
return hashes_py;
|
|
466
632
|
}
|
|
467
633
|
}
|
|
468
634
|
|
|
469
|
-
|
|
635
|
+
py::array_t<bits_numpy_word_t> hash_buffer(py::buffer vector, std::size_t bits) {
|
|
636
|
+
if (std::bitset<64>(bits).count() == 1)
|
|
637
|
+
throw std::invalid_argument("Number of bits must be a power of two!");
|
|
470
638
|
py::buffer_info info = vector.request();
|
|
471
|
-
if (info.ndim
|
|
472
|
-
throw std::invalid_argument("
|
|
639
|
+
if (info.ndim > 2)
|
|
640
|
+
throw std::invalid_argument("Only one or two-dimensional arrays are supported!");
|
|
473
641
|
|
|
474
642
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
475
643
|
if (info.format == "h" || info.format == "H")
|
|
476
|
-
return hash_typed_buffer<std::uint16_t>(
|
|
644
|
+
return hash_typed_buffer<std::uint16_t>(info, bits);
|
|
477
645
|
else if (info.format == "i" || info.format == "I" || info.format == "l" || info.format == "L")
|
|
478
|
-
return hash_typed_buffer<std::uint32_t>(
|
|
646
|
+
return hash_typed_buffer<std::uint32_t>(info, bits);
|
|
479
647
|
else if (info.format == "q" || info.format == "Q" || info.format == "n" || info.format == "N")
|
|
480
|
-
return hash_typed_buffer<std::uint64_t>(
|
|
648
|
+
return hash_typed_buffer<std::uint64_t>(info, bits);
|
|
481
649
|
else
|
|
482
650
|
throw std::invalid_argument("Array elements must be 16, 32, or 64 bit hashable integers!");
|
|
483
651
|
}
|
|
652
|
+
#endif
|
|
484
653
|
|
|
485
654
|
PYBIND11_MODULE(compiled, m) {
|
|
486
655
|
m.doc() = "Smaller & Faster Single-File Vector Search Engine from Unum";
|
|
@@ -489,14 +658,25 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
489
658
|
m.attr("DEFAULT_EXPANSION_ADD") = py::int_(default_expansion_add());
|
|
490
659
|
m.attr("DEFAULT_EXPANSION_SEARCH") = py::int_(default_expansion_search());
|
|
491
660
|
|
|
492
|
-
|
|
661
|
+
py::enum_<common_metric_kind_t>(m, "MetricKind")
|
|
662
|
+
.value("Unknown", common_metric_kind_t::unknown_k)
|
|
663
|
+
.value("IP", common_metric_kind_t::ip_k)
|
|
664
|
+
.value("Cos", common_metric_kind_t::cos_k)
|
|
665
|
+
.value("L2sq", common_metric_kind_t::l2sq_k)
|
|
666
|
+
.value("Haversine", common_metric_kind_t::haversine_k)
|
|
667
|
+
.value("Pearson", common_metric_kind_t::pearson_k)
|
|
668
|
+
.value("Jaccard", common_metric_kind_t::jaccard_k)
|
|
669
|
+
.value("BitwiseHamming", common_metric_kind_t::bitwise_hamming_k)
|
|
670
|
+
.value("BitwiseTanimoto", common_metric_kind_t::bitwise_tanimoto_k)
|
|
671
|
+
.value("BitwiseSorensen", common_metric_kind_t::bitwise_sorensen_k);
|
|
672
|
+
|
|
673
|
+
auto i = py::class_<punned_index_py_t>(m, "Index");
|
|
493
674
|
|
|
494
675
|
i.def(py::init(&make_index), //
|
|
495
676
|
py::kw_only(), //
|
|
496
677
|
py::arg("ndim") = 0, //
|
|
497
|
-
py::arg("capacity") = 0, //
|
|
498
678
|
py::arg("dtype") = std::string("f32"), //
|
|
499
|
-
py::arg("metric") =
|
|
679
|
+
py::arg("metric") = common_metric_kind_t::ip_k, //
|
|
500
680
|
py::arg("connectivity") = default_connectivity(), //
|
|
501
681
|
py::arg("expansion_add") = default_expansion_add(), //
|
|
502
682
|
py::arg("expansion_search") = default_expansion_search(), //
|
|
@@ -530,29 +710,69 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
530
710
|
py::arg("threads") = 0 //
|
|
531
711
|
);
|
|
532
712
|
|
|
533
|
-
i.def("__len__", &
|
|
534
|
-
i.def_property_readonly("size", &
|
|
535
|
-
i.def_property_readonly("ndim", &
|
|
536
|
-
i.def_property_readonly("connectivity", &
|
|
537
|
-
i.def_property_readonly("capacity", &
|
|
713
|
+
i.def("__len__", &punned_index_py_t::size);
|
|
714
|
+
i.def_property_readonly("size", &punned_index_py_t::size);
|
|
715
|
+
i.def_property_readonly("ndim", &punned_index_py_t::dimensions);
|
|
716
|
+
i.def_property_readonly("connectivity", &punned_index_py_t::connectivity);
|
|
717
|
+
i.def_property_readonly("capacity", &punned_index_py_t::capacity);
|
|
538
718
|
i.def_property_readonly( //
|
|
539
|
-
"dtype", [](
|
|
540
|
-
i.def_property_readonly("memory_usage", &
|
|
719
|
+
"dtype", [](punned_index_py_t const& index) -> std::string { return accuracy_name(index.accuracy()); });
|
|
720
|
+
i.def_property_readonly("memory_usage", &punned_index_py_t::memory_usage);
|
|
721
|
+
|
|
722
|
+
i.def_property("expansion_add", &get_expansion_add<punned_index_py_t>, &punned_index_py_t::change_expansion_add);
|
|
723
|
+
i.def_property("expansion_search", &get_expansion_search<punned_index_py_t>,
|
|
724
|
+
&punned_index_py_t::change_expansion_search);
|
|
725
|
+
|
|
726
|
+
i.def("save", &save_index<punned_index_py_t>, py::arg("path"));
|
|
727
|
+
i.def("load", &load_index<punned_index_py_t>, py::arg("path"));
|
|
728
|
+
i.def("view", &view_index<punned_index_py_t>, py::arg("path"));
|
|
729
|
+
i.def("clear", &clear_index<punned_index_py_t>);
|
|
730
|
+
|
|
731
|
+
auto bi = py::class_<bits_index_py_t>(m, "BitsIndex");
|
|
732
|
+
|
|
733
|
+
bi.def( //
|
|
734
|
+
py::init(&make_hash_index), //
|
|
735
|
+
py::kw_only(), //
|
|
736
|
+
py::arg("bits"), //
|
|
737
|
+
py::arg("metric") = common_metric_kind_t::bitwise_hamming_k, //
|
|
738
|
+
py::arg("connectivity") = default_connectivity(), //
|
|
739
|
+
py::arg("expansion_add") = default_expansion_add(), //
|
|
740
|
+
py::arg("expansion_search") = default_expansion_search() //
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
bi.def( //
|
|
744
|
+
"add",
|
|
745
|
+
&add_many_to_bits_index, //
|
|
746
|
+
py::arg("label"), //
|
|
747
|
+
py::arg("array"), //
|
|
748
|
+
py::kw_only(), //
|
|
749
|
+
py::arg("copy") = true, //
|
|
750
|
+
py::arg("threads") = 0 //
|
|
751
|
+
);
|
|
541
752
|
|
|
542
|
-
|
|
543
|
-
|
|
753
|
+
bi.def( //
|
|
754
|
+
"search", &search_many_in_bits_index, //
|
|
755
|
+
py::arg("query"), //
|
|
756
|
+
py::arg("count") = 10, //
|
|
757
|
+
py::arg("exact") = false, //
|
|
758
|
+
py::arg("threads") = 0 //
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
bi.def("__len__", &bits_index_py_t::size);
|
|
762
|
+
bi.def_property_readonly("size", &bits_index_py_t::size);
|
|
763
|
+
bi.def_property_readonly("connectivity", &bits_index_py_t::connectivity);
|
|
764
|
+
bi.def_property_readonly("capacity", &bits_index_py_t::capacity);
|
|
544
765
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
766
|
+
bi.def("save", &save_index<bits_index_py_t>, py::arg("path"));
|
|
767
|
+
bi.def("load", &load_index<bits_index_py_t>, py::arg("path"));
|
|
768
|
+
bi.def("view", &view_index<bits_index_py_t>, py::arg("path"));
|
|
769
|
+
bi.def("clear", &clear_index<bits_index_py_t>);
|
|
549
770
|
|
|
550
771
|
auto si = py::class_<sets_index_py_t>(m, "SetsIndex");
|
|
551
772
|
|
|
552
773
|
si.def( //
|
|
553
774
|
py::init(&make_sets_index), //
|
|
554
775
|
py::kw_only(), //
|
|
555
|
-
py::arg("capacity") = 0, //
|
|
556
776
|
py::arg("connectivity") = default_connectivity(), //
|
|
557
777
|
py::arg("expansion_add") = default_expansion_add(), //
|
|
558
778
|
py::arg("expansion_search") = default_expansion_search() //
|
|
@@ -565,10 +785,11 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
565
785
|
if (index.size() + 1 >= index.capacity())
|
|
566
786
|
index.reserve(ceil2(index.size() + 1));
|
|
567
787
|
auto proxy = set.unchecked<1>();
|
|
568
|
-
auto view = set_view_t{proxy
|
|
788
|
+
auto view = set_view_t{&proxy(0), static_cast<std::size_t>(proxy.shape(0))};
|
|
789
|
+
|
|
569
790
|
add_config_t config;
|
|
570
791
|
config.store_vector = copy;
|
|
571
|
-
index.add(label, view, config);
|
|
792
|
+
index.add(label, view, config).error.raise();
|
|
572
793
|
}, //
|
|
573
794
|
py::arg("label"), //
|
|
574
795
|
py::arg("set"), //
|
|
@@ -581,10 +802,12 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
581
802
|
[](sets_index_py_t& index, py::array_t<set_member_t> set, std::size_t count) -> py::array_t<label_t> {
|
|
582
803
|
validate_set(set);
|
|
583
804
|
auto proxy = set.unchecked<1>();
|
|
584
|
-
auto view = set_view_t{proxy
|
|
805
|
+
auto view = set_view_t{&proxy(0), static_cast<std::size_t>(proxy.shape(0))};
|
|
585
806
|
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
586
807
|
auto labels_proxy = labels_py.mutable_unchecked<1>();
|
|
587
|
-
auto
|
|
808
|
+
auto result = index.search(view, count);
|
|
809
|
+
result.error.raise();
|
|
810
|
+
auto found = result.dump_to(&labels_proxy(0));
|
|
588
811
|
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(found)});
|
|
589
812
|
return labels_py;
|
|
590
813
|
},
|
|
@@ -601,54 +824,4 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
601
824
|
si.def("load", &load_index<sets_index_py_t>, py::arg("path"));
|
|
602
825
|
si.def("view", &view_index<sets_index_py_t>, py::arg("path"));
|
|
603
826
|
si.def("clear", &clear_index<sets_index_py_t>);
|
|
604
|
-
|
|
605
|
-
auto hi = py::class_<hash_index_py_t>(m, "HashIndex");
|
|
606
|
-
|
|
607
|
-
hi.def( //
|
|
608
|
-
py::init(&make_hash_index), //
|
|
609
|
-
py::kw_only(), //
|
|
610
|
-
py::arg("bits"), //
|
|
611
|
-
py::arg("capacity") = 0, //
|
|
612
|
-
py::arg("connectivity") = default_connectivity(), //
|
|
613
|
-
py::arg("expansion_add") = default_expansion_add(), //
|
|
614
|
-
py::arg("expansion_search") = default_expansion_search() //
|
|
615
|
-
);
|
|
616
|
-
|
|
617
|
-
hi.def( //
|
|
618
|
-
"add",
|
|
619
|
-
[](hash_index_py_t& index, label_t label, py::buffer array) {
|
|
620
|
-
if (index.size() + 1 >= index.capacity())
|
|
621
|
-
index.reserve(ceil2(index.size() + 1));
|
|
622
|
-
hash_buffer(index, array);
|
|
623
|
-
index.add(label, index.buffer());
|
|
624
|
-
}, //
|
|
625
|
-
py::arg("label"), //
|
|
626
|
-
py::arg("array") //
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
hi.def( //
|
|
630
|
-
"search",
|
|
631
|
-
[](hash_index_py_t& index, py::buffer array, std::size_t count) -> py::array_t<label_t> {
|
|
632
|
-
if (index.size() + 1 >= index.capacity())
|
|
633
|
-
index.reserve(ceil2(index.size() + 1));
|
|
634
|
-
hash_buffer(index, array);
|
|
635
|
-
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
636
|
-
auto labels_proxy = labels_py.mutable_unchecked<1>();
|
|
637
|
-
auto found = index.search(index.buffer(), count).dump_to(&labels_proxy(0), nullptr);
|
|
638
|
-
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(found)});
|
|
639
|
-
return labels_py;
|
|
640
|
-
},
|
|
641
|
-
py::arg("array"), //
|
|
642
|
-
py::arg("count") = 10 //
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
hi.def("__len__", &hash_index_py_t::size);
|
|
646
|
-
hi.def_property_readonly("size", &hash_index_py_t::size);
|
|
647
|
-
hi.def_property_readonly("connectivity", &hash_index_py_t::connectivity);
|
|
648
|
-
hi.def_property_readonly("capacity", &hash_index_py_t::capacity);
|
|
649
|
-
|
|
650
|
-
hi.def("save", &save_index<hash_index_py_t>, py::arg("path"));
|
|
651
|
-
hi.def("load", &load_index<hash_index_py_t>, py::arg("path"));
|
|
652
|
-
hi.def("view", &view_index<hash_index_py_t>, py::arg("path"));
|
|
653
|
-
hi.def("clear", &clear_index<hash_index_py_t>);
|
|
654
827
|
}
|