usearch 2.7.2 → 2.7.6

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 (56) hide show
  1. package/.github/workflows/Dockerfile.libs +4 -6
  2. package/.github/workflows/prerelease.yml +4 -1
  3. package/.github/workflows/release.yml +8 -28
  4. package/.github/workflows/update_version.sh +1 -0
  5. package/CITATION.cff +2 -2
  6. package/CMakeLists.txt +1 -1
  7. package/Cargo.toml +1 -1
  8. package/README.md +4 -4
  9. package/binding.gyp +1 -1
  10. package/build.gradle +20 -5
  11. package/conanfile.py +1 -1
  12. package/csharp/nuget/nuget-package.props +1 -1
  13. package/docs/compilation.md +0 -2
  14. package/golang/README.md +0 -1
  15. package/include/usearch/index.hpp +1 -1
  16. package/include/usearch/index_plugins.hpp +119 -243
  17. package/javascript/lib.cpp +4 -3
  18. package/package.json +1 -1
  19. package/simsimd/.github/workflows/prerelease.yml +33 -58
  20. package/simsimd/.github/workflows/release.yml +22 -9
  21. package/simsimd/.github/workflows/update_version.sh +3 -1
  22. package/simsimd/.releaserc +9 -1
  23. package/simsimd/.vscode/launch.json +3 -0
  24. package/simsimd/.vscode/settings.json +15 -2
  25. package/simsimd/.vscode/tasks.json +2 -2
  26. package/simsimd/CMakeLists.txt +36 -55
  27. package/simsimd/README.md +163 -109
  28. package/simsimd/binding.gyp +18 -0
  29. package/simsimd/cpp/bench.cxx +187 -0
  30. package/simsimd/go.mod +3 -0
  31. package/simsimd/golang/bench_test.go +40 -0
  32. package/simsimd/golang/simsimd.go +47 -0
  33. package/simsimd/golang/unit_test.go +40 -0
  34. package/simsimd/include/simsimd/binary.h +186 -0
  35. package/simsimd/include/simsimd/probability.h +645 -0
  36. package/simsimd/include/simsimd/simsimd.h +335 -575
  37. package/simsimd/include/simsimd/spatial.h +924 -0
  38. package/simsimd/include/simsimd/types.h +196 -0
  39. package/simsimd/javascript/bench.js +117 -0
  40. package/simsimd/javascript/lib.c +102 -0
  41. package/simsimd/javascript/simsimd.js +45 -0
  42. package/simsimd/javascript/test.js +29 -0
  43. package/simsimd/package.json +31 -2
  44. package/simsimd/pyproject.toml +5 -6
  45. package/simsimd/python/bench.ipynb +187 -0
  46. package/simsimd/python/bench.py +268 -0
  47. package/simsimd/python/lib.c +453 -72
  48. package/simsimd/python/test.py +167 -35
  49. package/simsimd/setup.py +51 -8
  50. package/wasmer.toml +1 -1
  51. package/simsimd/bench.cxx +0 -137
  52. package/simsimd/include/simsimd/simsimd_bio.h +0 -54
  53. package/simsimd/include/simsimd/simsimd_chem.h +0 -167
  54. package/simsimd/include/simsimd/simsimd_text.h +0 -92
  55. package/simsimd/python/run.sh +0 -1
  56. package/simsimd/python/test.c +0 -109
