usearch 2.16.8 → 2.16.9

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.
@@ -24,7 +24,6 @@ import test from "node:test";
24
24
  import assert from "node:assert";
25
25
 
26
26
  import * as simsimd from "../javascript/dist/esm/simsimd.js";
27
-
28
27
  import * as fallback from "../javascript/dist/esm/fallback.js";
29
28
 
30
29
  function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
@@ -40,168 +39,182 @@ test("Distance from itself", () => {
40
39
  const f32s = new Float32Array([1.0, 2.0, 3.0]);
41
40
  assertAlmostEqual(simsimd.sqeuclidean(f32s, f32s), 0.0, 0.01);
42
41
  assertAlmostEqual(simsimd.cosine(f32s, f32s), 0.0, 0.01);
42
+ assertAlmostEqual(fallback.sqeuclidean(f32s, f32s), 0.0, 0.01);
43
+ assertAlmostEqual(fallback.cosine(f32s, f32s), 0.0, 0.01);
43
44
 
44
45
  const f64s = new Float64Array([1.0, 2.0, 3.0]);
45
46
  assertAlmostEqual(simsimd.sqeuclidean(f64s, f64s), 0.0, 0.01);
46
47
  assertAlmostEqual(simsimd.cosine(f64s, f64s), 0.0, 0.01);
48
+ assertAlmostEqual(fallback.sqeuclidean(f64s, f64s), 0.0, 0.01);
49
+ assertAlmostEqual(fallback.cosine(f64s, f64s), 0.0, 0.01);
47
50
 
48
- const f32sNormalized = new Float32Array([
49
- 1.0 / Math.sqrt(14),
50
- 2.0 / Math.sqrt(14),
51
- 3.0 / Math.sqrt(14),
52
- ]);
51
+ const f32sNormalized = new Float32Array([1 / Math.sqrt(14), 2 / Math.sqrt(14), 3 / Math.sqrt(14)]);
53
52
  assertAlmostEqual(simsimd.inner(f32sNormalized, f32sNormalized), 1.0, 0.01);
53
+ assertAlmostEqual(fallback.inner(f32sNormalized, f32sNormalized), 1.0, 0.01);
54
54
 
55
- const f32sDistribution = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
56
- assertAlmostEqual(
57
- simsimd.kullbackleibler(f32sDistribution, f32sDistribution),
58
- 0.0,
59
- 0.01
60
- );
61
- assertAlmostEqual(
62
- simsimd.jensenshannon(f32sDistribution, f32sDistribution),
63
- 0.0,
64
- 0.01
65
- );
55
+ const f32sHistogram = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
56
+ assertAlmostEqual(simsimd.kullbackleibler(f32sHistogram, f32sHistogram), 0.0, 0.01);
57
+ assertAlmostEqual(simsimd.jensenshannon(f32sHistogram, f32sHistogram), 0.0, 0.01);
58
+ assertAlmostEqual(fallback.kullbackleibler(f32sHistogram, f32sHistogram), 0.0, 0.01);
59
+ assertAlmostEqual(fallback.jensenshannon(f32sHistogram, f32sHistogram), 0.0, 0.01);
66
60
 
67
61
  const u8s = new Uint8Array([1, 2, 3]);
68
62
  assertAlmostEqual(simsimd.hamming(u8s, u8s), 0.0, 0.01);
69
63
  assertAlmostEqual(simsimd.jaccard(u8s, u8s), 0.0, 0.01);
70
- });
71
-
72
- test("Distance from itself JS fallback", () => {
73
- const f32s = new Float32Array([1.0, 2.0, 3.0]);
74
-
75
- assertAlmostEqual(fallback.sqeuclidean(f32s, f32s), 0.0, 0.01);
76
- assertAlmostEqual(fallback.cosine(f32s, f32s), 0.0, 0.01);
77
-
78
- const arrNormalized = new Float32Array([
79
- 1.0 / Math.sqrt(14),
80
- 2.0 / Math.sqrt(14),
81
- 3.0 / Math.sqrt(14),
82
- ]);
83
- assertAlmostEqual(fallback.inner(arrNormalized, arrNormalized), 1.0, 0.01);
84
-
85
- const f32sDistribution = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
86
- assertAlmostEqual(
87
- fallback.kullbackleibler(f32sDistribution, f32sDistribution),
88
- 0.0,
89
- 0.01
90
- );
91
- assertAlmostEqual(
92
- fallback.jensenshannon(f32sDistribution, f32sDistribution),
93
- 0.0,
94
- 0.01
95
- );
96
-
97
- const u8s = new Uint8Array([1, 2, 3]);
98
64
  assertAlmostEqual(fallback.hamming(u8s, u8s), 0.0, 0.01);
99
65
  assertAlmostEqual(fallback.jaccard(u8s, u8s), 0.0, 0.01);
100
66
  });
