usearch 0.14.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.
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,528 @@
1
+ #pragma once
2
+ #include <stdlib.h> // `aligned_alloc`
3
+
4
+ #include <cstring> // `std::strncmp`
5
+ #include <numeric> // `std::iota`
6
+ #include <thread> // `std::thread`
7
+ #include <vector> // `std::vector`
8
+
9
+ #include <fp16/fp16.h>
10
+
11
+ #if defined(USEARCH_USE_OPENMP)
12
+ #include <omp.h> // `omp_get_num_threads()`
13
+ #endif
14
+
15
+ #if defined(USEARCH_IS_LINUX)
16
+ #include <sys/auxv.h> // `getauxval()`
17
+ #endif
18
+
19
+ #if defined(USEARCH_IS_ARM)
20
+ #include <arm_fp16.h> // `__fp16`
21
+ #endif
22
+
23
+ #include <usearch/index.hpp> // `expected_gt`
24
+
25
+ namespace unum {
26
+ namespace usearch {
27
+
28
+ template <typename at, typename compare_at> inline at clamp(at v, at lo, at hi, compare_at comp) noexcept {
29
+ return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
30
+ }
31
+ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
32
+ return usearch::clamp(v, lo, hi, std::less<at>{});
33
+ }
34
+
35
+ inline bool str_equals(char const* begin, std::size_t len, char const* other_begin) noexcept {
36
+ std::size_t other_len = std::strlen(other_begin);
37
+ return len == other_len && std::strncmp(begin, other_begin, len) == 0;
38
+ }
39
+
40
+ enum class isa_t {
41
+ auto_k,
42
+ neon_k,
43
+ sve_k,
44
+ avx2_k,
45
+ avx512_k,
46
+ };
47
+
48
+ inline char const* isa_name(isa_t isa) noexcept {
49
+ switch (isa) {
50
+ case isa_t::auto_k: return "auto";
51
+ case isa_t::neon_k: return "neon";
52
+ case isa_t::sve_k: return "sve";
53
+ case isa_t::avx2_k: return "avx2";
54
+ case isa_t::avx512_k: return "avx512";
55
+ default: return "";
56
+ }
57
+ }
58
+
59
+ inline bool supports_arm_sve() noexcept {
60
+ #if defined(USEARCH_IS_ARM)
61
+ #if defined(USEARCH_IS_LINUX)
62
+ unsigned long capabilities = getauxval(AT_HWCAP);
63
+ if (capabilities & HWCAP_SVE)
64
+ return true;
65
+ #endif
66
+ #endif
67
+ return false;
68
+ }
69
+
70
+ enum class b1x8_t : unsigned char {};
71
+
72
+ enum class scalar_kind_t {
73
+ unknown_k = 0,
74
+ f64_k,
75
+ f32_k,
76
+ f16_k,
77
+ f8_k,
78
+ b1x8_k,
79
+ };
80
+
81
+ inline std::size_t bytes_per_scalar(scalar_kind_t accuracy) noexcept {
82
+ switch (accuracy) {
83
+ case scalar_kind_t::f32_k: return 4;
84
+ case scalar_kind_t::f16_k: return 2;
85
+ case scalar_kind_t::f64_k: return 8;
86
+ case scalar_kind_t::f8_k: return 1;
87
+ case scalar_kind_t::b1x8_k: return 1;
88
+ default: return 0;
89
+ }
90
+ }
91
+
92
+ inline char const* scalar_kind_name(scalar_kind_t accuracy) noexcept {
93
+ switch (accuracy) {
94
+ case scalar_kind_t::f32_k: return "f32";
95
+ case scalar_kind_t::f16_k: return "f16";
96
+ case scalar_kind_t::f64_k: return "f64";
97
+ case scalar_kind_t::f8_k: return "f8";
98
+ case scalar_kind_t::b1x8_k: return "b1x8";
99
+ default: return "";
100
+ }
101
+ }
102
+
103
+ inline char const* metric_kind_name(metric_kind_t metric) noexcept {
104
+ switch (metric) {
105
+ case metric_kind_t::unknown_k: return "unknown";
106
+ case metric_kind_t::ip_k: return "ip";
107
+ case metric_kind_t::cos_k: return "cos";
108
+ case metric_kind_t::l2sq_k: return "l2sq";
109
+ case metric_kind_t::pearson_k: return "pearson";
110
+ case metric_kind_t::haversine_k: return "haversine";
111
+ case metric_kind_t::jaccard_k: return "jaccard";
112
+ case metric_kind_t::hamming_k: return "hamming";
113
+ case metric_kind_t::tanimoto_k: return "tanimoto";
114
+ case metric_kind_t::sorensen_k: return "sorensen";
115
+ }
116
+ return "";
117
+ }
118
+ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::size_t len) {
119
+ expected_gt<scalar_kind_t> parsed;
120
+ if (str_equals(name, len, "f32"))
121
+ parsed.result = scalar_kind_t::f32_k;
122
+ else if (str_equals(name, len, "f64"))
123
+ parsed.result = scalar_kind_t::f64_k;
124
+ else if (str_equals(name, len, "f16"))
125
+ parsed.result = scalar_kind_t::f16_k;
126
+ else if (str_equals(name, len, "f8"))
127
+ parsed.result = scalar_kind_t::f8_k;
128
+ else
129
+ parsed.failed("Unknown type, choose: f32, f16, f64, f8");
130
+ return parsed;
131
+ }
132
+
133
+ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t len) {
134
+ expected_gt<metric_kind_t> parsed;
135
+ if (str_equals(name, len, "l2sq") || str_equals(name, len, "euclidean_sq")) {
136
+ parsed.result = metric_kind_t::l2sq_k;
137
+ } else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot")) {
138
+ parsed.result = metric_kind_t::ip_k;
139
+ } else if (str_equals(name, len, "cos") || str_equals(name, len, "angular")) {
140
+ parsed.result = metric_kind_t::cos_k;
141
+ } else if (str_equals(name, len, "haversine")) {
142
+ parsed.result = metric_kind_t::haversine_k;
143
+ } else if (str_equals(name, len, "pearson")) {
144
+ parsed.result = metric_kind_t::pearson_k;
145
+ } else if (str_equals(name, len, "hamming")) {
146
+ parsed.result = metric_kind_t::hamming_k;
147
+ } else if (str_equals(name, len, "tanimoto")) {
148
+ parsed.result = metric_kind_t::tanimoto_k;
149
+ } else if (str_equals(name, len, "sorensen")) {
150
+ parsed.result = metric_kind_t::sorensen_k;
151
+ } else
152
+ parsed.failed(
153
+ "Unknown distance, choose: l2sq, ip, cos, haversine, jaccard, pearson, hamming, tanimoto, sorensen");
154
+ return parsed;
155
+ }
156
+
157
+ using punned_distance_t = float;
158
+ using punned_vector_view_t = span_gt<byte_t const>;
159
+ using punned_metric_t = std::function<punned_distance_t(punned_vector_view_t, punned_vector_view_t)>;
160
+
161
+ class f8_bits_t;
162
+ class f16_bits_t;
163
+
164
+ // using f16_native_t = __bf16;
165
+
166
+ #if defined(__AVX512F__)
167
+ #define USEARCH_F16_NATIVE_SUPPORT 1
168
+ using f16_native_t = _Float16;
169
+ #elif defined(USEARCH_IS_ARM)
170
+ #define USEARCH_F16_NATIVE_SUPPORT 1
171
+ using f16_native_t = __fp16;
172
+ #else
173
+ using f16_native_t = void;
174
+ #endif
175
+
176
+ #if defined(USEARCH_F16_NATIVE_SUPPORT)
177
+ using f16_t = f16_native_t;
178
+ #else
179
+ using f16_t = f16_bits_t;
180
+ #endif
181
+
182
+ inline float f16_to_f32(std::uint16_t u16) noexcept {
183
+ #if defined(USEARCH_F16_NATIVE_SUPPORT)
184
+ f16_native_t f16;
185
+ std::memcpy(&f16, &u16, sizeof(std::uint16_t));
186
+ return float(f16);
187
+ #else
188
+ return fp16_ieee_to_fp32_value(u16);
189
+ #endif
190
+ }
191
+
192
+ inline std::uint16_t f32_to_f16(float f32) noexcept {
193
+ #if defined(USEARCH_F16_NATIVE_SUPPORT)
194
+ f16_native_t f16 = f16_native_t(f32);
195
+ std::uint16_t u16;
196
+ std::memcpy(&u16, &f16, sizeof(std::uint16_t));
197
+ return u16;
198
+ #else
199
+ return fp16_ieee_from_fp32_value(f32);
200
+ #endif
201
+ }
202
+
203
+ class f16_bits_t {
204
+ std::uint16_t uint16_{};
205
+
206
+ public:
207
+ inline f16_bits_t() noexcept : uint16_(0) {}
208
+ inline f16_bits_t(f16_bits_t&&) = default;
209
+ inline f16_bits_t& operator=(f16_bits_t&&) = default;
210
+ inline f16_bits_t(f16_bits_t const&) = default;
211
+ inline f16_bits_t& operator=(f16_bits_t const&) = default;
212
+
213
+ inline operator float() const noexcept { return f16_to_f32(uint16_); }
214
+ inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
215
+
216
+ inline f16_bits_t(f8_bits_t) noexcept;
217
+ inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
218
+ inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
219
+ inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
220
+
221
+ inline f16_bits_t operator+(f16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
222
+ inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
223
+ inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
224
+ inline f16_bits_t operator/(f16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
225
+ inline f16_bits_t operator+(float other) const noexcept { return {float(*this) + other}; }
226
+ inline f16_bits_t operator-(float other) const noexcept { return {float(*this) - other}; }
227
+ inline f16_bits_t operator*(float other) const noexcept { return {float(*this) * other}; }
228
+ inline f16_bits_t operator/(float other) const noexcept { return {float(*this) / other}; }
229
+ inline f16_bits_t operator+(double other) const noexcept { return {float(*this) + other}; }
230
+ inline f16_bits_t operator-(double other) const noexcept { return {float(*this) - other}; }
231
+ inline f16_bits_t operator*(double other) const noexcept { return {float(*this) * other}; }
232
+ inline f16_bits_t operator/(double other) const noexcept { return {float(*this) / other}; }
233
+
234
+ inline f16_bits_t& operator+=(float v) noexcept {
235
+ uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
236
+ return *this;
237
+ }
238
+
239
+ inline f16_bits_t& operator-=(float v) noexcept {
240
+ uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
241
+ return *this;
242
+ }
243
+
244
+ inline f16_bits_t& operator*=(float v) noexcept {
245
+ uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
246
+ return *this;
247
+ }
248
+
249
+ inline f16_bits_t& operator/=(float v) noexcept {
250
+ uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
251
+ return *this;
252
+ }
253
+ };
254
+
255
+ /**
256
+ * @brief Numeric type for uniformly-distributed floating point
257
+ * values within [-1,1] range, quantized to integers [-100,100].
258
+ */
259
+ class f8_bits_t {
260
+ std::int8_t int8_{};
261
+
262
+ public:
263
+ constexpr static float divisor_k = 100.f;
264
+ constexpr static std::int8_t min_k = -100;
265
+ constexpr static std::int8_t max_k = 100;
266
+
267
+ inline f8_bits_t() noexcept : int8_(0) {}
268
+ inline f8_bits_t(bool v) noexcept : int8_(v ? max_k : 0) {}
269
+
270
+ inline f8_bits_t(f8_bits_t&&) = default;
271
+ inline f8_bits_t& operator=(f8_bits_t&&) = default;
272
+ inline f8_bits_t(f8_bits_t const&) = default;
273
+ inline f8_bits_t& operator=(f8_bits_t const&) = default;
274
+
275
+ inline operator float() const noexcept { return float(int8_) / divisor_k; }
276
+ inline operator f16_t() const noexcept { return float(int8_) / divisor_k; }
277
+ inline operator double() const noexcept { return double(int8_) / divisor_k; }
278
+ inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
279
+ inline explicit operator std::int8_t() const noexcept { return int8_; }
280
+ inline explicit operator std::int16_t() const noexcept { return int8_; }
281
+ inline explicit operator std::int32_t() const noexcept { return int8_; }
282
+ inline explicit operator std::int64_t() const noexcept { return int8_; }
283
+
284
+ inline f8_bits_t(float v)
285
+ : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
286
+ inline f8_bits_t(double v)
287
+ : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
288
+ };
289
+
290
+ inline f16_bits_t::f16_bits_t(f8_bits_t v) noexcept : f16_bits_t(float(v)) {}
291
+
292
+ struct uuid_t {
293
+ std::uint8_t octets[16];
294
+ };
295
+
296
+ class executor_stl_t {
297
+ std::size_t threads_count_{};
298
+
299
+ public:
300
+ executor_stl_t(std::size_t threads_count) noexcept
301
+ : threads_count_(threads_count ? threads_count : std::thread::hardware_concurrency()) {}
302
+
303
+ template <typename thread_aware_function_at>
304
+ void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
305
+ std::vector<std::thread> threads_pool;
306
+ std::size_t tasks_per_thread = (tasks / threads_count_) + ((tasks % threads_count_) != 0);
307
+ for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx) {
308
+ threads_pool.emplace_back([=]() {
309
+ for (std::size_t task_idx = thread_idx * tasks_per_thread;
310
+ task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
311
+ thread_aware_function(thread_idx, task_idx);
312
+ });
313
+ }
314
+ for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
315
+ threads_pool[thread_idx].join();
316
+ }
317
+ };
318
+
319
+ #if defined(USEARCH_USE_OPENMP)
320
+
321
+ class executor_openmp_t {
322
+ public:
323
+ executor_openmp_t(std::size_t threads_count) noexcept {
324
+ omp_set_num_threads(threads_count ? threads_count : std::thread::hardware_concurrency());
325
+ }
326
+
327
+ template <typename thread_aware_function_at>
328
+ void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
329
+ #pragma omp parallel for schedule(dynamic)
330
+ for (std::size_t i = 0; i < tasks; ++i)
331
+ thread_aware_function(omp_get_thread_num(), i);
332
+ }
333
+ };
334
+
335
+ using executor_default_t = executor_openmp_t;
336
+
337
+ #else
338
+
339
+ using executor_default_t = executor_stl_t;
340
+
341
+ #endif
342
+
343
+ /**
344
+ * @brief Uses OS-specific APIs for aligned memory allocations.
345
+ */
346
+ template <typename element_at = char, std::size_t alignment_ak = 64> //
347
+ class aligned_allocator_gt {
348
+ public:
349
+ using value_type = element_at;
350
+ using size_type = std::size_t;
351
+ using pointer = element_at*;
352
+ using const_pointer = element_at const*;
353
+ template <typename other_element_at> struct rebind {
354
+ using other = aligned_allocator_gt<other_element_at>;
355
+ };
356
+
357
+ constexpr std::size_t alignment() const { return alignment_ak; }
358
+
359
+ pointer allocate(size_type length) const {
360
+ std::size_t length_bytes = alignment_ak * divide_round_up<alignment_ak>(length * sizeof(value_type));
361
+ std::size_t alignment = alignment_ak;
362
+ // void* result = nullptr;
363
+ // int status = posix_memalign(&result, alignment, length_bytes);
364
+ // return status == 0 ? (pointer)result : nullptr;
365
+ #if defined(USEARCH_IS_WINDOWS)
366
+ return (pointer)_aligned_malloc(length_bytes, alignment);
367
+ #else
368
+ return (pointer)aligned_alloc(alignment, length_bytes);
369
+ #endif
370
+ }
371
+
372
+ void deallocate(pointer begin, size_type) const {
373
+ #if defined(USEARCH_IS_WINDOWS)
374
+ _aligned_free(begin);
375
+ #else
376
+ free(begin);
377
+ #endif
378
+ }
379
+ };
380
+
381
+ using aligned_allocator_t = aligned_allocator_gt<>;
382
+
383
+ #if !defined(USEARCH_IS_WINDOWS)
384
+
385
+ /**
386
+ * @brief Memory-mapping allocator designed for "alloc many, free at once" usage patterns.
387
+ * Thread-safe.
388
+ *
389
+ * Using this memory allocator won't affect your overall speed much, as that is not the bottleneck.
390
+ * However, it can drastically improve memory usage especcially for huge indexes of small vectors.
391
+ */
392
+ template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
393
+
394
+ static constexpr std::size_t min_size() { return 1024 * 1024 * 4; }
395
+ static constexpr std::size_t head_size() {
396
+ /// Pointer to the the previous arena and the size of the current one.
397
+ return divide_round_up<alignment_ak>(sizeof(byte_t*) + sizeof(std::size_t)) * alignment_ak;
398
+ }
399
+
400
+ std::mutex mutex_;
401
+ byte_t* last_arena_ = nullptr;
402
+ std::size_t last_usage_ = head_size();
403
+ std::size_t last_capacity_ = min_size();
404
+
405
+ void reset() noexcept {
406
+ byte_t* last_arena = last_arena_;
407
+ while (last_arena) {
408
+ byte_t* previous_arena;
409
+ std::memcpy(&previous_arena, last_arena, sizeof(byte_t*));
410
+ std::size_t current_size;
411
+ std::memcpy(&current_size, last_arena + sizeof(byte_t*), sizeof(std::size_t));
412
+ munmap(last_arena, current_size);
413
+ last_arena = previous_arena;
414
+ }
415
+
416
+ // Clear the references:
417
+ last_arena_ = nullptr;
418
+ last_usage_ = head_size();
419
+ last_capacity_ = min_size();
420
+ }
421
+
422
+ public:
423
+ using value_type = byte_t;
424
+ using size_type = std::size_t;
425
+ using pointer = byte_t*;
426
+ using const_pointer = byte_t const*;
427
+
428
+ memory_mapping_allocator_gt() = default;
429
+ memory_mapping_allocator_gt(memory_mapping_allocator_gt&& other) noexcept
430
+ : last_arena_(other.last_arena_), last_usage_(other.last_usage_), last_capacity_(other.last_capacity_) {}
431
+ memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt&& other) noexcept {
432
+ std::swap(last_arena_, other.last_arena_);
433
+ std::swap(last_usage_, other.last_usage_);
434
+ std::swap(last_capacity_, other.last_capacity_);
435
+ return *this;
436
+ }
437
+
438
+ ~memory_mapping_allocator_gt() noexcept { reset(); }
439
+
440
+ inline byte_t* allocate(std::size_t count_bytes) noexcept {
441
+ count_bytes = divide_round_up<alignment_ak>(count_bytes) * alignment_ak;
442
+
443
+ std::unique_lock<std::mutex> lock(mutex_);
444
+ if (!last_arena_ || (last_usage_ + count_bytes > last_capacity_)) {
445
+ std::size_t new_capacity = last_capacity_ * 2;
446
+ int prot = PROT_WRITE | PROT_READ;
447
+ int flags = MAP_PRIVATE | MAP_ANONYMOUS;
448
+ byte_t* new_arena = (byte_t*)mmap(NULL, new_capacity, prot, flags, 0, 0);
449
+ std::memcpy(new_arena, &last_arena_, sizeof(byte_t*));
450
+ std::memcpy(new_arena + sizeof(byte_t*), &new_capacity, sizeof(std::size_t));
451
+
452
+ last_arena_ = new_arena;
453
+ last_capacity_ = new_capacity;
454
+ last_usage_ = head_size();
455
+ }
456
+
457
+ return last_arena_ + exchange(last_usage_, last_usage_ + count_bytes);
458
+ }
459
+
460
+ /**
461
+ * @warning The very first memory de-allocation discards all the arenas!
462
+ */
463
+ void deallocate(byte_t*, std::size_t) noexcept { reset(); }
464
+ };
465
+
466
+ using memory_mapping_allocator_t = memory_mapping_allocator_gt<>;
467
+
468
+ #else
469
+
470
+ using memory_mapping_allocator_t = aligned_allocator_t;
471
+
472
+ #endif
473
+
474
+ template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
475
+ inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
476
+ from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
477
+ to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
478
+ std::size_t size_input = bytes_in_input / sizeof(from_scalar_at);
479
+ auto converter = [](from_scalar_at from) { return to_scalar_at(from); };
480
+ std::transform(typed_input, typed_input + size_input, typed_output, converter);
481
+ return true;
482
+ }
483
+ };
484
+
485
+ template <> struct cast_gt<f32_t, f32_t> {
486
+ bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
487
+ };
488
+
489
+ template <> struct cast_gt<f64_t, f64_t> {
490
+ bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
491
+ };
492
+
493
+ template <> struct cast_gt<f16_bits_t, f16_bits_t> {
494
+ bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
495
+ };
496
+
497
+ template <> struct cast_gt<f8_bits_t, f8_bits_t> {
498
+ bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
499
+ };
500
+
501
+ template <> struct cast_gt<b1x8_t, b1x8_t> {
502
+ bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
503
+ };
504
+
505
+ template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
506
+ inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
507
+ from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
508
+ unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
509
+ std::size_t size_input = bytes_in_input / sizeof(from_scalar_at);
510
+ for (std::size_t i = 0; i != size_input; ++i)
511
+ typed_output[i / CHAR_BIT] |= bool(typed_input[i]) ? (1 << (i & (CHAR_BIT - 1))) : 0;
512
+ return true;
513
+ }
514
+ };
515
+
516
+ template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
517
+ inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
518
+ unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
519
+ to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
520
+ std::size_t size_input = bytes_in_input * CHAR_BIT;
521
+ for (std::size_t i = 0; i != size_input; ++i)
522
+ typed_output[i] = bool(typed_input[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))));
523
+ return true;
524
+ }
525
+ };
526
+
527
+ } // namespace usearch
528
+ } // namespace unum
@@ -4,7 +4,7 @@
4
4
 
