usearch 2.10.3 → 2.11.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 (114) hide show
  1. package/README.md +28 -8
  2. package/binding.gyp +101 -0
  3. package/c/CMakeLists.txt +27 -0
  4. package/c/README.md +244 -0
  5. package/c/lib.cpp +459 -0
  6. package/c/test.c +386 -0
  7. package/c/usearch.h +451 -0
  8. package/cmake/clipp.cmake +7 -0
  9. package/cmake/config.cmake.in +15 -0
  10. package/cmake/emscripten.cmake +8 -0
  11. package/cmake/jemalloc.cmake +62 -0
  12. package/cmake/pkg-config.pc.in +4 -0
  13. package/cmake/usearchConfigVersion.cmake.in +20 -0
  14. package/cpp/CMakeLists.txt +14 -0
  15. package/cpp/README.md +161 -0
  16. package/cpp/bench.cpp +616 -0
  17. package/cpp/test.cpp +335 -0
  18. package/fp16/CMakeLists.txt +176 -0
  19. package/fp16/LICENSE +11 -0
  20. package/fp16/README.md +20 -0
  21. package/fp16/bench/alt-element.cc +89 -0
  22. package/fp16/bench/from-alt-array.cc +175 -0
  23. package/fp16/bench/from-ieee-array.cc +399 -0
  24. package/fp16/bench/ieee-element.cc +272 -0
  25. package/fp16/bench/to-alt-array.cc +91 -0
  26. package/fp16/bench/to-ieee-array.cc +303 -0
  27. package/fp16/cmake/DownloadGoogleBenchmark.cmake +15 -0
  28. package/fp16/cmake/DownloadGoogleTest.cmake +15 -0
  29. package/fp16/cmake/DownloadPSimd.cmake +15 -0
  30. package/fp16/configure.py +65 -0
  31. package/fp16/confu.yaml +8 -0
  32. package/fp16/include/fp16/__init__.py +0 -0
  33. package/fp16/include/fp16/avx.py +50 -0
  34. package/fp16/include/fp16/avx2.py +53 -0
  35. package/fp16/include/fp16/bitcasts.h +92 -0
  36. package/fp16/include/fp16/fp16.h +451 -0
  37. package/fp16/include/fp16/psimd.h +131 -0
  38. package/fp16/include/fp16.h +11 -0
  39. package/fp16/jni/Android.mk +6 -0
  40. package/fp16/jni/Application.mk +4 -0
  41. package/fp16/test/alt-from-fp32-value.cc +498 -0
  42. package/fp16/test/alt-to-fp32-bits.cc +263 -0
  43. package/fp16/test/alt-to-fp32-psimd.cc +145 -0
  44. package/fp16/test/alt-to-fp32-value.cc +348 -0
  45. package/fp16/test/alt-to-fp32x2-psimd.cc +245 -0
  46. package/fp16/test/bitcasts.cc +56 -0
  47. package/fp16/test/ieee-from-fp32-value.cc +523 -0
  48. package/fp16/test/ieee-to-fp32-bits.cc +333 -0
  49. package/fp16/test/ieee-to-fp32-psimd.cc +312 -0
  50. package/fp16/test/ieee-to-fp32-value.cc +420 -0
  51. package/fp16/test/ieee-to-fp32x2-psimd.cc +541 -0
  52. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +96 -0
  53. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +96 -0
  54. package/fp16/test/peachpy/stubs.py +38 -0
  55. package/fp16/test/tables.cc +530 -0
  56. package/fp16/test/tables.h +13 -0
  57. package/fp16/third-party/THHalf.h +92 -0
  58. package/fp16/third-party/eigen-half.h +170 -0
  59. package/fp16/third-party/float16-compressor.h +80 -0
  60. package/fp16/third-party/half.hpp +2916 -0
  61. package/fp16/third-party/npy-halffloat.h +328 -0
  62. package/include/usearch/index.hpp +3838 -0
  63. package/include/usearch/index_dense.hpp +2022 -0
  64. package/include/usearch/index_plugins.hpp +2317 -0
  65. package/javascript/dist/cjs/usearch.js +5 -5
  66. package/javascript/dist/esm/usearch.js +2 -2
  67. package/javascript/dist-package-cjs.json +3 -0
  68. package/javascript/dist-package-esm.json +3 -0
  69. package/javascript/lib.cpp +350 -0
  70. package/javascript/node-gyp-build.d.ts +1 -0
  71. package/javascript/tsconfig-base.json +25 -0
  72. package/javascript/tsconfig-cjs.json +8 -0
  73. package/javascript/tsconfig-esm.json +8 -0
  74. package/javascript/usearch.test.js +84 -0
  75. package/javascript/usearch.ts +640 -0
  76. package/package.json +14 -7
  77. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  78. package/prebuilds/linux-arm64/usearch.node +0 -0
  79. package/prebuilds/linux-x64/usearch.node +0 -0
  80. package/prebuilds/win32-ia32/usearch.node +0 -0
  81. package/prebuilds/win32-x64/usearch.node +0 -0
  82. package/simsimd/.releaserc +87 -0
  83. package/simsimd/CONTRIBUTING.md +114 -0
  84. package/simsimd/Cargo.lock +709 -0
  85. package/simsimd/Cargo.toml +44 -0
  86. package/simsimd/LICENSE +201 -0
  87. package/simsimd/README.md +708 -0
  88. package/simsimd/binding.gyp +36 -0
  89. package/simsimd/build.rs +51 -0
  90. package/simsimd/c/lib.c +110 -0
  91. package/simsimd/include/simsimd/binary.h +258 -0
  92. package/simsimd/include/simsimd/dot.h +1237 -0
  93. package/simsimd/include/simsimd/geospatial.h +37 -0
  94. package/simsimd/include/simsimd/probability.h +563 -0
  95. package/simsimd/include/simsimd/simsimd.h +1129 -0
  96. package/simsimd/include/simsimd/spatial.h +1019 -0
  97. package/simsimd/include/simsimd/types.h +309 -0
  98. package/simsimd/javascript/bench.js +127 -0
  99. package/simsimd/javascript/dist-package-cjs.json +3 -0
  100. package/simsimd/javascript/dist-package-esm.json +3 -0
  101. package/simsimd/javascript/fallback.ts +203 -0
  102. package/simsimd/javascript/lib.c +107 -0
  103. package/simsimd/javascript/node-gyp-build.d.ts +1 -0
  104. package/simsimd/javascript/simsimd.ts +135 -0
  105. package/simsimd/javascript/test.mjs +179 -0
  106. package/simsimd/javascript/tsconfig-base.json +37 -0
  107. package/simsimd/javascript/tsconfig-cjs.json +8 -0
  108. package/simsimd/javascript/tsconfig-esm.json +8 -0
  109. package/simsimd/package-lock.json +1709 -0
  110. package/simsimd/package.json +61 -0
  111. package/simsimd/rust/benches/cosine.rs +27 -0
  112. package/simsimd/rust/benches/native.rs +30 -0
  113. package/simsimd/rust/benches/sqeuclidean.rs +27 -0
  114. package/simsimd/rust/lib.rs +778 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @file geospatial.h
