usearch 2.9.1 → 2.9.2

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 (43) hide show
  1. package/CITATION.cff +1 -1
  2. package/CONTRIBUTING.md +10 -2
  3. package/Cargo.toml +1 -1
  4. package/README.md +12 -15
  5. package/VERSION +1 -1
  6. package/c/test.c +58 -50
  7. package/conanfile.py +1 -1
  8. package/include/usearch/index.hpp +1 -1
  9. package/include/usearch/index_plugins.hpp +20 -10
  10. package/javascript/types/usearch.d.ts.map +1 -1
  11. package/javascript/usearch.js +4 -1
  12. package/package.json +1 -1
  13. package/simsimd/CONTRIBUTING.md +88 -0
  14. package/simsimd/Cargo.lock +4 -3
  15. package/simsimd/Cargo.toml +10 -2
  16. package/simsimd/README.md +102 -60
  17. package/simsimd/include/simsimd/binary.h +44 -0
  18. package/simsimd/include/simsimd/complex.h +174 -0
  19. package/simsimd/include/simsimd/simsimd.h +18 -9
  20. package/simsimd/include/simsimd/spatial.h +128 -36
  21. package/simsimd/include/simsimd/types.h +3 -2
  22. package/simsimd/package-lock.json +5 -5
  23. package/simsimd/package.json +1 -1
  24. package/simsimd/rust/benches/native.rs +12 -9
  25. package/simsimd/rust/lib.c +32 -55
  26. package/simsimd/rust/lib.rs +374 -56
  27. package/stringzilla/CONTRIBUTING.md +17 -0
  28. package/stringzilla/Cargo.lock +1 -1
  29. package/stringzilla/Cargo.toml +1 -1
  30. package/stringzilla/README.md +175 -34
  31. package/stringzilla/VERSION +1 -1
  32. package/stringzilla/build.rs +77 -4
  33. package/stringzilla/c/lib.c +33 -0
  34. package/stringzilla/cli/README.md +30 -13
  35. package/stringzilla/cli/__init__.py +0 -0
  36. package/stringzilla/cli/split.py +66 -33
  37. package/stringzilla/cli/wc.py +101 -21
  38. package/stringzilla/include/stringzilla/experimental.h +122 -1
  39. package/stringzilla/include/stringzilla/stringzilla.h +262 -132
  40. package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
  41. package/stringzilla/package.json +1 -1
  42. package/stringzilla/rust/lib.rs +673 -162
  43. package/wasmer.toml +1 -1
package/simsimd/README.md CHANGED
@@ -1,20 +1,41 @@
1
1
  # SimSIMD 📏
2
2
 
3
3
  <div>
4
- <a href="https://pepy.tech/project/simsimd"> <img alt="PyPI" src="https://static.pepy.tech/personalized-badge/simsimd?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=SimSIMD%20Python%20installs"> </a>
5
- <a href="https://www.npmjs.com/package/simsimd"> <img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads"> </a>
6
- <a href="https://crates.io/crates/simsimd"> <img alt="rust" src="https://img.shields.io/crates/d/simsimd?logo=rust" /> </a>
4
+ <a href="https://pepy.tech/project/simsimd">
5
+ <img alt="PyPI" src="https://static.pepy.tech/personalized-badge/simsimd?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=SimSIMD%20Python%20installs">
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/simsimd">
8
+ <img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads">
9
+ </a>
10
+ <a href="https://crates.io/crates/simsimd">
11
+ <img alt="rust" src="https://img.shields.io/crates/d/simsimd?logo=rust" />
12
+ </a>
7
13
  <img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/ashvardanian/simsimd">
14
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
15
+ <img alt="GitHub Actions Ubuntu" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=Ubuntu&logo=github&color=blue">
16
+ </a>
17
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
18
+ <img alt="GitHub Actions Windows" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=Windows&logo=windows&color=blue">
19
+ </a>
20
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
21
+ <img alt="GitHub Actions MacOS" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=MacOS&logo=apple&color=blue">
22
+ </a>
23
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
24
+ <img alt="GitHub Actions CentOS Linux" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=CentOS&logo=centos&color=blue">
25
+ </a>
26
+
8
27
  </div>
