usearch 2.7.3 → 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 (55) 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/package.json +1 -1
  18. package/simsimd/.github/workflows/prerelease.yml +33 -58
  19. package/simsimd/.github/workflows/release.yml +22 -9
  20. package/simsimd/.github/workflows/update_version.sh +3 -1
  21. package/simsimd/.releaserc +9 -1
  22. package/simsimd/.vscode/launch.json +3 -0
  23. package/simsimd/.vscode/settings.json +15 -2
  24. package/simsimd/.vscode/tasks.json +2 -2
  25. package/simsimd/CMakeLists.txt +36 -55
  26. package/simsimd/README.md +163 -109
  27. package/simsimd/binding.gyp +18 -0
  28. package/simsimd/cpp/bench.cxx +187 -0
  29. package/simsimd/go.mod +3 -0
  30. package/simsimd/golang/bench_test.go +40 -0
  31. package/simsimd/golang/simsimd.go +47 -0
  32. package/simsimd/golang/unit_test.go +40 -0
  33. package/simsimd/include/simsimd/binary.h +186 -0
  34. package/simsimd/include/simsimd/probability.h +645 -0
  35. package/simsimd/include/simsimd/simsimd.h +335 -575
  36. package/simsimd/include/simsimd/spatial.h +924 -0
  37. package/simsimd/include/simsimd/types.h +196 -0
  38. package/simsimd/javascript/bench.js +117 -0
  39. package/simsimd/javascript/lib.c +102 -0
  40. package/simsimd/javascript/simsimd.js +45 -0
  41. package/simsimd/javascript/test.js +29 -0
  42. package/simsimd/package.json +31 -2
  43. package/simsimd/pyproject.toml +5 -6
  44. package/simsimd/python/bench.ipynb +187 -0
  45. package/simsimd/python/bench.py +268 -0
  46. package/simsimd/python/lib.c +453 -72
  47. package/simsimd/python/test.py +167 -35
  48. package/simsimd/setup.py +51 -8
  49. package/wasmer.toml +1 -1
  50. package/simsimd/bench.cxx +0 -137
  51. package/simsimd/include/simsimd/simsimd_bio.h +0 -54
  52. package/simsimd/include/simsimd/simsimd_chem.h +0 -167
  53. package/simsimd/include/simsimd/simsimd_text.h +0 -92
  54. package/simsimd/python/run.sh +0 -1
  55. package/simsimd/python/test.c +0 -109
@@ -1,38 +1,170 @@
1
- import platform
2
-
1
+ import pytest
2
+ import numpy as np
3
3
  import simsimd as simd
