usearch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.clang-format +10 -0
  2. package/.github/workflows/package.json +82 -0
  3. package/.github/workflows/release.yml +111 -0
  4. package/.gitmodules +6 -0
  5. package/.vscode/launch.json +116 -0
  6. package/.vscode/settings.json +109 -0
  7. package/.vscode/tasks.json +35 -0
  8. package/CMakeLists.txt +84 -0
  9. package/CMakeListsInstructuons.md +109 -0
  10. package/README.md +69 -0
  11. package/VERSION +1 -0
  12. package/assets/unum.png +0 -0
  13. package/binding.gyp +28 -0
  14. package/build.rs +14 -0
  15. package/cargo.toml +15 -0
  16. package/cmake/clipp.cmake +8 -0
  17. package/cmake/jemalloc.cmake +80 -0
  18. package/docs/Makefile +20 -0
  19. package/docs/_static/custom.css +26 -0
  20. package/docs/_static/custom.js +7 -0
  21. package/docs/conf.py +48 -0
  22. package/docs/index.rst +10 -0
  23. package/fp16/CMakeLists.txt +176 -0
  24. package/fp16/LICENSE +11 -0
  25. package/fp16/README.md +20 -0
  26. package/fp16/bench/alt-element.cc +89 -0
  27. package/fp16/bench/from-alt-array.cc +175 -0
  28. package/fp16/bench/from-ieee-array.cc +399 -0
  29. package/fp16/bench/ieee-element.cc +272 -0
  30. package/fp16/bench/to-alt-array.cc +91 -0
  31. package/fp16/bench/to-ieee-array.cc +303 -0
  32. package/fp16/cmake/DownloadGoogleBenchmark.cmake +15 -0
  33. package/fp16/cmake/DownloadGoogleTest.cmake +15 -0
  34. package/fp16/cmake/DownloadPSimd.cmake +15 -0
  35. package/fp16/configure.py +65 -0
  36. package/fp16/confu.yaml +8 -0
  37. package/fp16/include/fp16/__init__.py +0 -0
  38. package/fp16/include/fp16/avx.py +50 -0
  39. package/fp16/include/fp16/avx2.py +53 -0
  40. package/fp16/include/fp16/bitcasts.h +92 -0
  41. package/fp16/include/fp16/fp16.h +451 -0
  42. package/fp16/include/fp16/psimd.h +131 -0
  43. package/fp16/include/fp16.h +11 -0
  44. package/fp16/jni/Android.mk +6 -0
  45. package/fp16/jni/Application.mk +4 -0
  46. package/fp16/test/alt-from-fp32-value.cc +498 -0
  47. package/fp16/test/alt-to-fp32-bits.cc +263 -0
  48. package/fp16/test/alt-to-fp32-psimd.cc +145 -0
  49. package/fp16/test/alt-to-fp32-value.cc +348 -0
  50. package/fp16/test/alt-to-fp32x2-psimd.cc +245 -0
  51. package/fp16/test/bitcasts.cc +56 -0
  52. package/fp16/test/ieee-from-fp32-value.cc +523 -0
  53. package/fp16/test/ieee-to-fp32-bits.cc +333 -0
  54. package/fp16/test/ieee-to-fp32-psimd.cc +312 -0
  55. package/fp16/test/ieee-to-fp32-value.cc +420 -0
  56. package/fp16/test/ieee-to-fp32x2-psimd.cc +541 -0
  57. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +96 -0
  58. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +96 -0
  59. package/fp16/test/peachpy/stubs.py +38 -0
  60. package/fp16/test/tables.cc +530 -0
  61. package/fp16/test/tables.h +13 -0
  62. package/fp16/third-party/THHalf.h +92 -0
  63. package/fp16/third-party/eigen-half.h +170 -0
  64. package/fp16/third-party/float16-compressor.h +80 -0
  65. package/fp16/third-party/half.hpp +2916 -0
  66. package/fp16/third-party/npy-halffloat.h +328 -0
  67. package/include/usearch/usearch.hpp +1345 -0
  68. package/package.json +19 -0
  69. package/pyproject.toml +33 -0
  70. package/setup.py +63 -0
  71. package/simsimd/.clang-format +10 -0
  72. package/simsimd/.vscode/launch.json +24 -0
  73. package/simsimd/.vscode/settings.json +73 -0
  74. package/simsimd/.vscode/tasks.json +17 -0
  75. package/simsimd/CMakeLists.txt +82 -0
  76. package/simsimd/LICENSE +201 -0
  77. package/simsimd/README.md +73 -0
  78. package/simsimd/bench.cxx +82 -0
  79. package/simsimd/include/simsimd/simsimd.h +322 -0
  80. package/simsimd/include/simsimd/simsimd.hpp +443 -0
  81. package/src/advanced.hpp +489 -0
  82. package/src/bench.cpp +415 -0
  83. package/src/bench.py +89 -0
  84. package/src/javascript.cpp +198 -0
  85. package/src/main.rs +42 -0
  86. package/src/python.cpp +226 -0
  87. package/src/rust.cpp +23 -0
  88. package/src/rust.h +22 -0
  89. package/src/test.js +17 -0
  90. package/src/test.py +20 -0