@@ -0,0 +1,187 @@
1
+ #include <cmath> // `std::sqrt`
2
+ #include <thread> // `std::thread`
3
+
4
+ #include <benchmark/benchmark.h>
5
+
6
+ #define SIMSIMD_RSQRT simsimd_approximate_inverse_square_root
7
+ #define SIMSIMD_LOG simsimd_approximate_log
8
+ #include <simsimd/simsimd.h>
9
+
10
+ namespace bm = benchmark;
11
+
12
+ template <typename return_at, typename... args_at>
13
+ constexpr std::size_t number_of_arguments(return_at (*f)(args_at...)) {
14
+ return sizeof...(args_at);
15
+ }
16
+
17
+ template <typename scalar_at, std::size_t dimensions_ak> struct vectors_pair_gt {
18
+ scalar_at a[dimensions_ak]{};
19
+ scalar_at b[dimensions_ak]{};
20
+
21
+ std::size_t dimensions() const noexcept { return dimensions_ak; }
22
+ std::size_t size_bytes() const noexcept { return dimensions_ak * sizeof(scalar_at); }
23
+
24
+ void set(scalar_at v) noexcept {
25
+ for (std::size_t i = 0; i != dimensions_ak; ++i)
26
+ a[i] = b[i] = v;
27
+ }
28
+
29
+ void randomize() noexcept {
30
+
31
+ double a2_sum = 0, b2_sum = 0;
32
+ for (std::size_t i = 0; i != dimensions_ak; ++i) {
33
+ if constexpr (std::is_integral_v<scalar_at>)
34
+ a[i] = static_cast<scalar_at>(rand()), b[i] = static_cast<scalar_at>(rand());
35
+ else {
36
+ double ai = double(rand()) / double(RAND_MAX), bi = double(rand()) / double(RAND_MAX);
37
+ a2_sum += ai * ai, b2_sum += bi * bi;
38
+ a[i] = static_cast<scalar_at>(ai), b[i] = static_cast<scalar_at>(bi);
39
+ }
40
+ }
41
+
42
+ // Normalize the vectors:
43
+ if constexpr (!std::is_integral_v<scalar_at>) {
44
+ a2_sum = std::sqrt(a2_sum);
45
+ b2_sum = std::sqrt(b2_sum);
46
+ for (std::size_t i = 0; i != dimensions_ak; ++i)
47
+ a[i] /= a2_sum, b[i] /= b2_sum;
48
+ }
49
+ }
50
+ };
51
+
52
+ template <typename pair_at, typename metric_at = void>
53
+ static void measure(bm::State& state, metric_at metric, metric_at baseline) {
54
+
55
+ pair_at pair;
56
+ pair.randomize();
57
+ // pair.set(1);
58
+
59
+ double c_baseline = baseline(pair.a, pair.b, pair.dimensions());
60
+ double c = 0;
61
+ std::size_t iterations = 0;
62
+ for (auto _ : state)
63
+ bm::DoNotOptimize((c = metric(pair.a, pair.b, pair.dimensions()))), iterations++;
64
+
65
+ state.counters["bytes"] = bm::Counter(iterations * pair.size_bytes() * 2, bm::Counter::kIsRate);
66
+ state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
67
+
68
+ double delta = std::abs(c - c_baseline) > 0.0001 ? std::abs(c - c_baseline) : 0;
69
+ double error = delta != 0 && c_baseline != 0 ? delta / c_baseline : 0;
70
+ state.counters["abs_delta"] = delta;
71
+ state.counters["relative_error"] = error;
72
+ }
73
+
74
+ template <typename scalar_at, typename metric_at = void>
75
+ void register_(std::string name, metric_at* distance_func, metric_at* baseline_func) {
76
+
77
+ using pair_dims_t = vectors_pair_gt<scalar_at, 1536>;
78
+ using pair_bytes_t = vectors_pair_gt<scalar_at, 1536 / sizeof(scalar_at)>;
79
+
80
+ std::size_t seconds = 10;
81
+ std::size_t threads = std::thread::hardware_concurrency(); // 1;
82
+ std::string name_dims = name + "_" + std::to_string(pair_dims_t{}.dimensions()) + "d";
83
+ std::string name_bytes = name + "_" + std::to_string(pair_bytes_t{}.size_bytes()) + "b";
84
+
85
+ bm::RegisterBenchmark(name_dims.c_str(), measure<pair_dims_t, metric_at*>, distance_func, baseline_func)
86
+ ->MinTime(seconds)
87
+ ->Threads(threads);
88
+ bm::RegisterBenchmark(name_bytes.c_str(), measure<pair_bytes_t, metric_at*>, distance_func, baseline_func)
89
+ ->MinTime(seconds)
90
+ ->Threads(threads);
91
+ }
92
+
93
+ int main(int argc, char** argv) {
94
+
95
+ bool compiled_with_sve = false;
96
+ bool compiled_with_neon = false;
97
+ bool compiled_with_avx2 = false;
98
+ bool compiled_with_avx512popcnt = false;
99
+
100
+ #if defined(__ARM_FEATURE_SVE)
101
+ compiled_with_sve = true;
102
+ #endif
103
+ #if defined(__ARM_NEON)
104
+ compiled_with_neon = true;
105
+ #endif
106
+ #if defined(__AVX2__)
107
+ compiled_with_avx2 = true;
108
+ #endif
109
+ #if defined(__AVX512VPOPCNTDQ__)
110
+ compiled_with_avx512popcnt = true;
111
+ #endif
112
+
113
+ // Log supported functionality
114
+ char const* flags[2] = {"false", "true"};
115
+ std::printf("Benchmarking Similarity Measures\n");
116
+ std::printf("\n");
117
+ std::printf("- Arm NEON support enabled: %s\n", flags[compiled_with_neon]);
118
+ std::printf("- Arm SVE support enabled: %s\n", flags[compiled_with_sve]);
119
+ std::printf("- x86 AVX2 support enabled: %s\n", flags[compiled_with_avx2]);
120
+ std::printf("- x86 AVX512VPOPCNTDQ support enabled: %s\n", flags[compiled_with_avx512popcnt]);
121
+ std::printf("\n");
122
+
123
+ // Run the benchmarks
124
+ bm::Initialize(&argc, argv);
125
+ if (bm::ReportUnrecognizedArguments(argc, argv))
126
+ return 1;
127
+
128
+ #if SIMSIMD_TARGET_ARM_NEON
129
+ register_<simsimd_f16_t>("neon_f16_ip", simsimd_neon_f16_ip, simsimd_accurate_f16_ip);
130
+ register_<simsimd_f16_t>("neon_f16_cos", simsimd_neon_f16_cos, simsimd_accurate_f16_cos);
131
+ register_<simsimd_f16_t>("neon_f16_l2sq", simsimd_neon_f16_l2sq, simsimd_accurate_f16_l2sq);
132
+
133
+ register_<simsimd_f32_t>("neon_f32_ip", simsimd_neon_f32_ip, simsimd_accurate_f32_ip);
134
+ register_<simsimd_f32_t>("neon_f32_cos", simsimd_neon_f32_cos, simsimd_accurate_f32_cos);
135
+ register_<simsimd_f32_t>("neon_f32_l2sq", simsimd_neon_f32_l2sq, simsimd_accurate_f32_l2sq);
136
+
137
+ register_<simsimd_i8_t>("neon_i8_cos", simsimd_neon_i8_cos, simsimd_accurate_i8_cos);
138
+ register_<simsimd_i8_t>("neon_i8_l2sq", simsimd_neon_i8_l2sq, simsimd_accurate_i8_l2sq);
139
+ #endif
140
+
141
+ #if SIMSIMD_TARGET_ARM_SVE
142
+ register_<simsimd_f16_t>("sve_f16_ip", simsimd_sve_f16_ip, simsimd_accurate_f16_ip);
143
+ register_<simsimd_f16_t>("sve_f16_cos", simsimd_sve_f16_cos, simsimd_accurate_f16_cos);
144
+ register_<simsimd_f16_t>("sve_f16_l2sq", simsimd_sve_f16_l2sq, simsimd_accurate_f16_l2sq);
145
+
146
+ register_<simsimd_f32_t>("sve_f32_ip", simsimd_sve_f32_ip, simsimd_accurate_f32_ip);
147
+ register_<simsimd_f32_t>("sve_f32_cos", simsimd_sve_f32_cos, simsimd_accurate_f32_cos);
148
+ register_<simsimd_f32_t>("sve_f32_l2sq", simsimd_sve_f32_l2sq, simsimd_accurate_f32_l2sq);
149
+ #endif
150
+
151
+ #if SIMSIMD_TARGET_X86_AVX2
152
+ register_<simsimd_f16_t>("avx2_f16_ip", simsimd_avx2_f16_ip, simsimd_accurate_f16_ip);
153
+ register_<simsimd_f16_t>("avx2_f16_cos", simsimd_avx2_f16_cos, simsimd_accurate_f16_cos);
154
+ register_<simsimd_f16_t>("avx2_f16_l2sq", simsimd_avx2_f16_l2sq, simsimd_accurate_f16_l2sq);
155
+
156
+ register_<simsimd_i8_t>("avx2_i8_cos", simsimd_avx2_i8_cos, simsimd_accurate_i8_cos);
157
+ register_<simsimd_i8_t>("avx2_i8_l2sq", simsimd_avx2_i8_l2sq, simsimd_accurate_i8_l2sq);
158
+ #endif
159
+
160
+ #if SIMSIMD_TARGET_X86_AVX512
161
+ register_<simsimd_f16_t>("avx512_f16_ip", simsimd_avx512_f16_ip, simsimd_accurate_f16_ip);
162
+ register_<simsimd_f16_t>("avx512_f16_cos", simsimd_avx512_f16_cos, simsimd_accurate_f16_cos);
163
+ register_<simsimd_f16_t>("avx512_f16_l2sq", simsimd_avx512_f16_l2sq, simsimd_accurate_f16_l2sq);
164
+
165
+ register_<simsimd_i8_t>("avx512_i8_cos", simsimd_avx512_i8_cos, simsimd_accurate_i8_cos);
166
+ register_<simsimd_i8_t>("avx512_i8_l2sq", simsimd_avx512_i8_l2sq, simsimd_accurate_i8_l2sq);
167
+
168
+ register_<simsimd_f32_t>("avx512_f32_ip", simsimd_avx512_f32_ip, simsimd_accurate_f32_ip);
169
+ register_<simsimd_f32_t>("avx512_f32_cos", simsimd_avx512_f32_cos, simsimd_accurate_f32_cos);
170
+ register_<simsimd_f32_t>("avx512_f32_l2sq", simsimd_avx512_f32_l2sq, simsimd_accurate_f32_l2sq);
171
+ #endif
172
+
173
+ register_<simsimd_f16_t>("serial_f16_ip", simsimd_serial_f16_ip, simsimd_accurate_f16_ip);
174
+ register_<simsimd_f16_t>("serial_f16_cos", simsimd_serial_f16_cos, simsimd_accurate_f16_cos);
175
+ register_<simsimd_f16_t>("serial_f16_l2sq", simsimd_serial_f16_l2sq, simsimd_accurate_f16_l2sq);
176
+
177
+ register_<simsimd_f32_t>("serial_f32_ip", simsimd_serial_f32_ip, simsimd_accurate_f32_ip);
178
+ register_<simsimd_f32_t>("serial_f32_cos", simsimd_serial_f32_cos, simsimd_accurate_f32_cos);
179
+ register_<simsimd_f32_t>("serial_f32_l2sq", simsimd_serial_f32_l2sq, simsimd_accurate_f32_l2sq);
180
+
181
+ register_<simsimd_i8_t>("serial_i8_cos", simsimd_serial_i8_cos, simsimd_accurate_i8_cos);
182
+ register_<simsimd_i8_t>("serial_i8_l2sq", simsimd_serial_i8_l2sq, simsimd_accurate_i8_l2sq);
183
+
184
+ bm::RunSpecifiedBenchmarks();
185
+ bm::Shutdown();
186
+ return 0;
187
+ }
package/simsimd/go.mod ADDED
@@ -0,0 +1,3 @@
1
+ module github.com/ashvardanian/simsimd
2
+
3
+ go 1.19
@@ -0,0 +1,40 @@
1
+ package simsimd
2
+
3
+ import (
4
+ "math"
5
+ "math/rand"
6
+ "testing"
7
+ )
8
+
9
+ func cosineSimilarity(a, b []float32) float32 {
10
+ var dotProduct float32
11
+ var normA, normB float32
12
+ for i := range a {
13
+ dotProduct += a[i] * b[i]
14
+ normA += a[i] * a[i]
15
+ normB += b[i] * b[i]
16
+ }
17
+ return dotProduct / (float32(math.Sqrt(float64(normA))) * float32(math.Sqrt(float64(normB))))
18
+ }
19
+
20
+ func generateRandomVector(dim int) []float32 {
21
+ vec := make([]float32, dim)
22
+ for i := range vec {
23
+ vec[i] = rand.Float32()
24
+ }
25
+ return vec
26
+ }
27
+
28
+ func BenchmarkCosineSimilarityNative(b *testing.B) {
29
+ first, second := generateRandomVector(1536), generateRandomVector(1536)
30
+ for i := 0; i < b.N; i++ {
31
+ cosineSimilarity(first, second)
32
+ }
33
+ }
34
+
35
+ func BenchmarkCosineSimilaritySIMD(b *testing.B) {
36
+ first, second := generateRandomVector(1536), generateRandomVector(1536)
37
+ for i := 0; i < b.N; i++ {
38
+ CosineF32(first, second)
39
+ }
40
+ }
@@ -0,0 +1,47 @@
1
+ package simsimd
2
+
3
+ /*
4
+ #cgo CFLAGS: -O3
5
+ #cgo LDFLAGS: -O3 -L.
6
+ #include "../simsimd/simsimd.h"
7
+ #include <stdlib.h>
8
+
9
+ inline static simsimd_f32_t cosine_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_cosine_k, simsimd_datatype_i8_k, simsimd_cap_any_k)(a, b, d, d); }
10
+ inline static simsimd_f32_t cosine_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) { return simsimd_neon_f32_cos(a, b, d); }
11
+ inline static simsimd_f32_t inner_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_inner_k, simsimd_datatype_i8_k, simsimd_cap_any_k)(a, b, d, d); }
12
+ inline static simsimd_f32_t inner_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_inner_k, simsimd_datatype_f32_k, simsimd_cap_any_k)(a, b, d, d); }
13
+ inline static simsimd_f32_t sqeuclidean_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_i8_k, simsimd_cap_any_k)(a, b, d, d); }
14
+ inline static simsimd_f32_t sqeuclidean_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_f32_k, simsimd_cap_any_k)(a, b, d, d); }
15
+ */
16
+ import "C"
17
+
18
+
19
+ // CosineI8 computes the cosine similarity between two i8 vectors using the most suitable SIMD instruction set available.
20
+ func CosineI8(a, b []int8) float32 {
21
+ return float32(C.cosine_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a))))
22
+ }
23
+
24
+ // CosineF32 computes the cosine similarity between two i8 vectors using the most suitable SIMD instruction set available.
25
+ func CosineF32(a, b []float32) float32 {
26
+ return float32(C.cosine_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a))))
27
+ }
28
+
29
+ // InnerI8 computes the inner-product similarity between two i8 vectors using the most suitable SIMD instruction set available.
30
+ func InnerI8(a, b []int8) float32 {
31
+ return float32(C.inner_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a))))
32
+ }
33
+
34
+ // InnerF32 computes the inner-product similarity between two i8 vectors using the most suitable SIMD instruction set available.
35
+ func InnerF32(a, b []float32) float32 {
36
+ return float32(C.inner_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a))))
37
+ }
38
+
39
+ // SqEuclideanI8 computes the squared euclidean similarity between two i8 vectors using the most suitable SIMD instruction set available.
40
+ func SqEuclideanI8(a, b []int8) float32 {
41
+ return float32(C.sqeuclidean_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a))))
42
+ }
43
+
44
+ // SqEuclideanF32 computes the squared euclidean similarity between two i8 vectors using the most suitable SIMD instruction set available.
45
+ func SqEuclideanF32(a, b []float32) float32 {
46
+ return float32(C.sqeuclidean_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a))))
47
+ }
@@ -0,0 +1,40 @@
1
+ package simsimd
2
+
3
+ import (
4
+ "math"
5
+ "testing"
6
+ )
7
+
8
+ func TestCosineI8(t *testing.T) {
9
+ a := []int8{1, 0}
10
+ b := []int8{0, 1}
11
+
12
+ result := CosineI8(a, b)
13
+ expected := float32(1.0) // Cosine similarity of orthogonal vectors is 0
14
+ if math.Abs(float64(result-expected)) > 1e-3 {
15
+ t.Errorf("Expected %v, got %v", expected, result)
16
+ }
17
+ }
18
+
19
+ func TestCosineF32(t *testing.T) {
20
+ a := []float32{1, 0}
21
+ b := []float32{0, 1}
22
+
23
+ result := CosineF32(a, b)
24
+ expected := float32(1.0) // Cosine similarity of orthogonal vectors is 0
25
+ if math.Abs(float64(result-expected)) > 1e-3 {
26
+ t.Errorf("Expected %v, got %v", expected, result)
27
+ }
28
+ }
29
+
30
+ func TestVectorLengthMismatch(t *testing.T) {
31
+ defer func() {
32
+ if r := recover(); r == nil {
33
+ t.Errorf("The code did not panic")
34
+ }
35
+ }()
36
+
37
+ a := []int8{1, 0}
38
+ b := []int8{0}
39
+ _ = CosineI8(a, b) // This should panic
40
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @brief SIMD-accelerated Binary Similarity Measures.
3
+ * @author Ash Vardanian
4
+ * @date July 1, 2023
5
+ *
6
+ * Contains:
7
+ * - Hamming distance
8
+ * - Jaccard similarity (Tanimoto coefficient)
9
+ *
10
+ * For datatypes:
11
+ * - 8-bit bitsets
12
+ *
13
+ * For hardware architectures:
14
+ * - Arm (NEON, SVE)
15
+ * - x86 (AVX512)
16
+ *
17
+ * x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
18
+ * Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
19
+ */
20
+
21
+ #pragma once
22
+ #include "types.h"
23
+
24
+ #ifdef __cplusplus
25
+ extern "C" {
26
+ #endif
27
+
28
+ inline static unsigned char simsimd_popcount_b8(simsimd_b8_t x) {
29
+ static unsigned char lookup_table[] = {
30
+ 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //
31
+ 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
32
+ 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
33
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
34
+ 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
35
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
36
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
37
+ 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
38
+ return lookup_table[x];
39
+ }
40
+
41
+ inline static simsimd_f32_t simsimd_serial_b8_hamming( //
42
+ simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
43
+ simsimd_i32_t differences = 0;
44
+ for (simsimd_size_t i = 0; i != n_words; ++i)
45
+ differences += simsimd_popcount_b8(a[i] ^ b[i]);
46
+ return (simsimd_f32_t)differences;
47
+ }
48
+
49
+ inline static simsimd_f32_t simsimd_serial_b8_jaccard( //
50
+ simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
51
+ simsimd_i32_t intersection = 0, union_ = 0;
52
+ for (simsimd_size_t i = 0; i != n_words; ++i)
53
+ intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
54
+ return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
55
+ }
56
+
57
+ #if SIMSIMD_TARGET_ARM
58
+ #if SIMSIMD_TARGET_ARM_NEON
59
+
60
+ __attribute__((target("+simd"))) //
61
+ inline static simsimd_f32_t
62
+ simsimd_neon_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
63
+ simsimd_i32_t differences = 0;
64
+ simsimd_size_t i = 0;
65
+ for (; i + 16 <= n_words; i += 16) {
66
+ uint8x16_t a_first = vld1q_u8(a + i);
67
+ uint8x16_t b_first = vld1q_u8(b + i);
68
+ differences += vaddvq_u8(vcntq_u8(veorq_u8(a_first, b_first)));
69
+ }
70
+ // Handle the tail
71
+ for (; i != n_words; ++i)
72
+ differences += simsimd_popcount_b8(a[i] ^ b[i]);
73
+ return (simsimd_f32_t)differences;
74
+ }
75
+
76
+ __attribute__((target("+simd"))) //
77
+ inline static simsimd_f32_t
78
+ simsimd_neon_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
79
+ simsimd_i32_t intersection = 0, union_ = 0;
80
+ simsimd_size_t i = 0;
81
+ for (; i + 16 <= n_words; i += 16) {
82
+ uint8x16_t a_first = vld1q_u8(a + i);
83
+ uint8x16_t b_first = vld1q_u8(b + i);
84
+ intersection += vaddvq_u8(vcntq_u8(vandq_u8(a_first, b_first)));
85
+ union_ += vaddvq_u8(vcntq_u8(vorrq_u8(a_first, b_first)));
86
+ }
87
+ // Handle the tail
88
+ for (; i != n_words; ++i)
89
+ intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
90
+ return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
91
+ }
92
+
93
+ #endif // SIMSIMD_TARGET_ARM_NEON
94
+
95
+ #if SIMSIMD_TARGET_ARM_SVE
96
+
97
+ __attribute__((target("+sve"))) //
98
+ inline static simsimd_f32_t
99
+ simsimd_sve_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
100
+ simsimd_size_t i = 0;
101
+ simsimd_i32_t differences = 0;
102
+ do {
103
+ svbool_t pg_vec = svwhilelt_b8((unsigned int)i, (unsigned int)n_words);
104
+ svuint8_t a_vec = svld1_u8(pg_vec, a + i);
105
+ svuint8_t b_vec = svld1_u8(pg_vec, b + i);
106
+ differences += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), sveor_u8_m(svptrue_b8(), a_vec, b_vec)));
107
+ i += svcntb();
108
+ } while (i < n_words);
109
+ return (simsimd_f32_t)differences;
110
+ }
111
+
112
+ __attribute__((target("+sve"))) //
113
+ inline static simsimd_f32_t
114
+ simsimd_sve_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
115
+ simsimd_size_t i = 0;
116
+ simsimd_i32_t intersection = 0, union_ = 0;
117
+ do {
118
+ svbool_t pg_vec = svwhilelt_b8((unsigned int)i, (unsigned int)n_words);
119
+ svuint8_t a_vec = svld1_u8(pg_vec, a + i);
120
+ svuint8_t b_vec = svld1_u8(pg_vec, b + i);
121
+ intersection += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), svand_u8_m(svptrue_b8(), a_vec, b_vec)));
122
+ union_ += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), svorr_u8_m(svptrue_b8(), a_vec, b_vec)));
123
+ i += svcntb();
124
+ } while (i < n_words);
125
+ return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
126
+ }
127
+
128
+ #endif // SIMSIMD_TARGET_ARM_SVE
129
+
130
+ #endif // SIMSIMD_TARGET_ARM
131
+
132
+ #if SIMSIMD_TARGET_X86
133
+
134
+ #if SIMSIMD_TARGET_X86_AVX512
135
+
136
+ __attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
137
+ inline static simsimd_f32_t
138
+ simsimd_avx512_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
139
+ __m512i differences_vec = _mm512_setzero_si512(), union_vec = _mm512_setzero_si512();
140
+ for (simsimd_size_t i = 0; i < n_words; i += 64) {
141
+
142
+ // Compute mask for tail elements
143
+ __mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
144
+
145
+ __m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
146
+ __m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
147
+ __m512i xor_vec = _mm512_xor_si512(a_vec, b_vec);
148
+
149
+ differences_vec = _mm512_add_epi64(differences_vec, _mm512_popcnt_epi64(xor_vec));
150
+ }
151
+
152
+ simsimd_size_t differences = _mm512_reduce_add_epi64(differences_vec);
153
+ return (simsimd_f32_t)differences;
154
+ }
155
+
156
+ __attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
157
+ inline static simsimd_f32_t
158
+ simsimd_avx512_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
159
+ __m512i intersection_vec = _mm512_setzero_si512(), union_vec = _mm512_setzero_si512();
160
+ for (simsimd_size_t i = 0; i < n_words; i += 64) {
161
+
162
+ // Compute mask for tail elements
163
+ __mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
164
+
165
+ __m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
166
+ __m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
167
+ __m512i and_vec = _mm512_and_si512(a_vec, b_vec);
168
+ __m512i or_vec = _mm512_or_si512(a_vec, b_vec);
169
+
170
+ intersection_vec = _mm512_add_epi64(intersection_vec, _mm512_popcnt_epi64(and_vec));
171
+ union_vec = _mm512_add_epi64(union_vec, _mm512_popcnt_epi64(or_vec));
172
+ }
173
+
174
+ simsimd_size_t intersection = _mm512_reduce_add_epi64(intersection_vec),
175
+ union_ = _mm512_reduce_add_epi64(union_vec);
176
+ return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
177
+ }
178
+
179
+ #endif // SIMSIMD_TARGET_X86_AVX512
180
+ #endif // SIMSIMD_TARGET_X86
181
+
182
+ #undef popcount64
183
+
184
+ #ifdef __cplusplus
185
+ }
186
+ #endif