4
+ import scipy.spatial.distance as spd
5
+
6
+ SIMSIMD_RTOL = 2
7
+ SIMSIMD_ATOL = 3e-1
8
+
9
+
10
+ def test_pointers_availability():
11
+ """Tests the availability of pre-compiled functions for compatibility with USearch."""
12
+ assert simd.pointer_to_sqeuclidean("f32") != 0
13
+ assert simd.pointer_to_cosine("f32") != 0
14
+ assert simd.pointer_to_inner("f32") != 0
15
+
16
+ assert simd.pointer_to_sqeuclidean("f16") != 0
17
+ assert simd.pointer_to_cosine("f16") != 0
18
+ assert simd.pointer_to_inner("f16") != 0
19
+
20
+ assert simd.pointer_to_sqeuclidean("i8") != 0
21
+ assert simd.pointer_to_cosine("i8") != 0
22
+ assert simd.pointer_to_inner("i8") != 0
23
+
24
+
25
+ @pytest.mark.repeat(50)
26
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
27
+ @pytest.mark.parametrize("dtype", [np.float32, np.float16])
28
+ def test_dot(ndim, dtype):
29
+ """Compares the simd.dot() function with numpy.dot(), measuring the accuracy error for f16, and f32 types."""
30
+ a = np.random.randn(ndim).astype(dtype)
31
+ b = np.random.randn(ndim).astype(dtype)
32
+ a /= np.linalg.norm(a)
33
+ b /= np.linalg.norm(b)
34
+
35
+ expected = 1 - np.inner(a, b)
36
+ result = simd.inner(a, b)
37
+
38
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
39
+
40
+
41
+ @pytest.mark.repeat(50)
42
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
43
+ @pytest.mark.parametrize("dtype", [np.float32, np.float16])
44
+ def test_sqeuclidean(ndim, dtype):
45
+ """Compares the simd.sqeuclidean() function with scipy.spatial.distance.sqeuclidean(), measuring the accuracy error for f16, and f32 types."""
46
+ a = np.random.randn(ndim).astype(dtype)
47
+ b = np.random.randn(ndim).astype(dtype)
48
+
49
+ expected = spd.sqeuclidean(a, b)
50
+ result = simd.sqeuclidean(a, b)
51
+
52
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
53
+
54
+
55
+ @pytest.mark.repeat(50)
56
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
57
+ @pytest.mark.parametrize("dtype", [np.float32, np.float16])
58
+ def test_cosine(ndim, dtype):
59
+ """Compares the simd.cosine() function with scipy.spatial.distance.cosine(), measuring the accuracy error for f16, and f32 types."""
60
+ a = np.random.randn(ndim).astype(dtype)
61
+ b = np.random.randn(ndim).astype(dtype)
62
+
63
+ expected = spd.cosine(a, b)
64
+ result = simd.cosine(a, b)
65
+
66
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
67
+
68
+
69
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
70
+ @pytest.mark.parametrize("dtype", [np.float32, np.float16])
71
+ def test_cosine_zero_vector(ndim, dtype):
72
+ """Tests the simd.cosine() function with zero vectors, to catch division by zero errors."""
73
+ a = np.zeros(ndim, dtype=dtype)
74
+ b = np.random.randn(ndim).astype(dtype)
75
+
76
+ # SciPy raises: "RuntimeWarning: invalid value encountered in scalar divide"
77
+ with pytest.raises(RuntimeWarning):
78
+ expected = spd.cosine(a, b)
79
+
80
+ expected = 1
81
+ result = simd.cosine(a, b)
82
+
83
+ assert result == expected, f"Expected {expected}, but got {result}"
84
+
85
+
86
+ @pytest.mark.repeat(50)
87
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
88
+ def test_hamming(ndim):
89
+ """Compares the simd.hamming() function with scipy.spatial.distance.hamming."""
90
+ a = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
91
+ b = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
92
+
93
+ expected = spd.hamming(a, b)
94
+ result = simd.hamming(a, b)
95
+
96
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
97
+
98
+
99
+ @pytest.mark.repeat(50)
100
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
101
+ def test_jaccard(ndim):
102
+ """Compares the simd.jaccard() function with scipy.spatial.distance.jaccard."""
103
+ a = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
104
+ b = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
105
+
106
+ expected = spd.jaccard(a, b)
107
+ result = simd.jaccard(a, b)
108
+
109
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
110
+
111
+
112
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
113
+ @pytest.mark.parametrize("dtype", [np.float32, np.float16])
114
+ def test_batch(ndim, dtype):
115
+ """Compares the simd.simd.sqeuclidean() function with scipy.spatial.distance.sqeuclidean() for a batch of vectors, measuring the accuracy error for f16, and f32 types."""
116
+
117
+ # Distance between matrixes A (N x D scalars) and B (N x D scalars) is an array with N floats.
118
+ A = np.random.randn(10, ndim).astype(dtype)
119
+ B = np.random.randn(10, ndim).astype(dtype)
120
+ result_np = [spd.sqeuclidean(A[i], B[i]) for i in range(10)]
121
+ result_simd = simd.sqeuclidean(A, B)
122
+ assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
123
+
124
+ # Distance between matrixes A (N x D scalars) and B (1 x D scalars) is an array with N floats.
125
+ B = np.random.randn(1, ndim).astype(dtype)
126
+ result_np = [spd.sqeuclidean(A[i], B[0]) for i in range(10)]
127
+ result_simd = simd.sqeuclidean(A, B)
128
+ assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
129
+
130
+ # Distance between matrixes A (1 x D scalars) and B (N x D scalars) is an array with N floats.
131
+ A = np.random.randn(1, ndim).astype(dtype)
132
+ B = np.random.randn(10, ndim).astype(dtype)
133
+ result_np = [spd.sqeuclidean(A[0], B[i]) for i in range(10)]
134
+ result_simd = simd.sqeuclidean(A, B)
135
+ assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
136
+
137
+ # Distance between matrix A (N x D scalars) and array B (D scalars) is an array with N floats.
138
+ A = np.random.randn(10, ndim).astype(dtype)
139
+ B = np.random.randn(ndim).astype(dtype)
140
+ result_np = [spd.sqeuclidean(A[i], B) for i in range(10)]
141
+ result_simd = simd.sqeuclidean(A, B)
142
+ assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
143
+
144
+ # Distance between matrix B (N x D scalars) and array A (D scalars) is an array with N floats.
145
+ B = np.random.randn(10, ndim).astype(dtype)
146
+ A = np.random.randn(ndim).astype(dtype)
147
+ result_np = [spd.sqeuclidean(B[i], A) for i in range(10)]
148
+ result_simd = simd.sqeuclidean(B, A)
149
+ assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
150
+
151
+
152
+ @pytest.mark.parametrize("ndim", [3, 97, 1536])
153
+ @pytest.mark.parametrize("dtype", [np.float32, np.float16])
154
+ @pytest.mark.parametrize("metric", ["sqeuclidean", "cosine"])
155
+ def test_cdist(ndim, dtype, metric):
156
+ """Compares the simd.cdist() function with scipy.spatial.distance.cdist(), measuring the accuracy error for f16, and f32 types using sqeuclidean and cosine metrics."""
157
+
158
+ # Create random matrices A (M x D) and B (N x D).
159
+ M, N = 10, 15 # or any other sizes you deem appropriate
160
+ A = np.random.randn(M, ndim).astype(dtype)
161
+ B = np.random.randn(N, ndim).astype(dtype)
162
+
163
+ # Compute cdist using scipy.
164
+ expected = spd.cdist(A, B, metric)
4
165
 
