usearch 0.14.0 → 0.15.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.
Files changed (64) hide show
  1. package/.github/workflows/release.yml +56 -4
  2. package/.github/workflows/update_version.sh +6 -1
  3. package/.vscode/launch.json +21 -22
  4. package/.vscode/settings.json +7 -1
  5. package/CITATION.cff +4 -4
  6. package/CMakeLists.txt +1 -1
  7. package/Cargo.toml +1 -1
  8. package/Package.swift +0 -1
  9. package/README.md +100 -27
  10. package/binding.gyp +0 -1
  11. package/build.gradle +1 -1
  12. package/build.rs +3 -3
  13. package/c/lib.cpp +32 -9
  14. package/c/usearch.h +67 -36
  15. package/conanfile.py +1 -1
  16. package/{src → cpp}/bench.cpp +52 -36
  17. package/{src → cpp}/test.cpp +17 -7
  18. package/docs/benchmarks.md +33 -10
  19. package/docs/c/index.rst +11 -0
  20. package/docs/c/reference.rst +7 -0
  21. package/docs/compilation.md +24 -9
  22. package/docs/conf.dox +6 -6
  23. package/docs/conf.py +3 -1
  24. package/docs/cpp/index.rst +11 -0
  25. package/docs/cpp/reference.rst +7 -0
  26. package/docs/index.rst +10 -0
  27. package/docs/java/index.rst +11 -0
  28. package/docs/java/reference.rst +7 -0
  29. package/docs/javascript/index.rst +11 -0
  30. package/docs/javascript/reference.rst +9 -0
  31. package/docs/python/index.rst +11 -0
  32. package/docs/python/reference.rst +35 -0
  33. package/golang/lib.go +1 -1
  34. package/include/usearch/{usearch.hpp → index.hpp} +152 -148
  35. package/include/usearch/index_punned_dense.hpp +633 -0
  36. package/include/usearch/index_punned_helpers.hpp +528 -0
  37. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +8 -7
  38. package/javascript/lib.cpp +26 -50
  39. package/javascript/usearch.js +117 -2
  40. package/objc/USearchObjective.mm +145 -49
  41. package/objc/include/USearchObjective.h +106 -30
  42. package/package.json +2 -2
  43. package/python/lib.cpp +134 -331
  44. package/python/scripts/bench.ipynb +170 -0
  45. package/python/scripts/bench.py +87 -175
  46. package/python/scripts/index_faiss.py +100 -0
  47. package/python/scripts/test.py +41 -37
  48. package/python/usearch/eval.py +353 -0
  49. package/python/usearch/index.py +117 -113
  50. package/python/usearch/io.py +7 -2
  51. package/python/usearch/join.py +100 -0
  52. package/python/usearch/numba.py +101 -0
  53. package/rust/lib.cpp +27 -14
  54. package/rust/lib.hpp +4 -10
  55. package/rust/lib.rs +50 -17
  56. package/setup.py +12 -2
  57. package/simsimd/include/simsimd/simsimd.h +32 -3
  58. package/swift/Index+Sugar.swift +55 -29
  59. package/swift/Test.swift +1 -1
  60. package/wolfram/lib.cpp +4 -4
  61. package/python/scripts/bench_params.py +0 -201
  62. package/python/usearch/synthetic.py +0 -31
  63. package/src/punned.hpp +0 -906
  64. /package/{src → cpp}/CMakeLists.txt +0 -0