101
67
 
102
- const f32Array1 = new Float32Array([1.0, 2.0, 3.0]);
103
- const f32Array2 = new Float32Array([4.0, 5.0, 6.0]);
104
-
105
- const uint8Array1 = new Uint8Array([1, 2, 3]);
106
- const uint8Array2 = new Uint8Array([4, 5, 6]);
107
68
 
108
69
  test("Squared Euclidean Distance", () => {
109
- const result = simsimd.sqeuclidean(f32Array1, f32Array2);
110
- assertAlmostEqual(result, 27.0, 0.01);
111
-
112
- const uint8Result = simsimd.sqeuclidean(uint8Array1, uint8Array2);
113
- assertAlmostEqual(uint8Result, 27.0, 0.01);
70
+ const f64sOne = new Float64Array([1.0, 2.0, 3.0]);
71
+ const f64sTwo = new Float64Array([4.0, 5.0, 6.0]);
72
+ const f64sResult = simsimd.sqeuclidean(f64sOne, f64sTwo);
73
+ const f64sResultJS = fallback.sqeuclidean(f64sOne, f64sTwo);
74
+ assertAlmostEqual(f64sResultJS, 27.0, 0.01);
75
+ assertAlmostEqual(f64sResult, 27.0, 0.01);
76
+
77
+ const f32sOne = new Float32Array([1.0, 2.0, 3.0]);
78
+ const f32sTwo = new Float32Array([4.0, 5.0, 6.0]);
79
+ const f32sResult = simsimd.sqeuclidean(f32sOne, f32sTwo);
80
+ const f32sResultJS = fallback.sqeuclidean(f32sOne, f32sTwo);
81
+ assertAlmostEqual(f32sResultJS, 27.0, 0.01);
82
+ assertAlmostEqual(f32sResult, 27.0, 0.01);
83
+
84
+ const u8sOne = new Uint8Array([1, 2, 3]);
85
+ const u8sTwo = new Uint8Array([4, 5, 6]);
86
+ const u8sResult = simsimd.sqeuclidean(u8sOne, u8sTwo);
87
+ const u8sResultJS = fallback.sqeuclidean(u8sOne, u8sTwo);
88
+ assertAlmostEqual(u8sResultJS, 27.0, 0.01);
89
+ assertAlmostEqual(u8sResult, 27.0, 0.01);
90
+
91
+ const i8sOne = new Int8Array([1, 2, 3]);
92
+ const i8sTwo = new Int8Array([4, 5, 6]);
93
+ const i8sResult = simsimd.sqeuclidean(i8sOne, i8sTwo);
94
+ const i8sResultJS = fallback.sqeuclidean(i8sOne, i8sTwo);
95
+ assertAlmostEqual(i8sResultJS, 27.0, 0.01);
96
+ assertAlmostEqual(i8sResult, 27.0, 0.01);
114
97
  });
115
98
 