166
+ # Compute cdist using simd.
167
+ result = simd.cdist(A, B, metric=metric)
5
168
 
6
- def test_non_null():
7
- # Some distance functions are provided for every platform
8
- assert simd.to_int(simd.tanimoto_b1x8_naive) != 0
9
- assert simd.to_int(simd.tanimoto_maccs_naive) != 0
10
-
11
- # Arm Neon variants should be precompiled for any 64-bit Arm machine:
12
- if platform.machine() == "arm64":
13
- assert simd.to_int(simd.cos_f16x4_neon) != 0
14
- assert simd.to_int(simd.cos_i8x16_neon) != 0
15
- assert simd.to_int(simd.cos_f32x4_neon) != 0
16
- assert simd.to_int(simd.dot_f32x4_neon) != 0
17
- assert simd.to_int(simd.tanimoto_maccs_neon) != 0
18
-
19
- # Arm SVE variants should be precompiled for 64-bit Arm machines running Linux:
20
- if platform.machine() == "arm64" and platform.system().lower() == "linux":
21
- assert simd.to_int(simd.dot_f32_sve) != 0
22
- assert simd.to_int(simd.cos_f32_sve) != 0
23
- assert simd.to_int(simd.cos_f16_sve) != 0
24
- assert simd.to_int(simd.l2sq_f32_sve) != 0
25
- assert simd.to_int(simd.l2sq_f16_sve) != 0
26
- assert simd.to_int(simd.hamming_b1x8_sve) != 0
27
- assert simd.to_int(simd.hamming_b1x128_sve) != 0
28
- assert simd.to_int(simd.tanimoto_maccs_sve) != 0
29
-
30
- # x86 AVX2 variants are precompiled for every 64-bit x86 platform:
31
- if platform.machine() == "x86_64":
32
- assert simd.to_int(simd.cos_f32x4_avx2) != 0
33
-
34
- # x86 AVX-512 variants are precompiled for 64-bit x86 machines running Linux:
35
- if platform.machine() == "x86_64" and platform.system().lower() == "linux":
36
- assert simd.to_int(simd.cos_f16x16_avx512) != 0
37
- assert simd.to_int(simd.hamming_b1x128_avx512) != 0
38
- assert simd.to_int(simd.tanimoto_maccs_avx512) != 0
169
+ # Assert they're close.
170
+ np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
package/simsimd/setup.py CHANGED
@@ -1,10 +1,59 @@
1
1
  import os
2
+ import sys
3
+ import platform
2
4
  from os.path import dirname
3
5
  from setuptools import setup, Extension
4
6
 
7
+ import glob
8
+ import numpy
9
+
5
10
  __version__ = open("VERSION", "r").read().strip()
6
11
  __lib_name__ = "simsimd"
7
12
 