@@ -0,0 +1,489 @@
1
+ #pragma once
2
+ #include <functional>
3
+ #include <thread>
4
+ #include <vector>
5
+
6
+ #if __linux__
7
+ #include <sys/auxv.h>
8
+ #endif
9
+
10
+ #include <fp16/fp16.h>
11
+ #include <simsimd/simsimd.h>
12
+
13
+ #include <usearch/usearch.hpp>
14
+
15
+ namespace unum {
16
+ namespace usearch {
17
+
18
+ using byte_t = char;
19
+ using punned_distance_t = float;
20
+ using punned_metric_t = punned_distance_t (*)(byte_t const*, byte_t const*, std::size_t, std::size_t);
21
+
22
+ template <typename metric_at>
23
+ static punned_distance_t punned_metric( //
24
+ byte_t const* a, byte_t const* b, std::size_t a_bytes, std::size_t b_bytes) noexcept {
25
+ using scalar_t = typename metric_at::scalar_t;
26
+ return metric_at{}((scalar_t const*)a, (scalar_t const*)b, a_bytes / sizeof(scalar_t), b_bytes / sizeof(scalar_t));
27
+ }
28
+
29
+ using punned_stateful_metric_t =
30
+ std::function<punned_distance_t(byte_t const*, byte_t const*, std::size_t, std::size_t)>;
31
+
32
+ template <typename at, typename compare_at> inline at clamp(at v, at lo, at hi, compare_at comp) noexcept {
33
+ return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
34
+ }
35
+ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
36
+ return usearch::clamp(v, lo, hi, std::less<at>{});
37
+ }
38
+
39
+ class i8q100_converted_t;
40
+ class f16_converted_t;
41
+
42
+ class f16_converted_t {
43
+ std::uint16_t uint16_{};
44
+
45
+ public:
46
+ inline f16_converted_t() noexcept : uint16_(0) {}
47
+ inline f16_converted_t(f16_converted_t&&) = default;
48
+ inline f16_converted_t& operator=(f16_converted_t&&) = default;
49
+ inline f16_converted_t(f16_converted_t const&) = default;
50
+ inline f16_converted_t& operator=(f16_converted_t const&) = default;
51
+
52
+ inline operator float() const noexcept { return fp16_ieee_to_fp32_value(uint16_); }
53
+
54
+ inline f16_converted_t(i8q100_converted_t) noexcept;
55
+ inline f16_converted_t(float v) noexcept : uint16_(fp16_ieee_from_fp32_value(v)) {}
56
+ inline f16_converted_t(double v) noexcept : uint16_(fp16_ieee_from_fp32_value(v)) {}
57
+
58
+ inline f16_converted_t operator+(f16_converted_t other) const noexcept { return {float(*this) + float(other)}; }
59
+ inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
60
+ inline f16_converted_t operator*(f16_converted_t other) const noexcept { return {float(*this) * float(other)}; }
61
+ inline f16_converted_t operator/(f16_converted_t other) const noexcept { return {float(*this) / float(other)}; }
62
+
63
+ inline f16_converted_t& operator+=(float v) noexcept {
64
+ uint16_ = fp16_ieee_from_fp32_value(v + fp16_ieee_to_fp32_value(uint16_));
65
+ return *this;
66
+ }
67
+
68
+ inline f16_converted_t& operator-=(float v) noexcept {
69
+ uint16_ = fp16_ieee_from_fp32_value(v - fp16_ieee_to_fp32_value(uint16_));
70
+ return *this;
71
+ }
72
+
73
+ inline f16_converted_t& operator*=(float v) noexcept {
74
+ uint16_ = fp16_ieee_from_fp32_value(v * fp16_ieee_to_fp32_value(uint16_));
75
+ return *this;
76
+ }
77
+
78
+ inline f16_converted_t& operator/=(float v) noexcept {
79
+ uint16_ = fp16_ieee_from_fp32_value(v / fp16_ieee_to_fp32_value(uint16_));
80
+ return *this;
81
+ }
82
+ };
83
+
84
+ class i8q100_converted_t {
85
+ std::int8_t int8_{};
86
+
87
+ public:
88
+ inline i8q100_converted_t() noexcept : int8_(0) {}
89
+ inline i8q100_converted_t(i8q100_converted_t&&) = default;
90
+ inline i8q100_converted_t& operator=(i8q100_converted_t&&) = default;
91
+ inline i8q100_converted_t(i8q100_converted_t const&) = default;
92
+ inline i8q100_converted_t& operator=(i8q100_converted_t const&) = default;
93
+
94
+ inline operator float() const noexcept { return float(int8_) / 100.f; }
95
+ inline operator f16_converted_t() const noexcept { return float(int8_) / 100.f; }
96
+ inline operator double() const noexcept { return double(int8_) / 100.0; }
97
+
98
+ inline i8q100_converted_t(float v) noexcept
99
+ : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.f), -100, 100)) {}
100
+ inline i8q100_converted_t(double v) noexcept
101
+ : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.0), -100, 100)) {}
102
+ };
103
+
104
+ inline f16_converted_t::f16_converted_t(i8q100_converted_t v) noexcept : f16_converted_t(float(v)) {}
105
+
106
+ struct uuid_t {
107
+ std::uint8_t octets[16];
108
+ };
109
+
110
+ template <typename callback_at> //
111
+ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callback) {
112
+
113
+ if (threads == 0)
114
+ threads = std::thread::hardware_concurrency();
115
+ if (threads == 1) {
116
+ for (std::size_t task_idx = 0; task_idx < tasks; ++task_idx)
117
+ callback(0, task_idx);
118
+ return;
119
+ }
120
+
121
+ std::vector<std::thread> threads_pool;
122
+ std::size_t tasks_per_thread = threads / tasks + (threads % tasks) != 0;
123
+ for (std::size_t thread = 0; thread != threads; ++thread) {
124
+ threads_pool.emplace_back([=]() {
125
+ for (std::size_t task_idx = thread * tasks_per_thread;
126
+ task_idx < std::min(tasks, thread * tasks_per_thread + tasks_per_thread); ++task_idx)
127
+ callback(thread, task_idx);
128
+ });
129
+ }
130
+
131
+ for (std::size_t thread = 0; thread != threads; ++thread)
132
+ threads_pool[thread].join();
133
+ }
134
+
135
+ /**
136
+ * @brief Relies on `posix_memalign` for aligned memory allocations.
137
+ */
138
+ template <typename element_at = char, std::size_t alignment_ak = 64> //
139
+ class aligned_allocator_gt {
140
+ public:
141
+ using value_type = element_at;
142
+ using size_type = std::size_t;
143
+ using pointer = element_at*;
144
+ using const_pointer = element_at const*;
145
+ template <typename other_element_at> struct rebind {
146
+ using other = aligned_allocator_gt<other_element_at>;
147
+ };
148
+
149
+ pointer allocate(size_type length) const noexcept {
150
+ void* result = nullptr;
151
+ int status = posix_memalign(&result, alignment_ak, ceil2(length * sizeof(value_type)));
152
+ return status == 0 ? (pointer)result : nullptr;
153
+ }
154
+
155
+ void deallocate(pointer begin, size_type) const noexcept { std::free(begin); }
156
+ };
157
+
158
+ using aligned_allocator_t = aligned_allocator_gt<>;
159
+
160
+ enum class accuracy_t {
161
+ f32_k,
162
+ f16_k,
163
+ f64_k,
164
+ i8q100_k,
165
+ };
166
+
167
+ enum class isa_t {
168
+ auto_k,
169
+ neon_k,
170
+ sve_k,
171
+ avx2_k,
172
+ avx512_k,
173
+ };
174
+
175
+ inline char const* isa(isa_t isa) noexcept {
176
+ switch (isa) {
177
+ case isa_t::auto_k: return "auto";
178
+ case isa_t::neon_k: return "neon";
179
+ case isa_t::sve_k: return "sve";
180
+ case isa_t::avx2_k: return "avx2";
181
+ case isa_t::avx512_k: return "avx512";
182
+ }
183
+ }
184
+
185
+ inline std::size_t bytes_per_scalar(accuracy_t accuracy) noexcept {
186
+ switch (accuracy) {
187
+ case accuracy_t::f32_k: return 4;
188
+ case accuracy_t::f16_k: return 2;
189
+ case accuracy_t::f64_k: return 8;
190
+ case accuracy_t::i8q100_k: return 1;
191
+ default: return 0;
192
+ }
193
+ }
194
+
195
+ inline bool supports_arm_sve() {
196
+ #if defined(__aarch64__)
197
+ #if __linux__
198
+ unsigned long hwcaps = getauxval(AT_HWCAP);
199
+ if (hwcaps & HWCAP_SVE)
200
+ return true;
201
+ #endif
202
+ #endif
203
+ return false;
204
+ }
205
+
206
+ template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
207
+ bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) noexcept {
208
+ from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
209
+ to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
210
+ std::transform( //
211
+ typed_input, typed_input + bytes_in_input / sizeof(from_scalar_at), typed_output,
212
+ [](from_scalar_at from) { return to_scalar_at(from); });
213
+ return true;
214
+ }
215
+ };
216
+
217
+ template <> struct cast_gt<f32_t, f32_t> {
218
+ bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
219
+ };
220
+
221
+ template <> struct cast_gt<f64_t, f64_t> {
222
+ bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
223
+ };
224
+
225
+ template <> struct cast_gt<f16_converted_t, f16_converted_t> {
226
+ bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
227
+ };
228
+
229
+ /**
230
+ * @brief Oversimplified type-punned index for equidimensional floating-point
231
+ * vectors with automatic down-casting and isa acceleration.
232
+ */
233
+ template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
234
+ class auto_index_gt {
235
+ public:
236
+ using label_t = label_at;
237
+ using id_t = id_at;
238
+ using distance_t = punned_distance_t;
239
+
240
+ using f32_t = float;
241
+ using f64_t = double;
242
+
243
+ private:
244
+ /// @brief Schema: input buffer, bytes in input buffer, output buffer.
245
+ using cast_t = std::function<bool(byte_t const*, std::size_t, byte_t*)>;
246
+ /// @brief Punned index.
247
+ using index_t = index_gt<punned_stateful_metric_t, label_t, id_t, byte_t>;
248
+ /// @brief A type-punned metric and metadata about present isa support.
249
+ struct metric_and_meta_t {
250
+ punned_stateful_metric_t metric;
251
+ isa_t acceleration;
252
+ };
253
+
254
+ std::size_t dimensions_ = 0;
255
+ accuracy_t accuracy_ = accuracy_t::f32_k;
256
+ std::unique_ptr<index_t> index_;
257
+
258
+ isa_t acceleration_ = isa_t::auto_k;
259
+ std::size_t casted_vector_bytes_ = 0;
260
+ mutable std::vector<byte_t> cast_buffer_;
261
+ struct casts_t {
262
+ cast_t from_i8q100{};
263
+ cast_t from_f16{};
264
+ cast_t from_f32{};
265
+ cast_t from_f64{};
266
+ } casts_;
267
+
268
+ punned_stateful_metric_t root_metric_;
269
+ config_t root_config_;
270
+
271
+ public:
272
+ std::size_t dimensions() const noexcept { return dimensions_; }
273
+ std::size_t connectivity() const noexcept { return index_->connectivity(); }
274
+ std::size_t size() const noexcept { return index_->size(); }
275
+ std::size_t capacity() const noexcept { return index_->capacity(); }
276
+
277
+ isa_t acceleration() const noexcept { return acceleration_; }
278
+ std::size_t concurrency() const noexcept {
279
+ return std::min(root_config_.max_threads_add, root_config_.max_threads_search);
280
+ }
281
+
282
+ void save(char const* path) const { index_->save(path); }
283
+ void load(char const* path) { index_->load(path); }
284
+ void view(char const* path) { index_->view(path); }
285
+ void reserve(std::size_t capacity) { index_->reserve(capacity); }
286
+
287
+ // clang-format off
288
+ void add(label_t label, f16_converted_t const* vector, std::size_t thread = 0, bool copy = true) { return add(label, vector, thread, copy, casts_.from_f16); }
289
+ void add(label_t label, f32_t const* vector, std::size_t thread = 0, bool copy = true) { return add(label, vector, thread, copy, casts_.from_f32); }
290
+ void add(label_t label, f64_t const* vector, std::size_t thread = 0, bool copy = true) { return add(label, vector, thread, copy, casts_.from_f64); }
291
+
292
+ std::size_t search(f16_converted_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f16); }
293
+ std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f32); }
294
+ std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f64); }
295
+ // clang-format on
296
+
297
+ static auto_index_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) {
298
+ return make(dimensions, accuracy, ip_metric(dimensions, accuracy), make_casts(accuracy), config);
299
+ }
300
+
301
+ static auto_index_gt l2(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) {
302
+ return make(dimensions, accuracy, l2_metric(dimensions, accuracy), make_casts(accuracy), config);
303
+ }
304
+
305
+ static auto_index_gt cos(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) {
306
+ return {};
307
+ }
308
+ static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return {}; }
309
+
310
+ auto_index_gt fork() const {
311
+ auto_index_gt result;
312
+
313
+ result.dimensions_ = dimensions_;
314
+ result.accuracy_ = accuracy_;
315
+ result.acceleration_ = acceleration_;
316
+ result.casted_vector_bytes_ = casted_vector_bytes_;
317
+ result.cast_buffer_ = cast_buffer_;
318
+ result.casts_ = casts_;
319
+
320
+ result.root_metric_ = root_metric_;
321
+ result.root_config_ = root_config_;
322
+ result.index_.reset(new index_t(result.root_config_, result.root_metric_));
323
+
324
+ return result;
325
+ }
326
+
327
+ private:
328
+ template <typename scalar_at>
329
+ void add(label_t label, scalar_at const* vector, std::size_t thread, bool copy, cast_t const& cast) {
330
+ byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
331
+ std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
332
+
333
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * thread;
334
+ bool casted = cast(vector_data, casted_vector_bytes_, casted_data);
335
+ if (casted)
336
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_, copy = true;
337
+
338
+ index_->add(label, {vector_data, vector_bytes}, thread, copy);
339
+ }
340
+
341
+ template <typename scalar_at>
342
+ std::size_t search(scalar_at const* vector, std::size_t wanted, label_t* matches, distance_t* distances,
343
+ std::size_t thread, cast_t const& cast) const {
344
+
345
+ byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
346
+ std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
347
+
348
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * thread;
349
+ bool casted = cast(vector_data, casted_vector_bytes_, casted_data);
350
+ if (casted)
351
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_;
352
+
353
+ return index_->search({vector_data, vector_bytes}, wanted, matches, distances, thread);
354
+ }
355
+
356
+ static auto_index_gt make( //
357
+ std::size_t dimensions, accuracy_t accuracy, //
358
+ metric_and_meta_t metric_and_meta, casts_t casts, //
359
+ config_t config) {
360
+
361
+ std::size_t max_threads = std::max(config.max_threads_add, config.max_threads_search);
362
+ auto_index_gt result;
363
+ result.dimensions_ = dimensions;
364
+ result.accuracy_ = accuracy;
365
+ result.casted_vector_bytes_ = bytes_per_scalar(accuracy) * dimensions;
366
+ result.cast_buffer_.resize(max_threads * result.casted_vector_bytes_);
367
+ result.casts_ = casts;
368
+ result.index_.reset(new index_t(config, metric_and_meta.metric));
369
+ result.acceleration_ = metric_and_meta.acceleration;
370
+ result.root_metric_ = metric_and_meta.metric;
371
+ result.root_config_ = config;
372
+ return result;
373
+ }
374
+
375
+ template <typename to_scalar_at> static casts_t make_casts() {
376
+ casts_t result;
377
+ result.from_i8q100 = cast_gt<i8q100_converted_t, to_scalar_at>{};
378
+ result.from_f16 = cast_gt<f16_converted_t, to_scalar_at>{};
379
+ result.from_f32 = cast_gt<f32_t, to_scalar_at>{};
380
+ result.from_f64 = cast_gt<f64_t, to_scalar_at>{};
381
+ return result;
382
+ }
383
+
384
+ static casts_t make_casts(accuracy_t accuracy) {
385
+ switch (accuracy) {
386
+ case accuracy_t::f64_k: return make_casts<f64_t>();
387
+ case accuracy_t::f32_k: return make_casts<f32_t>();
388
+ case accuracy_t::f16_k: return make_casts<f16_converted_t>();
389
+ case accuracy_t::i8q100_k: return make_casts<i8q100_converted_t>();
390
+ default: return {};
391
+ }
392
+ }
393
+
394
+ template <typename scalar_at, typename typed_at> //
395
+ static punned_stateful_metric_t pun_metric(typed_at metric) {
396
+ return [=](byte_t const* a, byte_t const* b, std::size_t bytes, std::size_t) noexcept -> float {
397
+ return metric((scalar_at const*)a, (scalar_at const*)b, bytes / sizeof(scalar_at));
398
+ };
399
+ }
400
+
401
+ static metric_and_meta_t ip_metric_f32(std::size_t dimensions) {
402
+ #if 0
403
+ #if defined(__x86_64__)
404
+ if (dimensions % 4 == 0)
405
+ return {
406
+ pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
407
+ return 1 - simsimd_dot_f32x4avx2(a, b, d);
408
+ }),
409
+ isa_t::avx2_k,
410
+ };
411
+ #elif defined(__aarch64__)
412
+ if (supports_arm_sve())
413
+ return {
414
+ pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
415
+ return 1 - simsimd_dot_f32sve(a, b, d);
416
+ }),
417
+ isa_t::sve_k,
418
+ };
419
+ if (dimensions % 4 == 0)
420
+ return {
421
+ pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
422
+ return 1 - simsimd_dot_f32x4neon(a, b, d);
423
+ }),
424
+ isa_t::neon_k,
425
+ };
426
+ #endif
427
+ #endif
428
+ return {pun_metric<f32_t>(ip_gt<f32_t>{}), isa_t::auto_k};
429
+ }
430
+
431
+ static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
432
+ #if 0
433
+ #if defined(__x86_64__)
434
+ #elif defined(__aarch64__)
435
+ if (supports_arm_sve())
436
+ return {
437
+ pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
438
+ return 1 - simsimd_dot_f16sve(a, b, d);
439
+ }),
440
+ isa_t::sve_k,
441
+ };
442
+ if (dimensions % 8 == 0)
443
+ return {
444
+ pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
445
+ return 1 - simsimd_dot_f16x8neon(a, b, d);
446
+ }),
447
+ isa_t::neon_k,
448
+ };
449
+ #endif
450
+ #endif
451
+ return {pun_metric<f16_converted_t>(ip_gt<f16_converted_t>{}), isa_t::auto_k};
452
+ }
453
+
454
+ static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
455
+ switch (accuracy) {
456
+ case accuracy_t::i8q100_k: return {};
457
+ case accuracy_t::f16_k: return ip_metric_f16(dimensions);
458
+ case accuracy_t::f32_k: return ip_metric_f32(dimensions);
459
+ case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
460
+ default: return {};
461
+ }
462
+ }
463
+
464
+ static metric_and_meta_t l2_metric(std::size_t dimensions, accuracy_t accuracy) {
465
+ switch (accuracy) {
466
+ case accuracy_t::i8q100_k: return {};
467
+ case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(l2_squared_gt<f16_converted_t>{}), isa_t::auto_k};
468
+ case accuracy_t::f32_k: return {pun_metric<f32_t>(l2_squared_gt<f32_t>{}), isa_t::auto_k};
469
+ case accuracy_t::f64_k: return {pun_metric<f64_t>(l2_squared_gt<f64_t>{}), isa_t::auto_k};
470
+ default: return {};
471
+ }
472
+ }
473
+
474
+ static metric_and_meta_t cos_metric(std::size_t dimensions, accuracy_t accuracy) {
475
+ switch (accuracy) {
476
+ case accuracy_t::i8q100_k: return {};
477
+ case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(cos_gt<f16_converted_t>{}), isa_t::auto_k};
478
+ case accuracy_t::f32_k: return {pun_metric<f32_t>(cos_gt<f32_t>{}), isa_t::auto_k};
479
+ case accuracy_t::f64_k: return {pun_metric<f64_t>(cos_gt<f64_t>{}), isa_t::auto_k};
480
+ default: return {};
481
+ }
482
+ }
483
+ };
484
+
485
+ using auto_index_t = auto_index_gt<std::int64_t, std::uint32_t>;
486
+ using auto_index_big_t = auto_index_gt<uuid_t, uint40_t>;
487
+
488
+ } // namespace usearch
489
+ } // namespace unum