5
5
  #include <thread>
6
6
 
7
- #include "punned.hpp"
7
+ #include <usearch/index_punned_dense.hpp>
8
8
 
9
9
  using namespace unum::usearch;
10
10
  using namespace unum;
@@ -12,7 +12,7 @@ using namespace unum;
12
12
  using label_t = jint;
13
13
  using span_t = span_gt<float>;
14
14
  using distance_t = punned_distance_t;
15
- using punned_t = punned_gt<label_t>;
15
+ using punned_t = index_punned_dense_gt<label_t>;
16
16
 
17
17
  using add_result_t = typename punned_t::add_result_t;
18
18
  using search_result_t = typename punned_t::search_result_t;
@@ -30,18 +30,19 @@ JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1create( //
30
30
  try {
31
31
 
32
32
  index_config_t config;
33
- config.expansion_add = static_cast<std::size_t>(expansion_add);
34
- config.expansion_search = static_cast<std::size_t>(expansion_search);
35
33
  config.connectivity = static_cast<std::size_t>(connectivity);
34
+ std::size_t expansion_add = static_cast<std::size_t>(expansion_add);
35
+ std::size_t expansion_search = static_cast<std::size_t>(expansion_search);
36
36
 
37
37
  metric_cstr = (*env).GetStringUTFChars(metric, 0);
38
38
  std::size_t metric_length = (*env).GetStringUTFLength(metric);
39
39
  accuracy_cstr = (*env).GetStringUTFChars(accuracy, 0);
40
40
  std::size_t accuracy_length = (*env).GetStringUTFLength(accuracy);
41
41
 
42
- accuracy_t accuracy = accuracy_from_name(accuracy_cstr, accuracy_length);
43
- common_metric_kind_t metric_kind = common_metric_from_name(metric_cstr, metric_length);
44
- punned_t index = make_punned<punned_t>(metric_kind, static_cast<std::size_t>(dimensions), accuracy, config);
42
+ scalar_kind_t accuracy = scalar_kind_from_name(accuracy_cstr, accuracy_length);
43
+ metric_kind_t metric_kind = metric_from_name(metric_cstr, metric_length);
44
+ punned_t index = punned_t::make(static_cast<std::size_t>(dimensions), metric_kind, config, accuracy,
45
+ expansion_add, expansion_search);
45
46
  if (!index.reserve(static_cast<std::size_t>(capacity))) {
46
47
  jclass jc = (*env).FindClass("java/lang/Error");
47
48
  if (jc)
@@ -20,14 +20,14 @@
20
20
  #include <napi.h>
21
21
  #include <node_api.h>
22
22
 
23
- #include "punned.hpp"
23
+ #include <usearch/index_punned_dense.hpp>
24
24
 
25
25
  using namespace unum::usearch;
26
26
  using namespace unum;
27
27
 
28
28
  using label_t = std::uint32_t;
29
29
  using distance_t = punned_distance_t;
30
- using punned_t = punned_gt<label_t>;
30
+ using punned_t = index_punned_dense_gt<label_t>;
31
31
 
32
32
  class Index : public Napi::ObjectWrap<Index> {
33
33
  public:
@@ -78,8 +78,7 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
78
78
 
79
79
  int length = ctx.Length();
80
80
  if (length == 0 || length >= 2 || !ctx[0].IsObject()) {
81
- Napi::TypeError::New(
82
- env, "Pass args as named objects: dimensions: uint, capacity: uint, metric: [ip, cos, l2sq, haversine]")
81
+ Napi::TypeError::New(env, "Pass args as named objects: dimensions: uint, capacity: uint, metric: str")
83
82
  .ThrowAsJavaScriptException();
84
83
  return;
85
84
  }
@@ -89,67 +88,44 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
89
88
 
90
89
  index_config_t config;
91
90
  index_limits_t limits;
91
+ std::size_t expansion_add = default_expansion_add();
92
+ std::size_t expansion_search = default_expansion_search();
93
+
92
94
  if (params.Has("capacity"))
93
95
  limits.elements = params.Get("capacity").As<Napi::Number>().Uint32Value();
94
96
  if (params.Has("connectivity"))
95
97
  config.connectivity = params.Get("connectivity").As<Napi::Number>().Uint32Value();
98
+ if (params.Has("expansion_add"))
99
+ expansion_add = params.Get("expansion_add").As<Napi::Number>().Uint32Value();
100
+ if (params.Has("expansion_search"))
101
+ expansion_search = params.Get("expansion_search").As<Napi::Number>().Uint32Value();
96
102
 
97
- accuracy_t accuracy = accuracy_t::f32_k;
103
+ scalar_kind_t accuracy = scalar_kind_t::f32_k;
98
104
  if (params.Has("accuracy")) {
99
- std::string accuracy_str = params.Get("connectivity").As<Napi::String>().Utf8Value();
100
- if (accuracy_str == "f32")
101
- accuracy = accuracy_t::f32_k;
102
- else if (accuracy_str == "f64")
103
- accuracy = accuracy_t::f64_k;
104
- else if (accuracy_str == "f16")
105
- accuracy = accuracy_t::f16_k;
106
- else if (accuracy_str == "f8")
107
- accuracy = accuracy_t::f8_k;
108
- else {
109
- Napi::TypeError::New(env, "Supported metrics are: [ip, cos, l2sq, haversine]").ThrowAsJavaScriptException();
105
+ std::string accuracy_str = params.Get("accuracy").As<Napi::String>().Utf8Value();
106
+ expected_gt<scalar_kind_t> expected = scalar_kind_from_name(accuracy_str.c_str(), accuracy_str.size());
107
+ if (!expected) {
108
+ Napi::TypeError::New(env, expected.error.what()).ThrowAsJavaScriptException();
110
109
  return;
111
110
  }
111
+ accuracy = *expected;
112
112
  }
113
113
 
114
114
  // By default we use the Inner Product similarity
115
+ metric_kind_t metric_kind = metric_kind_t::ip_k;
115
116
  if (params.Has("metric")) {
116
- std::string name = params.Get("metric").As<Napi::String>().Utf8Value();
117
- if (name == "l2sq" || name == "euclidean_sq") {
118
- if (!dimensions) {
119
- Napi::TypeError::New(env, "Please define the number of dimensions").ThrowAsJavaScriptException();
120
- return;
121
- }
122
- native_.reset(new punned_t(punned_t::l2sq(dimensions, accuracy, config)));
123
- native_->reserve(limits);
124
- } else if (name == "ip" || name == "inner" || name == "dot") {
125
- if (!dimensions) {
126
- Napi::TypeError::New(env, "Please define the number of dimensions").ThrowAsJavaScriptException();
127
- return;
128
- }
129
- native_.reset(new punned_t(punned_t::ip(dimensions, accuracy, config)));
130
- native_->reserve(limits);
131
- } else if (name == "cos" || name == "angular") {
132
- if (!dimensions) {
133
- Napi::TypeError::New(env, "Please define the number of dimensions").ThrowAsJavaScriptException();
134
- return;
135
- }
136
- native_.reset(new punned_t(punned_t::cos(dimensions, accuracy, config)));
137
- native_->reserve(limits);
138
- } else if (name == "haversine") {
139
- native_.reset(new punned_t(punned_t::haversine(accuracy, config)));
140
- native_->reserve(limits);
141
- } else {
142
- Napi::TypeError::New(env, "Supported metrics are: [ip, cos, l2sq, haversine]").ThrowAsJavaScriptException();
143
- return;
144
- }
145
- } else {
146
- if (!dimensions) {
147
- Napi::TypeError::New(env, "Please define the number of dimensions").ThrowAsJavaScriptException();
117
+ std::string metric_str = params.Get("metric").As<Napi::String>().Utf8Value();
118
+ expected_gt<metric_kind_t> expected = metric_from_name(metric_str.c_str(), metric_str.size());
119
+ if (!expected) {
120
+ Napi::TypeError::New(env, expected.error.what()).ThrowAsJavaScriptException();
148
121
  return;
149
122
  }
150
- native_.reset(new punned_t(punned_t::ip(dimensions, accuracy, config)));
151
- native_->reserve(limits);
123
+ metric_kind = *expected;
152
124
  }
125
+
126
+ native_.reset(
127
+ new punned_t(punned_t::make(dimensions, metric_kind, config, accuracy, expansion_add, expansion_search)));
128
+ native_->reserve(limits);
153
129
  }
154
130
 
155
131
  Napi::Value Index::GetDimensions(Napi::CallbackInfo const& ctx) {