usearch 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +62 -7
- package/.github/workflows/update_version.sh +6 -1
- package/.vscode/launch.json +21 -22
- package/.vscode/settings.json +7 -1
- package/CITATION.cff +4 -4
- package/CMakeLists.txt +1 -1
- package/Cargo.toml +1 -1
- package/Package.swift +0 -1
- package/README.md +100 -27
- package/binding.gyp +0 -1
- package/build.gradle +1 -1
- package/build.rs +3 -3
- package/c/lib.cpp +32 -9
- package/c/usearch.h +67 -36
- package/conanfile.py +1 -1
- package/{src → cpp}/bench.cpp +52 -36
- package/{src → cpp}/test.cpp +17 -7
- package/docs/benchmarks.md +33 -10
- package/docs/c/index.rst +11 -0
- package/docs/c/reference.rst +7 -0
- package/docs/compilation.md +24 -8
- package/docs/conf.dox +6 -6
- package/docs/conf.py +3 -1
- package/docs/cpp/index.rst +11 -0
- package/docs/cpp/reference.rst +7 -0
- package/docs/index.rst +10 -0
- package/docs/java/index.rst +11 -0
- package/docs/java/reference.rst +7 -0
- package/docs/javascript/index.rst +11 -0
- package/docs/javascript/reference.rst +9 -0
- package/docs/python/index.rst +11 -0
- package/docs/python/reference.rst +35 -0
- package/golang/lib.go +1 -1
- package/include/usearch/{usearch.hpp → index.hpp} +152 -148
- package/include/usearch/index_punned_dense.hpp +633 -0
- package/include/usearch/index_punned_helpers.hpp +528 -0
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +8 -7
- package/javascript/lib.cpp +26 -50
- package/javascript/usearch.js +117 -2
- package/objc/USearchObjective.mm +145 -49
- package/objc/include/USearchObjective.h +106 -30
- package/package.json +2 -2
- package/python/lib.cpp +178 -320
- package/python/scripts/bench.ipynb +170 -0
- package/python/scripts/bench.py +102 -185
- package/python/scripts/index_faiss.py +100 -0
- package/python/scripts/test.py +57 -34
- package/python/usearch/eval.py +353 -0
- package/python/usearch/index.py +138 -117
- package/python/usearch/io.py +7 -2
- package/python/usearch/join.py +100 -0
- package/python/usearch/numba.py +101 -0
- package/rust/lib.cpp +27 -14
- package/rust/lib.hpp +4 -10
- package/rust/lib.rs +50 -17
- package/setup.py +12 -2
- package/simsimd/include/simsimd/simsimd.h +32 -3
- package/swift/Index+Sugar.swift +55 -29
- package/swift/Test.swift +1 -1
- package/wolfram/lib.cpp +4 -4
- package/python/scripts/bench_params.py +0 -161
- package/python/usearch/synthetic.py +0 -32
- package/src/punned.hpp +0 -887
- /package/{src → cpp}/CMakeLists.txt +0 -0
package/python/lib.cpp
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
#include <pybind11/numpy.h>
|
|
19
19
|
#include <pybind11/pybind11.h>
|
|
20
20
|
|
|
21
|
-
#include
|
|
21
|
+
#include <usearch/index_punned_dense.hpp>
|
|
22
22
|
|
|
23
23
|
using namespace unum::usearch;
|
|
24
24
|
using namespace unum;
|
|
@@ -28,9 +28,11 @@ using py_shape_t = py::array::ShapeContainer;
|
|
|
28
28
|
|
|
29
29
|
using label_t = Py_ssize_t;
|
|
30
30
|
using distance_t = punned_distance_t;
|
|
31
|
+
using metric_t = punned_metric_t;
|
|
31
32
|
using id_t = std::uint32_t;
|
|
33
|
+
using big_id_t = std::uint64_t;
|
|
32
34
|
|
|
33
|
-
using punned_index_t =
|
|
35
|
+
using punned_index_t = index_punned_dense_gt<label_t, id_t>;
|
|
34
36
|
using punned_add_result_t = typename punned_index_t::add_result_t;
|
|
35
37
|
using punned_search_result_t = typename punned_index_t::search_result_t;
|
|
36
38
|
|
|
@@ -47,89 +49,41 @@ struct punned_index_py_t : public punned_index_t {
|
|
|
47
49
|
|
|
48
50
|
using set_member_t = std::uint32_t;
|
|
49
51
|
using set_view_t = span_gt<set_member_t const>;
|
|
50
|
-
using
|
|
52
|
+
using sparse_index_t = index_gt<jaccard_gt<set_member_t>, label_t, id_t, set_member_t>;
|
|
51
53
|
|
|
52
|
-
struct
|
|
53
|
-
using native_t =
|
|
54
|
+
struct sparse_index_py_t : public sparse_index_t {
|
|
55
|
+
using native_t = sparse_index_t;
|
|
54
56
|
using native_t::add;
|
|
55
57
|
using native_t::capacity;
|
|
56
58
|
using native_t::reserve;
|
|
57
59
|
using native_t::search;
|
|
58
60
|
using native_t::size;
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
sparse_index_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
using
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
74
|
-
|
|
75
|
-
struct bits_index_py_t : public bits_index_t {
|
|
76
|
-
using native_t = bits_index_t;
|
|
77
|
-
using native_t::add;
|
|
78
|
-
using native_t::capacity;
|
|
79
|
-
using native_t::limits;
|
|
80
|
-
using native_t::reserve;
|
|
81
|
-
using native_t::search;
|
|
82
|
-
using native_t::size;
|
|
83
|
-
|
|
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_;
|
|
88
|
-
|
|
89
|
-
bits_index_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
90
|
-
|
|
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()) {}
|
|
96
|
-
};
|
|
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
|
-
|
|
110
|
-
template <typename scalar_at> punned_stateful_metric_t udf(std::size_t metric_uintptr) {
|
|
111
|
-
return [metric_uintptr](byte_t const* a_bytes, byte_t const* b_bytes, std::size_t n, std::size_t m) -> distance_t {
|
|
112
|
-
using metric_t = punned_distance_t (*)(scalar_at const*, scalar_at const*, std::size_t, std::size_t);
|
|
113
|
-
metric_t metric_ptr = reinterpret_cast<metric_t>(metric_uintptr);
|
|
114
|
-
scalar_at const* a = reinterpret_cast<scalar_at const*>(a_bytes);
|
|
115
|
-
scalar_at const* b = reinterpret_cast<scalar_at const*>(b_bytes);
|
|
116
|
-
return metric_ptr(a, b, n / sizeof(scalar_at), m / sizeof(scalar_at));
|
|
65
|
+
template <typename scalar_at> metric_t udf(std::size_t metric_uintptr) {
|
|
66
|
+
return [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
67
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*);
|
|
68
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
69
|
+
return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data());
|
|
117
70
|
};
|
|
118
71
|
}
|
|
119
72
|
|
|
120
|
-
|
|
73
|
+
metric_t udf(std::size_t metric_uintptr, scalar_kind_t accuracy) {
|
|
121
74
|
switch (accuracy) {
|
|
122
|
-
case
|
|
123
|
-
case
|
|
124
|
-
case
|
|
125
|
-
case
|
|
75
|
+
case scalar_kind_t::f8_k: return udf<f8_bits_t>(metric_uintptr);
|
|
76
|
+
case scalar_kind_t::f16_k: return udf<f16_t>(metric_uintptr);
|
|
77
|
+
case scalar_kind_t::f32_k: return udf<f32_t>(metric_uintptr);
|
|
78
|
+
case scalar_kind_t::f64_k: return udf<f64_t>(metric_uintptr);
|
|
79
|
+
default: return {};
|
|
126
80
|
}
|
|
127
81
|
}
|
|
128
82
|
|
|
129
83
|
static punned_index_py_t make_index( //
|
|
130
84
|
std::size_t dimensions, //
|
|
131
|
-
|
|
132
|
-
|
|
85
|
+
scalar_kind_t scalar_kind, //
|
|
86
|
+
metric_kind_t metric_kind, //
|
|
133
87
|
std::size_t connectivity, //
|
|
134
88
|
std::size_t expansion_add, //
|
|
135
89
|
std::size_t expansion_search, //
|
|
@@ -137,45 +91,27 @@ static punned_index_py_t make_index( //
|
|
|
137
91
|
bool tune) {
|
|
138
92
|
|
|
139
93
|
index_config_t config;
|
|
140
|
-
config.expansion_add = expansion_add;
|
|
141
|
-
config.expansion_search = expansion_search;
|
|
142
94
|
config.connectivity = connectivity;
|
|
143
95
|
|
|
144
96
|
if (tune)
|
|
145
97
|
config = punned_index_t::optimize(config);
|
|
146
98
|
|
|
147
|
-
accuracy_t accuracy = accuracy_from_name(scalar_type.c_str(), scalar_type.size());
|
|
148
99
|
if (metric_uintptr)
|
|
149
|
-
return punned_index_t::
|
|
100
|
+
return punned_index_t::make( //
|
|
101
|
+
dimensions, udf(metric_uintptr, scalar_kind), config, scalar_kind, expansion_add, expansion_search);
|
|
150
102
|
else
|
|
151
|
-
return punned_index_t(
|
|
103
|
+
return punned_index_t::make(dimensions, metric_kind, config, scalar_kind, expansion_add, expansion_search);
|
|
152
104
|
}
|
|
153
105
|
|
|
154
|
-
static std::unique_ptr<
|
|
155
|
-
std::size_t connectivity,
|
|
156
|
-
std::size_t expansion_add,
|
|
157
|
-
std::size_t expansion_search
|
|
106
|
+
static std::unique_ptr<sparse_index_py_t> make_sparse_index( //
|
|
107
|
+
std::size_t connectivity, //
|
|
108
|
+
std::size_t expansion_add, //
|
|
109
|
+
std::size_t expansion_search //
|
|
158
110
|
) {
|
|
159
111
|
index_config_t config;
|
|
160
|
-
config.expansion_add = expansion_add;
|
|
161
|
-
config.expansion_search = expansion_search;
|
|
162
112
|
config.connectivity = connectivity;
|
|
163
113
|
|
|
164
|
-
return std::unique_ptr<
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
static std::unique_ptr<bits_index_py_t> make_hash_index( //
|
|
168
|
-
std::size_t bits, //
|
|
169
|
-
common_metric_kind_t metric, //
|
|
170
|
-
std::size_t connectivity, //
|
|
171
|
-
std::size_t expansion_add, //
|
|
172
|
-
std::size_t expansion_search //
|
|
173
|
-
) {
|
|
174
|
-
index_config_t config;
|
|
175
|
-
config.expansion_add = expansion_add;
|
|
176
|
-
config.expansion_search = expansion_search;
|
|
177
|
-
config.connectivity = connectivity;
|
|
178
|
-
return std::unique_ptr<bits_index_py_t>(new bits_index_py_t(config, bits, bits_metric(metric)));
|
|
114
|
+
return std::unique_ptr<sparse_index_py_t>(new sparse_index_py_t(sparse_index_t(config)));
|
|
179
115
|
}
|
|
180
116
|
|
|
181
117
|
static void add_one_to_index(punned_index_py_t& index, label_t label, py::buffer vector, bool copy, std::size_t) {
|
|
@@ -186,7 +122,7 @@ static void add_one_to_index(punned_index_py_t& index, label_t label, py::buffer
|
|
|
186
122
|
|
|
187
123
|
Py_ssize_t vector_dimensions = vector_info.shape[0];
|
|
188
124
|
char const* vector_data = reinterpret_cast<char const*>(vector_info.ptr);
|
|
189
|
-
if (vector_dimensions != static_cast<Py_ssize_t>(index.
|
|
125
|
+
if (vector_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
190
126
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
191
127
|
|
|
192
128
|
if (index.size() + 1 >= index.capacity())
|
|
@@ -196,16 +132,18 @@ static void add_one_to_index(punned_index_py_t& index, label_t label, py::buffer
|
|
|
196
132
|
config.store_vector = copy;
|
|
197
133
|
|
|
198
134
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
199
|
-
if (vector_info.format == "
|
|
135
|
+
if (vector_info.format == "B")
|
|
136
|
+
index.add(label, reinterpret_cast<b1x8_t const*>(vector_data), config).error.raise();
|
|
137
|
+
else if (vector_info.format == "c" || vector_info.format == "b")
|
|
200
138
|
index.add(label, reinterpret_cast<f8_bits_t const*>(vector_data), config).error.raise();
|
|
201
139
|
else if (vector_info.format == "e")
|
|
202
|
-
index.add(label, reinterpret_cast<
|
|
140
|
+
index.add(label, reinterpret_cast<f16_t const*>(vector_data), config).error.raise();
|
|
203
141
|
else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4")
|
|
204
142
|
index.add(label, reinterpret_cast<float const*>(vector_data), config).error.raise();
|
|
205
143
|
else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8")
|
|
206
144
|
index.add(label, reinterpret_cast<double const*>(vector_data), config).error.raise();
|
|
207
145
|
else
|
|
208
|
-
throw std::invalid_argument("Incompatible scalars in the vector
|
|
146
|
+
throw std::invalid_argument("Incompatible scalars in the vector: " + vector_info.format);
|
|
209
147
|
}
|
|
210
148
|
|
|
211
149
|
static void add_many_to_index( //
|
|
@@ -230,7 +168,7 @@ static void add_many_to_index( //
|
|
|
230
168
|
Py_ssize_t labels_count = labels_info.shape[0];
|
|
231
169
|
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
232
170
|
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
233
|
-
if (vectors_dimensions != static_cast<Py_ssize_t>(index.
|
|
171
|
+
if (vectors_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
234
172
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
235
173
|
|
|
236
174
|
if (labels_count != vectors_count)
|
|
@@ -243,8 +181,17 @@ static void add_many_to_index( //
|
|
|
243
181
|
char const* labels_data = reinterpret_cast<char const*>(labels_info.ptr);
|
|
244
182
|
|
|
245
183
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
246
|
-
if (vectors_info.format == "
|
|
247
|
-
|
|
184
|
+
if (vectors_info.format == "B")
|
|
185
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
186
|
+
add_config_t config;
|
|
187
|
+
config.store_vector = copy;
|
|
188
|
+
config.thread = thread_idx;
|
|
189
|
+
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
190
|
+
b1x8_t const* vector = reinterpret_cast<b1x8_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
191
|
+
index.add(label, vector, config).error.raise();
|
|
192
|
+
});
|
|
193
|
+
else if (vectors_info.format == "c" || vectors_info.format == "b")
|
|
194
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
248
195
|
add_config_t config;
|
|
249
196
|
config.store_vector = copy;
|
|
250
197
|
config.thread = thread_idx;
|
|
@@ -254,17 +201,16 @@ static void add_many_to_index( //
|
|
|
254
201
|
index.add(label, vector, config).error.raise();
|
|
255
202
|
});
|
|
256
203
|
else if (vectors_info.format == "e")
|
|
257
|
-
|
|
204
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
258
205
|
add_config_t config;
|
|
259
206
|
config.store_vector = copy;
|
|
260
207
|
config.thread = thread_idx;
|
|
261
208
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
262
|
-
|
|
263
|
-
reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
209
|
+
f16_t const* vector = reinterpret_cast<f16_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
264
210
|
index.add(label, vector, config).error.raise();
|
|
265
211
|
});
|
|
266
212
|
else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
|
|
267
|
-
|
|
213
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
268
214
|
add_config_t config;
|
|
269
215
|
config.store_vector = copy;
|
|
270
216
|
config.thread = thread_idx;
|
|
@@ -273,7 +219,7 @@ static void add_many_to_index( //
|
|
|
273
219
|
index.add(label, vector, config).error.raise();
|
|
274
220
|
});
|
|
275
221
|
else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
|
|
276
|
-
|
|
222
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
277
223
|
add_config_t config;
|
|
278
224
|
config.store_vector = copy;
|
|
279
225
|
config.thread = thread_idx;
|
|
@@ -282,58 +228,7 @@ static void add_many_to_index( //
|
|
|
282
228
|
index.add(label, vector, config).error.raise();
|
|
283
229
|
});
|
|
284
230
|
else
|
|
285
|
-
throw std::invalid_argument("Incompatible scalars in the vectors matrix
|
|
286
|
-
}
|
|
287
|
-
|
|
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
|
-
});
|
|
231
|
+
throw std::invalid_argument("Incompatible scalars in the vectors matrix: " + vectors_info.format);
|
|
337
232
|
}
|
|
338
233
|
|
|
339
234
|
static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector, std::size_t wanted, bool exact) {
|
|
@@ -341,25 +236,29 @@ static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector
|
|
|
341
236
|
py::buffer_info vector_info = vector.request();
|
|
342
237
|
Py_ssize_t vector_dimensions = vector_info.shape[0];
|
|
343
238
|
char const* vector_data = reinterpret_cast<char const*>(vector_info.ptr);
|
|
344
|
-
if (vector_dimensions != static_cast<Py_ssize_t>(index.
|
|
239
|
+
if (vector_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
345
240
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
346
241
|
|
|
347
242
|
py::array_t<label_t> labels_py(static_cast<Py_ssize_t>(wanted));
|
|
348
243
|
py::array_t<distance_t> distances_py(static_cast<Py_ssize_t>(wanted));
|
|
349
244
|
std::size_t count{};
|
|
350
|
-
auto labels_py1d = labels_py.mutable_unchecked<1>();
|
|
351
|
-
auto distances_py1d = distances_py.mutable_unchecked<1>();
|
|
245
|
+
auto labels_py1d = labels_py.template mutable_unchecked<1>();
|
|
246
|
+
auto distances_py1d = distances_py.template mutable_unchecked<1>();
|
|
352
247
|
|
|
353
248
|
search_config_t config;
|
|
354
249
|
config.exact = exact;
|
|
355
250
|
|
|
356
251
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
357
|
-
if (vector_info.format == "
|
|
252
|
+
if (vector_info.format == "B") {
|
|
253
|
+
punned_search_result_t result = index.search(reinterpret_cast<b1x8_t const*>(vector_data), wanted, config);
|
|
254
|
+
result.error.raise();
|
|
255
|
+
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
256
|
+
} else if (vector_info.format == "c" || vector_info.format == "b") {
|
|
358
257
|
punned_search_result_t result = index.search(reinterpret_cast<f8_bits_t const*>(vector_data), wanted, config);
|
|
359
258
|
result.error.raise();
|
|
360
259
|
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
361
260
|
} else if (vector_info.format == "e") {
|
|
362
|
-
punned_search_result_t result = index.search(reinterpret_cast<
|
|
261
|
+
punned_search_result_t result = index.search(reinterpret_cast<f16_t const*>(vector_data), wanted, config);
|
|
363
262
|
result.error.raise();
|
|
364
263
|
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
365
264
|
} else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4") {
|
|
@@ -371,7 +270,7 @@ static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector
|
|
|
371
270
|
result.error.raise();
|
|
372
271
|
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
373
272
|
} else
|
|
374
|
-
throw std::invalid_argument("Incompatible scalars in the query vector
|
|
273
|
+
throw std::invalid_argument("Incompatible scalars in the query vector: " + vector_info.format);
|
|
375
274
|
|
|
376
275
|
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
377
276
|
distances_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
@@ -410,19 +309,30 @@ static py::tuple search_many_in_index( //
|
|
|
410
309
|
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
411
310
|
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
412
311
|
char const* vectors_data = reinterpret_cast<char const*>(vectors_info.ptr);
|
|
413
|
-
if (vectors_dimensions != static_cast<Py_ssize_t>(index.
|
|
312
|
+
if (vectors_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
414
313
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
415
314
|
|
|
416
315
|
py::array_t<label_t> labels_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
417
316
|
py::array_t<distance_t> distances_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
418
317
|
py::array_t<Py_ssize_t> counts_py(vectors_count);
|
|
419
|
-
auto labels_py2d = labels_py.mutable_unchecked<2>();
|
|
420
|
-
auto distances_py2d = distances_py.mutable_unchecked<2>();
|
|
421
|
-
auto counts_py1d = counts_py.mutable_unchecked<1>();
|
|
318
|
+
auto labels_py2d = labels_py.template mutable_unchecked<2>();
|
|
319
|
+
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
320
|
+
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
422
321
|
|
|
423
322
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
424
|
-
if (vectors_info.format == "
|
|
425
|
-
|
|
323
|
+
if (vectors_info.format == "B")
|
|
324
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
325
|
+
search_config_t config;
|
|
326
|
+
config.thread = thread_idx;
|
|
327
|
+
config.exact = exact;
|
|
328
|
+
b1x8_t const* vector = (b1x8_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
329
|
+
punned_search_result_t result = index.search(vector, wanted, config);
|
|
330
|
+
result.error.raise();
|
|
331
|
+
counts_py1d(task_idx) =
|
|
332
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
333
|
+
});
|
|
334
|
+
else if (vectors_info.format == "c" || vectors_info.format == "b")
|
|
335
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
426
336
|
search_config_t config;
|
|
427
337
|
config.thread = thread_idx;
|
|
428
338
|
config.exact = exact;
|
|
@@ -433,18 +343,18 @@ static py::tuple search_many_in_index( //
|
|
|
433
343
|
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
434
344
|
});
|
|
435
345
|
else if (vectors_info.format == "e")
|
|
436
|
-
|
|
346
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
437
347
|
search_config_t config;
|
|
438
348
|
config.thread = thread_idx;
|
|
439
349
|
config.exact = exact;
|
|
440
|
-
|
|
350
|
+
f16_t const* vector = (f16_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
441
351
|
punned_search_result_t result = index.search(vector, wanted, config);
|
|
442
352
|
result.error.raise();
|
|
443
353
|
counts_py1d(task_idx) =
|
|
444
354
|
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
445
355
|
});
|
|
446
356
|
else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
|
|
447
|
-
|
|
357
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
448
358
|
search_config_t config;
|
|
449
359
|
config.thread = thread_idx;
|
|
450
360
|
config.exact = exact;
|
|
@@ -455,7 +365,7 @@ static py::tuple search_many_in_index( //
|
|
|
455
365
|
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
456
366
|
});
|
|
457
367
|
else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
|
|
458
|
-
|
|
368
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
459
369
|
search_config_t config;
|
|
460
370
|
config.thread = thread_idx;
|
|
461
371
|
config.exact = exact;
|
|
@@ -466,71 +376,7 @@ static py::tuple search_many_in_index( //
|
|
|
466
376
|
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
467
377
|
});
|
|
468
378
|
else
|
|
469
|
-
throw std::invalid_argument("Incompatible scalars in the query matrix
|
|
470
|
-
|
|
471
|
-
py::tuple results(3);
|
|
472
|
-
results[0] = labels_py;
|
|
473
|
-
results[1] = distances_py;
|
|
474
|
-
results[2] = counts_py;
|
|
475
|
-
return results;
|
|
476
|
-
}
|
|
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
|
-
});
|
|
379
|
+
throw std::invalid_argument("Incompatible scalars in the query matrix: " + vectors_info.format);
|
|
534
380
|
|
|
535
381
|
py::tuple results(3);
|
|
536
382
|
results[0] = labels_py;
|
|
@@ -543,11 +389,39 @@ static py::tuple search_many_in_bits_index( //
|
|
|
543
389
|
template <typename index_at> void save_index(index_at const& index, std::string const& path) { index.save(path.c_str()).error.raise(); }
|
|
544
390
|
template <typename index_at> void load_index(index_at& index, std::string const& path) { index.load(path.c_str()).error.raise(); }
|
|
545
391
|
template <typename index_at> void view_index(index_at& index, std::string const& path) { index.view(path.c_str()).error.raise(); }
|
|
546
|
-
template <typename index_at> void clear_index(index_at& index) { index.clear(); }
|
|
547
|
-
template <typename index_at> std::size_t get_expansion_add(index_at const &index) { return index.config().expansion_add; }
|
|
548
|
-
template <typename index_at> std::size_t get_expansion_search(index_at const &index) { return index.config().expansion_search; }
|
|
549
392
|
// clang-format on
|
|
550
393
|
|
|
394
|
+
template <typename internal_at, typename external_at = internal_at, typename index_at = void>
|
|
395
|
+
py::array_t<external_at> get_typed_member(index_at const& index, label_t label) {
|
|
396
|
+
py::array_t<external_at> result_py(static_cast<Py_ssize_t>(index.scalar_words()));
|
|
397
|
+
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
398
|
+
index.reconstruct(label, (internal_at*)&result_py1d(0));
|
|
399
|
+
return result_py;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
template <typename index_at> py::array get_member(index_at const& index, label_t label, scalar_kind_t scalar_kind) {
|
|
403
|
+
if (scalar_kind == scalar_kind_t::f32_k)
|
|
404
|
+
return get_typed_member<f32_t>(index, label);
|
|
405
|
+
else if (scalar_kind == scalar_kind_t::f64_k)
|
|
406
|
+
return get_typed_member<f64_t>(index, label);
|
|
407
|
+
else if (scalar_kind == scalar_kind_t::f16_k)
|
|
408
|
+
return get_typed_member<f16_t>(index, label);
|
|
409
|
+
else if (scalar_kind == scalar_kind_t::f8_k)
|
|
410
|
+
return get_typed_member<f8_bits_t, std::int8_t>(index, label);
|
|
411
|
+
else if (scalar_kind == scalar_kind_t::b1x8_k)
|
|
412
|
+
return get_typed_member<b1x8_t, std::uint8_t>(index, label);
|
|
413
|
+
else
|
|
414
|
+
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
template <typename index_at> py::array_t<label_t> get_labels(index_at const& index) {
|
|
418
|
+
std::size_t result_length = index.size();
|
|
419
|
+
py::array_t<label_t> result_py(static_cast<Py_ssize_t>(result_length));
|
|
420
|
+
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
421
|
+
index.export_labels(&result_py1d(0), result_length);
|
|
422
|
+
return result_py;
|
|
423
|
+
}
|
|
424
|
+
|
|
551
425
|
template <typename element_at> bool has_duplicates(element_at const* begin, element_at const* end) {
|
|
552
426
|
if (begin == end)
|
|
553
427
|
return false;
|
|
@@ -573,10 +447,11 @@ void validate_set(py::array_t<set_member_t> const& set) {
|
|
|
573
447
|
if (has_duplicates(begin, end))
|
|
574
448
|
throw std::invalid_argument("Set must be deduplicated!");
|
|
575
449
|
}
|
|
450
|
+
#if 0
|
|
576
451
|
|
|
577
|
-
inline
|
|
452
|
+
inline std::size_t hash_ror64(std::size_t v, int r) noexcept { return (v >> r) | (v << (64 - r)); }
|
|
578
453
|
|
|
579
|
-
inline
|
|
454
|
+
inline std::size_t hash(std::size_t v) noexcept {
|
|
580
455
|
v ^= hash_ror64(v, 25) ^ hash_ror64(v, 50);
|
|
581
456
|
v *= 0xA24BAED4963EE407UL;
|
|
582
457
|
v ^= hash_ror64(v, 24) ^ hash_ror64(v, 49);
|
|
@@ -587,18 +462,17 @@ inline bits_native_word_t hash(bits_native_word_t v) noexcept {
|
|
|
587
462
|
template <typename scalar_at>
|
|
588
463
|
void hash_typed_row( //
|
|
589
464
|
byte_t const* scalars, std::size_t scalar_stride, std::size_t scalars_count, //
|
|
590
|
-
|
|
465
|
+
b1x8_t* hashes, std::size_t bits) noexcept {
|
|
591
466
|
|
|
592
467
|
std::size_t words = divide_round_up<8>(bits);
|
|
593
468
|
for (std::size_t i = 0; i != scalars_count; ++i) {
|
|
594
469
|
scalar_at scalar;
|
|
595
470
|
std::memcpy(&scalar, scalars + i * scalar_stride, sizeof(scalar_at));
|
|
596
|
-
|
|
471
|
+
std::size_t scalar_hash = hash(scalar);
|
|
597
472
|
hashes[scalar_hash & (words - 1)] |= std::uint8_t(1) << (scalar_hash % 8);
|
|
598
473
|
}
|
|
599
474
|
}
|
|
600
475
|
|
|
601
|
-
#if 0
|
|
602
476
|
template <typename scalar_at>
|
|
603
477
|
py::array_t<bits_numpy_word_t> hash_typed_buffer(py::buffer_info const& info, std::size_t bits) {
|
|
604
478
|
byte_t const* data = reinterpret_cast<byte_t const*>(info.ptr);
|
|
@@ -611,7 +485,7 @@ py::array_t<bits_numpy_word_t> hash_typed_buffer(py::buffer_info const& info, st
|
|
|
611
485
|
Py_ssize_t rows_stride = info.strides[1];
|
|
612
486
|
auto hashes_shape = py_shape_t{rows_count, words_per_row};
|
|
613
487
|
auto hashes_py = py::array_t<bits_numpy_word_t>(hashes_shape);
|
|
614
|
-
auto hashes_proxy = hashes_py.mutable_unchecked<2>();
|
|
488
|
+
auto hashes_proxy = hashes_py.template mutable_unchecked<2>();
|
|
615
489
|
|
|
616
490
|
for (Py_ssize_t i = 0; i != rows_count; ++i)
|
|
617
491
|
hash_typed_row( //
|
|
@@ -622,7 +496,7 @@ py::array_t<bits_numpy_word_t> hash_typed_buffer(py::buffer_info const& info, st
|
|
|
622
496
|
} else {
|
|
623
497
|
auto hashes_shape = py_shape_t{words_per_row};
|
|
624
498
|
auto hashes_py = py::array_t<bits_numpy_word_t>(hashes_shape);
|
|
625
|
-
auto hashes_proxy = hashes_py.mutable_unchecked<1>();
|
|
499
|
+
auto hashes_proxy = hashes_py.template mutable_unchecked<1>();
|
|
626
500
|
|
|
627
501
|
hash_typed_row( //
|
|
628
502
|
data, hashes_info.strides[0], static_cast<std::size_t>(elements_per_row), //
|
|
@@ -658,25 +532,44 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
658
532
|
m.attr("DEFAULT_EXPANSION_ADD") = py::int_(default_expansion_add());
|
|
659
533
|
m.attr("DEFAULT_EXPANSION_SEARCH") = py::int_(default_expansion_search());
|
|
660
534
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
535
|
+
#if defined(USEARCH_USE_OPENMP)
|
|
536
|
+
m.attr("USES_OPENMP") = py::int_(1);
|
|
537
|
+
#else
|
|
538
|
+
m.attr("USES_OPENMP") = py::int_(0);
|
|
539
|
+
#endif
|
|
540
|
+
|
|
541
|
+
#if defined(USEARCH_USE_SIMSIMD)
|
|
542
|
+
m.attr("USES_SIMSIMD") = py::int_(1);
|
|
543
|
+
#else
|
|
544
|
+
m.attr("USES_SIMSIMD") = py::int_(0);
|
|
545
|
+
#endif
|
|
546
|
+
|
|
547
|
+
py::enum_<metric_kind_t>(m, "MetricKind")
|
|
548
|
+
.value("Unknown", metric_kind_t::unknown_k)
|
|
549
|
+
.value("IP", metric_kind_t::ip_k)
|
|
550
|
+
.value("Cos", metric_kind_t::cos_k)
|
|
551
|
+
.value("L2sq", metric_kind_t::l2sq_k)
|
|
552
|
+
.value("Haversine", metric_kind_t::haversine_k)
|
|
553
|
+
.value("Pearson", metric_kind_t::pearson_k)
|
|
554
|
+
.value("Jaccard", metric_kind_t::jaccard_k)
|
|
555
|
+
.value("Hamming", metric_kind_t::hamming_k)
|
|
556
|
+
.value("Tanimoto", metric_kind_t::tanimoto_k)
|
|
557
|
+
.value("Sorensen", metric_kind_t::sorensen_k);
|
|
558
|
+
|
|
559
|
+
py::enum_<scalar_kind_t>(m, "ScalarKind")
|
|
560
|
+
.value("F64", scalar_kind_t::f64_k)
|
|
561
|
+
.value("F32", scalar_kind_t::f32_k)
|
|
562
|
+
.value("F16", scalar_kind_t::f16_k)
|
|
563
|
+
.value("F8", scalar_kind_t::f8_k)
|
|
564
|
+
.value("B1", scalar_kind_t::b1x8_k);
|
|
672
565
|
|
|
673
566
|
auto i = py::class_<punned_index_py_t>(m, "Index");
|
|
674
567
|
|
|
675
568
|
i.def(py::init(&make_index), //
|
|
676
569
|
py::kw_only(), //
|
|
677
570
|
py::arg("ndim") = 0, //
|
|
678
|
-
py::arg("dtype") =
|
|
679
|
-
py::arg("metric") =
|
|
571
|
+
py::arg("dtype") = scalar_kind_t::f32_k, //
|
|
572
|
+
py::arg("metric") = metric_kind_t::ip_k, //
|
|
680
573
|
py::arg("connectivity") = default_connectivity(), //
|
|
681
574
|
py::arg("expansion_add") = default_expansion_add(), //
|
|
682
575
|
py::arg("expansion_search") = default_expansion_search(), //
|
|
@@ -716,63 +609,28 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
716
609
|
i.def_property_readonly("connectivity", &punned_index_py_t::connectivity);
|
|
717
610
|
i.def_property_readonly("capacity", &punned_index_py_t::capacity);
|
|
718
611
|
i.def_property_readonly( //
|
|
719
|
-
"dtype", [](punned_index_py_t const& index) -> std::string { return
|
|
720
|
-
i.def_property_readonly(
|
|
721
|
-
|
|
612
|
+
"dtype", [](punned_index_py_t const& index) -> std::string { return scalar_kind_name(index.scalar_kind()); });
|
|
613
|
+
i.def_property_readonly( //
|
|
614
|
+
"memory_usage", [](punned_index_py_t const& index) -> std::size_t { return index.memory_usage(); });
|
|
722
615
|
|
|
723
|
-
i.def_property("expansion_add", &
|
|
724
|
-
i.def_property("expansion_search", &
|
|
616
|
+
i.def_property("expansion_add", &punned_index_py_t::expansion_add, &punned_index_py_t::change_expansion_add);
|
|
617
|
+
i.def_property("expansion_search", &punned_index_py_t::expansion_search,
|
|
725
618
|
&punned_index_py_t::change_expansion_search);
|
|
726
619
|
|
|
620
|
+
i.def_property_readonly("labels", &get_labels<punned_index_py_t>);
|
|
621
|
+
i.def("__contains__", &punned_index_py_t::contains);
|
|
622
|
+
i.def( //
|
|
623
|
+
"__getitem__", &get_member<punned_index_py_t>, py::arg("label"), py::arg("dtype") = scalar_kind_t::f32_k);
|
|
624
|
+
|
|
727
625
|
i.def("save", &save_index<punned_index_py_t>, py::arg("path"));
|
|
728
626
|
i.def("load", &load_index<punned_index_py_t>, py::arg("path"));
|
|
729
627
|
i.def("view", &view_index<punned_index_py_t>, py::arg("path"));
|
|
730
|
-
i.def("clear", &
|
|
731
|
-
|
|
732
|
-
auto bi = py::class_<bits_index_py_t>(m, "BitsIndex");
|
|
733
|
-
|
|
734
|
-
bi.def( //
|
|
735
|
-
py::init(&make_hash_index), //
|
|
736
|
-
py::kw_only(), //
|
|
737
|
-
py::arg("bits"), //
|
|
738
|
-
py::arg("metric") = common_metric_kind_t::bitwise_hamming_k, //
|
|
739
|
-
py::arg("connectivity") = default_connectivity(), //
|
|
740
|
-
py::arg("expansion_add") = default_expansion_add(), //
|
|
741
|
-
py::arg("expansion_search") = default_expansion_search() //
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
bi.def( //
|
|
745
|
-
"add",
|
|
746
|
-
&add_many_to_bits_index, //
|
|
747
|
-
py::arg("label"), //
|
|
748
|
-
py::arg("array"), //
|
|
749
|
-
py::kw_only(), //
|
|
750
|
-
py::arg("copy") = true, //
|
|
751
|
-
py::arg("threads") = 0 //
|
|
752
|
-
);
|
|
753
|
-
|
|
754
|
-
bi.def( //
|
|
755
|
-
"search", &search_many_in_bits_index, //
|
|
756
|
-
py::arg("query"), //
|
|
757
|
-
py::arg("count") = 10, //
|
|
758
|
-
py::arg("exact") = false, //
|
|
759
|
-
py::arg("threads") = 0 //
|
|
760
|
-
);
|
|
761
|
-
|
|
762
|
-
bi.def("__len__", &bits_index_py_t::size);
|
|
763
|
-
bi.def_property_readonly("size", &bits_index_py_t::size);
|
|
764
|
-
bi.def_property_readonly("connectivity", &bits_index_py_t::connectivity);
|
|
765
|
-
bi.def_property_readonly("capacity", &bits_index_py_t::capacity);
|
|
766
|
-
|
|
767
|
-
bi.def("save", &save_index<bits_index_py_t>, py::arg("path"));
|
|
768
|
-
bi.def("load", &load_index<bits_index_py_t>, py::arg("path"));
|
|
769
|
-
bi.def("view", &view_index<bits_index_py_t>, py::arg("path"));
|
|
770
|
-
bi.def("clear", &clear_index<bits_index_py_t>);
|
|
628
|
+
i.def("clear", &punned_index_py_t::clear);
|
|
771
629
|
|
|
772
|
-
auto si = py::class_<
|
|
630
|
+
auto si = py::class_<sparse_index_py_t>(m, "SparseIndex");
|
|
773
631
|
|
|
774
632
|
si.def( //
|
|
775
|
-
py::init(&
|
|
633
|
+
py::init(&make_sparse_index), //
|
|
776
634
|
py::kw_only(), //
|
|
777
635
|
py::arg("connectivity") = default_connectivity(), //
|
|
778
636
|
py::arg("expansion_add") = default_expansion_add(), //
|
|
@@ -781,7 +639,7 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
781
639
|
|
|
782
640
|
si.def( //
|
|
783
641
|
"add",
|
|
784
|
-
[](
|
|
642
|
+
[](sparse_index_py_t& index, label_t label, py::array_t<set_member_t> set, bool copy) {
|
|
785
643
|
validate_set(set);
|
|
786
644
|
if (index.size() + 1 >= index.capacity())
|
|
787
645
|
index.reserve(ceil2(index.size() + 1));
|
|
@@ -800,12 +658,12 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
800
658
|
|
|
801
659
|
si.def( //
|
|
802
660
|
"search",
|
|
803
|
-
[](
|
|
661
|
+
[](sparse_index_py_t& index, py::array_t<set_member_t> set, std::size_t count) -> py::array_t<label_t> {
|
|
804
662
|
validate_set(set);
|
|
805
663
|
auto proxy = set.unchecked<1>();
|
|
806
664
|
auto view = set_view_t{&proxy(0), static_cast<std::size_t>(proxy.shape(0))};
|
|
807
665
|
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
808
|
-
auto labels_proxy = labels_py.mutable_unchecked<1>();
|
|
666
|
+
auto labels_proxy = labels_py.template mutable_unchecked<1>();
|
|
809
667
|
auto result = index.search(view, count);
|
|
810
668
|
result.error.raise();
|
|
811
669
|
auto found = result.dump_to(&labels_proxy(0));
|
|
@@ -816,13 +674,13 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
816
674
|
py::arg("count") = 10 //
|
|
817
675
|
);
|
|
818
676
|
|
|
819
|
-
si.def("__len__", &
|
|
820
|
-
si.def_property_readonly("size", &
|
|
821
|
-
si.def_property_readonly("connectivity", &
|
|
822
|
-
si.def_property_readonly("capacity", &
|
|
677
|
+
si.def("__len__", &sparse_index_py_t::size);
|
|
678
|
+
si.def_property_readonly("size", &sparse_index_py_t::size);
|
|
679
|
+
si.def_property_readonly("connectivity", &sparse_index_py_t::connectivity);
|
|
680
|
+
si.def_property_readonly("capacity", &sparse_index_py_t::capacity);
|
|
823
681
|
|
|
824
|
-
si.def("save", &
|
|
825
|
-
si.def("load", &
|
|
826
|
-
si.def("view", &
|
|
827
|
-
si.def("clear", &
|
|
682
|
+
si.def("save", &sparse_index_py_t::save, py::arg("path"));
|
|
683
|
+
si.def("load", &sparse_index_py_t::load, py::arg("path"));
|
|
684
|
+
si.def("view", &sparse_index_py_t::view, py::arg("path"));
|
|
685
|
+
si.def("clear", &sparse_index_py_t::clear);
|
|
828
686
|
}
|