9
28
 
29
+
10
30
  ## Hardware-Accelerated Similarity Metrics and Distance Functions
11
31
 
12
32
  - Zero-dependency [header-only C 99](#using-simsimd-in-c) library.
13
- - Bindings for [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust) and [JavaScript](#using-simsimd-in-javascript).
14
- - Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
15
- - Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
16
33
  - Handles `f64` double-, `f32` single-, and `f16` half-precision, `i8` integral, and binary vectors.
34
+ - Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
35
+ - Bindings for [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust) and [JavaScript](#using-simsimd-in-javascript).
17
36
  - __Up to 200x faster__ than [`scipy.spatial.distance`][scipy] and [`numpy.inner`][numpy].
37
+ - [Supports more Python versions][compatibility] than SciPy and NumPy.
38
+ - Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
18
39
  - Used in [USearch](https://github.com/unum-cloud/usearch) and several DBMS products.
19
40
 
20
41
  __Implemented distance functions__ include:
@@ -25,6 +46,7 @@ __Implemented distance functions__ include:
25
46
 
26
47
  [scipy]: https://docs.scipy.org/doc/scipy/reference/spatial.distance.html#module-scipy.spatial.distance
27
48
  [numpy]: https://numpy.org/doc/stable/reference/generated/numpy.inner.html
49
+ [compatibility]: https://pypi.org/project/simsimd/#files
28
50
 
29
51
  __Technical Insights__ and related articles:
30
52
 
@@ -67,6 +89,11 @@ __Broader Benchmarking Results__:
67
89
 
68
90
  ## Using SimSIMD in Python
69
91
 
92
+ The package is intended to replace the usage of `numpy.inner`, `numpy.dot`, and `scipy.spatial.distance`.
93
+ Aside from drastic performance improvements, SimSIMD significantly improves accuracy in mixed precision setups.
94
+ NumPy and SciPy, processing `i8` or `f16` vectors, will use the same types for accumulators, while SimSIMD can combine `i8` enumeration, `i16` multiplication, and `i32` accumulation to entirely avoid overflows.
95
+ The same applies to processing `f16` values with `f32` precision.
96
+
70
97
  ### Installation
71
98
 
72
99
  ```sh
@@ -85,6 +112,14 @@ dist = simsimd.cosine(vec1, vec2)
85
112
  ```
86
113
 
87
114
  Supported functions include `cosine`, `inner`, `sqeuclidean`, `hamming`, and `jaccard`.
115
+ Unlike SciPy, SimSIMD allows explicitly stating the precision of the input vectors, which is especially useful for mixed-precision setups.
116
+
117
+ ```py
118
+ dist = simsimd.cosine(vec1, vec2, "i8")
119
+ dist = simsimd.cosine(vec1, vec2, "f16")
120
+ dist = simsimd.cosine(vec1, vec2, "f32")
121
+ dist = simsimd.cosine(vec1, vec2, "f64")
122
+ ```
88
123
 
89
124
  ### Distance Between 2 Batches
90
125
 
@@ -150,20 +185,73 @@ To install, add the following to your `Cargo.toml`:
150
185
  simsimd = "..."
151
186
  ```
152
187
 
153
- To use it:
188
+ Before using the SimSIMD library, ensure you have imported the necessary traits and types into your Rust source file.
189
+ The library provides several traits for different distance/similarity kinds - `SpatialSimilarity`, `BinarySimilarity`, and `ProbabilitySimilarity`.
154
190
 
155
191
  ```rust
156
- use simsimd::{cosine, sqeuclidean};
192
+ use simsimd::SpatialSimilarity;
157
193
 
158
194
  fn main() {
159
- let vector_a = vec![1.0, 2.0, 3.0];
160
- let vector_b = vec![4.0, 5.0, 6.0];
195
+ let vector_a: Vec<f32> = vec![1.0, 2.0, 3.0];
196
+ let vector_b: Vec<f32> = vec![4.0, 5.0, 6.0];
161
197
 
162
- let distance = cosine(&vector_a, &vector_b);
163
- println!("Cosine Distance: {}", distance);
198
+ // Compute the cosine similarity between vector_a and vector_b
199
+ let cosine_similarity = f32::cosine(&vector_a, &vector_b)
200
+ .expect("Vectors must be of the same length");
164
201
 
165
- let distance = sqeuclidean(&vector_a, &vector_b);
166
- println!("Squared Euclidean Distance: {}", distance);
202
+ println!("Cosine Similarity: {}", cosine_similarity);
203
+
204
+ // Compute the squared Euclidean distance between vector_a and vector_b
205
+ let sq_euclidean_distance = f32::sqeuclidean(&vector_a, &vector_b)
206
+ .expect("Vectors must be of the same length");
207
+
208
+ println!("Squared Euclidean Distance: {}", sq_euclidean_distance);
209
+ }
210
+ ```
211
+
212
+ Similarly, one can compute bit-level distance functions between slices of unsigned integers:
213
+
214
+ ```rust
215
+ use simsimd::BinarySimilarity;
216
+
217
+ fn main() {
218
+ let vector_a = &[0b11110000, 0b00001111, 0b10101010];
219
+ let vector_b = &[0b11110000, 0b00001111, 0b01010101];
220
+
221
+ // Compute the Hamming distance between vector_a and vector_b
222
+ let hamming_distance = u8::hamming(&vector_a, &vector_b)
223
+ .expect("Vectors must be of the same length");
224
+
225
+ println!("Hamming Distance: {}", hamming_distance);
226
+
227
+ // Compute the Jaccard distance between vector_a and vector_b
228
+ let jaccard_distance = u8::jaccard(&vector_a, &vector_b)
229
+ .expect("Vectors must be of the same length");
230
+
231
+ println!("Jaccard Distance: {}", jaccard_distance);
232
+ }
233
+ ```
234
+
235
+ Rust has no native support for half-precision floating-point numbers, but SimSIMD provides a `f16` type for this purpose.
236
+ It doesn't have any functionality and is a `transparent` wrapper around `u16`, so it can be used with `half`, or any other half-precision library.
237
+
238
+ ```rust
239
+ use simsimd::SpatialSimilarity;
240
+ use simsimd::f16 as SimF16;
241
+ use half::f16 as HalfF16;
242
+
243
+ fn main() {
244
+ let vector_a: Vec<HalfF16> = ...
245
+ let vector_b: Vec<HalfF16> = ...
246
+
247
+ let buffer_a: &[SimF16] = unsafe { std::slice::from_raw_parts(a_half.as_ptr() as *const SimF16, a_half.len()) };
248
+ let buffer_b: &[SimF16] = unsafe { std::slice::from_raw_parts(b_half.as_ptr() as *const SimF16, b_half.len()) };
249
+
250
+ // Compute the cosine similarity between vector_a and vector_b
251
+ let cosine_similarity = SimF16::cosine(&vector_a, &vector_b)
252
+ .expect("Vectors must be of the same length");
253
+
254
+ println!("Cosine Similarity: {}", cosine_similarity);
167
255
  }
168
256
  ```
169
257
 
@@ -227,49 +315,3 @@ All of the functions names follow the same pattern: `simsimd_{backend}_{type}_{m
227
315
  - The metric can be `cos`, `ip`, `l2sq`, `hamming`, `jaccard`, `kl`, or `js`.
228
316
 
229
317
  In case you want to avoid hard-coding the backend, you can use the `simsimd_metric_punned_t` to pun the function pointer, and `simsimd_capabilities` function to get the available backends at runtime.
230
-
231
- ## Benchmarking and Contributing
232
-
233
- __To rerun experiments__ utilize the following command:
234
-
235
- ```sh
236
- cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
237
- cmake --build build_release --config Release
238
- ./build_release/simsimd_bench
239
- ./build_release/simsimd_bench --benchmark_filter=js
240
- ```
241
-
242
- __To test and benchmark with Python bindings__:
243
-
244
- ```sh
245
- pip install -e .
246
- pytest python/test.py -s -x
247
-
248
- pip install numpy scipy scikit-learn # for comparison baselines
249
- python python/bench.py # to run default benchmarks
250
- python python/bench.py --n 1000 --ndim 1000000 # batch size and dimensions
251
- ```
252
-
253
- __To test and benchmark JavaScript bindings__:
254
-
255
- ```sh
256
- npm install --dev
257
- npm test
258
- npm run bench
259
- ```
260
-
261
- __To test and benchmark GoLang bindings__:
262
-
263
- ```sh
264
- cd golang
265
- go test # To test
266
- go test -run=^$ -bench=. -benchmem # To benchmark
267
- ```
268
-
269
- __To test and benchmark Rust bindings__:
270
-
271
- ```sh
272
- cargo test
273
- cargo bench
274
- open ./target/criterion/report/index.html
275
- ```
@@ -190,6 +190,50 @@ simsimd_avx512_b8_jaccard_cycle:
190
190
  }
191
191
 
192
192
  #endif // SIMSIMD_TARGET_X86_AVX512
193
+
194
+ /*
195
+ * @file x86_avx2_b8.h
196
+ * @brief x86 AVX2 implementation of the most common similarity metrics for bitsets.
197
+ * @author Ash Vardanian
198
+ *
199
+ * - Implements: Hamming and Jaccard similarity.
200
+ * - Requires compiler capabilities: avx2.
201
+ *
202
+ * AVX2 provides no built-in functionality for popcount, so we use the BMI2 instruction set.
203
+ * Wojciech Mu la, Nathan Kurz and Daniel Lemire propose an alternative solution based on Harley-Seal
204
+ * transforms and lookup tables. Those are very fast on large vectors, but on short vectors they lose
205
+ * to built-in scalar population counts. https://arxiv.org/pdf/1611.07612.pdf
206
+ */
207
+
208
+ #if SIMSIMD_TARGET_X86_AVX2
209
+
210
+ __attribute__((target("popcnt"))) //
211
+ inline static simsimd_f32_t
212
+ simsimd_avx2_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
213
+ // x86 supports unaligned loads and works just fine with the scalar version for small vectors.
214
+ simsimd_size_t differences = 0;
215
+ for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
216
+ differences += _mm_popcnt_u64(*(simsimd_u64_t const*)a ^ *(simsimd_u64_t const*)b);
217
+ for (; n_words; --n_words, ++a, ++b)
218
+ differences += _mm_popcnt_u32(*a ^ *b);
219
+ return (simsimd_f32_t)differences;
220
+ }
221
+
222
+ __attribute__((target("popcnt"))) //
223
+ inline static simsimd_f32_t
224
+ simsimd_avx2_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
225
+ // x86 supports unaligned loads and works just fine with the scalar version for small vectors.
226
+ simsimd_size_t intersection = 0, union_ = 0;
227
+ for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
228
+ intersection += _mm_popcnt_u64(*(simsimd_u64_t const*)a & *(simsimd_u64_t const*)b),
229
+ union_ += _mm_popcnt_u64(*(simsimd_u64_t const*)a | *(simsimd_u64_t const*)b);
230
+ for (; n_words; --n_words, ++a, ++b)
231
+ intersection += _mm_popcnt_u32(*a & *b), union_ += _mm_popcnt_u32(*a | *b);
232
+ return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
233
+ }
234
+
235
+ #endif // SIMSIMD_TARGET_X86_AVX2
236
+
193
237
  #endif // SIMSIMD_TARGET_X86
194
238
 
195
239
  #undef popcount64
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @brief SIMD-accelerated Similarity Measures for Complex Vectors.
3
+ * @author Ash Vardanian
4
+ * @date February 24, 2024
5
+ *
6
+ * Contains:
7
+ * - Inner Product
8
+ * - Cosine Distance
9
+ *
10
+ * For datatypes:
11
+ * - 32-bit floating point numbers
12
+ * - 16-bit floating point numbers
13
+ *
14
+ * For hardware architectures:
15
+ * - Arm (NEON, SVE?)
16
+ * - x86 (AVX2?, AVX512?)
17
+ *
18
+ * x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
19
+ * Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
20
+ */
21
+ #ifndef SIMSIMD_COMPLEX_H
22
+ #define SIMSIMD_COMPLEX_H
23
+
24
+ #include "types.h"
25
+
26
+ #define SIMSIMD_MAKE_COMPLEX_IP(name, input_type, accumulator_type, converter, epsilon) \
27
+ inline static simsimd_f32_t simsimd_##name##_##input_type##_complex_ip( \
28
+ simsimd_##input_type##_t const* a, simsimd_##input_type##_t const* b, simsimd_size_t n, \
29
+ simsimd_f32_t* real_out, simsimd_f32_t* imag_out) { \
30
+ simsimd_##accumulator_type##_t ab_real, ab_imag; \
31
+ for (simsimd_size_t i = 0; i + 2 <= n; i += 2) { \
32
+ simsimd_##accumulator_type##_t ar = converter(a[i]); \
33
+ simsimd_##accumulator_type##_t br = converter(b[i]); \
34
+ simsimd_##accumulator_type##_t ai = converter(a[i + 1]); \
35
+ simsimd_##accumulator_type##_t bi = converter(b[i + 1]); \
36
+ ab_real += ar * br + ai * bi; \
37
+ ab_imag += ai * br - ar * bi; \
38
+ } \
39
+ *real_out = ab_real; \
40
+ *imag_out = ab_imag; \
41
+ return 1 - ab_real; \
42
+ }
43
+
44
+ #define SIMSIMD_MAKE_COMPLEX_COS(name, input_type, accumulator_type, converter, epsilon) \
45
+ inline static simsimd_f32_t simsimd_##name##_##input_type##_complex_cos( \
46
+ simsimd_##input_type##_t const* a, simsimd_##input_type##_t const* b, simsimd_size_t n, \
47
+ simsimd_f32_t* real_out, simsimd_f32_t* imag_out) { \
48
+ simsimd_##accumulator_type##_t ab_real, ab_imag, a2_sum, b2_sum; \
49
+ for (simsimd_size_t i = 0; i + 2 <= n; i += 2) { \
50
+ simsimd_##accumulator_type##_t ar = converter(a[i]); \
51
+ simsimd_##accumulator_type##_t br = converter(b[i]); \
52
+ simsimd_##accumulator_type##_t ai = converter(a[i + 1]); \
53
+ simsimd_##accumulator_type##_t bi = converter(b[i + 1]); \
54
+ ab_real += ar * br + ai * bi; \
55
+ ab_imag += ai * br - ar * bi; \
56
+ a2_sum += ar * ar + ai * ai; \
57
+ b2_sum += br * br + bi * bi; \
58
+ } \
59
+ *real_out = ab_real; \
60
+ *imag_out = ab_imag; \
61
+ simsimd_##accumulator_type##_t ab_angle = ab_real * SIMSIMD_RSQRT(a2_sum) * SIMSIMD_RSQRT(b2_sum); \
62
+ return 1 - ab_angle; \
63
+ }
64
+
65
+ #ifdef __cplusplus
66
+ extern "C" {
67
+ #endif
68
+
69
+ SIMSIMD_MAKE_COMPLEX_IP(serial, f64, f64, SIMSIMD_IDENTIFY,
70
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f64_complex_ip
71
+ SIMSIMD_MAKE_COMPLEX_COS(serial, f64, f64, SIMSIMD_IDENTIFY,
72
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f64_complex_cos
73
+
74
+ SIMSIMD_MAKE_COMPLEX_IP(serial, f32, f32, SIMSIMD_IDENTIFY,
75
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f32_complex_ip
76
+ SIMSIMD_MAKE_COMPLEX_COS(serial, f32, f32, SIMSIMD_IDENTIFY,
77
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f32_complex_cos
78
+
79
+ SIMSIMD_MAKE_COMPLEX_IP(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16,
80
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f16_complex_ip
81
+ SIMSIMD_MAKE_COMPLEX_COS(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16,
82
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f16_complex_cos
83
+
84
+ SIMSIMD_MAKE_COMPLEX_IP(accurate, f32, f64, SIMSIMD_IDENTIFY,
85
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f32_complex_ip
86
+ SIMSIMD_MAKE_COMPLEX_COS(accurate, f32, f64, SIMSIMD_IDENTIFY,
87
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f32_complex_cos
88
+
89
+ SIMSIMD_MAKE_COMPLEX_IP(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16,
90
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f16_complex_ip
91
+ SIMSIMD_MAKE_COMPLEX_COS(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16,
92
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f16_complex_cos
93
+
94
+ #if SIMSIMD_TARGET_ARM
95
+ #if SIMSIMD_TARGET_ARM_NEON
96
+
97
+ /*
98
+ * @file arm_neon_f32.h
99
+ * @brief Arm NEON implementation of the most common similarity metrics for 32-bit floating point numbers.
100
+ * @author Ash Vardanian
101
+ *
102
+ * - Implements: Kullback-Leibler and Jensen–Shannon divergence.
103
+ * - Uses `f32` for storage and `f32` for accumulation.
104
+ * - Requires compiler capabilities: +simd.
105
+ */
106
+
107
+ __attribute__((target("+simd"))) //
108
+ inline static simsimd_f32_t
109
+ simsimd_neon_f32_complex_cos(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, //
110
+ simsimd_f32_t* real_out, simsimd_f32_t* imag_out) {
111
+ float32x4_t ab_real_vec = vdupq_n_f32(0);
112
+ float32x4_t ab_imag_vec = vdupq_n_f32(0);
113
+ float32x4_t a2_vec = vdupq_n_f32(0);
114
+ float32x4_t b2_vec = vdupq_n_f32(0);
115
+ simsimd_size_t i = 0;
116
+ for (; i + 8 <= n; i += 8) {
117
+ // Unpack the input arrays into real and imaginary parts:
118
+ // a_real = a[::2]
119
+ // a_imag = a[1::2]
120
+ // b_real = b[::2]
121
+ // b_imag = b[1::2]
122
+ float32x4x2_t a_vec = vld2q_f32(a + i);
123
+ float32x4x2_t b_vec = vld2q_f32(b + i);
124
+ float32x4_t a_real_vec = a_vec.val[0];
125
+ float32x4_t a_imag_vec = a_vec.val[1];
126
+ float32x4_t b_real_vec = b_vec.val[0];
127
+ float32x4_t b_imag_vec = b_vec.val[1];
128
+
129
+ // Compute the dot product:
130
+ // ab_real = sum(ar * br + ai * bi for ar, ai, br, bi in zip(a_real, a_imag, b_real, b_imag))
131
+ // ab_imag = sum(ai * br - ar * bi for ar, ai, br, bi in zip(a_real, a_imag, b_real, b_imag))
132
+ ab_real_vec = vfmaq_f32(ab_real_vec, a_real_vec, b_real_vec);
133
+ ab_real_vec = vfmaq_f32(ab_real_vec, a_imag_vec, b_imag_vec);
134
+ ab_imag_vec = vfmaq_f32(ab_imag_vec, a_imag_vec, b_real_vec);
135
+ ab_imag_vec = vfmsq_f32(ab_imag_vec, a_real_vec, b_imag_vec);
136
+
137
+ // Compute the vector norms:
138
+ // a2 = sum(ar * ar + ai * ai for ar, ai in zip(a_real, a_imag))
139
+ // b2 = sum(br * br + bi * bi for br, bi in zip(b_real, b_imag))
140
+ a2_vec = vfmaq_f32(a2_vec, a_real_vec, a_real_vec);
141
+ a2_vec = vfmaq_f32(a2_vec, a_imag_vec, a_imag_vec);
142
+ b2_vec = vfmaq_f32(b2_vec, b_real_vec, b_real_vec);
143
+ b2_vec = vfmaq_f32(b2_vec, b_imag_vec, b_imag_vec);
144
+ }
145
+
146
+ // Reduce horizontal sums:
147
+ simsimd_f32_t ab_real = vaddvq_f32(ab_real_vec);
148
+ simsimd_f32_t ab_imag = vaddvq_f32(ab_imag_vec);
149
+ simsimd_f32_t a2 = vaddvq_f32(a2_vec);
150
+ simsimd_f32_t b2 = vaddvq_f32(b2_vec);
151
+
152
+ // Handle the tail:
153
+ for (; i < n; i += 2) {
154
+ simsimd_f32_t ar = a[i], ai = a[i + 1], br = b[i], bi = b[i + 1];
155
+ ab_real += ar * br + ai * bi, ab_imag += ai * br - ar * bi;
156
+ a2 += ar * ar + ai * ai, b2 += br * br + bi * bi;
157
+ }
158
+ *real_out = ab_real;
159
+ *imag_out = ab_imag;
160
+
161
+ // Avoid `simsimd_approximate_inverse_square_root` on Arm NEON
162
+ simsimd_f32_t a2_b2_arr[2] = {a2, b2};
163
+ vst1_f32(a2_b2_arr, vrsqrte_f32(vld1_f32(a2_b2_arr)));
164
+ return ab_real != 0 ? 1 - ab_real * a2_b2_arr[0] * a2_b2_arr[1] : 1;
165
+ }
166
+
167
+ #endif // SIMSIMD_TARGET_ARM_NEON
168
+ #endif // SIMSIMD_TARGET_ARM
169
+
170
+ #ifdef __cplusplus
171
+ }
172
+ #endif
173
+
174
+ #endif
@@ -15,10 +15,11 @@
15
15
  #define SIMSIMD_H
16
16
 
17
17
  #define SIMSIMD_VERSION_MAJOR 3
18
- #define SIMSIMD_VERSION_MINOR 8
18
+ #define SIMSIMD_VERSION_MINOR 9
19
19
  #define SIMSIMD_VERSION_PATCH 0
20
20
 
21
21
  #include "binary.h" // Hamming, Jaccard
22
+ #include "complex.h" // Complex numbers
22
23
  #include "probability.h" // Kullback-Leibler, Jensen–Shannon
23
24
  #include "spatial.h" // L2, Inner Product, Cosine
24
25
 
@@ -388,14 +389,6 @@ inline static void simsimd_find_metric_punned( //
388
389
  // Binary vectors
389
390
  case simsimd_datatype_b8_k:
390
391
 
391
- #if SIMSIMD_TARGET_ARM_NEON
392
- if (viable & simsimd_cap_arm_neon_k)
393
- switch (kind) {
394
- case simsimd_metric_hamming_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_hamming, *c = simsimd_cap_arm_neon_k; return;
395
- case simsimd_metric_jaccard_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_jaccard, *c = simsimd_cap_arm_neon_k; return;
396
- default: break;
397
- }
398
- #endif
399
392
  #if SIMSIMD_TARGET_ARM_SVE
400
393
  if (viable & simsimd_cap_arm_sve_k)
401
394
  switch (kind) {
@@ -404,6 +397,14 @@ inline static void simsimd_find_metric_punned( //
404
397
  default: break;
405
398
  }
406
399
  #endif
400
+ #if SIMSIMD_TARGET_ARM_NEON
401
+ if (viable & simsimd_cap_arm_neon_k)
402
+ switch (kind) {
403
+ case simsimd_metric_hamming_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_hamming, *c = simsimd_cap_arm_neon_k; return;
404
+ case simsimd_metric_jaccard_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_jaccard, *c = simsimd_cap_arm_neon_k; return;
405
+ default: break;
406
+ }
407
+ #endif
407
408
  #if SIMSIMD_TARGET_X86_AVX512
408
409
  if (viable & simsimd_cap_x86_avx512vpopcntdq_k)
409
410
  switch (kind) {
@@ -412,6 +413,14 @@ inline static void simsimd_find_metric_punned( //
412
413
  default: break;
413
414
  }
414
415
  #endif
416
+ #if SIMSIMD_TARGET_X86_AVX2
417
+ if (viable & simsimd_cap_x86_avx2_k)
418
+ switch (kind) {
419
+ case simsimd_metric_hamming_k: *m = (simsimd_metric_punned_t)&simsimd_avx2_b8_hamming, *c = simsimd_cap_x86_avx2_k; return;
420
+ case simsimd_metric_jaccard_k: *m = (simsimd_metric_punned_t)&simsimd_avx2_b8_jaccard, *c = simsimd_cap_x86_avx2_k; return;
421
+ default: break;
422
+ }
423
+ #endif
415
424
 
416
425
  if (viable & simsimd_cap_serial_k)
417
426
  switch (kind) {