116
99
  test("Euclidean Distance", () => {
117
- const result = simsimd.euclidean(f32Array1, f32Array2);
118
- assertAlmostEqual(result, 5.2, 0.01);
119
- });
120
-
121
- test("Inner Distance", () => {
122
- const result = simsimd.inner(f32Array1, f32Array2);
123
- assertAlmostEqual(result, 32.0, 0.01);
124
- });
125
-
126
- test("Cosine Similarity", () => {
127
- const result = simsimd.cosine(f32Array1, f32Array2);
128
- assertAlmostEqual(result, 0.029, 0.01);
129
- });
130
-
131
- test("Squared Euclidean Distance JS", () => {
132
- const result = fallback.sqeuclidean(f32Array1, f32Array2);
133
- assertAlmostEqual(result, 27.0, 0.01);
134
-
135
- const uint8Result = fallback.sqeuclidean(uint8Array1, uint8Array2);
136
- assertAlmostEqual(uint8Result, 27.0, 0.01);
137
- });
138
-
139
- test("Inner Distance JS", () => {
140
- const result = fallback.inner(f32Array1, f32Array2);
141
- assertAlmostEqual(result, 32.0, 0.01);
142
- });
143
-
144
- test("Cosine Similarity JS", () => {
145
- const result = fallback.cosine(f32Array1, f32Array2);
146
- assertAlmostEqual(result, 0.029, 0.01);
147
- });
148
-
149
- test("Squared Euclidean Distance C vs JS", () => {
150
- const result = simsimd.sqeuclidean(f32Array1, f32Array2);
151
- const resultjs = fallback.sqeuclidean(f32Array1, f32Array2);
152
- assertAlmostEqual(resultjs, result, 0.01);
153
-
154
- const uint8result = simsimd.sqeuclidean(uint8Array1, uint8Array2);
155
- const uint8resultjs = fallback.sqeuclidean(uint8Array1, uint8Array2);
156
- assertAlmostEqual(uint8resultjs, uint8result, 0.01);
157
- });
158
-
159
- test("Euclidean Distance C vs JS", () => {
160
- const result = simsimd.euclidean(f32Array1, f32Array2);
161
- const resultjs = fallback.euclidean(f32Array1, f32Array2);
162
- assertAlmostEqual(resultjs, result, 0.01);
163
-
164
- const uint8result = simsimd.euclidean(uint8Array1, uint8Array2);
165
- const uint8resultjs = fallback.euclidean(uint8Array1, uint8Array2);
166
- assertAlmostEqual(uint8resultjs, uint8result, 0.01);
167
- });
168
-
169
- test("Inner Distance C vs JS", () => {
170
- const result = simsimd.inner(f32Array1, f32Array2);
171
- const resultjs = fallback.inner(f32Array1, f32Array2);
172
- assertAlmostEqual(resultjs, result, 0.01);
173
- });
174
-
175
- test("Cosine Similarity C vs JS", () => {
176
- const result = simsimd.cosine(f32Array1, f32Array2);
177
- const resultjs = fallback.cosine(f32Array1, f32Array2);
178
- assertAlmostEqual(resultjs, result, 0.01);
179
- });
180
-
181
- test("Hamming C vs JS", () => {
182
- const u8s = new Uint8Array([1, 2, 3]);
183
- const result = simsimd.hamming(u8s, u8s);
184
- const resultjs = fallback.hamming(u8s, u8s);
185
- assertAlmostEqual(resultjs, result, 0.01);
186
- });
187
-
188
- test("Jaccard C vs JS", () => {
189
- const u8s = new Uint8Array([1, 2, 3]);
190
- const result = simsimd.jaccard(u8s, u8s);
191
- const resultjs = fallback.jaccard(u8s, u8s);
192
- assertAlmostEqual(resultjs, result, 0.01);
193
- });
194
-
195
- test("Kullback-Leibler C vs JS", () => {
196
- const f32sDistribution = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
197
- const result = simsimd.kullbackleibler(f32sDistribution, f32sDistribution);
198
- const resultjs = fallback.kullbackleibler(f32sDistribution, f32sDistribution);
199
- assertAlmostEqual(resultjs, result, 0.01);
200
- });
201
-
202
- test("Jensen-Shannon C vs JS", () => {
203
- const f32sDistribution = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
204
- const result = simsimd.jensenshannon(f32sDistribution, f32sDistribution);
205
- const resultjs = fallback.jensenshannon(f32sDistribution, f32sDistribution);
206
- assertAlmostEqual(resultjs, result, 0.01);
100
+ const f64sOne = new Float64Array([1.0, 2.0, 3.0]);
101
+ const f64sTwo = new Float64Array([4.0, 5.0, 6.0]);
102
+ const f64sResult = simsimd.euclidean(f64sOne, f64sTwo);
103
+ const f64sResultJS = fallback.euclidean(f64sOne, f64sTwo);
104
+ assertAlmostEqual(f64sResultJS, 5.2, 0.01);
105
+ assertAlmostEqual(f64sResult, 5.2, 0.01);
106
+
107
+ const f32sOne = new Float32Array([1.0, 2.0, 3.0]);
108
+ const f32sTwo = new Float32Array([4.0, 5.0, 6.0]);
109
+ const f32sResult = simsimd.euclidean(f32sOne, f32sTwo);
110
+ const f32sResultJS = fallback.euclidean(f32sOne, f32sTwo);
111
+ assertAlmostEqual(f32sResultJS, 5.2, 0.01);
112
+ assertAlmostEqual(f32sResult, 5.2, 0.01);
113
+
114
+ const u8sOne = new Uint8Array([1, 2, 3]);
115
+ const u8sTwo = new Uint8Array([4, 5, 6]);
116
+ const u8sResult = simsimd.euclidean(u8sOne, u8sTwo);
117
+ const u8sResultJS = fallback.euclidean(u8sOne, u8sTwo);
118
+ assertAlmostEqual(u8sResultJS, 5.2, 0.01);
119
+ assertAlmostEqual(u8sResult, 5.2, 0.01);
120
+
121
+ const i8sOne = new Int8Array([1, 2, 3]);
122
+ const i8sTwo = new Int8Array([4, 5, 6]);
123
+ const i8sResult = simsimd.euclidean(i8sOne, i8sTwo);
124
+ const i8sResultJS = fallback.euclidean(i8sOne, i8sTwo);
125
+ assertAlmostEqual(i8sResultJS, 5.2, 0.01);
126
+ assertAlmostEqual(i8sResult, 5.2, 0.01);
127
+ });
128
+
129
+
130
+ test("Inner Product", () => {
131
+ const f64sOne = new Float64Array([1.0, 2.0, 3.0]);
132
+ const f64sTwo = new Float64Array([4.0, 5.0, 6.0]);
133
+ const f64sResult = simsimd.inner(f64sOne, f64sTwo);
134
+ const f64sResultJS = fallback.inner(f64sOne, f64sTwo);
135
+ assertAlmostEqual(f64sResultJS, 32.0, 0.01);
136
+ assertAlmostEqual(f64sResult, 32.0, 0.01);
137
+
138
+ const f32sOne = new Float32Array([1.0, 2.0, 3.0]);
139
+ const f32sTwo = new Float32Array([4.0, 5.0, 6.0]);
140
+ const f32sResult = simsimd.inner(f32sOne, f32sTwo);
141
+ const f32sResultJS = fallback.inner(f32sOne, f32sTwo);
142
+ assertAlmostEqual(f32sResultJS, 32.0, 0.01);
143
+ assertAlmostEqual(f32sResult, 32.0, 0.01);
144
+
145
+ const u8sOne = new Uint8Array([1, 2, 3]);
146
+ const u8sTwo = new Uint8Array([4, 5, 6]);
147
+ const u8sResult = simsimd.inner(u8sOne, u8sTwo);
148
+ const u8sResultJS = fallback.inner(u8sOne, u8sTwo);
149
+ assertAlmostEqual(u8sResultJS, 32.0, 0.01);
150
+ assertAlmostEqual(u8sResult, 32.0, 0.01);
151
+
152
+ const i8sOne = new Int8Array([1, 2, 3]);
153
+ const i8sTwo = new Int8Array([4, 5, 6]);
154
+ const i8sResult = simsimd.inner(i8sOne, i8sTwo);
155
+ const i8sResultJS = fallback.inner(i8sOne, i8sTwo);
156
+ assertAlmostEqual(i8sResultJS, 32.0, 0.01);
157
+ assertAlmostEqual(i8sResult, 32.0, 0.01);
158
+ });
159
+
160
+ test("Cosine Distance", () => {
161
+ const f64sOne = new Float64Array([1.0, 2.0, 3.0]);
162
+ const f64sTwo = new Float64Array([4.0, 5.0, 6.0]);
163
+ const f64sResult = simsimd.cosine(f64sOne, f64sTwo);
164
+ const f64sResultJS = fallback.cosine(f64sOne, f64sTwo);
165
+ assertAlmostEqual(f64sResultJS, 0.03, 0.01);
166
+ assertAlmostEqual(f64sResult, 0.03, 0.01);
167
+
168
+ const f32sOne = new Float32Array([1.0, 2.0, 3.0]);
169
+ const f32sTwo = new Float32Array([4.0, 5.0, 6.0]);
170
+ const f32sResult = simsimd.cosine(f32sOne, f32sTwo);
171
+ const f32sResultJS = fallback.cosine(f32sOne, f32sTwo);
172
+ assertAlmostEqual(f32sResultJS, 0.03, 0.01);
173
+ assertAlmostEqual(f32sResult, 0.03, 0.01);
174
+
175
+ const u8sOne = new Uint8Array([1, 2, 3]);
176
+ const u8sTwo = new Uint8Array([4, 5, 6]);
177
+ const u8sResult = simsimd.cosine(u8sOne, u8sTwo);
178
+ const u8sResultJS = fallback.cosine(u8sOne, u8sTwo);
179
+ assertAlmostEqual(u8sResultJS, 0.03, 0.01);
180
+ assertAlmostEqual(u8sResult, 0.03, 0.01);
181
+
182
+ const i8sOne = new Int8Array([1, 2, 3]);
183
+ const i8sTwo = new Int8Array([4, 5, 6]);
184
+ const i8sResult = simsimd.cosine(i8sOne, i8sTwo);
185
+ const i8sResultJS = fallback.cosine(i8sOne, i8sTwo);
186
+ assertAlmostEqual(i8sResultJS, 0.03, 0.01);
187
+ assertAlmostEqual(i8sResult, 0.03, 0.01);
188
+ });
189
+
190
+ test("Kullback-Leibler", () => {
191
+ const f64sOne = new Float64Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
192
+ const f64sTwo = new Float64Array([4.0 / 17, 5.0 / 17, 6.0 / 17]);
193
+ const f64sResult = simsimd.kullbackleibler(f64sOne, f64sTwo);
194
+ const f64sResultJS = fallback.kullbackleibler(f64sOne, f64sTwo);
195
+ assertAlmostEqual(f64sResultJS, 0.16, 0.01);
196
+ assertAlmostEqual(f64sResult, 0.16, 0.01);
197
+
198
+ const f32sOne = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
199
+ const f32sTwo = new Float32Array([4.0 / 17, 5.0 / 17, 6.0 / 17]);
200
+ const f32sResult = simsimd.kullbackleibler(f32sOne, f32sTwo);
201
+ const f32sResultJS = fallback.kullbackleibler(f32sOne, f32sTwo);
202
+ assertAlmostEqual(f32sResultJS, 0.16, 0.01);
203
+ assertAlmostEqual(f32sResult, 0.16, 0.01);
204
+ });
205
+
206
+ test("Jensen-Shannon", () => {
207
+ const f64sOne = new Float64Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
208
+ const f64sTwo = new Float64Array([4.0 / 17, 5.0 / 17, 6.0 / 17]);
209
+ const f64sResult = simsimd.jensenshannon(f64sOne, f64sTwo);
210
+ const f64sResultJS = fallback.jensenshannon(f64sOne, f64sTwo);
211
+ assertAlmostEqual(f64sResultJS, 0.095, 0.01);
212
+ assertAlmostEqual(f64sResult, 0.095, 0.01);
213
+
214
+ const f32sOne = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]);
215
+ const f32sTwo = new Float32Array([4.0 / 17, 5.0 / 17, 6.0 / 17]);
216
+ const f32sResult = simsimd.jensenshannon(f32sOne, f32sTwo);
217
+ const f32sResultJS = fallback.jensenshannon(f32sOne, f32sTwo);
218
+ assertAlmostEqual(f32sResultJS, 0.095, 0.01);
219
+ assertAlmostEqual(f32sResult, 0.095, 0.01);
207
220
  });
