usearch 2.21.2 → 2.21.3

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.
package/README.md CHANGED
@@ -552,7 +552,7 @@ doi = {10.5281/zenodo.7949416},
552
552
  author = {Vardanian, Ash},
553
553
  title = {{USearch by Unum Cloud}},
554
554
  url = {https://github.com/unum-cloud/usearch},
555
- version = {2.21.2},
555
+ version = {2.21.3},
556
556
  year = {2023},
557
557
  month = oct,
558
558
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
11
  #define USEARCH_VERSION_MINOR 21
12
- #define USEARCH_VERSION_PATCH 2
12
+ #define USEARCH_VERSION_PATCH 3
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.21.2",
3
+ "version": "2.21.3",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
Binary file
Binary file
@@ -49,7 +49,7 @@ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100
49
49
  sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100
50
50
  ```
51
51
 
52
- To compile with the default Apple Clang on MacOS, use:
52
+ To compile with the default Apple Clang on macOS, use:
53
53
 
54
54
  ```sh
55
55
  brew install openblas
@@ -63,8 +63,8 @@ cmake -D CMAKE_BUILD_TYPE=Release \
63
63
  cmake --build build_release --config Release
64
64
  ```
65
65
 
66
- On MacOS it's recommended to use Homebrew and install Clang, as opposed to "Apple Clang".
67
- Replacing the default compiler across the entire system is not recommended on MacOS, as it may break the system, but you can pass it as an environment variable:
66
+ On macOS it's recommended to use Homebrew and install Clang, as opposed to "Apple Clang".
67
+ Replacing the default compiler across the entire system is not recommended on macOS, as it may break the system, but you can pass it as an environment variable:
68
68
 
69
69
  ```sh
70
70
  brew install llvm openblas
@@ -124,7 +124,7 @@ Here, `-s` will output the logs.
124
124
  The `-x` will stop on the first failure.
125
125
  The `-Wd` will silence overflows and runtime warnings.
126
126
 
127
- When building on MacOS, same as with C/C++, use non-Apple Clang version:
127
+ When building on macOS, same as with C/C++, use non-Apple Clang version:
128
128
 
129
129
  ```sh
130
130
  brew install llvm
@@ -174,7 +174,7 @@ $ python scripts/bench_vectors.py --help
174
174
 
175
175
 
176
176
  Before merging your changes you may want to test your changes against the entire matrix of Python versions USearch supports.
177
- For that you need the `cibuildwheel`, which is tricky to use on MacOS and Windows, as it would target just the local environment.
177
+ For that you need the `cibuildwheel`, which is tricky to use on macOS and Windows, as it would target just the local environment.
178
178
  Still, if you have Docker running on any desktop OS, you can use it to build and test the Python bindings for all Python versions for Linux:
179
179
 
180
180
  ```sh
@@ -184,7 +184,7 @@ cibuildwheel --platform linux # works on any OS and builds all
184
184
  cibuildwheel --platform linux --archs x86_64 # 64-bit x86, the most common on desktop and servers
185
185
  cibuildwheel --platform linux --archs aarch64 # 64-bit Arm for mobile devices, Apple M-series, and AWS Graviton
186
186
  cibuildwheel --platform linux --archs i686 # 32-bit Linux
187
- cibuildwheel --platform macos # works only on MacOS
187
+ cibuildwheel --platform macos # works only on macOS
188
188
  cibuildwheel --platform windows # works only on Windows
189
189
  ```
190
190
 
@@ -194,7 +194,7 @@ You may need root privileges for multi-architecture builds:
194
194
  sudo $(which cibuildwheel) --platform linux
195
195
  ```
196
196
 
197
- On Windows and MacOS, to avoid frequent path resolution issues, you may want to use:
197
+ On Windows and macOS, to avoid frequent path resolution issues, you may want to use:
198
198
 
199
199
  ```sh
200
200
  python -m cibuildwheel --platform windows
@@ -209,6 +209,13 @@ cargo bench
209
209
  open target/criterion/report/index.html
210
210
  ```
211
211
 
212
+ To automatically detect the Minimum Supported Rust Version (MSRV):
213
+
214
+ ```sh
215
+ cargo +stable install cargo-msrv
216
+ cargo msrv find --ignore-lockfile
217
+ ```
218
+
212
219
  ## JavaScript
213
220
 
214
221
  ### NodeJS
@@ -217,7 +224,7 @@ If you don't have the environment configured, here are the [installation options
217
224
 
218
225
  ```sh
219
226
  wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash # Linux
220
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash # MacOS
227
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash # macOS
221
228
  ```
222
229
 
223
230
  Install dependencies:
@@ -242,7 +249,7 @@ If you don't have the environment configured, here are [installation options](ht
242
249
 
243
250
  ```sh
244
251
  wget -qO- https://deno.land/x/install/install.sh | sh # Linux
245
- curl -fsSL https://deno.land/install.sh | sh # MacOS
252
+ curl -fsSL https://deno.land/install.sh | sh # macOS
246
253
  irm https://deno.land/install.ps1 | iex # Windows
247
254
  ```
248
255
 
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "simsimd"
3
3
  description = "Portable mixed-precision BLAS-like vector math library for x86 and ARM"
4
- version = "6.5.1"
4
+ version = "6.5.5"
5
5
  edition = "2021"
6
6
  license = "Apache-2.0"
7
7
  authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
@@ -16,7 +16,8 @@ categories = [
16
16
  "wasm",
17
17
  "external-ffi-bindings",
18
18
  ]
19
- include = ["/rust/**", "/c/**", "/include/**", "/build.rs"]
19
+ rust-version = "1.64" # Introduced Core C FFI in stable Rust
20
+ include = ["rust/**", "c/**", "include/**", "build.rs"]
20
21
 
21
22
 
22
23
  [lib]
@@ -24,18 +25,13 @@ name = "simsimd"
24
25
  path = "rust/lib.rs"
25
26
 
26
27
  [build-dependencies]
27
- cc = "1.0.83"
28
+ cc = "1.2.36"
28
29
 
29
30
 
30
31
  [[bench]]
31
- name = "cosine"
32
+ name = "bench"
32
33
  harness = false
33
- path = "scripts/bench_cosine.rs"
34
-
35
- [[bench]]
36
- name = "sqeuclidean"
37
- harness = false
38
- path = "scripts/bench_sqeuclidean.rs"
34
+ path = "scripts/bench.rs"
39
35
 
40
36
  [profile.bench]
41
37
  opt-level = 3 # Corresponds to -O3
@@ -48,6 +44,6 @@ default = []
48
44
  std = []
49
45
 
50
46
  [dev-dependencies]
51
- criterion = { version = "0.6.0" }
47
+ criterion = { version = "0.7.0" }
52
48
  rand = { version = "0.9.1" }
53
49
  half = { version = "2.6.0" }
@@ -1,3 +1,4 @@
1
1
  include include/simsimd/*.h
2
2
  include include/*
3
+ include scripts/test.py
3
4
  include VERSION
package/simsimd/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ![SimSIMD banner](https://github.com/ashvardanian/ashvardanian/blob/master/repositories/SimSIMD.jpg?raw=true)
2
2
 
3
- Computing dot-products, similarity measures, and distances between low- and high-dimensional vectors is ubiquitous in Machine Learning, Scientific Computing, Geo-Spatial Analysis, and Information Retrieval.
3
+ Computing dot-products, similarity measures, and distances between low- and high-dimensional vectors is ubiquitous in Machine Learning, Scientific Computing, Geospatial Analysis, and Information Retrieval.
4
4
  These algorithms generally have linear complexity in time, constant or linear complexity in space, and are data-parallel.
5
5
  In other words, it is easily parallelizable and vectorizable and often available in packages like BLAS (level 1) and LAPACK, as well as higher-level `numpy` and `scipy` Python libraries.
6
6
  Ironically, even with decades of evolution in compilers and numerical computing, [most libraries can be 3-200x slower than hardware potential][benchmarks] even on the most popular hardware, like 64-bit x86 and Arm CPUs.
@@ -32,7 +32,7 @@ SimSIMD provides an alternative.
32
32
  <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">
33
33
  </a>
34
34
  <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
35
- <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">
35
+ <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">
36
36
  </a>
37
37
  <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
38
38
  <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">
@@ -43,7 +43,7 @@ SimSIMD provides an alternative.
43
43
  ## Features
44
44
 
45
45
  __SimSIMD__ (Arabic: "سيمسيم دي") is a mixed-precision math library of __over 350 SIMD-optimized kernels__ extensively used in AI, Search, and DBMS workloads.
46
- Named after the iconic ["Open Sesame"](https://en.wikipedia.org/wiki/Open_sesame) command that opened doors to treasure in _Ali Baba and the Forty Thieves_, SimSimd can help you 10x the cost-efficiency of your computational pipelines.
46
+ Named after the iconic ["Open Sesame"](https://en.wikipedia.org/wiki/Open_sesame) command that opened doors to treasure in _Ali Baba and the Forty Thieves_, SimSIMD can help you 10x the cost-efficiency of your computational pipelines.
47
47
  Implemented distance functions include:
48
48
 
49
49
  - Euclidean (L2) and Cosine (Angular) spatial distances for Vector Search. _[docs][docs-spatial]_
@@ -85,7 +85,7 @@ You can learn more about the technical implementation details in the following b
85
85
 
86
86
  - [Uses Horner's method for polynomial approximations, beating GCC 12 by 119x](https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/).
87
87
  - [Uses Arm SVE and x86 AVX-512's masked loads to eliminate tail `for`-loops](https://ashvardanian.com/posts/simsimd-faster-scipy/#tails-of-the-past-the-significance-of-masked-loads).
88
- - [Substitutes LibC's `sqrt` with Newton Raphson iterations](https://github.com/ashvardanian/SimSIMD/releases/tag/v5.4.0).
88
+ - [Substitutes libc's `sqrt` with Newton Raphson iterations](https://github.com/ashvardanian/SimSIMD/releases/tag/v5.4.0).
89
89
  - [Uses Galloping and SVE2 histograms to intersect sparse vectors](https://ashvardanian.com/posts/simd-set-intersections-sve2-avx512/).
90
90
  - For Python: [avoids slow PyBind11, SWIG, & `PyArg_ParseTuple`](https://ashvardanian.com/posts/pybind11-cpython-tutorial/) [using faster calling convention](https://ashvardanian.com/posts/discount-on-keyword-arguments-in-python/).
91
91
  - For JavaScript: [uses typed arrays and NAPI for zero-copy calls](https://ashvardanian.com/posts/javascript-ai-vector-search/).
@@ -625,13 +625,13 @@ fn main() {
625
625
  let vector_a: Vec<f32> = vec![1.0, 2.0, 3.0];
626
626
  let vector_b: Vec<f32> = vec![4.0, 5.0, 6.0];
627
627
 
628
- // Compute the cosine similarity between vector_a and vector_b
629
- let cosine_similarity = f32::cosine(&vector_a, &vector_b)
628
+ // Compute the cosine distance between vectors
629
+ let cosine_distance = f32::cosine(&vector_a, &vector_b)
630
630
  .expect("Vectors must be of the same length");
631
631
 
632
- println!("Cosine Similarity: {}", cosine_similarity);
632
+ println!("Cosine Distance: {}", cosine_distance);
633
633
 
634
- // Compute the squared Euclidean distance between vector_a and vector_b
634
+ // Compute the squared Euclidean distance between vectors
635
635
  let sq_euclidean_distance = f32::sqeuclidean(&vector_a, &vector_b)
636
636
  .expect("Vectors must be of the same length");
637
637
 
@@ -648,16 +648,17 @@ use simsimd::SpatialSimilarity;
648
648
  use simsimd::ComplexProducts;
649
649
 
650
650
  fn main() {
651
+ // Complex vectors have interleaved real & imaginary components
651
652
  let vector_a: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
652
653
  let vector_b: Vec<f32> = vec![5.0, 6.0, 7.0, 8.0];
653
654
 
654
- // Compute the inner product between vector_a and vector_b
655
+ // Compute the inner product between vectors
655
656
  let inner_product = SpatialSimilarity::dot(&vector_a, &vector_b)
656
657
  .expect("Vectors must be of the same length");
657
658
 
658
659
  println!("Inner Product: {}", inner_product);
659
660
 
660
- // Compute the complex inner product between complex_vector_a and complex_vector_b
661
+ // Compute the complex inner product between vectors
661
662
  let complex_inner_product = ComplexProducts::dot(&vector_a, &vector_b)
662
663
  .expect("Vectors must be of the same length");
663
664
 
@@ -705,13 +706,13 @@ fn main() {
705
706
  let vector_a = &[0b11110000, 0b00001111, 0b10101010];
706
707
  let vector_b = &[0b11110000, 0b00001111, 0b01010101];
707
708
 
708
- // Compute the Hamming distance between vector_a and vector_b
709
+ // Compute the Hamming distance between vectors
709
710
  let hamming_distance = u8::hamming(&vector_a, &vector_b)
710
711
  .expect("Vectors must be of the same length");
711
712
 
712
713
  println!("Hamming Distance: {}", hamming_distance);
713
714
 
714
- // Compute the Jaccard distance between vector_a and vector_b
715
+ // Compute the Jaccard distance between vectors
715
716
  let jaccard_distance = u8::jaccard(&vector_a, &vector_b)
716
717
  .expect("Vectors must be of the same length");
717
718
 
@@ -734,11 +735,11 @@ fn main() {
734
735
  let vector_a: Vec<f16> = vec![1.0, 2.0, 3.0].iter().map(|&x| f16::from_f32(x)).collect();
735
736
  let vector_b: Vec<f16> = vec![4.0, 5.0, 6.0].iter().map(|&x| f16::from_f32(x)).collect();
736
737
 
737
- // Compute the cosine similarity
738
- let cosine_similarity = f16::cosine(&vector_a, &vector_b)
738
+ // Compute the cosine distance
739
+ let cosine_distance = f16::cosine(&vector_a, &vector_b)
739
740
  .expect("Vectors must be of the same length");
740
741
 
741
- println!("Cosine Similarity: {}", cosine_similarity);
742
+ println!("Cosine Distance: {}", cosine_distance);
742
743
 
743
744
  // Direct bit manipulation
744
745
  let half = f16::from_f32(3.14159);
@@ -764,10 +765,10 @@ fn main() {
764
765
  let buffer_a: &[SimF16] = unsafe { std::slice::from_raw_parts(vector_a.as_ptr() as *const SimF16, vector_a.len()) };
765
766
  let buffer_b: &[SimF16] = unsafe { std::slice::from_raw_parts(vector_b.as_ptr() as *const SimF16, vector_b.len()) };
766
767
 
767
- let cosine_similarity = SimF16::cosine(buffer_a, buffer_b)
768
+ let cosine_distance = SimF16::cosine(buffer_a, buffer_b)
768
769
  .expect("Vectors must be of the same length");
769
770
 
770
- println!("Cosine Similarity: {}", cosine_similarity);
771
+ println!("Cosine Distance: {}", cosine_distance);
771
772
  }
772
773
  ```
773
774
 
@@ -787,10 +788,10 @@ fn main() {
787
788
  let vector_b: Vec<bf16> = vec![4.0, 5.0, 6.0].iter().map(|&x| bf16::from_f32(x)).collect();
788
789
 
789
790
  // Compute the cosine similarity
790
- let cosine_similarity = bf16::cosine(&vector_a, &vector_b)
791
+ let cosine_distance = bf16::cosine(&vector_a, &vector_b)
791
792
  .expect("Vectors must be of the same length");
792
793
 
793
- println!("Cosine Similarity: {}", cosine_similarity);
794
+ println!("Cosine Distance: {}", cosine_distance);
794
795
 
795
796
  // Direct bit manipulation
796
797
  let brain_half = bf16::from_f32(3.14159);
@@ -899,8 +900,8 @@ import SimSIMD
899
900
  let vectorA: [Int8] = [1, 2, 3]
900
901
  let vectorB: [Int8] = [4, 5, 6]
901
902
 
902
- let cosineSimilarity = vectorA.cosine(vectorB) // Computes the cosine similarity
903
903
  let dotProduct = vectorA.dot(vectorB) // Computes the dot product
904
+ let cosineDistance = vectorA.cosine(vectorB) // Computes the cosine distance
904
905
  let sqEuclidean = vectorA.sqeuclidean(vectorB) // Computes the squared Euclidean distance
905
906
  ```
906
907
 
package/simsimd/build.rs CHANGED
@@ -2,14 +2,15 @@ fn main() -> Result<(), cc::Error> {
2
2
  let mut build = cc::Build::new();
3
3
 
4
4
  build
5
+ // Prefer portable flags to support MSVC and older toolchains
6
+ .std("c99") // Enforce C99 standard when supported
5
7
  .file("c/lib.c")
6
8
  .include("include")
7
9
  .define("SIMSIMD_NATIVE_F16", "0")
8
10
  .define("SIMSIMD_NATIVE_BF16", "0")
9
11
  .define("SIMSIMD_DYNAMIC_DISPATCH", "1")
10
- .flag("-O3")
11
- .flag("-std=c99") // Enforce C99 standard
12
- .flag("-pedantic") // Ensure strict compliance with the C standard
12
+ .opt_level(3)
13
+ .flag_if_supported("-pedantic") // Strict compliance when supported
13
14
  .warnings(false);
14
15
 
15
16
  if let Err(e) = build.try_compile("simsimd") {
package/simsimd/c/lib.c CHANGED
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * - Linux: everything is available in GCC 12+ and Clang 16+.
20
20
  * - Windows - MSVC: everything except Sapphire Rapids and ARM SVE.
21
- * - MacOS - Apple Clang: only Arm NEON and x86 AVX2 Haswell extensions are available.
21
+ * - macOS - Apple Clang: only Arm NEON and x86 AVX2 Haswell extensions are available.
22
22
  */
23
23
  #if !defined(SIMSIMD_TARGET_NEON) && (defined(__APPLE__) || defined(__linux__))
24
24
  #define SIMSIMD_TARGET_NEON 1
@@ -5,8 +5,9 @@
5
5
  * @date July 1, 2023
6
6
  *
7
7
  * Contains:
8
- * - Hamming distance
9
- * - Jaccard similarity (Tanimoto coefficient)
8
+ * - Bit-level Hamming distance
9
+ * - Bit-level Jaccard distance (Tanimoto coefficient)
10
+ * - TODO: Hamming distance for integer vectors - `u16`, `u32`
10
11
  *
11
12
  * For hardware architectures:
12
13
  * - Arm: NEON, SVE
@@ -19,7 +20,7 @@
19
20
  * - Lookup tables, mostly using nibbles (4-bit lookups)
20
21
  * - Harley-Seal population counts: https://arxiv.org/pdf/1611.07612
21
22
  *
22
- * On binary vectors, when computing Jaccard similarity we can clearly see how the CPU struggles
23
+ * On binary vectors, when computing Jaccard distance we can clearly see how the CPU struggles
23
24
  * to compute that many population counts. There are several instructions we should keep in mind
24
25
  * for future optimizations:
25
26
  *
@@ -55,23 +56,23 @@ extern "C" {
55
56
 
56
57
  // clang-format off
57
58
 
58
- /* Serial backends for bitsets. */
59
+ /* Serial backends for bitsets and integers. */
59
60
  SIMSIMD_PUBLIC void simsimd_hamming_b8_serial(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
60
61
  SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
61
62
 
62
- /* Arm NEON backend for bitsets. */
63
+ /* Arm NEON backend for bitsets and integers. */
63
64
  SIMSIMD_PUBLIC void simsimd_hamming_b8_neon(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
64
65
  SIMSIMD_PUBLIC void simsimd_jaccard_b8_neon(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
65
66
 
66
- /* Arm SVE backend for bitsets. */
67
+ /* Arm SVE backend for bitsets and integers. */
67
68
  SIMSIMD_PUBLIC void simsimd_hamming_b8_sve(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
68
69
  SIMSIMD_PUBLIC void simsimd_jaccard_b8_sve(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
69
70
 
70
- /* x86 AVX2 backend for bitsets for Intel Haswell CPUs and newer, needs only POPCNT extensions. */
71
+ /* x86 AVX2 backend for bitsets and integers for Intel Haswell CPUs and newer, needs only POPCNT extensions. */
71
72
  SIMSIMD_PUBLIC void simsimd_hamming_b8_haswell(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
72
73
  SIMSIMD_PUBLIC void simsimd_jaccard_b8_haswell(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
73
74
 
74
- /* x86 AVX512 backend for bitsets for Intel Ice Lake CPUs and newer, using VPOPCNTDQ extensions. */
75
+ /* x86 AVX512 backend for bitsets and integers for Intel Ice Lake CPUs and newer, using VPOPCNTDQ extensions. */
75
76
  SIMSIMD_PUBLIC void simsimd_hamming_b8_ice(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
76
77
  SIMSIMD_PUBLIC void simsimd_jaccard_b8_ice(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words, simsimd_distance_t* distance);
77
78
  // clang-format on
@@ -91,14 +92,14 @@ SIMSIMD_PUBLIC unsigned char simsimd_popcount_b8(simsimd_b8_t x) {
91
92
 
92
93
  SIMSIMD_PUBLIC void simsimd_hamming_b8_serial(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
93
94
  simsimd_distance_t *result) {
94
- simsimd_i32_t differences = 0;
95
+ simsimd_u32_t differences = 0;
95
96
  for (simsimd_size_t i = 0; i != n_words; ++i) differences += simsimd_popcount_b8(a[i] ^ b[i]);
96
97
  *result = differences;
97
98
  }
98
99
 
99
100
  SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
100
101
  simsimd_distance_t *result) {
101
- simsimd_i32_t intersection = 0, union_ = 0;
102
+ simsimd_u32_t intersection = 0, union_ = 0;
102
103
  for (simsimd_size_t i = 0; i != n_words; ++i)
103
104
  intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
104
105
  *result = (union_ != 0) ? 1 - (simsimd_f64_t)intersection / (simsimd_f64_t)union_ : 1;
@@ -545,7 +545,7 @@ SIMSIMD_PUBLIC void simsimd_dot_bf16_neon(simsimd_bf16_t const *a_scalars, simsi
545
545
  float32x4_t ab_vec = vdupq_n_f32(0);
546
546
 
547
547
  simsimd_dot_bf16_neon_cycle:
548
- if (count_scalars < 4) {
548
+ if (count_scalars < 8) {
549
549
  a_vec = _simsimd_partial_load_bf16x8_neon(a_scalars, count_scalars);
550
550
  b_vec = _simsimd_partial_load_bf16x8_neon(b_scalars, count_scalars);
551
551
  count_scalars = 0;
@@ -553,7 +553,7 @@ simsimd_dot_bf16_neon_cycle:
553
553
  else {
554
554
  a_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const *)a_scalars);
555
555
  b_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const *)b_scalars);
556
- a_scalars += 4, b_scalars += 4, count_scalars -= 4;
556
+ a_scalars += 8, b_scalars += 8, count_scalars -= 8;
557
557
  }
558
558
  ab_vec = vbfdotq_f32(ab_vec, a_vec, b_vec);
559
559
  if (count_scalars) goto simsimd_dot_bf16_neon_cycle;
@@ -1163,7 +1163,7 @@ SIMSIMD_PUBLIC void simsimd_dot_i8_haswell(simsimd_i8_t const *a_scalars, simsim
1163
1163
  __m256i b_i16_low_vec = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b_i8_vec, 0));
1164
1164
  __m256i b_i16_high_vec = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b_i8_vec, 1));
1165
1165
 
1166
- // Multiply and accumulate at int16 level, accumulate at `int32` level
1166
+ // Multiply and accumulate at `int16` level, accumulate at `int32` level
1167
1167
  ab_i32_low_vec = _mm256_add_epi32(ab_i32_low_vec, _mm256_madd_epi16(a_i16_low_vec, b_i16_low_vec));
1168
1168
  ab_i32_high_vec = _mm256_add_epi32(ab_i32_high_vec, _mm256_madd_epi16(a_i16_high_vec, b_i16_high_vec));
1169
1169
  }
@@ -1197,7 +1197,7 @@ SIMSIMD_PUBLIC void simsimd_dot_u8_haswell(simsimd_u8_t const *a_scalars, simsim
1197
1197
  __m256i b_i16_low_vec = _mm256_unpacklo_epi8(b_u8_vec, zeros_vec);
1198
1198
  __m256i b_i16_high_vec = _mm256_unpackhi_epi8(b_u8_vec, zeros_vec);
1199
1199
 
1200
- // Multiply and accumulate at int16 level, accumulate at int32 level
1200
+ // Multiply and accumulate at `int16` level, accumulate at `int32` level
1201
1201
  ab_i32_low_vec = _mm256_add_epi32(ab_i32_low_vec, _mm256_madd_epi16(a_i16_low_vec, b_i16_low_vec));
1202
1202
  ab_i32_high_vec = _mm256_add_epi32(ab_i32_high_vec, _mm256_madd_epi16(a_i16_high_vec, b_i16_high_vec));
1203
1203
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file geospatial.h
3
- * @brief SIMD-accelerated Geo-Spatial distance functions.
3
+ * @brief SIMD-accelerated Geospatial distance functions.
4
4
  * @author Ash Vardanian
5
5
  * @date July 1, 2023
6
6
  *
@@ -15,7 +15,7 @@
15
15
  * - 16-bit brain-floating point
16
16
  *
17
17
  * For hardware architectures:
18
- * - Arm: Neon
18
+ * - Arm: NEON
19
19
  * - x86: Genoa, Sapphire
20
20
  *
21
21
  * x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
@@ -389,11 +389,9 @@ simsimd_js_f16_haswell_cycle:
389
389
  b_vec = _mm256_cvtph_ps(_mm_lddqu_si128((__m128i const *)b));
390
390
  n -= 8, a += 8, b += 8;
391
391
  }
392
- a_vec = _mm256_add_ps(a_vec, epsilon_vec);
393
- b_vec = _mm256_add_ps(b_vec, epsilon_vec);
394
392
  __m256 m_vec = _mm256_mul_ps(_mm256_add_ps(a_vec, b_vec), _mm256_set1_ps(0.5f)); // M = (P + Q) / 2
395
- __m256 ratio_a_vec = _mm256_div_ps(a_vec, m_vec);
396
- __m256 ratio_b_vec = _mm256_div_ps(b_vec, m_vec);
393
+ __m256 ratio_a_vec = _mm256_div_ps(_mm256_add_ps(a_vec, epsilon_vec), _mm256_add_ps(m_vec, epsilon_vec));
394
+ __m256 ratio_b_vec = _mm256_div_ps(_mm256_add_ps(b_vec, epsilon_vec), _mm256_add_ps(m_vec, epsilon_vec));
397
395
  __m256 log_ratio_a_vec = _simsimd_log2_f32_haswell(ratio_a_vec);
398
396
  __m256 log_ratio_b_vec = _simsimd_log2_f32_haswell(ratio_b_vec);
399
397
  __m256 prod_a_vec = _mm256_mul_ps(a_vec, log_ratio_a_vec);
@@ -487,13 +485,14 @@ simsimd_js_f32_skylake_cycle:
487
485
  __mmask16 nonzero_mask_a = _mm512_cmp_ps_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
488
486
  __mmask16 nonzero_mask_b = _mm512_cmp_ps_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
489
487
  __mmask16 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
490
- __m512 m_recip_approx = _mm512_rcp14_ps(m_vec);
491
- __m512 ratio_a_vec = _mm512_mul_ps(a_vec, m_recip_approx);
492
- __m512 ratio_b_vec = _mm512_mul_ps(b_vec, m_recip_approx);
488
+ __m512 m_with_epsilon = _mm512_add_ps(m_vec, epsilon_vec);
489
+ __m512 m_recip_approx = _mm512_rcp14_ps(m_with_epsilon);
490
+ __m512 ratio_a_vec = _mm512_mul_ps(_mm512_add_ps(a_vec, epsilon_vec), m_recip_approx);
491
+ __m512 ratio_b_vec = _mm512_mul_ps(_mm512_add_ps(b_vec, epsilon_vec), m_recip_approx);
493
492
  __m512 log_ratio_a_vec = _simsimd_log2_f32_skylake(ratio_a_vec);
494
493
  __m512 log_ratio_b_vec = _simsimd_log2_f32_skylake(ratio_b_vec);
495
- sum_a_vec = _mm512_maskz_fmadd_ps(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
496
- sum_b_vec = _mm512_maskz_fmadd_ps(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
494
+ sum_a_vec = _mm512_mask3_fmadd_ps(a_vec, log_ratio_a_vec, sum_a_vec, nonzero_mask);
495
+ sum_b_vec = _mm512_mask3_fmadd_ps(b_vec, log_ratio_b_vec, sum_b_vec, nonzero_mask);
497
496
  if (n) goto simsimd_js_f32_skylake_cycle;
498
497
 
499
498
  simsimd_f32_t log2_normalizer = 0.693147181f;
@@ -579,13 +578,14 @@ simsimd_js_f16_sapphire_cycle:
579
578
  __mmask32 nonzero_mask_a = _mm512_cmp_ph_mask(a_vec, epsilon_vec, _CMP_GE_OQ);
580
579
  __mmask32 nonzero_mask_b = _mm512_cmp_ph_mask(b_vec, epsilon_vec, _CMP_GE_OQ);
581
580
  __mmask32 nonzero_mask = nonzero_mask_a & nonzero_mask_b;
582
- __m512h m_recip_approx = _mm512_rcp_ph(m_vec);
583
- __m512h ratio_a_vec = _mm512_mul_ph(a_vec, m_recip_approx);
584
- __m512h ratio_b_vec = _mm512_mul_ph(b_vec, m_recip_approx);
581
+ __m512h m_with_epsilon = _mm512_add_ph(m_vec, epsilon_vec);
582
+ __m512h m_recip_approx = _mm512_rcp_ph(m_with_epsilon);
583
+ __m512h ratio_a_vec = _mm512_mul_ph(_mm512_add_ph(a_vec, epsilon_vec), m_recip_approx);
584
+ __m512h ratio_b_vec = _mm512_mul_ph(_mm512_add_ph(b_vec, epsilon_vec), m_recip_approx);
585
585
  __m512h log_ratio_a_vec = _simsimd_log2_f16_sapphire(ratio_a_vec);
586
586
  __m512h log_ratio_b_vec = _simsimd_log2_f16_sapphire(ratio_b_vec);
587
- sum_a_vec = _mm512_maskz_fmadd_ph(nonzero_mask, a_vec, log_ratio_a_vec, sum_a_vec);
588
- sum_b_vec = _mm512_maskz_fmadd_ph(nonzero_mask, b_vec, log_ratio_b_vec, sum_b_vec);
587
+ sum_a_vec = _mm512_mask3_fmadd_ph(a_vec, log_ratio_a_vec, sum_a_vec, nonzero_mask);
588
+ sum_b_vec = _mm512_mask3_fmadd_ph(b_vec, log_ratio_b_vec, sum_b_vec, nonzero_mask);
589
589
  if (n) goto simsimd_js_f16_sapphire_cycle;
590
590
 
591
591
  simsimd_f32_t log2_normalizer = 0.693147181f;