13
+ compile_args = []
14
+ link_args = []
15
+ macros_args = []
16
+
17
+ if sys.platform == "linux":
18
+ compile_args.append("-std=c11")
19
+ compile_args.append("-O3")
20
+ compile_args.append("-ffast-math")
21
+ compile_args.append("-fdiagnostics-color=always")
22
+
23
+ # Disable warnings
24
+ compile_args.append("-w")
25
+
26
+ # Enable OpenMP for Linux
27
+ compile_args.append("-fopenmp")
28
+ link_args.append("-lgomp")
29
+
30
+ if sys.platform == "darwin":
31
+ compile_args.append("-std=c11")
32
+ compile_args.append("-O3")
33
+ compile_args.append("-ffast-math")
34
+
35
+ # Disable warnings
36
+ compile_args.append("-w")
37
+
38
+ if sys.platform == "win32":
39
+ compile_args.append("/std:c11")
40
+ compile_args.append("/O2")
41
+ compile_args.append("/fp:fast")
42
+
43
+ # Dealing with MinGW linking errors
44
+ # https://cibuildwheel.readthedocs.io/en/stable/faq/#windows-importerror-dll-load-failed-the-specific-module-could-not-be-found
45
+ compile_args.append("/d2FH4-")
46
+
47
+ ext_modules = [
48
+ Extension(
49
+ "simsimd",
50
+ sources=["python/lib.c"],
51
+ include_dirs=["include", numpy.get_include()],
52
+ extra_compile_args=compile_args,
53
+ extra_link_args=link_args,
54
+ define_macros=macros_args,
55
+ ),
56
+ ]
8
57
 
9
58
  this_directory = os.path.abspath(dirname(__file__))
10
59
  with open(os.path.join(this_directory, "README.md"), "r", encoding="utf8") as f:
@@ -16,7 +65,7 @@ setup(
16
65
  author="Ash Vardanian",
17
66
  author_email="1983160+ashvardanian@users.noreply.github.com",
18
67
  url="https://github.com/ashvardanian/simsimd",
19
- description="SIMD-accelerated similarity measures for x86 and Arm",
68
+ description="Vector Similarity Functions 3x-200x Faster than SciPy and NumPy",
20
69
  long_description=long_description,
21
70
  long_description_content_type="text/markdown",
22
71
  license="Apache-2.0",
@@ -29,12 +78,6 @@ setup(
29
78
  "Programming Language :: C",
30
79
  "Programming Language :: Python :: Implementation :: CPython",
31
80
  ],
32
- ext_modules=[
33
- Extension(
34
- "simsimd",
35
- sources=["python/lib.c"],
36
- include_dirs=["include"],
37
- )
38
- ],
81
+ ext_modules=ext_modules,
39
82
  zip_safe=False,
40
83
  )
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="2.7.3"
3
+ version="2.7.6"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"
package/simsimd/bench.cxx DELETED
@@ -1,137 +0,0 @@
1
- #include <thread>
2
-
3
- #include <benchmark/benchmark.h>
4
-
5
- #include <simsimd/simsimd.h>
6
- #include <simsimd/simsimd_chem.h>
7
-
8
- namespace bm = benchmark;
9
-
10
- static const std::size_t threads_k = std::thread::hardware_concurrency();
11
- static constexpr std::size_t time_k = 10;
12
- static constexpr std::size_t bytes_k = 1024;
13
-
14
- template <typename return_at, typename... args_at>
15
- constexpr std::size_t number_of_arguments(return_at (*f)(args_at...)) {
16
- return sizeof...(args_at);
17
- }
18
-
19
- template <typename scalar_at, std::size_t bytes_per_vector_ak = bytes_k,
20
- typename metric_at = void> //
21
- static void measure(bm::State& state, metric_at metric) {
22
-
23
- constexpr ::size_t dimensions_ak = bytes_per_vector_ak / sizeof(scalar_at);
24
- alignas(64) scalar_at a[dimensions_ak]{};
25
- alignas(64) scalar_at b[dimensions_ak]{};
26
- float c{};
27
-
28
- std::fill_n(a, dimensions_ak, static_cast<scalar_at>(1));
29
- std::fill_n(b, dimensions_ak, static_cast<scalar_at>(2));
30
-
31
- std::size_t iterations = 0;
32
- for (auto _ : state)
33
- if constexpr (number_of_arguments(metric_at{}) == 3)
34
- bm::DoNotOptimize((c = metric(a, b, dimensions_ak))), iterations++;
35
- else
36
- bm::DoNotOptimize((c = metric(a, b))), iterations++;
37
-
38
- state.counters["bytes"] = bm::Counter(iterations * bytes_per_vector_ak * 2u, bm::Counter::kIsRate);
39
- state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
40
- }
41
-
42
- template <typename scalar_at, std::size_t bytes_per_vector_ak = bytes_k, typename metric_at = void>
43
- void register_(char const* name, metric_at distance_func) {
44
- bm::RegisterBenchmark(name, measure<scalar_at, bytes_per_vector_ak, metric_at>, distance_func)
45
- ->Threads(1)
46
- ->MinTime(time_k);
47
- bm::RegisterBenchmark(name, measure<scalar_at, bytes_per_vector_ak, metric_at>, distance_func)
48
- ->Threads(threads_k)
49
- ->MinTime(time_k);
50
- }
51
-
52
- simsimd_f32_t cos_f32_naive(simsimd_f32_t* v1, simsimd_f32_t* v2, std::size_t n) {
53
- simsimd_f32_t inner_product = 0;
54
- simsimd_f32_t magnitude1 = 0;
55
- simsimd_f32_t magnitude2 = 0;
56
- for (std::size_t i = 0; i != n; ++i) {
57
- inner_product += v1[i] * v2[i];
58
- magnitude1 += v1[i] * v1[i];
59
- magnitude2 += v2[i] * v2[i];
60
- }
61
- return 1 - inner_product / (std::sqrt(magnitude1) * std::sqrt(magnitude2));
62
- }
63
-
64
- int main(int argc, char** argv) {
65
-
66
- bool compiled_with_sve = false;
67
- bool compiled_with_neon = false;
68
- bool compiled_with_avx2 = false;
69
- bool compiled_with_avx512popcnt = false;
70
-
71
- #if defined(__ARM_FEATURE_SVE)
72
- compiled_with_sve = true;
73
- #endif
74
- #if defined(__ARM_NEON)
75
- compiled_with_neon = true;
76
- #endif
77
- #if defined(__AVX2__)
78
- compiled_with_avx2 = true;
79
- #endif
80
- #if defined(__AVX512VPOPCNTDQ__)
81
- compiled_with_avx512popcnt = true;
82
- #endif
83
-
84
- // Log supported functionality
85
- char const* flags[2] = {"false", "true"};
86
- std::printf("Benchmarking Similarity Measures\n");
87
- std::printf("\n");
88
- std::printf("- Arm NEON support enabled: %s\n", flags[compiled_with_neon]);
89
- std::printf("- Arm SVE support enabled: %s\n", flags[compiled_with_sve]);
90
- std::printf("- x86 AVX2 support enabled: %s\n", flags[compiled_with_avx2]);
91
- std::printf("- x86 AVX512VPOPCNTDQ support enabled: %s\n", flags[compiled_with_avx512popcnt]);
92
- std::printf("Default vector length: %zu bytes\n", bytes_k);
93
- std::printf("\n");
94
-
95
- // Run the benchmarks
96
- bm::Initialize(&argc, argv);
97
- if (bm::ReportUnrecognizedArguments(argc, argv))
98
- return 1;
99
-
100
- register_<std::uint8_t, 21>("tanimoto_maccs_naive", simsimd_tanimoto_maccs_naive);
101
- register_<simsimd_f32_t>("cos_f32_naive", cos_f32_naive);
102
-
103
- #if SIMSIMD_TARGET_ARM_SVE
104
- register_<simsimd_f32_t>("dot_f32_sve", simsimd_dot_f32_sve);
105
- register_<simsimd_f32_t>("cos_f32_sve", simsimd_cos_f32_sve);
106
- register_<std::int16_t>("cos_f16_sve", simsimd_cos_f16_sve);
107
- register_<simsimd_f32_t>("l2sq_f32_sve", simsimd_l2sq_f32_sve);
108
- register_<std::int16_t>("l2sq_f16_sve", simsimd_l2sq_f16_sve);
109
- register_<std::uint8_t>("hamming_b1x8_sve", simsimd_hamming_b1x8_sve);
110
- register_<std::uint8_t>("hamming_b1x128_sve", simsimd_hamming_b1x128_sve);
111
- register_<std::uint8_t, 21>("tanimoto_maccs_sve", simsimd_tanimoto_maccs_sve);
112
- #endif
113
-
114
- #if SIMSIMD_TARGET_ARM_NEON
115
- register_<simsimd_f32_t>("dot_f32x4_neon", simsimd_dot_f32x4_neon);
116
- register_<std::int16_t>("cos_f16x4_neon", simsimd_cos_f16x4_neon);
117
- register_<std::int8_t>("cos_i8x16_neon", simsimd_cos_i8x16_neon);
118
- register_<std::int8_t>("l2sq_i8x16_neon", simsimd_l2sq_i8x16_neon);
119
- register_<simsimd_f32_t>("cos_f32x4_neon", simsimd_cos_f32x4_neon);
120
- register_<std::uint8_t, 21>("tanimoto_maccs_neon", simsimd_tanimoto_maccs_neon);
121
- #endif
122
-
123
- #if SIMSIMD_TARGET_X86_AVX2
124
- register_<simsimd_f32_t>("dot_f32x4_avx2", simsimd_dot_f32x4_avx2);
125
- register_<simsimd_f32_t>("cos_f32x4_avx2", simsimd_cos_f32x4_avx2);
126
- #endif
127
-
128
- #if SIMSIMD_TARGET_X86_AVX512
129
- register_<std::int16_t>("cos_f16x16_avx512", simsimd_cos_f16x16_avx512);
130
- register_<std::uint8_t>("hamming_b1x128_avx512", simsimd_hamming_b1x128_avx512);
131
- register_<std::uint8_t, 21>("tanimoto_maccs_avx512", simsimd_tanimoto_maccs_avx512);
132
- #endif
133
-
134
- bm::RunSpecifiedBenchmarks();
135
- bm::Shutdown();
136
- return 0;
137
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * @brief Collection of Biology-oriented Similarity Measures, SIMD-accelerated with SSE, AVX, NEON, SVE.
3
- *
4
- * @author Ash Vardanian
5
- * @date August 8, 2023
6
- */
7
-
8
- #pragma once
9
-
10
- #include <simsimd/simsimd_chem.h>
11
-
12
- #ifdef _MSC_VER
13
- #define hash32
14
- #define hash64
15
- #else
16
- #define hash32
17
- #define hash64
18
- #endif
19
-
20
- #ifdef __cplusplus
21
- extern "C" {
22
- #endif
23
-
24
- /**
25
- * @brief Used to compute the similarity of two DNA or RNA sequences in "TwoBit" form.
26
- *
27
- * By design uses a separate bit-set for every string, expanding the compressed
28
- * representation into bitsets, and then computes the tanimoto distance on those.
29
- *
30
- * Optimal `length_hash` should be derived from `max(a_length, b_length)`, as
31
- */
32
- inline static simsimd_f32_t simsimd_tanimoto_twobit( //
33
- uint8_t const* a_chars, uint8_t const* b_chars, //
34
- size_t a_length, size_t b_length, //
35
- uint8_t* a_hash, uint8_t* b_hash, size_t length_hash) {
36
-
37
- // uint64 -> 32-gram
38
- // uint32 -> 16-gram
39
- uint64_t last_word = 0;
40
- for (size_t i = 0; i + 8 <= a_length; i += 8) {
41
- uint64_t a_word;
42
- memcpy(&a_word, a_chars, 8);
43
- }
44
-
45
- size_t b_words = (b_length + 7) / 8;
46
-
47
- return simsimd_tanimoto_b1x8_naive(a_hash, b_hash, length_hash);
48
- }
49
-
50
- #undef popcount64
51
-
52
- #ifdef __cplusplus
53
- }
54
- #endif
@@ -1,167 +0,0 @@
1
- /**
2
- * @brief Collection of Chemistry-oriented Similarity Measures, SIMD-accelerated with SSE, AVX, NEON, SVE.
3
- *
4
- * @author Ashot Vardanian
5
- * @date July 1, 2023
6
- */
7
-
8
- #pragma once
9
- #include <string.h> // `memcpy`
10
-
11
- #include <simsimd/simsimd.h>
12
-
13
- #ifdef _MSC_VER
14
- #include <intrin.h>
15
- #define popcount64 __popcnt64
16
- #else
17
- #define popcount64 __builtin_popcountll
18
- #endif
19
-
20
- #ifdef __cplusplus
21
- extern "C" {
22
- #endif
23
-
24
- /**
25
- * @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
26
- * forming 21 incomplete bytes for the MACCS fingerprints used in
27
- * computation chemistry.
28
- */
29
- inline static simsimd_f32_t //
30
- simsimd_tanimoto_maccs_naive(uint8_t const* a_chars, uint8_t const* b_chars) {
31
- unsigned long a[3] = {0};
32
- unsigned long b[3] = {0};
33
- memcpy(&a[0], a_chars, 21);
34
- memcpy(&b[0], b_chars, 21);
35
- float and_count = popcount64(a[0] & b[0]) + popcount64(a[1] & b[1]) + popcount64(a[2] & b[2]);
36
- float or_count = popcount64(a[0] | b[0]) + popcount64(a[1] | b[1]) + popcount64(a[2] | b[2]);
37
- return 1 - and_count / or_count;
38
- }
39
-
40
- /**
41
- * @brief Weird Tanimoto distance implementation for concatenated MACCS and ECFP4
42
- * representations, forming a grand-total of 56 bytes. The last chunk is evaluated
43
- * if only the first one matches well.
44
- *
45
- * - First 166 bits in 21 bytes is a MACCS vector.
46
- * - After that 3 empty bytes of a padding are expected.
47
- * - After which, 256 bytes (32 words) of ECFP4 follow.
48
- */
49
- inline static simsimd_f32_t //
50
- simsimd_tanimoto_conditional_naive(uint8_t const* a_chars, uint8_t const* b_chars) {
51
- float const threshold = 0.2;
52
- unsigned long const* a = (unsigned long const*)(a_chars);
53
- unsigned long const* b = (unsigned long const*)(b_chars);
54
- float and_count = popcount64(a[0] & b[0]) + popcount64(a[1] & b[1]) + popcount64(a[2] & b[2]);
55
- float or_count = popcount64(a[0] | b[0]) + popcount64(a[1] | b[1]) + popcount64(a[2] | b[2]);
56
- float result = 1 - and_count / or_count;
57
- if (result > threshold)
58
- return result;
59
-
60
- // Start comparing ECFP4
61
- and_count = 0, or_count = 0;
62
- for (int i = 0; i != 32; ++i)
63
- and_count += popcount64(a[3 + i] & b[3 + i]), or_count += popcount64(a[3 + i] | b[3 + i]);
64
- result = 1 - and_count / or_count;
65
- return result * threshold;
66
- }
67
-
68
- #if SIMSIMD_TARGET_ARM
69
- #if SIMSIMD_TARGET_ARM_NEON
70
-
71
- /**
72
- * @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
73
- * forming 21 incomplete bytes for the MACCS fingerprints used in
74
- * computation chemistry, accelerated for Arm NEON ISA.
75
- */
76
- inline static simsimd_f32_t //
77
- simsimd_tanimoto_maccs_neon(uint8_t const* a_chars, uint8_t const* b_chars) {
78
- unsigned char a[32] = {0};
79
- unsigned char b[32] = {0};
80
- memcpy(&a[0], a_chars, 21);
81
- memcpy(&b[0], b_chars, 21);
82
- uint8x16_t a_first = vld1q_u8(&a[0]);
83
- uint8x16_t a_second = vld1q_u8(&a[16]);
84
- uint8x16_t b_first = vld1q_u8(&b[0]);
85
- uint8x16_t b_second = vld1q_u8(&b[16]);
86
- uint8x16_t a_and_b_first = vandq_u8(a_first, b_first);
87
- uint8x16_t a_and_b_second = vandq_u8(a_second, b_second);
88
- uint8x16_t a_or_b_first = vorrq_u8(a_first, b_first);
89
- uint8x16_t a_or_b_second = vorrq_u8(a_second, b_second);
90
- float and_populations = vaddvq_u8(vaddq_u8(vcntq_u8(a_and_b_first), vcntq_u8(a_and_b_second)));
91
- float or_populations = vaddvq_u8(vaddq_u8(vcntq_u8(a_or_b_first), vcntq_u8(a_or_b_second)));
92
- return 1 - and_populations / or_populations;
93
- }
94
-
95
- #endif // SIMSIMD_TARGET_ARM_NEON
96
-
97
- #if SIMSIMD_TARGET_ARM_SVE
98
-
99
- /**
100
- * @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
101
- * forming 21 incomplete bytes for the MACCS fingerprints used in
102
- * computation chemistry, accelerated for Arm NEON ISA.
103
- */
104
- __attribute__((target("+sve"))) inline static simsimd_f32_t //
105
- simsimd_tanimoto_maccs_sve(uint8_t const* a_chars, uint8_t const* b_chars) {
106
- svbool_t pg_vec = svwhilelt_b8(0ul, 21ul);
107
- svuint8_t a_vec = svld1_u8(pg_vec, (uint8_t const*)a_chars);
108
- svuint8_t b_vec = svld1_u8(pg_vec, (uint8_t const*)b_chars);
109
- svuint8_t a_and_b_vec = svand_u8_m(pg_vec, a_vec, b_vec);
110
- svuint8_t a_or_b_vec = svorr_u8_m(pg_vec, a_vec, b_vec);
111
- svuint8_t and_populations_vec = svcnt_u8_x(pg_vec, a_and_b_vec);
112
- svuint8_t or_populations_vec = svcnt_u8_x(pg_vec, a_or_b_vec);
113
-
114
- // We are gonna need at most two cycles like this, regardless of the architecture,
115
- // assuming the SVE registers are at least 128 bits (16 bytes), and we onlu need 21 bytes.
116
- pg_vec = svwhilelt_b8(svcntb(), 21ul);
117
- a_vec = svld1_u8(pg_vec, (uint8_t const*)a_chars + svcntb());
118
- b_vec = svld1_u8(pg_vec, (uint8_t const*)b_chars + svcntb());
119
- a_and_b_vec = svand_u8_m(pg_vec, a_vec, b_vec);
120
- a_or_b_vec = svorr_u8_m(pg_vec, a_vec, b_vec);
121
- and_populations_vec = svadd_u8_m(pg_vec, svcnt_u8_x(pg_vec, a_and_b_vec), and_populations_vec);
122
- or_populations_vec = svadd_u8_m(pg_vec, svcnt_u8_x(pg_vec, a_or_b_vec), or_populations_vec);
123
-
124
- float and_populations = svaddv_u8(svptrue_b8(), and_populations_vec);
125
- float or_populations = svaddv_u8(svptrue_b8(), or_populations_vec);
126
- return 1 - and_populations / or_populations;
127
- }
128
- #endif
129
- #endif // SIMSIMD_TARGET_ARM
130
-
131
- #if SIMSIMD_TARGET_X86
132
- #if SIMSIMD_TARGET_X86_AVX512
133
-
134
- /**
135
- * @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
136
- * forming 21 incomplete bytes for the MACCS fingerprints used in
137
- * computation chemistry, accelerated with AVX-512 population count
138
- * instructions.
139
- */
140
- __attribute__((target("avx512vpopcntdq"))) //
141
- __attribute__((target("avx512vl"))) //
142
- __attribute__((target("avx512bw"))) //
143
- __attribute__((target("avx512f"))) inline static simsimd_f32_t //
144
- simsimd_tanimoto_maccs_avx512(uint8_t const* a, uint8_t const* b) {
145
- __m256i a_vec = _mm256_maskz_loadu_epi8(0b11111111111111111111100000000000, a);
146
- __m256i b_vec = _mm256_maskz_loadu_epi8(0b11111111111111111111100000000000, b);
147
- __m256i and_vec = _mm256_and_si256(a_vec, b_vec);
148
- __m256i or_vec = _mm256_or_si256(a_vec, b_vec);
149
- __m256i and_counts_vec = _mm256_popcnt_epi64(and_vec);
150
- __m256i or_counts_vec = _mm256_popcnt_epi64(or_vec);
151
-
152
- float and_counts = _mm256_extract_epi64(and_counts_vec, 0) + _mm256_extract_epi64(and_counts_vec, 1) +
153
- _mm256_extract_epi64(and_counts_vec, 2) + _mm256_extract_epi64(and_counts_vec, 3);
154
- float or_counts = _mm256_extract_epi64(or_counts_vec, 0) + _mm256_extract_epi64(or_counts_vec, 1) +
155
- _mm256_extract_epi64(or_counts_vec, 2) + _mm256_extract_epi64(or_counts_vec, 3);
156
-
157
- return 1 - and_counts / or_counts;
158
- }
159
-
160
- #endif // SIMSIMD_TARGET_X86_AVX512
161
- #endif // SIMSIMD_TARGET_X86
162
-
163
- #undef popcount64
164
-
165
- #ifdef __cplusplus
166
- }
167
- #endif