@@ -38,7 +38,6 @@ Or run the script directly:
38
38
  python test.py
39
39
 
40
40
  """
41
-
42
41
  import os
43
42
  import math
44
43
  import time
@@ -124,7 +123,7 @@ try:
124
123
  baseline_euclidean = lambda x, y: np.array(spd.euclidean(x, y)) #! SciPy returns a scalar
125
124
  baseline_sqeuclidean = spd.sqeuclidean
126
125
  baseline_cosine = spd.cosine
127
- baseline_jensenshannon = lambda x, y: spd.jensenshannon(x, y) ** 2
126
+ baseline_jensenshannon = lambda x, y: spd.jensenshannon(x, y)
128
127
  baseline_hamming = lambda x, y: spd.hamming(x, y) * len(x)
129
128
  baseline_jaccard = spd.jaccard
130
129
 
@@ -453,6 +452,8 @@ def name_to_kernels(name: str):
453
452
  return baseline_fma, simd.fma
454
453
  elif name == "wsum":
455
454
  return baseline_wsum, simd.wsum
455
+ elif name == "jensenshannon":
456
+ return baseline_jensenshannon, simd.jensenshannon
456
457
  else:
457
458
  raise ValueError(f"Unknown kernel name: {name}")
458
459
 
@@ -587,7 +588,7 @@ def test_invalid_argument_handling(function, expected_error, args, kwargs):
587
588
 
588
589
 
589
590
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
590
- @pytest.mark.repeat(50)
591
+ @pytest.mark.repeat(5)
591
592
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
592
593
  @pytest.mark.parametrize("dtype", ["float64", "float32", "float16"])
593
594
  @pytest.mark.parametrize("metric", ["inner", "euclidean", "sqeuclidean", "cosine"])
@@ -616,7 +617,7 @@ def test_dense(ndim, dtype, metric, capability, stats_fixture):
616
617
 
617
618
 
618
619
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
619
- @pytest.mark.repeat(50)
620
+ @pytest.mark.repeat(5)
620
621
  @pytest.mark.parametrize("ndim", [11, 97])
621
622
  @pytest.mark.parametrize(
622
623
  "dtypes", # representation datatype and compute precision
@@ -671,8 +672,42 @@ def test_curved(ndim, dtypes, metric, capability, stats_fixture):
671
672
  collect_errors(metric, ndim, dtype, accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture)
672
673
 
673
674
 
675
+ @pytest.mark.skipif(is_running_under_qemu(), reason="Complex math in QEMU fails")
676
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
677
+ @pytest.mark.repeat(5)
678
+ @pytest.mark.parametrize("ndim", [11, 97])
679
+ @pytest.mark.parametrize("dtype", ["complex128", "complex64"])
680
+ @pytest.mark.parametrize("capability", possible_capabilities)
681
+ def test_curved_complex(ndim, dtype, capability, stats_fixture):
682
+ """Compares various SIMD kernels (like Bilinear Forms and Mahalanobis distances) for curved spaces
683
+ with their NumPy or baseline counterparts, testing accuracy for complex IEEE standard floating-point types."""
684
+
685
+ # Let's generate some uniform complex numbers
686
+ np.random.seed()
687
+ a = (np.random.randn(ndim) + 1.0j * np.random.randn(ndim)).astype(dtype)
688
+ b = (np.random.randn(ndim) + 1.0j * np.random.randn(ndim)).astype(dtype)
689
+ c = (np.random.randn(ndim, ndim) + 1.0j * np.random.randn(ndim, ndim)).astype(dtype)
690
+
691
+ keep_one_capability(capability)
692
+ baseline_kernel, simd_kernel = name_to_kernels("bilinear")
693
+ accurate_dt, accurate = profile(
694
+ baseline_kernel,
695
+ a.astype(np.complex128),
696
+ b.astype(np.complex128),
697
+ c.astype(np.complex128),
698
+ )
699
+ expected_dt, expected = profile(baseline_kernel, a, b, c)
700
+ result_dt, result = profile(simd_kernel, a, b, c)
701
+ result = np.array(result)
702
+
703
+ np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
704
+ collect_errors(
705
+ "bilinear", ndim, dtype, accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture
706
+ )
707
+
708
+
674
709
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
675
- @pytest.mark.repeat(50)
710
+ @pytest.mark.repeat(5)
676
711
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
677
712
  @pytest.mark.parametrize("metric", ["inner", "euclidean", "sqeuclidean", "cosine"])
678
713
  @pytest.mark.parametrize("capability", possible_capabilities)
@@ -712,7 +747,7 @@ def test_dense_bf16(ndim, metric, capability, stats_fixture):
712
747
 
713
748
 
714
749
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
715
- @pytest.mark.repeat(50)
750
+ @pytest.mark.repeat(5)
716
751
  @pytest.mark.parametrize("ndim", [11, 16, 33])
717
752
  @pytest.mark.parametrize("metric", ["bilinear", "mahalanobis"])
718
753
  @pytest.mark.parametrize("capability", possible_capabilities)
@@ -771,7 +806,7 @@ def test_curved_bf16(ndim, metric, capability, stats_fixture):
771
806
 
772
807
 
773
808
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
774
- @pytest.mark.repeat(50)
809
+ @pytest.mark.repeat(5)
775
810
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
776
811
  @pytest.mark.parametrize("dtype", ["int8", "uint8"])
777
812
  @pytest.mark.parametrize("metric", ["inner", "euclidean", "sqeuclidean", "cosine"])
@@ -817,7 +852,7 @@ def test_dense_i8(ndim, dtype, metric, capability, stats_fixture):
817
852
 
818
853
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
819
854
  @pytest.mark.skipif(not scipy_available, reason="SciPy is not installed")
820
- @pytest.mark.repeat(50)
855
+ @pytest.mark.repeat(5)
821
856
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
822
857
  @pytest.mark.parametrize("metric", ["jaccard", "hamming"])
823
858
  @pytest.mark.parametrize("capability", possible_capabilities)
@@ -838,15 +873,27 @@ def test_dense_bits(ndim, metric, capability, stats_fixture):
838
873
  np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
839
874
  collect_errors(metric, ndim, "bin8", accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture)
840
875
 
876
+ # Aside from overriding the `dtype` parameter, we can also view as booleans
877
+ result_dt, result = profile(simd_kernel, np.packbits(a).view(np.bool_), np.packbits(b).view(np.bool_))
878
+ result = np.array(result)
879
+
880
+ np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
881
+ collect_errors(metric, ndim, "bin8", accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture)
882
+
841
883
 
842
- @pytest.mark.skip(reason="Problems inferring the tolerance bounds for numerical errors")
843
- @pytest.mark.repeat(50)
884
+ @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
885
+ @pytest.mark.skipif(not scipy_available, reason="SciPy is not installed")
886
+ @pytest.mark.repeat(5)
844
887
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
845
888
  @pytest.mark.parametrize("dtype", ["float32", "float16"])
846
889
  @pytest.mark.parametrize("capability", possible_capabilities)
847
- def test_jensen_shannon(ndim, dtype, capability):
890
+ def test_jensen_shannon(ndim, dtype, capability, stats_fixture):
848
891
  """Compares the simd.jensenshannon() function with scipy.spatial.distance.jensenshannon(), measuring the accuracy error for f16, and f32 types."""
892
+
849
893
  np.random.seed()
894
+ if dtype == "float16" and is_running_under_qemu():
895
+ pytest.skip("Testing low-precision math isn't reliable in QEMU")
896
+
850
897
  a = np.abs(np.random.randn(ndim)).astype(dtype)
851
898
  b = np.abs(np.random.randn(ndim)).astype(dtype)
852
899
  a /= np.sum(a)
@@ -890,7 +937,7 @@ def test_cosine_zero_vector(ndim, dtype, capability):
890
937
 
891
938
  @pytest.mark.skip(reason="Lacks overflow protection: https://github.com/ashvardanian/SimSIMD/issues/206") # TODO
892
939
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
893
- @pytest.mark.repeat(50)
940
+ @pytest.mark.repeat(5)
894
941
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
895
942
  @pytest.mark.parametrize("dtype", ["float64", "float32", "float16"])
896
943
  @pytest.mark.parametrize("metric", ["inner", "euclidean", "sqeuclidean", "cosine"])
@@ -924,7 +971,7 @@ def test_overflow(ndim, dtype, metric, capability):
924
971
 
925
972
  @pytest.mark.skip(reason="Lacks overflow protection: https://github.com/ashvardanian/SimSIMD/issues/206") # TODO
926
973
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
927
- @pytest.mark.repeat(50)
974
+ @pytest.mark.repeat(5)
928
975
  @pytest.mark.parametrize("ndim", [131072, 262144])
929
976
  @pytest.mark.parametrize("metric", ["inner", "euclidean", "sqeuclidean", "cosine"])
930
977
  @pytest.mark.parametrize("capability", possible_capabilities)
@@ -954,16 +1001,15 @@ def test_overflow_i8(ndim, metric, capability):
954
1001
 
955
1002
  @pytest.mark.skipif(is_running_under_qemu(), reason="Complex math in QEMU fails")
956
1003
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
957
- @pytest.mark.repeat(50)
958
- @pytest.mark.parametrize("ndim", [22, 66, 1536])
959
- @pytest.mark.parametrize("dtype", ["float32", "float64"])
1004
+ @pytest.mark.repeat(5)
1005
+ @pytest.mark.parametrize("ndim", [11, 97, 1536])
1006
+ @pytest.mark.parametrize("dtype", ["complex128", "complex64"])
960
1007
  @pytest.mark.parametrize("capability", possible_capabilities)
961
1008
  def test_dot_complex(ndim, dtype, capability, stats_fixture):
962
1009
  """Compares the simd.dot() and simd.vdot() against NumPy for complex numbers."""
963
1010
  np.random.seed()
964
- dtype_view = np.complex64 if dtype == "float32" else np.complex128
965
- a = np.random.randn(ndim).astype(dtype=dtype).view(dtype_view)
966
- b = np.random.randn(ndim).astype(dtype=dtype).view(dtype_view)
1011
+ a = (np.random.randn(ndim) + 1.0j * np.random.randn(ndim)).astype(dtype)
1012
+ b = (np.random.randn(ndim) + 1.0j * np.random.randn(ndim)).astype(dtype)
967
1013
 
968
1014
  keep_one_capability(capability)
969
1015
  accurate_dt, accurate = profile(np.dot, a.astype(np.complex128), b.astype(np.complex128))
@@ -972,9 +1018,7 @@ def test_dot_complex(ndim, dtype, capability, stats_fixture):
972
1018
  result = np.array(result)
973
1019
 
974
1020
  np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
975
- collect_errors(
976
- "dot", ndim, dtype + "c", accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture
977
- )
1021
+ collect_errors("dot", ndim, dtype, accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture)
978
1022
 
979
1023
  accurate_dt, accurate = profile(np.vdot, a.astype(np.complex128), b.astype(np.complex128))
980
1024
  expected_dt, expected = profile(np.vdot, a, b)
@@ -982,36 +1026,11 @@ def test_dot_complex(ndim, dtype, capability, stats_fixture):
982
1026
  result = np.array(result)
983
1027
 
984
1028
  np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
985
- collect_errors(
986
- "vdot", ndim, dtype + "c", accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture
987
- )
988
-
989
-
990
- @pytest.mark.skipif(is_running_under_qemu(), reason="Complex math in QEMU fails")
991
- @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
992
- @pytest.mark.repeat(50)
993
- @pytest.mark.parametrize("ndim", [22, 66, 1536])
994
- @pytest.mark.parametrize("capability", possible_capabilities)
995
- def test_dot_complex_explicit(ndim, capability):
996
- """Compares the simd.dot() and simd.vdot() against NumPy for complex numbers."""
997
- np.random.seed()
998
- a = np.random.randn(ndim).astype(dtype=np.float32)
999
- b = np.random.randn(ndim).astype(dtype=np.float32)
1000
-
1001
- keep_one_capability(capability)
1002
- expected = np.dot(a.view(np.complex64), b.view(np.complex64))
1003
- result = simd.dot(a, b, "complex64")
1004
-
1005
- np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
1006
-
1007
- expected = np.vdot(a.view(np.complex64), b.view(np.complex64))
1008
- result = simd.vdot(a, b, "complex64")
1009
-
1010
- np.testing.assert_allclose(result, expected, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
1029
+ collect_errors("vdot", ndim, dtype, accurate, accurate_dt, expected, expected_dt, result, result_dt, stats_fixture)
1011
1030
 
1012
1031
 
1013
1032
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1014
- @pytest.mark.repeat(100)
1033
+ @pytest.mark.repeat(5)
1015
1034
  @pytest.mark.parametrize("dtype", ["uint16", "uint32"])
1016
1035
  @pytest.mark.parametrize("first_length_bound", [10, 100, 1000])
1017
1036
  @pytest.mark.parametrize("second_length_bound", [10, 100, 1000])
@@ -1041,7 +1060,7 @@ def test_intersect(dtype, first_length_bound, second_length_bound, capability):
1041
1060
 
1042
1061
 
1043
1062
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1044
- @pytest.mark.repeat(50)
1063
+ @pytest.mark.repeat(5)
1045
1064
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
1046
1065
  @pytest.mark.parametrize("dtype", ["float64", "float32", "float16", "int8", "uint8"])
1047
1066
  @pytest.mark.parametrize("kernel", ["fma"])
@@ -1102,7 +1121,7 @@ def test_fma(ndim, dtype, kernel, capability, stats_fixture):
1102
1121
 
1103
1122
 
1104
1123
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1105
- @pytest.mark.repeat(50)
1124
+ @pytest.mark.repeat(5)
1106
1125
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
1107
1126
  @pytest.mark.parametrize("dtype", ["float64", "float32", "float16", "int8", "uint8"])
1108
1127
  @pytest.mark.parametrize("kernel", ["wsum"])
@@ -1372,7 +1391,7 @@ def test_cdist_complex(ndim, input_dtype, out_dtype, metric, capability):
1372
1391
 
1373
1392
  @pytest.mark.skipif(not numpy_available, reason="NumPy is not installed")
1374
1393
  @pytest.mark.skipif(not scipy_available, reason="SciPy is not installed")
1375
- @pytest.mark.repeat(50)
1394
+ @pytest.mark.repeat(5)
1376
1395
  @pytest.mark.parametrize("ndim", [11, 97, 1536])
1377
1396
  @pytest.mark.parametrize("out_dtype", [None, "float32", "float16", "int8"])
1378
1397
  @pytest.mark.parametrize("capability", possible_capabilities)