@@ -0,0 +1,633 @@
1
+ #pragma once
2
+ #include <stdlib.h> // `aligned_alloc`
3
+
4
+ #include <functional> // `std::function`
5
+ #include <numeric> // `std::iota`
6
+ #include <shared_mutex> // `std::shared_mutex`
7
+ #include <thread> // `std::thread`
8
+ #include <vector> // `std::vector`
9
+
10
+ #include <fp16/fp16.h>
11
+ #include <tsl/robin_map.h>
12
+
13
+ #if defined(USEARCH_USE_SIMSIMD)
14
+ #include <simsimd/simsimd.h>
15
+ #endif
16
+
17
+ #include <usearch/index.hpp>
18
+ #include <usearch/index_punned_helpers.hpp>
19
+
20
+ namespace unum {
21
+ namespace usearch {
22
+
23
+ struct cos_f8_t {
24
+ using scalar_t = f8_bits_t;
25
+ std::size_t dimensions;
26
+
27
+ inline cos_f8_t(std::size_t dims) noexcept : dimensions(dims) {}
28
+ inline punned_distance_t operator()(f8_bits_t const* a, f8_bits_t const* b) const noexcept {
29
+ std::int32_t ab{}, a2{}, b2{};
30
+ for (std::size_t i = 0; i != dimensions; i++) {
31
+ std::int16_t ai{a[i]};
32
+ std::int16_t bi{b[i]};
33
+ ab += ai * bi;
34
+ a2 += square(ai);
35
+ b2 += square(bi);
36
+ }
37
+ return 1.f - ab / (std::sqrt(a2) * std::sqrt(b2));
38
+ }
39
+ };
40
+
41
+ struct l2sq_f8_t {
42
+ using scalar_t = f8_bits_t;
43
+ std::size_t dimensions;
44
+
45
+ inline l2sq_f8_t(std::size_t dims) noexcept : dimensions(dims) {}
46
+ inline punned_distance_t operator()(f8_bits_t const* a, f8_bits_t const* b) const noexcept {
47
+ std::int32_t ab_deltas_sq{};
48
+ for (std::size_t i = 0; i != dimensions; i++)
49
+ ab_deltas_sq += square(std::int16_t(a[i]) - std::int16_t(b[i]));
50
+ return ab_deltas_sq;
51
+ }
52
+ };
53
+
54
+ /**
55
+ * @brief Oversimplified type-punned index for equidimensional vectors
56
+ * with automatic down-casting and hardware isa.
57
+ */
58
+ template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
59
+ class index_punned_dense_gt {
60
+ public:
61
+ using label_t = label_at;
62
+ using id_t = id_at;
63
+ using distance_t = punned_distance_t;
64
+ /// @brief Punned metric object.
65
+ using metric_t = punned_metric_t;
66
+
67
+ private:
68
+ /// @brief Schema: input buffer, bytes in input buffer, output buffer.
69
+ using cast_t = std::function<bool(byte_t const*, std::size_t, byte_t*)>;
70
+ /// @brief Punned index.
71
+ using index_t = index_gt<metric_t, label_t, id_t, byte_t, aligned_allocator_t, memory_mapping_allocator_t>;
72
+ using index_allocator_t = aligned_allocator_gt<index_t, 64>;
73
+
74
+ /// @brief A type-punned metric and metadata about present isa support.
75
+ struct metric_and_meta_t {
76
+ metric_t metric;
77
+ isa_t isa;
78
+
79
+ metric_and_meta_t(metric_t m = {}, isa_t i = isa_t::auto_k) : metric(m), isa(i) {}
80
+ metric_and_meta_t(metric_and_meta_t const&) = default;
81
+ metric_and_meta_t& operator=(metric_and_meta_t const&) = default;
82
+ };
83
+
84
+ using member_iterator_t = typename index_t::member_iterator_t;
85
+ using member_citerator_t = typename index_t::member_citerator_t;
86
+ using member_ref_t = typename index_t::member_ref_t;
87
+ using member_cref_t = typename index_t::member_cref_t;
88
+
89
+ /// @brief Number of unique dimensions in the vectors.
90
+ std::size_t dimensions_ = 0;
91
+ /// @brief Similar to `dimensions_`, but different for fraction byte-length scalars.
92
+ std::size_t scalar_words_ = 0;
93
+ std::size_t expansion_add_ = 0;
94
+ std::size_t expansion_search_ = 0;
95
+ index_t* typed_ = nullptr;
96
+
97
+ std::size_t casted_vector_bytes_ = 0;
98
+ scalar_kind_t scalar_kind_ = scalar_kind_t::f32_k;
99
+ isa_t isa_ = isa_t::auto_k;
100
+
101
+ mutable std::vector<byte_t> cast_buffer_;
102
+ struct casts_t {
103
+ cast_t from_b1x8;
104
+ cast_t from_f8;
105
+ cast_t from_f16;
106
+ cast_t from_f32;
107
+ cast_t from_f64;
108
+
109
+ cast_t to_b1x8;
110
+ cast_t to_f8;
111
+ cast_t to_f16;
112
+ cast_t to_f32;
113
+ cast_t to_f64;
114
+ } casts_;
115
+
116
+ metric_t root_metric_;
117
+
118
+ mutable std::vector<std::size_t> available_threads_;
119
+ mutable std::mutex available_threads_mutex_;
120
+
121
+ using shared_mutex_t = std::mutex; // TODO: Find an OS-compatible solution
122
+ using shared_lock_t = std::unique_lock<shared_mutex_t>;
123
+ using unique_lock_t = std::unique_lock<shared_mutex_t>;
124
+
125
+ mutable shared_mutex_t lookup_table_mutex_;
126
+ tsl::robin_map<label_t, id_t> lookup_table_;
127
+
128
+ public:
129
+ using search_result_t = typename index_t::search_result_t;
130
+ using add_result_t = typename index_t::add_result_t;
131
+ using serialization_result_t = typename index_t::serialization_result_t;
132
+ using stats_t = typename index_t::stats_t;
133
+
134
+ index_punned_dense_gt() = default;
135
+ index_punned_dense_gt(index_punned_dense_gt&& other) { swap(other); }
136
+ index_punned_dense_gt& operator=(index_punned_dense_gt&& other) {
137
+ swap(other);
138
+ return *this;
139
+ }
140
+
141
+ ~index_punned_dense_gt() { index_allocator_t{}.deallocate(typed_, 1); }
142
+
143
+ void swap(index_punned_dense_gt& other) {
144
+ std::swap(dimensions_, other.dimensions_);
145
+ std::swap(scalar_words_, other.scalar_words_);
146
+ std::swap(casted_vector_bytes_, other.casted_vector_bytes_);
147
+ std::swap(scalar_kind_, other.scalar_kind_);
148
+ std::swap(isa_, other.isa_);
149
+ std::swap(typed_, other.typed_);
150
+ std::swap(cast_buffer_, other.cast_buffer_);
151
+ std::swap(casts_, other.casts_);
152
+ std::swap(root_metric_, other.root_metric_);
153
+ std::swap(available_threads_, other.available_threads_);
154
+ }
155
+
156
+ static index_config_t optimize(index_config_t config) { return index_t::optimize(config); }
157
+
158
+ std::size_t dimensions() const { return dimensions_; }
159
+ std::size_t scalar_words() const { return scalar_words_; }
160
+ std::size_t connectivity() const { return typed_->connectivity(); }
161
+ std::size_t size() const { return typed_->size(); }
162
+ std::size_t capacity() const { return typed_->capacity(); }
163
+ index_config_t const& config() const { return typed_->config(); }
164
+ index_limits_t const& limits() const { return typed_->limits(); }
165
+ void clear() { return typed_->clear(); }
166
+
167
+ std::size_t expansion_add() const { return expansion_add_; }
168
+ std::size_t expansion_search() const { return expansion_search_; }
169
+ void change_expansion_add(std::size_t n) { expansion_add_ = n; }
170
+ void change_expansion_search(std::size_t n) { expansion_search_ = n; }
171
+
172
+ member_citerator_t cbegin() const { return typed_->cbegin(); }
173
+ member_citerator_t cend() const { return typed_->cend(); }
174
+ member_citerator_t begin() const { return typed_->begin(); }
175
+ member_citerator_t end() const { return typed_->end(); }
176
+ member_iterator_t begin() { return typed_->begin(); }
177
+ member_iterator_t end() { return typed_->end(); }
178
+
179
+ stats_t stats() const { return typed_->stats(); }
180
+ stats_t stats(std::size_t level) const { return typed_->stats(level); }
181
+
182
+ scalar_kind_t scalar_kind() const { return scalar_kind_; }
183
+ isa_t isa() const { return isa_; }
184
+
185
+ serialization_result_t save(char const* path) const { return typed_->save(path); }
186
+ serialization_result_t load(char const* path) { return typed_->load(path); }
187
+ serialization_result_t view(char const* path) { return typed_->view(path); }
188
+ bool reserve(index_limits_t limits) { return typed_->reserve(limits); }
189
+
190
+ std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const {
191
+ return typed_->memory_usage(allocator_entry_bytes);
192
+ }
193
+
194
+ bool contains(label_t label) const {
195
+ shared_lock_t lock(lookup_table_mutex_);
196
+ return lookup_table_.contains(label);
197
+ }
198
+
199
+ void export_labels(label_t* labels, std::size_t limit) const {
200
+ shared_lock_t lock(lookup_table_mutex_);
201
+ for (auto it = lookup_table_.begin(); it != lookup_table_.end() && limit; ++it, ++labels, --limit)
202
+ *labels = it->first;
203
+ }
204
+
205
+ // clang-format off
206
+ add_result_t add(label_t label, b1x8_t const* vector) { return add_(label, vector, casts_.from_b1x8); }
207
+ add_result_t add(label_t label, f8_bits_t const* vector) { return add_(label, vector, casts_.from_f8); }
208
+ add_result_t add(label_t label, f16_t const* vector) { return add_(label, vector, casts_.from_f16); }
209
+ add_result_t add(label_t label, f32_t const* vector) { return add_(label, vector, casts_.from_f32); }
210
+ add_result_t add(label_t label, f64_t const* vector) { return add_(label, vector, casts_.from_f64); }
211
+
212
+ add_result_t add(label_t label, b1x8_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_b1x8); }
213
+ add_result_t add(label_t label, f8_bits_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f8); }
214
+ add_result_t add(label_t label, f16_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f16); }
215
+ add_result_t add(label_t label, f32_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f32); }
216
+ add_result_t add(label_t label, f64_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f64); }
217
+
218
+ search_result_t search(b1x8_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_b1x8); }
219
+ search_result_t search(f8_bits_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f8); }
220
+ search_result_t search(f16_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f16); }
221
+ search_result_t search(f32_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f32); }
222
+ search_result_t search(f64_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f64); }
223
+
224
+ search_result_t search(b1x8_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_b1x8); }
225
+ search_result_t search(f8_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f8); }
226
+ search_result_t search(f16_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f16); }
227
+ search_result_t search(f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f32); }
228
+ search_result_t search(f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f64); }
229
+
230
+ search_result_t search_around(label_t hint, b1x8_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_b1x8); }
231
+ search_result_t search_around(label_t hint, f8_bits_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f8); }
232
+ search_result_t search_around(label_t hint, f16_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f16); }
233
+ search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f32); }
234
+ search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f64); }
235
+
236
+ search_result_t search_around(label_t hint, b1x8_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_b1x8); }
237
+ search_result_t search_around(label_t hint, f8_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f8); }
238
+ search_result_t search_around(label_t hint, f16_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f16); }
239
+ search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f32); }
240
+ search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f64); }
241
+
242
+ void reconstruct(label_t label, b1x8_t* vector) const { return reconstruct_(label, vector, casts_.to_b1x8); }
243
+ void reconstruct(label_t label, f8_bits_t* vector) const { return reconstruct_(label, vector, casts_.to_f8); }
244
+ void reconstruct(label_t label, f16_t* vector) const { return reconstruct_(label, vector, casts_.to_f16); }
245
+ void reconstruct(label_t label, f32_t* vector) const { return reconstruct_(label, vector, casts_.to_f32); }
246
+ void reconstruct(label_t label, f64_t* vector) const { return reconstruct_(label, vector, casts_.to_f64); }
247
+ // clang-format on
248
+
249
+ static index_punned_dense_gt make( //
250
+ std::size_t dimensions, metric_kind_t metric, //
251
+ index_config_t config = {}, //
252
+ scalar_kind_t accuracy = scalar_kind_t::f32_k, //
253
+ std::size_t expansion_add = default_expansion_add(), //
254
+ std::size_t expansion_search = default_expansion_search()) {
255
+
256
+ return make_( //
257
+ dimensions, accuracy, //
258
+ config, expansion_add, expansion_search, //
259
+ make_metric_(metric, dimensions, accuracy), make_casts_(accuracy));
260
+ }
261
+
262
+ static index_punned_dense_gt make( //
263
+ std::size_t dimensions, metric_t metric, //
264
+ index_config_t config = {}, //
265
+ scalar_kind_t accuracy = scalar_kind_t::f32_k, //
266
+ std::size_t expansion_add = default_expansion_add(), //
267
+ std::size_t expansion_search = default_expansion_search()) {
268
+
269
+ return make_( //
270
+ dimensions, accuracy, //
271
+ config, expansion_add, expansion_search, //
272
+ metric_and_meta_t(metric), make_casts_(accuracy));
273
+ }
274
+
275
+ index_punned_dense_gt fork() const {
276
+ index_punned_dense_gt result;
277
+
278
+ result.dimensions_ = dimensions_;
279
+ result.scalar_words_ = scalar_words_;
280
+ result.scalar_kind_ = scalar_kind_;
281
+ result.isa_ = isa_;
282
+ result.casted_vector_bytes_ = casted_vector_bytes_;
283
+ result.cast_buffer_ = cast_buffer_;
284
+ result.casts_ = casts_;
285
+
286
+ result.root_metric_ = root_metric_;
287
+ index_t* raw = index_allocator_t{}.allocate(1);
288
+ new (raw) index_t(config(), root_metric_);
289
+ result.typed_ = raw;
290
+
291
+ return result;
292
+ }
293
+
294
+ private:
295
+ struct thread_lock_t {
296
+ index_punned_dense_gt const& parent;
297
+ std::size_t thread_id;
298
+
299
+ ~thread_lock_t() { parent.thread_unlock_(thread_id); }
300
+ };
301
+
302
+ thread_lock_t thread_lock_() const {
303
+ available_threads_mutex_.lock();
304
+ std::size_t thread_id = available_threads_.back();
305
+ available_threads_.pop_back();
306
+ available_threads_mutex_.unlock();
307
+ return {*this, thread_id};
308
+ }
309
+
310
+ void thread_unlock_(std::size_t thread_id) const {
311
+ available_threads_mutex_.lock();
312
+ available_threads_.push_back(thread_id);
313
+ available_threads_mutex_.unlock();
314
+ }
315
+
316
+ template <typename scalar_at>
317
+ add_result_t add_(label_t label, scalar_at const* vector, add_config_t config, cast_t const& cast) {
318
+ byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
319
+ std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
320
+
321
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
322
+ bool casted = cast(vector_data, vector_bytes, casted_data);
323
+ if (casted)
324
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
325
+
326
+ add_result_t result = typed_->add(label, {vector_data, vector_bytes}, config);
327
+ {
328
+ unique_lock_t lock(lookup_table_mutex_);
329
+ lookup_table_.emplace(label, result.id);
330
+ }
331
+ return result;
332
+ }
333
+
334
+ template <typename scalar_at>
335
+ search_result_t search_( //
336
+ scalar_at const* vector, std::size_t wanted, //
337
+ search_config_t config, cast_t const& cast) const {
338
+
339
+ byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
340
+ std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
341
+
342
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
343
+ bool casted = cast(vector_data, vector_bytes, casted_data);
344
+ if (casted)
345
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_;
346
+
347
+ return typed_->search({vector_data, vector_bytes}, wanted, config);
348
+ }
349
+
350
+ template <typename scalar_at>
351
+ search_result_t search_around_( //
352
+ label_t hint, scalar_at const* vector, std::size_t wanted, //
353
+ search_config_t config, cast_t const& cast) const {
354
+
355
+ byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
356
+ std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
357
+
358
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
359
+ bool casted = cast(vector_data, vector_bytes, casted_data);
360
+ if (casted)
361
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_;
362
+
363
+ return typed_->search_around(static_cast<id_t>(hint), {vector_data, vector_bytes}, wanted, config);
364
+ }
365
+
366
+ id_t lookup_id_(label_t label) const {
367
+ shared_lock_t lock(lookup_table_mutex_);
368
+ return lookup_table_.at(label);
369
+ }
370
+
371
+ template <typename scalar_at> void reconstruct_(label_t label, scalar_at* reconstructed, cast_t const& cast) const {
372
+ id_t id = lookup_id_(label);
373
+ member_citerator_t iterator = typed_->cbegin() + id;
374
+ member_cref_t member = *iterator;
375
+ byte_t const* casted_vector = reinterpret_cast<byte_t const*>(member.vector.data());
376
+ bool casted = cast(casted_vector, casted_vector_bytes_, (byte_t*)reconstructed);
377
+ if (!casted)
378
+ std::memcpy(reconstructed, casted_vector, casted_vector_bytes_);
379
+ }
380
+
381
+ template <typename scalar_at> add_result_t add_(label_t label, scalar_at const* vector, cast_t const& cast) {
382
+ thread_lock_t lock = thread_lock_();
383
+ add_config_t add_config;
384
+ add_config.thread = lock.thread_id;
385
+ return add_(label, vector, add_config, cast);
386
+ }
387
+
388
+ template <typename scalar_at>
389
+ search_result_t search_( //
390
+ scalar_at const* vector, std::size_t wanted, //
391
+ cast_t const& cast) const {
392
+ thread_lock_t lock = thread_lock_();
393
+ search_config_t search_config;
394
+ search_config.thread = lock.thread_id;
395
+ return search_(vector, wanted, search_config, cast);
396
+ }
397
+
398
+ template <typename scalar_at>
399
+ search_result_t search_around_( //
400
+ label_t hint, scalar_at const* vector, std::size_t wanted, //
401
+ cast_t const& cast) const {
402
+ thread_lock_t lock = thread_lock_();
403
+ search_config_t search_config;
404
+ search_config.thread = lock.thread_id;
405
+ return search_around_(hint, vector, wanted, search_config, cast);
406
+ }
407
+
408
+ static index_punned_dense_gt make_( //
409
+ std::size_t dimensions, scalar_kind_t scalar_kind, //
410
+ index_config_t config, std::size_t expansion_add, std::size_t expansion_search, //
411
+ metric_and_meta_t metric_and_meta, casts_t casts) {
412
+
413
+ std::size_t hardware_threads = std::thread::hardware_concurrency();
414
+ index_punned_dense_gt result;
415
+ result.dimensions_ = dimensions;
416
+ result.scalar_kind_ = scalar_kind;
417
+ result.scalar_words_ = count_scalar_words_(dimensions, scalar_kind);
418
+ result.expansion_add_ = expansion_add;
419
+ result.expansion_search_ = expansion_search;
420
+ result.casted_vector_bytes_ = bytes_per_scalar(scalar_kind) * result.scalar_words_;
421
+ result.cast_buffer_.resize(hardware_threads * result.casted_vector_bytes_);
422
+ result.casts_ = casts;
423
+ result.isa_ = metric_and_meta.isa;
424
+ result.root_metric_ = metric_and_meta.metric;
425
+
426
+ // Fill the thread IDs.
427
+ result.available_threads_.resize(hardware_threads);
428
+ std::iota(result.available_threads_.begin(), result.available_threads_.end(), 0ul);
429
+
430
+ // Available since C11, but only C++17, so we use the C version.
431
+ index_t* raw = index_allocator_t{}.allocate(1);
432
+ new (raw) index_t(config, metric_and_meta.metric);
433
+ result.typed_ = raw;
434
+ return result;
435
+ }
436
+
437
+ template <typename to_scalar_at> static casts_t make_casts_() {
438
+ casts_t result;
439
+
440
+ result.from_b1x8 = cast_gt<b1x8_t, to_scalar_at>{};
441
+ result.from_f8 = cast_gt<f8_bits_t, to_scalar_at>{};
442
+ result.from_f16 = cast_gt<f16_t, to_scalar_at>{};
443
+ result.from_f32 = cast_gt<f32_t, to_scalar_at>{};
444
+ result.from_f64 = cast_gt<f64_t, to_scalar_at>{};
445
+
446
+ result.to_b1x8 = cast_gt<to_scalar_at, b1x8_t>{};
447
+ result.to_f8 = cast_gt<to_scalar_at, f8_bits_t>{};
448
+ result.to_f16 = cast_gt<to_scalar_at, f16_t>{};
449
+ result.to_f32 = cast_gt<to_scalar_at, f32_t>{};
450
+ result.to_f64 = cast_gt<to_scalar_at, f64_t>{};
451
+
452
+ return result;
453
+ }
454
+
455
+ static casts_t make_casts_(scalar_kind_t accuracy) {
456
+ switch (accuracy) {
457
+ case scalar_kind_t::f64_k: return make_casts_<f64_t>();
458
+ case scalar_kind_t::f32_k: return make_casts_<f32_t>();
459
+ case scalar_kind_t::f16_k: return make_casts_<f16_t>();
460
+ case scalar_kind_t::f8_k: return make_casts_<f8_bits_t>();
461
+ case scalar_kind_t::b1x8_k: return make_casts_<b1x8_t>();
462
+ default: return {};
463
+ }
464
+ }
465
+
466
+ static std::size_t count_scalar_words_(std::size_t dimensions, scalar_kind_t accuracy) {
467
+ switch (accuracy) {
468
+ case scalar_kind_t::f64_k: return dimensions;
469
+ case scalar_kind_t::f32_k: return dimensions;
470
+ case scalar_kind_t::f16_k: return dimensions;
471
+ case scalar_kind_t::f8_k: return dimensions;
472
+ case scalar_kind_t::b1x8_k: return divide_round_up<CHAR_BIT>(dimensions);
473
+ default: return {};
474
+ }
475
+ }
476
+
477
+ template <typename scalar_at, typename typed_at> static metric_t pun_metric_(typed_at metric) {
478
+ using scalar_t = scalar_at;
479
+ using vector_view_t = span_gt<scalar_t const>;
480
+ return [=](punned_vector_view_t a, punned_vector_view_t b) -> punned_distance_t {
481
+ std::size_t dims = a.size() / sizeof(scalar_t);
482
+ vector_view_t a_typed{(scalar_t const*)a.data(), dims};
483
+ vector_view_t b_typed{(scalar_t const*)b.data(), dims};
484
+ return metric(a_typed, b_typed);
485
+ };
486
+ }
487
+
488
+ static metric_and_meta_t ip_metric_f32_(std::size_t dimensions) {
489
+ (void)dimensions;
490
+ #if defined(USEARCH_USE_SIMSIMD)
491
+ #if defined(USEARCH_IS_X86)
492
+ if (dimensions % 4 == 0)
493
+ return {
494
+ pun_metric_<simsimd_f32_t>([=](simsimd_f32_t const* a, simsimd_f32_t const* b) {
495
+ return 1.f - simsimd_dot_f32x4avx2(a, b, dimensions);
496
+ }),
497
+ isa_t::avx2_k,
498
+ };
499
+ #elif defined(USEARCH_IS_ARM)
500
+ if (supports_arm_sve())
501
+ return {
502
+ pun_metric_<simsimd_f32_t>([=](simsimd_f32_t const* a, simsimd_f32_t const* b) {
503
+ return 1.f - simsimd_dot_f32sve(a, b, dimensions);
504
+ }),
505
+ isa_t::sve_k,
506
+ };
507
+ if (dimensions % 4 == 0)
508
+ return {
509
+ pun_metric_<simsimd_f32_t>([=](simsimd_f32_t const* a, simsimd_f32_t const* b) {
510
+ return 1.f - simsimd_dot_f32x4neon(a, b, dimensions);
511
+ }),
512
+ isa_t::neon_k,
513
+ };
514
+ #endif
515
+ #endif
516
+ return pun_metric_<f32_t>(ip_gt<f32_t>{});
517
+ }
518
+
519
+ static metric_and_meta_t cos_metric_f16_(std::size_t dimensions) {
520
+ (void)dimensions;
521
+ #if defined(USEARCH_USE_SIMSIMD)
522
+ #if defined(USEARCH_IS_X86)
523
+ if (dimensions % 16 == 0)
524
+ return {
525
+ pun_metric_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) {
526
+ return 1.f - simsimd_cos_f16x16avx512(a, b, dimensions);
527
+ }),
528
+ isa_t::avx512_k,
529
+ };
530
+ #elif defined(USEARCH_IS_ARM)
531
+ if (dimensions % 4 == 0)
532
+ return {
533
+ pun_metric_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) {
534
+ return 1.f - simsimd_cos_f16x4neon(a, b, dimensions);
535
+ }),
536
+ isa_t::neon_k,
537
+ };
538
+ #endif
539
+ #endif
540
+ return pun_metric_<f16_t>(cos_gt<f16_t, f32_t>{});
541
+ }
542
+
543
+ static metric_and_meta_t cos_metric_f8_(std::size_t dimensions) {
544
+ (void)dimensions;
545
+ #if defined(USEARCH_USE_SIMSIMD)
546
+ #if defined(USEARCH_IS_ARM)
547
+ if (dimensions % 16 == 0)
548
+ return {
549
+ pun_metric_<int8_t>(
550
+ [=](int8_t const* a, int8_t const* b) { return 1.f - simsimd_cos_i8x16neon(a, b, dimensions); }),
551
+ isa_t::neon_k,
552
+ };
553
+ #endif
554
+ #endif
555
+ return pun_metric_<f8_bits_t>(cos_f8_t{dimensions});
556
+ }
557
+
558
+ static metric_and_meta_t ip_metric_(std::size_t dimensions, scalar_kind_t accuracy) {
559
+ switch (accuracy) {
560
+ case scalar_kind_t::f32_k:
561
+ // The two most common numeric types for the most common metric have optimized versions
562
+ return ip_metric_f32_(dimensions);
563
+ case scalar_kind_t::f16_k:
564
+ // Dot-product accumulates error, Cosine-distance normalizes it
565
+ return cos_metric_f16_(dimensions);
566
+
567
+ case scalar_kind_t::f8_k: return cos_metric_f8_(dimensions);
568
+ case scalar_kind_t::f64_k: return pun_metric_<f64_t>(ip_gt<f64_t>{});
569
+ default: return {};
570
+ }
571
+ }
572
+
573
+ static metric_and_meta_t l2sq_metric_(std::size_t dimensions, scalar_kind_t accuracy) {
574
+ switch (accuracy) {
575
+ case scalar_kind_t::f8_k: return pun_metric_<f8_bits_t>(l2sq_f8_t{dimensions});
576
+ case scalar_kind_t::f16_k: return pun_metric_<f16_t>(l2sq_gt<f16_t, f32_t>{});
577
+ case scalar_kind_t::f32_k: return pun_metric_<f32_t>(l2sq_gt<f32_t>{});
578
+ case scalar_kind_t::f64_k: return pun_metric_<f64_t>(l2sq_gt<f64_t>{});
579
+ default: return {};
580
+ }
581
+ }
582
+
583
+ static metric_and_meta_t cos_metric_(std::size_t dimensions, scalar_kind_t accuracy) {
584
+ switch (accuracy) {
585
+ case scalar_kind_t::f8_k: return cos_metric_f8_(dimensions);
586
+ case scalar_kind_t::f16_k: return cos_metric_f16_(dimensions);
587
+ case scalar_kind_t::f32_k: return pun_metric_<f32_t>(cos_gt<f32_t>{});
588
+ case scalar_kind_t::f64_k: return pun_metric_<f64_t>(cos_gt<f64_t>{});
589
+ default: return {};
590
+ }
591
+ }
592
+
593
+ static metric_and_meta_t haversine_metric_(scalar_kind_t accuracy) {
594
+ switch (accuracy) {
595
+ case scalar_kind_t::f8_k: return pun_metric_<f8_bits_t>(haversine_gt<f8_bits_t, f32_t>{});
596
+ case scalar_kind_t::f16_k: return pun_metric_<f16_t>(haversine_gt<f16_t, f32_t>{});
597
+ case scalar_kind_t::f32_k: return pun_metric_<f32_t>(haversine_gt<f32_t>{});
598
+ case scalar_kind_t::f64_k: return pun_metric_<f64_t>(haversine_gt<f64_t>{});
599
+ default: return {};
600
+ }
601
+ }
602
+
603
+ static metric_and_meta_t pearson_metric_(scalar_kind_t accuracy) {
604
+ switch (accuracy) {
605
+ case scalar_kind_t::f8_k: return pun_metric_<f8_bits_t>(pearson_correlation_gt<f8_bits_t, f32_t>{});
606
+ case scalar_kind_t::f16_k: return pun_metric_<f16_t>(pearson_correlation_gt<f16_t, f32_t>{});
607
+ case scalar_kind_t::f32_k: return pun_metric_<f32_t>(pearson_correlation_gt<f32_t>{});
608
+ case scalar_kind_t::f64_k: return pun_metric_<f64_t>(pearson_correlation_gt<f64_t>{});
609
+ default: return {};
610
+ }
611
+ }
612
+
613
+ static metric_and_meta_t make_metric_(metric_kind_t kind, std::size_t dimensions, scalar_kind_t accuracy) {
614
+ switch (kind) {
615
+ case metric_kind_t::ip_k: return ip_metric_(dimensions, accuracy);
616
+ case metric_kind_t::cos_k: return cos_metric_(dimensions, accuracy);
617
+ case metric_kind_t::l2sq_k: return l2sq_metric_(dimensions, accuracy);
618
+ case metric_kind_t::pearson_k: return pearson_metric_(accuracy);
619
+ case metric_kind_t::haversine_k: return haversine_metric_(accuracy);
620
+ case metric_kind_t::hamming_k: return pun_metric_<unsigned char>(hamming_gt<unsigned char>{});
621
+ case metric_kind_t::jaccard_k: // Equivalent to Tanimoto
622
+ case metric_kind_t::tanimoto_k: return pun_metric_<unsigned char>(tanimoto_gt<unsigned char>{});
623
+ case metric_kind_t::sorensen_k: return pun_metric_<unsigned char>(sorensen_gt<unsigned char>{});
624
+ default: return {};
625
+ }
626
+ }
627
+ };
628
+
629
+ using punned_small_t = index_punned_dense_gt<std::int64_t, std::uint32_t>;
630
+ using punned_big_t = index_punned_dense_gt<uuid_t, uint40_t>;
631
+
632
+ } // namespace usearch
633
+ } // namespace unum