3
+ * @brief SIMD-accelerated Geo-Spatial distance functions.
4
+ * @author Ash Vardanian
5
+ * @date July 1, 2023
6
+ *
7
+ * Contains:
8
+ * - Haversine (Great Circle) distance
9
+ * - TODO: Vincenty's distance function for Oblate Spheroid Geodesics
10
+ *
11
+ * For datatypes:
12
+ * - 32-bit IEEE-754 floating point
13
+ * - 64-bit IEEE-754 floating point
14
+ *
15
+ * For hardware architectures:
16
+ * - Arm (NEON, SVE)
17
+ * - x86 (AVX512)
18
+ *
19
+ * x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
20
+ * Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
21
+ * Oblate Spheroid Geodesic: https://mathworld.wolfram.com/OblateSpheroidGeodesic.html
22
+ * Staging experiments: https://github.com/ashvardanian/HaversineSimSIMD
23
+ */
24
+ #ifndef SIMSIMD_GEOSPATIAL_H
25
+ #define SIMSIMD_GEOSPATIAL_H
26
+
27
+ #include "types.h"
28
+
29
+ #ifdef __cplusplus
30
+ extern "C" {
31
+ #endif
32
+
33
+ #ifdef __cplusplus
34
+ }
35
+ #endif
36
+
37
+ #endif
@@ -0,0 +1,563 @@
1
+ /**
2
+ * @file probability.h
3
+ * @brief SIMD-accelerated Similarity Measures for Probability Distributions.
4
+ * @author Ash Vardanian
5
+ * @date October 20, 2023
6
+ *
7
+ * Contains:
8
+ * - Kullback-Leibler divergence
9
+ * - Jensen–Shannon divergence
10
+ *
11
+ * For datatypes:
12
+ * - 32-bit floating point numbers
13
+ * - 16-bit floating point numbers
14
+ *
15
+ * For hardware architectures:
16
+ * - Arm (NEON, SVE)
17
+ * - x86 (AVX2, AVX512)
18
+ *
19
+ * x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
20
+ * Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
21
+ */
22
+ #ifndef SIMSIMD_PROBABILITY_H
23
+ #define SIMSIMD_PROBABILITY_H
24
+
25
+ #include "types.h"
26
+
27
+ #ifdef __cplusplus
28
+ extern "C" {
29
+ #endif
30
+
31
+ // clang-format off
32
+
33
+ /* Serial backends for all numeric types.
34
+ * By default they use 32-bit arithmetic, unless the arguments themselves contain 64-bit floats.
35
+ * For double-precision computation check out the "*_accurate" variants of those "*_serial" functions.
36
+ */
37
+ SIMSIMD_PUBLIC void simsimd_kl_f64_serial(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
38
+ SIMSIMD_PUBLIC void simsimd_js_f64_serial(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
39
+ SIMSIMD_PUBLIC void simsimd_kl_f32_serial(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
40
+ SIMSIMD_PUBLIC void simsimd_js_f32_serial(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
41
+ SIMSIMD_PUBLIC void simsimd_kl_f16_serial(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
42
+ SIMSIMD_PUBLIC void simsimd_js_f16_serial(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
43
+
44
+ /* Double-precision serial backends for all numeric types.
45
+ * For single-precision computation check out the "*_serial" counterparts of those "*_accurate" functions.
46
+ */
47
+ SIMSIMD_PUBLIC void simsimd_kl_f32_accurate(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
48
+ SIMSIMD_PUBLIC void simsimd_js_f32_accurate(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
49
+ SIMSIMD_PUBLIC void simsimd_kl_f16_accurate(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
50
+ SIMSIMD_PUBLIC void simsimd_js_f16_accurate(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
51
+
52
+ /* SIMD-powered backends for Arm NEON, mostly using 32-bit arithmetic over 128-bit words.
53
+ * By far the most portable backend, covering most Arm v8 devices, over a billion phones, and almost all
54
+ * server CPUs produced before 2023.
55
+ */
56
+ SIMSIMD_PUBLIC void simsimd_kl_f32_neon(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
57
+ SIMSIMD_PUBLIC void simsimd_js_f32_neon(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
58
+ SIMSIMD_PUBLIC void simsimd_kl_f16_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
59
+ SIMSIMD_PUBLIC void simsimd_js_f16_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
60
+
61
+ /* SIMD-powered backends for AVX2 CPUs of Haswell generation and newer, using 32-bit arithmetic over 256-bit words.
62
+ * First demonstrated in 2011, at least one Haswell-based processor was still being sold in 2022 — the Pentium G3420.
63
+ * Practically all modern x86 CPUs support AVX2, FMA, and F16C, making it a perfect baseline for SIMD algorithms.
64
+ * On other hand, there is no need to implement AVX2 versions of `f32` and `f64` functions, as those are
65
+ * properly vectorized by recent compilers.
66
+ */
67
+ SIMSIMD_PUBLIC void simsimd_kl_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
68
+ SIMSIMD_PUBLIC void simsimd_js_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
69
+
70
+ /* SIMD-powered backends for various generations of AVX512 CPUs.
71
+ * Skylake is handy, as it supports masked loads and other operations, avoiding the need for the tail loop.
72
+ * Ice Lake added VNNI, VPOPCNTDQ, IFMA, VBMI, VAES, GFNI, VBMI2, BITALG, VPCLMULQDQ, and other extensions for integral operations.
73
+ * Sapphire Rapids added tiled matrix operations, but we are most interested in the new mixed-precision FMA instructions.
74
+ */
75
+ SIMSIMD_PUBLIC void simsimd_kl_f32_skylake(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
76
+ SIMSIMD_PUBLIC void simsimd_js_f32_skylake(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
77
+ SIMSIMD_PUBLIC void simsimd_kl_f16_sapphire(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
78
+ SIMSIMD_PUBLIC void simsimd_js_f16_sapphire(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t* divergence);
79
+ // clang-format on
80
+
81
+ #define SIMSIMD_MAKE_KL(name, input_type, accumulator_type, converter, epsilon) \
82
+ SIMSIMD_PUBLIC void simsimd_kl_##input_type##_##name(simsimd_##input_type##_t const* a, \
83
+ simsimd_##input_type##_t const* b, simsimd_size_t n, \
84
+ simsimd_distance_t* result) { \
85
+ simsimd_##accumulator_type##_t d = 0; \
86
+ for (simsimd_size_t i = 0; i != n; ++i) { \
87
+ simsimd_##accumulator_type##_t ai = converter(a[i]); \
88
+ simsimd_##accumulator_type##_t bi = converter(b[i]); \
89
+ d += ai * SIMSIMD_LOG((ai + epsilon) / (bi + epsilon)); \
90
+ } \
91
+ *result = (simsimd_distance_t)d; \
92
+ }
93
+
94
+ #define SIMSIMD_MAKE_JS(name, input_type, accumulator_type, converter, epsilon) \
95
+ SIMSIMD_PUBLIC void simsimd_js_##input_type##_##name(simsimd_##input_type##_t const* a, \
96
+ simsimd_##input_type##_t const* b, simsimd_size_t n, \
97
+ simsimd_distance_t* result) { \
98
+ simsimd_##accumulator_type##_t d = 0; \
99
+ for (simsimd_size_t i = 0; i != n; ++i) { \
100
+ simsimd_##accumulator_type##_t ai = converter(a[i]); \
101
+ simsimd_##accumulator_type##_t bi = converter(b[i]); \
102
+ simsimd_##accumulator_type##_t mi = (ai + bi) / 2; \
103
+ d += ai * SIMSIMD_LOG((ai + epsilon) / (mi + epsilon)); \
104
+ d += bi * SIMSIMD_LOG((bi + epsilon) / (mi + epsilon)); \
105
+ } \
106
+ *result = (simsimd_distance_t)d / 2; \
107
+ }
108
+
109
+ SIMSIMD_MAKE_KL(serial, f64, f64, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_kl_f64_serial
110
+ SIMSIMD_MAKE_JS(serial, f64, f64, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_js_f64_serial
111
+
112
+ SIMSIMD_MAKE_KL(serial, f32, f32, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_kl_f32_serial
113
+ SIMSIMD_MAKE_JS(serial, f32, f32, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_js_f32_serial
114
+
115
+ SIMSIMD_MAKE_KL(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_kl_f16_serial
116
+ SIMSIMD_MAKE_JS(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_js_f16_serial
117
+
118
+ SIMSIMD_MAKE_KL(accurate, f32, f64, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_kl_f32_accurate
119
+ SIMSIMD_MAKE_JS(accurate, f32, f64, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_js_f32_accurate
120
+
121
+ SIMSIMD_MAKE_KL(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_kl_f16_accurate
122
+ SIMSIMD_MAKE_JS(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_js_f16_accurate
123
+
124
+ #if SIMSIMD_TARGET_ARM
125
+ #if SIMSIMD_TARGET_NEON
126
+ #pragma GCC push_options
127
+ #pragma GCC target("+simd")
128
+ #pragma clang attribute push(__attribute__((target("+simd"))), apply_to = function)
129
+
130
+ SIMSIMD_PUBLIC float32x4_t simsimd_log2_f32_neon(float32x4_t x) {
131
+ // Extracting the exponent
132
+ int32x4_t i = vreinterpretq_s32_f32(x);
133
+ int32x4_t e = vsubq_s32(vshrq_n_s32(vandq_s32(i, vdupq_n_s32(0x7F800000)), 23), vdupq_n_s32(127));
134
+ float32x4_t e_float = vcvtq_f32_s32(e);
135
+
136
+ // Extracting the mantissa
137
+ float32x4_t m = vreinterpretq_f32_s32(vorrq_s32(vandq_s32(i, vdupq_n_s32(0x007FFFFF)), vdupq_n_s32(0x3F800000)));
138
+
139
+ // Constants for polynomial
140
+ float32x4_t one = vdupq_n_f32(1.0f);
141
+ float32x4_t p = vdupq_n_f32(-3.4436006e-2f);
142
+
143
+ // Compute polynomial using Horner's method
144
+ p = vmlaq_f32(vdupq_n_f32(3.1821337e-1f), m, p);
145
+ p = vmlaq_f32(vdupq_n_f32(-1.2315303f), m, p);
146
+ p = vmlaq_f32(vdupq_n_f32(2.5988452f), m, p);
147
+ p = vmlaq_f32(vdupq_n_f32(-3.3241990f), m, p);
148
+ p = vmlaq_f32(vdupq_n_f32(3.1157899f), m, p);
149
+
150
+ // Final computation
151
+ float32x4_t result = vaddq_f32(vmulq_f32(p, vsubq_f32(m, one)), e_float);
152
+ return result;
153
+ }
154
+
155
+ SIMSIMD_PUBLIC void simsimd_kl_f32_neon(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
156
+ simsimd_distance_t* result) {
157
+ float32x4_t sum_vec = vdupq_n_f32(0);
158
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
159
+ float32x4_t epsilon_vec = vdupq_n_f32(epsilon);
160
+ simsimd_size_t i = 0;
161
+ for (; i + 4 <= n; i += 4) {
162
+ float32x4_t a_vec = vld1q_f32(a + i);
163
+ float32x4_t b_vec = vld1q_f32(b + i);
164
+ float32x4_t ratio_vec = vdivq_f32(vaddq_f32(a_vec, epsilon_vec), vaddq_f32(b_vec, epsilon_vec));
165
+ float32x4_t log_ratio_vec = simsimd_log2_f32_neon(ratio_vec);
166
+ float32x4_t prod_vec = vmulq_f32(a_vec, log_ratio_vec);
167
+ sum_vec = vaddq_f32(sum_vec, prod_vec);
168
+ }
169
+ simsimd_f32_t log2_normalizer = 0.693147181f;
170
+ simsimd_f32_t sum = vaddvq_f32(sum_vec) * log2_normalizer;
171
+ for (; i < n; ++i)
172
+ sum += a[i] * SIMSIMD_LOG((a[i] + epsilon) / (b[i] + epsilon));
173
+ *result = sum;
174
+ }
175
+
176
+ SIMSIMD_PUBLIC void simsimd_js_f32_neon(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
177
+ simsimd_distance_t* result) {
178
+ float32x4_t sum_vec = vdupq_n_f32(0);
179
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
180
+ float32x4_t epsilon_vec = vdupq_n_f32(epsilon);
181
+ simsimd_size_t i = 0;
182
+ for (; i + 4 <= n; i += 4) {
183
+ float32x4_t a_vec = vld1q_f32(a + i);
184
+ float32x4_t b_vec = vld1q_f32(b + i);
185
+ float32x4_t m_vec = vmulq_f32(vaddq_f32(a_vec, b_vec), vdupq_n_f32(0.5));
186
+ float32x4_t ratio_a_vec = vdivq_f32(vaddq_f32(a_vec, epsilon_vec), vaddq_f32(m_vec, epsilon_vec));
187
+ float32x4_t ratio_b_vec = vdivq_f32(vaddq_f32(b_vec, epsilon_vec), vaddq_f32(m_vec, epsilon_vec));
188
+ float32x4_t log_ratio_a_vec = simsimd_log2_f32_neon(ratio_a_vec);
189
+ float32x4_t log_ratio_b_vec = simsimd_log2_f32_neon(ratio_b_vec);
190
+ float32x4_t prod_a_vec = vmulq_f32(a_vec, log_ratio_a_vec);
191
+ float32x4_t prod_b_vec = vmulq_f32(b_vec, log_ratio_b_vec);
192
+ sum_vec = vaddq_f32(sum_vec, vaddq_f32(prod_a_vec, prod_b_vec));
193
+ }
194
+ simsimd_f32_t log2_normalizer = 0.693147181f;
195
+ simsimd_f32_t sum = vaddvq_f32(sum_vec) * log2_normalizer;
196
+ for (; i < n; ++i) {
197
+ simsimd_f32_t mi = 0.5f * (a[i] + b[i]);
198
+ sum += a[i] * SIMSIMD_LOG((a[i] + epsilon) / (mi + epsilon));
199
+ sum += b[i] * SIMSIMD_LOG((b[i] + epsilon) / (mi + epsilon));
200
+ }
201
+ *result = sum;
202
+ }
203
+
204
+ #pragma clang attribute pop
205
+ #pragma GCC pop_options
206
+
207
+ #pragma GCC push_options
208
+ #pragma GCC target("+simd+fp16")
209
+ #pragma clang attribute push(__attribute__((target("+simd+fp16"))), apply_to = function)
210
+
211
+ SIMSIMD_PUBLIC void simsimd_kl_f16_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n,
212
+ simsimd_distance_t* result) {
213
+ float32x4_t sum_vec = vdupq_n_f32(0);
214
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
215
+ float32x4_t epsilon_vec = vdupq_n_f32(epsilon);
216
+ simsimd_size_t i = 0;
217
+ for (; i + 4 <= n; i += 4) {
218
+ float32x4_t a_vec = vcvt_f32_f16(vld1_f16((simsimd_f16_for_arm_simd_t const*)a + i));
219
+ float32x4_t b_vec = vcvt_f32_f16(vld1_f16((simsimd_f16_for_arm_simd_t const*)b + i));
220
+ float32x4_t ratio_vec = vdivq_f32(vaddq_f32(a_vec, epsilon_vec), vaddq_f32(b_vec, epsilon_vec));
221
+ float32x4_t log_ratio_vec = simsimd_log2_f32_neon(ratio_vec);
222
+ float32x4_t prod_vec = vmulq_f32(a_vec, log_ratio_vec);
223
+ sum_vec = vaddq_f32(sum_vec, prod_vec);
224
+ }
225
+ simsimd_f32_t log2_normalizer = 0.693147181f;
226
+ simsimd_f32_t sum = vaddvq_f32(sum_vec) * log2_normalizer;
227
+ for (; i < n; ++i)
228
+ sum += SIMSIMD_UNCOMPRESS_F16(a[i]) *
229
+ SIMSIMD_LOG((SIMSIMD_UNCOMPRESS_F16(a[i]) + epsilon) / (SIMSIMD_UNCOMPRESS_F16(b[i]) + epsilon));
230
+ *result = sum;
231
+ }
232
+
233
+ SIMSIMD_PUBLIC void simsimd_js_f16_neon(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n,
234
+ simsimd_distance_t* result) {
235
+ float32x4_t sum_vec = vdupq_n_f32(0);
236
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
237
+ float32x4_t epsilon_vec = vdupq_n_f32(epsilon);
238
+ simsimd_size_t i = 0;
239
+ for (; i + 4 <= n; i += 4) {
240
+ float32x4_t a_vec = vcvt_f32_f16(vld1_f16((simsimd_f16_for_arm_simd_t const*)a + i));
241
+ float32x4_t b_vec = vcvt_f32_f16(vld1_f16((simsimd_f16_for_arm_simd_t const*)b + i));
242
+ float32x4_t m_vec = vmulq_f32(vaddq_f32(a_vec, b_vec), vdupq_n_f32(0.5));
243
+ float32x4_t ratio_a_vec = vdivq_f32(vaddq_f32(a_vec, epsilon_vec), vaddq_f32(m_vec, epsilon_vec));
244
+ float32x4_t ratio_b_vec = vdivq_f32(vaddq_f32(b_vec, epsilon_vec), vaddq_f32(m_vec, epsilon_vec));
245
+ float32x4_t log_ratio_a_vec = simsimd_log2_f32_neon(ratio_a_vec);
246
+ float32x4_t log_ratio_b_vec = simsimd_log2_f32_neon(ratio_b_vec);
247
+ float32x4_t prod_a_vec = vmulq_f32(a_vec, log_ratio_a_vec);
248
+ float32x4_t prod_b_vec = vmulq_f32(b_vec, log_ratio_b_vec);
249
+ sum_vec = vaddq_f32(sum_vec, vaddq_f32(prod_a_vec, prod_b_vec));
250
+ }
251
+ simsimd_f32_t log2_normalizer = 0.693147181f;
252
+ simsimd_f32_t sum = vaddvq_f32(sum_vec) * log2_normalizer;
253
+ for (; i < n; ++i) {
254
+ simsimd_f32_t ai = SIMSIMD_UNCOMPRESS_F16(a[i]);
255
+ simsimd_f32_t bi = SIMSIMD_UNCOMPRESS_F16(b[i]);
256
+ simsimd_f32_t mi = 0.5f * (ai + bi);
257
+ sum += ai * SIMSIMD_LOG((ai + epsilon) / (mi + epsilon));
258
+ sum += bi * SIMSIMD_LOG((bi + epsilon) / (mi + epsilon));
259
+ }
260
+ *result = sum;
261
+ }
262
+
263
+ #pragma clang attribute pop
264
+ #pragma GCC pop_options
265
+ #endif // SIMSIMD_TARGET_NEON
266
+ #endif // SIMSIMD_TARGET_ARM
267
+
268
+ #if SIMSIMD_TARGET_X86
269
+ #if SIMSIMD_TARGET_HASWELL
270
+ #pragma GCC push_options
271
+ #pragma GCC target("avx2", "f16c", "fma")
272
+ #pragma clang attribute push(__attribute__((target("avx2,f16c,fma"))), apply_to = function)
273
+
274
+ inline __m256 simsimd_log2_f32_haswell(__m256 x) {
275
+ // Extracting the exponent
276
+ __m256i i = _mm256_castps_si256(x);
277
+ __m256i e = _mm256_srli_epi32(_mm256_and_si256(i, _mm256_set1_epi32(0x7F800000)), 23);
278
+ e = _mm256_sub_epi32(e, _mm256_set1_epi32(127)); // removing the bias
279
+ __m256 e_float = _mm256_cvtepi32_ps(e);
280
+
281
+ // Extracting the mantissa
282
+ __m256 m = _mm256_castsi256_ps(
283
+ _mm256_or_si256(_mm256_and_si256(i, _mm256_set1_epi32(0x007FFFFF)), _mm256_set1_epi32(0x3F800000)));
284
+
285
+ // Constants for polynomial
286
+ __m256 one = _mm256_set1_ps(1.0f);
287
+ __m256 p = _mm256_set1_ps(-3.4436006e-2f);
288
+
289
+ // Compute the polynomial using Horner's method
290
+ p = _mm256_fmadd_ps(m, p, _mm256_set1_ps(3.1821337e-1f));
291
+ p = _mm256_fmadd_ps(m, p, _mm256_set1_ps(-1.2315303f));
292
+ p = _mm256_fmadd_ps(m, p, _mm256_set1_ps(2.5988452f));
293
+ p = _mm256_fmadd_ps(m, p, _mm256_set1_ps(-3.3241990f));
294
+ p = _mm256_fmadd_ps(m, p, _mm256_set1_ps(3.1157899f));
295
+
296
+ // Final computation
297
+ __m256 result = _mm256_add_ps(_mm256_mul_ps(p, _mm256_sub_ps(m, one)), e_float);
298
+ return result;
299
+ }
300
+
301
+ SIMSIMD_PUBLIC void simsimd_kl_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n,
302
+ simsimd_distance_t* result) {
303
+ __m256 sum_vec = _mm256_setzero_ps();
304
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
305
+ __m256 epsilon_vec = _mm256_set1_ps(epsilon);
306
+ simsimd_size_t i = 0;
307
+ for (; i + 8 <= n; i += 8) {
308
+ __m256 a_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(a + i)));
309
+ __m256 b_vec = _mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(b + i)));
310
+ __m256 ratio_vec = _mm256_div_ps(_mm256_add_ps(a_vec, epsilon_vec), _mm256_add_ps(b_vec, epsilon_vec));
311
+ __m256 log_ratio_vec = simsimd_log2_f32_haswell(ratio_vec);
312
+ __m256 prod_vec = _mm256_mul_ps(a_vec, log_ratio_vec);
313
+ sum_vec = _mm256_add_ps(sum_vec, prod_vec);
314
+ }
315
+
316
+ sum_vec = _mm256_add_ps(_mm256_permute2f128_ps(sum_vec, sum_vec, 1), sum_vec);
317
+ sum_vec = _mm256_hadd_ps(sum_vec, sum_vec);
318
+ sum_vec = _mm256_hadd_ps(sum_vec, sum_vec);
319
+
320
+ simsimd_f32_t log2_normalizer = 0.693147181f;
321
+ simsimd_f32_t sum;
322
+ _mm_store_ss(&sum, _mm256_castps256_ps128(sum_vec));
323
+ sum *= log2_normalizer;
324
+
325
+ // Accumulate the tail:
326
+ for (; i < n; ++i)
327
+ sum += SIMSIMD_UNCOMPRESS_F16(a[i]) *
328
+ SIMSIMD_LOG((SIMSIMD_UNCOMPRESS_F16(a[i]) + epsilon) / (SIMSIMD_UNCOMPRESS_F16(b[i]) + epsilon));
329
+ *result = sum;
330
+ }
331
+
332
+ SIMSIMD_PUBLIC void simsimd_js_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n,
333
+ simsimd_distance_t* result) {
334
+ __m256 sum_vec = _mm256_setzero_ps();
335
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
336
+ __m256 epsilon_vec = _mm256_set1_ps(epsilon);
337
+ simsimd_size_t i = 0;
338
+ for (; i + 8 <= n; i += 8) {
339
+ __m256 a_vec = _mm256_add_ps(_mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(a + i))), epsilon_vec);
340
+ __m256 b_vec = _mm256_add_ps(_mm256_cvtph_ps(_mm_loadu_si128((__m128i const*)(b + i))), epsilon_vec);
341
+ __m256 m_vec = _mm256_mul_ps(_mm256_add_ps(a_vec, b_vec), _mm256_set1_ps(0.5f)); // M = (P + Q) / 2
342
+ __m256 ratio_a_vec = _mm256_div_ps(a_vec, m_vec);
343
+ __m256 ratio_b_vec = _mm256_div_ps(b_vec, m_vec);
344
+ __m256 log_ratio_a_vec = simsimd_log2_f32_haswell(ratio_a_vec);
345
+ __m256 log_ratio_b_vec = simsimd_log2_f32_haswell(ratio_b_vec);
346
+ __m256 prod_a_vec = _mm256_mul_ps(a_vec, log_ratio_a_vec);
347
+ __m256 prod_b_vec = _mm256_mul_ps(b_vec, log_ratio_b_vec);
348
+ sum_vec = _mm256_add_ps(sum_vec, prod_a_vec);
349
+ sum_vec = _mm256_add_ps(sum_vec, prod_b_vec);
350
+ }
351
+
352
+ sum_vec = _mm256_add_ps(_mm256_permute2f128_ps(sum_vec, sum_vec, 1), sum_vec);
353
+ sum_vec = _mm256_hadd_ps(sum_vec, sum_vec);
354
+ sum_vec = _mm256_hadd_ps(sum_vec, sum_vec);
355
+
356
+ simsimd_f32_t log2_normalizer = 0.693147181f;
357
+ simsimd_f32_t sum;
358
+ _mm_store_ss(&sum, _mm256_castps256_ps128(sum_vec));
359
+ sum *= log2_normalizer;
360
+
361
+ // Accumulate the tail:
362
+ for (; i < n; ++i) {
363
+ simsimd_f32_t ai = SIMSIMD_UNCOMPRESS_F16(a[i]);
364
+ simsimd_f32_t bi = SIMSIMD_UNCOMPRESS_F16(b[i]);
365
+ simsimd_f32_t mi = ai + bi;
366
+ sum += ai * SIMSIMD_LOG((ai + epsilon) / (mi + epsilon));
367
+ sum += bi * SIMSIMD_LOG((bi + epsilon) / (mi + epsilon));
368
+ }
369
+ *result = sum / 2;
370
+ }
371
+
372
+ #pragma clang attribute pop
373
+ #pragma GCC pop_options
374
+ #endif // SIMSIMD_TARGET_HASWELL
375
+
376
+ #if SIMSIMD_TARGET_SKYLAKE
377
+ #pragma GCC push_options
378
+ #pragma GCC target("avx512f", "avx512vl", "bmi2")
379
+ #pragma clang attribute push(__attribute__((target("avx512f,avx512vl,bmi2"))), apply_to = function)
380
+
381
+ inline __m512 simsimd_log2_f32_skylake(__m512 x) {
382
+ // Extract the exponent and mantissa
383
+ __m512 one = _mm512_set1_ps(1.0f);
384
+ __m512 e = _mm512_getexp_ps(x);
385
+ __m512 m = _mm512_getmant_ps(x, _MM_MANT_NORM_1_2, _MM_MANT_SIGN_src);
386
+
387
+ // Compute the polynomial using Horner's method
388
+ __m512 p = _mm512_set1_ps(-3.4436006e-2f);
389
+ p = _mm512_fmadd_ps(m, p, _mm512_set1_ps(3.1821337e-1f));
390
+ p = _mm512_fmadd_ps(m, p, _mm512_set1_ps(-1.2315303f));
391
+ p = _mm512_fmadd_ps(m, p, _mm512_set1_ps(2.5988452f));
392
+ p = _mm512_fmadd_ps(m, p, _mm512_set1_ps(-3.3241990f));
393
+ p = _mm512_fmadd_ps(m, p, _mm512_set1_ps(3.1157899f));
394
+
395
+ return _mm512_add_ps(_mm512_mul_ps(p, _mm512_sub_ps(m, one)), e);
396
+ }
397
+
398
+ SIMSIMD_PUBLIC void simsimd_kl_f32_skylake(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
399
+ simsimd_distance_t* result) {
400
+ __m512 sum_vec = _mm512_setzero();
401
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
402
+ __m512 epsilon_vec = _mm512_set1_ps(epsilon);
403
+ __m512 a_vec, b_vec;
404
+
405
+ simsimd_kl_f32_skylake_cycle:
406
+ if (n < 16) {
407
+ __mmask16 mask = (__mmask16)_bzhi_u32(0xFFFFFFFF, n);
408
+ a_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, a), epsilon_vec);
409
+ b_vec = _mm512_add_ps(_mm512_maskz_loadu_ps(mask, b), epsilon_vec);
410
+ n = 0;
411
+ } else {
412
+ a_vec = _mm512_add_ps(_mm512_loadu_ps(a), epsilon_vec);
413
+ b_vec = _mm512_add_ps(_mm512_loadu_ps(b), epsilon_vec);
414
+ a += 16, b += 16, n -= 16;
415
+ }
416
+ __m512 ratio_vec = _mm512_div_ps(a_vec, b_vec);
417
+ __m512 log_ratio_vec = simsimd_log2_f32_skylake(ratio_vec);
418
+ __m512 prod_vec = _mm512_mul_ps(a_vec, log_ratio_vec);
419
+ sum_vec = _mm512_add_ps(sum_vec, prod_vec);
420
+ if (n)
421
+ goto simsimd_kl_f32_skylake_cycle;
422
+
423
+ simsimd_f32_t log2_normalizer = 0.693147181f;
424
+ *result = _mm512_reduce_add_ps(sum_vec) * log2_normalizer;
425
+ }
426
+
427
+ SIMSIMD_PUBLIC void simsimd_js_f32_skylake(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
428
+ simsimd_distance_t* result) {
429
+ __m512 sum_a_vec = _mm512_setzero();
430
+ __m512 sum_b_vec = _mm512_setzero();
431
+ simsimd_f32_t epsilon = SIMSIMD_F32_DIVISION_EPSILON;
432
+ __m512 epsilon_vec = _mm512_set1_ps(epsilon);
433
+ __m512 a_vec, b_vec;
434
+
435
+ simsimd_js_f32_skylake_cycle:
436
+ if (n < 16) {
437
+ __mmask16 mask = (__mmask16)_bzhi_u32(0xFFFFFFFF, n);
438
+ a_vec = _mm512_maskz_loadu_ps(mask, a);
439
+ b_vec = _mm512_maskz_loadu_ps(mask, b);
440
+ n = 0;
441
+ } else {
442
+ a_vec = _mm512_loadu_ps(a);
443
+ b_vec = _mm512_loadu_ps(b);
444
+ a += 16, b += 16, n -= 16;
445
+ }
446
+ __m512 m_vec = _mm512_mul_ps(_mm512_add_ps(a_vec, b_vec), _mm512_set1_ps(0.5f));
447
+ __mmask16 nonzero_mask_a = _mm512_cmp_ps_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
448
+ __mmask16 nonzero_mask_b = _mm512_cmp_ps_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
449
+ __mmask16 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
450
+ __m512 m_recip_approx = _mm512_rcp14_ps(m_vec);
451
+ __m512 ratio_a_vec = _mm512_mul_ps(a_vec, m_recip_approx);
452
+ __m512 ratio_b_vec = _mm512_mul_ps(b_vec, m_recip_approx);
453
+ __m512 log_ratio_a_vec = simsimd_log2_f32_skylake(ratio_a_vec);
454
+ __m512 log_ratio_b_vec = simsimd_log2_f32_skylake(ratio_b_vec);
455
+ sum_a_vec = _mm512_maskz_fmadd_ps(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
456
+ sum_b_vec = _mm512_maskz_fmadd_ps(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
457
+ if (n)
458
+ goto simsimd_js_f32_skylake_cycle;
459
+
460
+ simsimd_f32_t log2_normalizer = 0.693147181f;
461
+ *result = _mm512_reduce_add_ps(_mm512_add_ps(sum_a_vec, sum_b_vec)) * 0.5f * log2_normalizer;
462
+ }
463
+
464
+ #pragma clang attribute pop
465
+ #pragma GCC pop_options
466
+ #endif // SIMSIMD_TARGET_HASWELL
467
+
468
+ #if SIMSIMD_TARGET_SAPPHIRE
469
+ #pragma GCC push_options
470
+ #pragma GCC target("avx512f", "avx512vl", "bmi2", "avx512fp16")
471
+ #pragma clang attribute push(__attribute__((target("avx512f,avx512vl,bmi2,avx512fp16"))), apply_to = function)
472
+
473
+ inline __m512h simsimd_log2_f16_sapphire(__m512h x) {
474
+ // Extract the exponent and mantissa
475
+ __m512h one = _mm512_set1_ph((_Float16)1);
476
+ __m512h e = _mm512_getexp_ph(x);
477
+ __m512h m = _mm512_getmant_ph(x, _MM_MANT_NORM_1_2, _MM_MANT_SIGN_src);
478
+
479
+ // Compute the polynomial using Horner's method
480
+ __m512h p = _mm512_set1_ph((_Float16)-3.4436006e-2f);
481
+ p = _mm512_fmadd_ph(m, p, _mm512_set1_ph((_Float16)3.1821337e-1f));
482
+ p = _mm512_fmadd_ph(m, p, _mm512_set1_ph((_Float16)-1.2315303f));
483
+ p = _mm512_fmadd_ph(m, p, _mm512_set1_ph((_Float16)2.5988452f));
484
+ p = _mm512_fmadd_ph(m, p, _mm512_set1_ph((_Float16)-3.3241990f));
485
+ p = _mm512_fmadd_ph(m, p, _mm512_set1_ph((_Float16)3.1157899f));
486
+
487
+ return _mm512_add_ph(_mm512_mul_ph(p, _mm512_sub_ph(m, one)), e);
488
+ }
489
+
490
+ SIMSIMD_PUBLIC void simsimd_kl_f16_sapphire(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n,
491
+ simsimd_distance_t* result) {
492
+ __m512h sum_vec = _mm512_setzero_ph();
493
+ __m512h epsilon_vec = _mm512_set1_ph((_Float16)SIMSIMD_F16_DIVISION_EPSILON);
494
+ __m512h a_vec, b_vec;
495
+
496
+ simsimd_kl_f16_sapphire_cycle:
497
+ if (n < 32) {
498
+ __mmask32 mask = (__mmask32)_bzhi_u32(0xFFFFFFFF, n);
499
+ a_vec = _mm512_maskz_add_ph(mask, _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a)), epsilon_vec);
500
+ b_vec = _mm512_maskz_add_ph(mask, _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b)), epsilon_vec);
501
+ n = 0;
502
+ } else {
503
+ a_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_loadu_epi16(a)), epsilon_vec);
504
+ b_vec = _mm512_add_ph(_mm512_castsi512_ph(_mm512_loadu_epi16(b)), epsilon_vec);
505
+ a += 32, b += 32, n -= 32;
506
+ }
507
+ __m512h ratio_vec = _mm512_div_ph(a_vec, b_vec);
508
+ __m512h log_ratio_vec = simsimd_log2_f16_sapphire(ratio_vec);
509
+ __m512h prod_vec = _mm512_mul_ph(a_vec, log_ratio_vec);
510
+ sum_vec = _mm512_add_ph(sum_vec, prod_vec);
511
+ if (n)
512
+ goto simsimd_kl_f16_sapphire_cycle;
513
+
514
+ simsimd_f32_t log2_normalizer = 0.693147181f;
515
+ *result = _mm512_reduce_add_ph(sum_vec) * log2_normalizer;
516
+ }
517
+
518
+ SIMSIMD_PUBLIC void simsimd_js_f16_sapphire(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n,
519
+ simsimd_distance_t* result) {
520
+ __m512h sum_a_vec = _mm512_setzero_ph();
521
+ __m512h sum_b_vec = _mm512_setzero_ph();
522
+ __m512h epsilon_vec = _mm512_set1_ph((_Float16)SIMSIMD_F16_DIVISION_EPSILON);
523
+ __m512h a_vec, b_vec;
524
+
525
+ simsimd_js_f16_sapphire_cycle:
526
+ if (n < 32) {
527
+ __mmask32 mask = (__mmask32)_bzhi_u32(0xFFFFFFFF, n);
528
+ a_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, a));
529
+ b_vec = _mm512_castsi512_ph(_mm512_maskz_loadu_epi16(mask, b));
530
+ n = 0;
531
+ } else {
532
+ a_vec = _mm512_castsi512_ph(_mm512_loadu_epi16(a));
533
+ b_vec = _mm512_castsi512_ph(_mm512_loadu_epi16(b));
534
+ a += 32, b += 32, n -= 32;
535
+ }
536
+ __m512h m_vec = _mm512_mul_ph(_mm512_add_ph(a_vec, b_vec), _mm512_set1_ph((_Float16)0.5f));
537
+ __mmask32 nonzero_mask_a = _mm512_cmp_ph_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
538
+ __mmask32 nonzero_mask_b = _mm512_cmp_ph_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
539
+ __mmask32 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
540
+ __m512h m_recip_approx = _mm512_rcp_ph(m_vec);
541
+ __m512h ratio_a_vec = _mm512_mul_ph(a_vec, m_recip_approx);
542
+ __m512h ratio_b_vec = _mm512_mul_ph(b_vec, m_recip_approx);
543
+ __m512h log_ratio_a_vec = simsimd_log2_f16_sapphire(ratio_a_vec);
544
+ __m512h log_ratio_b_vec = simsimd_log2_f16_sapphire(ratio_b_vec);
545
+ sum_a_vec = _mm512_maskz_fmadd_ph(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
546
+ sum_b_vec = _mm512_maskz_fmadd_ph(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
547
+ if (n)
548
+ goto simsimd_js_f16_sapphire_cycle;
549
+
550
+ simsimd_f32_t log2_normalizer = 0.693147181f;
551
+ *result = _mm512_reduce_add_ph(_mm512_add_ph(sum_a_vec, sum_b_vec)) * 0.5f * log2_normalizer;
552
+ }
553
+
554
+ #pragma clang attribute pop
555
+ #pragma GCC pop_options
556
+ #endif // SIMSIMD_TARGET_SAPPHIRE
557
+ #endif // SIMSIMD_TARGET_X86
558
+
559
+ #ifdef __cplusplus
560
+ }
561
+ #endif
562
+
563
+ #endif