usearch 2.21.2 → 2.21.4
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 +1 -1
- package/include/usearch/index.hpp +13 -6
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/CONTRIBUTING.md +16 -9
- package/simsimd/Cargo.toml +7 -11
- package/simsimd/MANIFEST.in +1 -0
- package/simsimd/README.md +21 -20
- package/simsimd/build.rs +4 -3
- package/simsimd/c/lib.c +1 -1
- package/simsimd/include/simsimd/binary.h +11 -10
- package/simsimd/include/simsimd/dot.h +4 -4
- package/simsimd/include/simsimd/geospatial.h +1 -1
- package/simsimd/include/simsimd/mesh.h +1 -1
- package/simsimd/include/simsimd/probability.h +14 -14
- package/simsimd/include/simsimd/simsimd.h +86 -6
- package/simsimd/include/simsimd/sparse.h +60 -105
- package/simsimd/include/simsimd/types.h +4 -3
- package/simsimd/javascript/fallback.ts +10 -14
- package/simsimd/javascript/lib.c +1 -1
- package/simsimd/javascript/simsimd.ts +4 -4
- package/simsimd/package.json +1 -1
- package/simsimd/rust/lib.rs +288 -130
- package/simsimd/scripts/bench.cxx +38 -7
- package/simsimd/scripts/bench.rs +876 -0
- package/simsimd/scripts/test.py +1 -1
- package/simsimd/Cargo.lock +0 -713
- package/simsimd/scripts/bench_cosine.rs +0 -41
- package/simsimd/scripts/bench_native.rs +0 -272
- package/simsimd/scripts/bench_sqeuclidean.rs +0 -41
package/simsimd/rust/lib.rs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//!
|
|
8
8
|
//! ## Implemented distance functions include:
|
|
9
9
|
//!
|
|
10
|
-
//! * Euclidean (L2),
|
|
10
|
+
//! * Euclidean (L2), inner product, and cosine (angular) spatial distances.
|
|
11
11
|
//! * Hamming (~ Manhattan) and Jaccard (~ Tanimoto) binary distances.
|
|
12
12
|
//! * Kullback-Leibler and Jensen-Shannon divergences for probability distributions.
|
|
13
13
|
//!
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
//! let a = &[1, 2, 3];
|
|
20
20
|
//! let b = &[4, 5, 6];
|
|
21
21
|
//!
|
|
22
|
-
//! // Compute cosine
|
|
23
|
-
//! let
|
|
22
|
+
//! // Compute cosine distance
|
|
23
|
+
//! let cos_dist = i8::cos(a, b);
|
|
24
24
|
//!
|
|
25
25
|
//! // Compute dot product distance
|
|
26
26
|
//! let dot_product = i8::dot(a, b);
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
//! // Work with half-precision floats
|
|
41
41
|
//! let half_a: Vec<f16> = vec![1.0, 2.0, 3.0].iter().map(|&x| f16::from_f32(x)).collect();
|
|
42
42
|
//! let half_b: Vec<f16> = vec![4.0, 5.0, 6.0].iter().map(|&x| f16::from_f32(x)).collect();
|
|
43
|
-
//! let
|
|
43
|
+
//! let half_cos_dist = f16::cos(&half_a, &half_b);
|
|
44
44
|
//!
|
|
45
45
|
//! // Work with brain floats
|
|
46
46
|
//! let brain_a: Vec<bf16> = vec![1.0, 2.0, 3.0].iter().map(|&x| bf16::from_f32(x)).collect();
|
|
47
47
|
//! let brain_b: Vec<bf16> = vec![4.0, 5.0, 6.0].iter().map(|&x| bf16::from_f32(x)).collect();
|
|
48
|
-
//! let
|
|
48
|
+
//! let brain_cos_dist = bf16::cos(&brain_a, &brain_b);
|
|
49
49
|
//!
|
|
50
50
|
//! // Direct bit manipulation
|
|
51
51
|
//! let half = f16::from_f32(3.14);
|
|
@@ -57,14 +57,14 @@
|
|
|
57
57
|
//!
|
|
58
58
|
//! The `SpatialSimilarity` trait covers following methods:
|
|
59
59
|
//!
|
|
60
|
-
//! - `cosine(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes cosine similarity between two slices.
|
|
60
|
+
//! - `cosine(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes cosine distance (1 - similarity) between two slices.
|
|
61
61
|
//! - `dot(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes dot product distance between two slices.
|
|
62
62
|
//! - `sqeuclidean(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes squared Euclidean distance between two slices.
|
|
63
63
|
//!
|
|
64
64
|
//! The `BinarySimilarity` trait covers following methods:
|
|
65
65
|
//!
|
|
66
66
|
//! - `hamming(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Hamming distance between two slices.
|
|
67
|
-
//! - `jaccard(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Jaccard
|
|
67
|
+
//! - `jaccard(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Jaccard distance between two slices.
|
|
68
68
|
//!
|
|
69
69
|
//! The `ProbabilitySimilarity` trait covers following methods:
|
|
70
70
|
//!
|
|
@@ -77,6 +77,12 @@
|
|
|
77
77
|
pub type Distance = f64;
|
|
78
78
|
pub type ComplexProduct = (f64, f64);
|
|
79
79
|
|
|
80
|
+
/// Compatibility function for pre 1.85 Rust versions lacking `f32::abs`.
|
|
81
|
+
#[inline(always)]
|
|
82
|
+
fn f32_abs_compat(x: f32) -> f32 {
|
|
83
|
+
f32::from_bits(x.to_bits() & 0x7FFF_FFFF)
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
#[link(name = "simsimd")]
|
|
81
87
|
extern "C" {
|
|
82
88
|
|
|
@@ -194,10 +200,10 @@ pub struct f16(pub u16);
|
|
|
194
200
|
impl f16 {
|
|
195
201
|
/// Positive zero.
|
|
196
202
|
pub const ZERO: Self = f16(0);
|
|
197
|
-
|
|
203
|
+
|
|
198
204
|
/// Positive one.
|
|
199
205
|
pub const ONE: Self = f16(0x3C00);
|
|
200
|
-
|
|
206
|
+
|
|
201
207
|
/// Negative one.
|
|
202
208
|
pub const NEG_ONE: Self = f16(0xBC00);
|
|
203
209
|
|
|
@@ -251,11 +257,11 @@ impl f16 {
|
|
|
251
257
|
/// Returns the absolute value of self.
|
|
252
258
|
#[inline(always)]
|
|
253
259
|
pub fn abs(self) -> Self {
|
|
254
|
-
Self::from_f32(self.to_f32()
|
|
260
|
+
Self::from_f32(f32_abs_compat(self.to_f32()))
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
/// Returns the largest integer less than or equal to a number.
|
|
258
|
-
///
|
|
264
|
+
///
|
|
259
265
|
/// This method is only available when the `std` feature is enabled.
|
|
260
266
|
#[cfg(feature = "std")]
|
|
261
267
|
#[inline(always)]
|
|
@@ -264,7 +270,7 @@ impl f16 {
|
|
|
264
270
|
}
|
|
265
271
|
|
|
266
272
|
/// Returns the smallest integer greater than or equal to a number.
|
|
267
|
-
///
|
|
273
|
+
///
|
|
268
274
|
/// This method is only available when the `std` feature is enabled.
|
|
269
275
|
#[cfg(feature = "std")]
|
|
270
276
|
#[inline(always)]
|
|
@@ -273,7 +279,7 @@ impl f16 {
|
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
/// Returns the nearest integer to a number. Round half-way cases away from 0.0.
|
|
276
|
-
///
|
|
282
|
+
///
|
|
277
283
|
/// This method is only available when the `std` feature is enabled.
|
|
278
284
|
#[cfg(feature = "std")]
|
|
279
285
|
#[inline(always)]
|
|
@@ -368,10 +374,10 @@ pub struct bf16(pub u16);
|
|
|
368
374
|
impl bf16 {
|
|
369
375
|
/// Positive zero.
|
|
370
376
|
pub const ZERO: Self = bf16(0);
|
|
371
|
-
|
|
377
|
+
|
|
372
378
|
/// Positive one.
|
|
373
379
|
pub const ONE: Self = bf16(0x3F80);
|
|
374
|
-
|
|
380
|
+
|
|
375
381
|
/// Negative one.
|
|
376
382
|
pub const NEG_ONE: Self = bf16(0xBF80);
|
|
377
383
|
|
|
@@ -425,11 +431,11 @@ impl bf16 {
|
|
|
425
431
|
/// Returns the absolute value of self.
|
|
426
432
|
#[inline(always)]
|
|
427
433
|
pub fn abs(self) -> Self {
|
|
428
|
-
Self::from_f32(self.to_f32()
|
|
434
|
+
Self::from_f32(f32_abs_compat(self.to_f32()))
|
|
429
435
|
}
|
|
430
436
|
|
|
431
437
|
/// Returns the largest integer less than or equal to a number.
|
|
432
|
-
///
|
|
438
|
+
///
|
|
433
439
|
/// This method is only available when the `std` feature is enabled.
|
|
434
440
|
#[cfg(feature = "std")]
|
|
435
441
|
#[inline(always)]
|
|
@@ -438,7 +444,7 @@ impl bf16 {
|
|
|
438
444
|
}
|
|
439
445
|
|
|
440
446
|
/// Returns the smallest integer greater than or equal to a number.
|
|
441
|
-
///
|
|
447
|
+
///
|
|
442
448
|
/// This method is only available when the `std` feature is enabled.
|
|
443
449
|
#[cfg(feature = "std")]
|
|
444
450
|
#[inline(always)]
|
|
@@ -447,7 +453,7 @@ impl bf16 {
|
|
|
447
453
|
}
|
|
448
454
|
|
|
449
455
|
/// Returns the nearest integer to a number. Round half-way cases away from 0.0.
|
|
450
|
-
///
|
|
456
|
+
///
|
|
451
457
|
/// This method is only available when the `std` feature is enabled.
|
|
452
458
|
#[cfg(feature = "std")]
|
|
453
459
|
#[inline(always)]
|
|
@@ -596,8 +602,8 @@ pub mod capabilities {
|
|
|
596
602
|
///
|
|
597
603
|
/// # Returns
|
|
598
604
|
///
|
|
599
|
-
/// Returns `true`
|
|
600
|
-
///
|
|
605
|
+
/// Returns `true` when the C backend is compiled with dynamic dispatch
|
|
606
|
+
/// (default for this crate via `build.rs`), otherwise `false`.
|
|
601
607
|
pub fn uses_dynamic_dispatch() -> bool {
|
|
602
608
|
unsafe { crate::simsimd_uses_dynamic_dispatch() != 0 }
|
|
603
609
|
}
|
|
@@ -605,19 +611,21 @@ pub mod capabilities {
|
|
|
605
611
|
|
|
606
612
|
/// `SpatialSimilarity` provides a set of trait methods for computing similarity
|
|
607
613
|
/// or distance between spatial data vectors in SIMD (Single Instruction, Multiple Data) context.
|
|
608
|
-
/// These methods can be used to calculate metrics like cosine
|
|
614
|
+
/// These methods can be used to calculate metrics like cosine distance, dot product,
|
|
609
615
|
/// and squared Euclidean distance between two slices of data.
|
|
610
616
|
///
|
|
611
617
|
/// Each method takes two slices of data (a and b) and returns an Option<Distance>.
|
|
612
618
|
/// The result is `None` if the slices are not of the same length, as these operations
|
|
613
619
|
/// require one-to-one correspondence between the elements of the slices.
|
|
614
|
-
/// Otherwise, it returns the computed similarity or distance as `Some(
|
|
620
|
+
/// Otherwise, it returns the computed similarity or distance as `Some(f64)`.
|
|
621
|
+
/// Convenience methods like `cosine`/`sqeuclidean` delegate to the core methods
|
|
622
|
+
/// `cos`/`l2sq` implemented by this trait.
|
|
615
623
|
pub trait SpatialSimilarity
|
|
616
624
|
where
|
|
617
625
|
Self: Sized,
|
|
618
626
|
{
|
|
619
|
-
/// Computes the cosine
|
|
620
|
-
/// The cosine
|
|
627
|
+
/// Computes the cosine distance between two slices.
|
|
628
|
+
/// The cosine distance is 1 minus the cosine similarity between two non-zero vectors
|
|
621
629
|
/// of an dot product space that measures the cosine of the angle between them.
|
|
622
630
|
fn cos(a: &[Self], b: &[Self]) -> Option<Distance>;
|
|
623
631
|
|
|
@@ -659,8 +667,8 @@ where
|
|
|
659
667
|
SpatialSimilarity::dot(a, b)
|
|
660
668
|
}
|
|
661
669
|
|
|
662
|
-
/// Computes the cosine
|
|
663
|
-
/// The cosine
|
|
670
|
+
/// Computes the cosine distance between two slices.
|
|
671
|
+
/// The cosine distance is 1 minus the cosine similarity between two non-zero vectors
|
|
664
672
|
/// of an dot product space that measures the cosine of the angle between them.
|
|
665
673
|
fn cosine(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
666
674
|
SpatialSimilarity::cos(a, b)
|
|
@@ -1121,7 +1129,7 @@ impl ProbabilitySimilarity for f64 {
|
|
|
1121
1129
|
|
|
1122
1130
|
impl ComplexProducts for f16 {
|
|
1123
1131
|
fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1124
|
-
if a.len() != b.len() {
|
|
1132
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1125
1133
|
return None;
|
|
1126
1134
|
}
|
|
1127
1135
|
// Prepare the output array where the real and imaginary parts will be stored
|
|
@@ -1130,26 +1138,28 @@ impl ComplexProducts for f16 {
|
|
|
1130
1138
|
// Explicitly cast `*const f16` to `*const u16`
|
|
1131
1139
|
let a_ptr = a.as_ptr() as *const u16;
|
|
1132
1140
|
let b_ptr = b.as_ptr() as *const u16;
|
|
1133
|
-
|
|
1141
|
+
// The C function expects the number of complex pairs, not the total number of f16 elements
|
|
1142
|
+
unsafe { simsimd_dot_f16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
|
|
1134
1143
|
Some((product[0], product[1]))
|
|
1135
1144
|
}
|
|
1136
1145
|
|
|
1137
1146
|
fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1138
|
-
if a.len() != b.len() {
|
|
1147
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1139
1148
|
return None;
|
|
1140
1149
|
}
|
|
1141
1150
|
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
1142
1151
|
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
1143
1152
|
let a_ptr = a.as_ptr() as *const u16;
|
|
1144
1153
|
let b_ptr = b.as_ptr() as *const u16;
|
|
1145
|
-
|
|
1154
|
+
// The C function expects the number of complex pairs, not the total number of f16 elements
|
|
1155
|
+
unsafe { simsimd_vdot_f16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
|
|
1146
1156
|
Some((product[0], product[1]))
|
|
1147
1157
|
}
|
|
1148
1158
|
}
|
|
1149
1159
|
|
|
1150
1160
|
impl ComplexProducts for bf16 {
|
|
1151
1161
|
fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1152
|
-
if a.len() != b.len() {
|
|
1162
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1153
1163
|
return None;
|
|
1154
1164
|
}
|
|
1155
1165
|
// Prepare the output array where the real and imaginary parts will be stored
|
|
@@ -1158,12 +1168,13 @@ impl ComplexProducts for bf16 {
|
|
|
1158
1168
|
// Explicitly cast `*const bf16` to `*const u16`
|
|
1159
1169
|
let a_ptr = a.as_ptr() as *const u16;
|
|
1160
1170
|
let b_ptr = b.as_ptr() as *const u16;
|
|
1161
|
-
|
|
1171
|
+
// The C function expects the number of complex pairs, not the total number of bf16 elements
|
|
1172
|
+
unsafe { simsimd_dot_bf16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
|
|
1162
1173
|
Some((product[0], product[1]))
|
|
1163
1174
|
}
|
|
1164
1175
|
|
|
1165
1176
|
fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1166
|
-
if a.len() != b.len() {
|
|
1177
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1167
1178
|
return None;
|
|
1168
1179
|
}
|
|
1169
1180
|
// Prepare the output array where the real and imaginary parts will be stored
|
|
@@ -1172,51 +1183,56 @@ impl ComplexProducts for bf16 {
|
|
|
1172
1183
|
// Explicitly cast `*const bf16` to `*const u16`
|
|
1173
1184
|
let a_ptr = a.as_ptr() as *const u16;
|
|
1174
1185
|
let b_ptr = b.as_ptr() as *const u16;
|
|
1175
|
-
|
|
1186
|
+
// The C function expects the number of complex pairs, not the total number of bf16 elements
|
|
1187
|
+
unsafe { simsimd_vdot_bf16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
|
|
1176
1188
|
Some((product[0], product[1]))
|
|
1177
1189
|
}
|
|
1178
1190
|
}
|
|
1179
1191
|
|
|
1180
1192
|
impl ComplexProducts for f32 {
|
|
1181
1193
|
fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1182
|
-
if a.len() != b.len() {
|
|
1194
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1183
1195
|
return None;
|
|
1184
1196
|
}
|
|
1185
1197
|
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
1186
1198
|
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
1187
|
-
|
|
1199
|
+
// The C function expects the number of complex pairs, not the total number of floats
|
|
1200
|
+
unsafe { simsimd_dot_f32c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
|
|
1188
1201
|
Some((product[0], product[1]))
|
|
1189
1202
|
}
|
|
1190
1203
|
|
|
1191
1204
|
fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1192
|
-
if a.len() != b.len() {
|
|
1205
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1193
1206
|
return None;
|
|
1194
1207
|
}
|
|
1195
1208
|
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
1196
1209
|
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
1197
|
-
|
|
1210
|
+
// The C function expects the number of complex pairs, not the total number of floats
|
|
1211
|
+
unsafe { simsimd_vdot_f32c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
|
|
1198
1212
|
Some((product[0], product[1]))
|
|
1199
1213
|
}
|
|
1200
1214
|
}
|
|
1201
1215
|
|
|
1202
1216
|
impl ComplexProducts for f64 {
|
|
1203
1217
|
fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1204
|
-
if a.len() != b.len() {
|
|
1218
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1205
1219
|
return None;
|
|
1206
1220
|
}
|
|
1207
1221
|
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
1208
1222
|
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
1209
|
-
|
|
1223
|
+
// The C function expects the number of complex pairs, not the total number of floats
|
|
1224
|
+
unsafe { simsimd_dot_f64c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
|
|
1210
1225
|
Some((product[0], product[1]))
|
|
1211
1226
|
}
|
|
1212
1227
|
|
|
1213
1228
|
fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
1214
|
-
if a.len() != b.len() {
|
|
1229
|
+
if a.len() != b.len() || a.len() % 2 != 0 {
|
|
1215
1230
|
return None;
|
|
1216
1231
|
}
|
|
1217
1232
|
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
1218
1233
|
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
1219
|
-
|
|
1234
|
+
// The C function expects the number of complex pairs, not the total number of floats
|
|
1235
|
+
unsafe { simsimd_vdot_f64c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
|
|
1220
1236
|
Some((product[0], product[1]))
|
|
1221
1237
|
}
|
|
1222
1238
|
}
|
|
@@ -1228,7 +1244,7 @@ mod tests {
|
|
|
1228
1244
|
use half::f16 as HalfF16;
|
|
1229
1245
|
|
|
1230
1246
|
#[test]
|
|
1231
|
-
fn
|
|
1247
|
+
fn hardware_features_detection() {
|
|
1232
1248
|
let uses_arm = capabilities::uses_neon() || capabilities::uses_sve();
|
|
1233
1249
|
let uses_x86 = capabilities::uses_haswell()
|
|
1234
1250
|
|| capabilities::uses_skylake()
|
|
@@ -1246,7 +1262,13 @@ mod tests {
|
|
|
1246
1262
|
}
|
|
1247
1263
|
|
|
1248
1264
|
println!("- uses_neon: {}", capabilities::uses_neon());
|
|
1265
|
+
println!("- uses_neon_f16: {}", capabilities::uses_neon_f16());
|
|
1266
|
+
println!("- uses_neon_bf16: {}", capabilities::uses_neon_bf16());
|
|
1267
|
+
println!("- uses_neon_i8: {}", capabilities::uses_neon_i8());
|
|
1249
1268
|
println!("- uses_sve: {}", capabilities::uses_sve());
|
|
1269
|
+
println!("- uses_sve_f16: {}", capabilities::uses_sve_f16());
|
|
1270
|
+
println!("- uses_sve_bf16: {}", capabilities::uses_sve_bf16());
|
|
1271
|
+
println!("- uses_sve_i8: {}", capabilities::uses_sve_i8());
|
|
1250
1272
|
println!("- uses_haswell: {}", capabilities::uses_haswell());
|
|
1251
1273
|
println!("- uses_skylake: {}", capabilities::uses_skylake());
|
|
1252
1274
|
println!("- uses_ice: {}", capabilities::uses_ice());
|
|
@@ -1265,127 +1287,109 @@ mod tests {
|
|
|
1265
1287
|
}
|
|
1266
1288
|
|
|
1267
1289
|
#[test]
|
|
1268
|
-
fn
|
|
1290
|
+
fn cos_i8() {
|
|
1269
1291
|
let a = &[3, 97, 127];
|
|
1270
1292
|
let b = &[3, 97, 127];
|
|
1271
1293
|
|
|
1272
1294
|
if let Some(result) = SpatialSimilarity::cosine(a, b) {
|
|
1273
|
-
println!("The result of cos_i8 is {:.8}", result);
|
|
1274
1295
|
assert_almost_equal(0.00012027938, result, 0.01);
|
|
1275
1296
|
}
|
|
1276
1297
|
}
|
|
1277
1298
|
|
|
1278
1299
|
#[test]
|
|
1279
|
-
fn
|
|
1300
|
+
fn cos_f32() {
|
|
1280
1301
|
let a = &[1.0, 2.0, 3.0];
|
|
1281
1302
|
let b = &[4.0, 5.0, 6.0];
|
|
1282
1303
|
|
|
1283
1304
|
if let Some(result) = SpatialSimilarity::cosine(a, b) {
|
|
1284
|
-
println!("The result of cos_f32 is {:.8}", result);
|
|
1285
1305
|
assert_almost_equal(0.025, result, 0.01);
|
|
1286
1306
|
}
|
|
1287
1307
|
}
|
|
1288
1308
|
|
|
1289
1309
|
#[test]
|
|
1290
|
-
fn
|
|
1310
|
+
fn dot_i8() {
|
|
1291
1311
|
let a = &[1, 2, 3];
|
|
1292
1312
|
let b = &[4, 5, 6];
|
|
1293
1313
|
|
|
1294
1314
|
if let Some(result) = SpatialSimilarity::dot(a, b) {
|
|
1295
|
-
println!("The result of dot_i8 is {:.8}", result);
|
|
1296
1315
|
assert_almost_equal(32.0, result, 0.01);
|
|
1297
1316
|
}
|
|
1298
1317
|
}
|
|
1299
1318
|
|
|
1300
1319
|
#[test]
|
|
1301
|
-
fn
|
|
1320
|
+
fn dot_f32() {
|
|
1302
1321
|
let a = &[1.0, 2.0, 3.0];
|
|
1303
1322
|
let b = &[4.0, 5.0, 6.0];
|
|
1304
1323
|
|
|
1305
1324
|
if let Some(result) = SpatialSimilarity::dot(a, b) {
|
|
1306
|
-
println!("The result of dot_f32 is {:.8}", result);
|
|
1307
1325
|
assert_almost_equal(32.0, result, 0.01);
|
|
1308
1326
|
}
|
|
1309
1327
|
}
|
|
1310
1328
|
|
|
1311
1329
|
#[test]
|
|
1312
|
-
fn
|
|
1330
|
+
fn dot_f32_complex() {
|
|
1313
1331
|
// Let's consider these as complex numbers where every pair is (real, imaginary)
|
|
1314
1332
|
let a: &[f32; 4] = &[1.0, 2.0, 3.0, 4.0]; // Represents two complex numbers: 1+2i, 3+4i
|
|
1315
1333
|
let b: &[f32; 4] = &[5.0, 6.0, 7.0, 8.0]; // Represents two complex numbers: 5+6i, 7+8i
|
|
1316
1334
|
|
|
1317
1335
|
if let Some((real, imag)) = ComplexProducts::dot(a, b) {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
real, imag
|
|
1321
|
-
);
|
|
1322
|
-
// These values should be replaced with the expected real and imaginary parts of the result
|
|
1323
|
-
assert_almost_equal(-18.0, real, 0.01); // Corrected expected real part
|
|
1324
|
-
assert_almost_equal(68.0, imag, 0.01); // Corrected expected imaginary part
|
|
1336
|
+
assert_almost_equal(-18.0, real, 0.01);
|
|
1337
|
+
assert_almost_equal(68.0, imag, 0.01);
|
|
1325
1338
|
}
|
|
1326
1339
|
}
|
|
1327
1340
|
|
|
1328
1341
|
#[test]
|
|
1329
|
-
fn
|
|
1342
|
+
fn vdot_f32_complex() {
|
|
1330
1343
|
// Here we're assuming a similar setup to the previous test, but for the Hermitian (conjugate) dot product
|
|
1331
1344
|
let a: &[f32; 4] = &[1.0, 2.0, 3.0, 4.0]; // Represents two complex numbers: 1+2i, 3+4i
|
|
1332
1345
|
let b: &[f32; 4] = &[5.0, 6.0, 7.0, 8.0]; // Represents two complex numbers: 5+6i, 7+8i
|
|
1333
1346
|
|
|
1334
1347
|
if let Some((real, imag)) = ComplexProducts::vdot(a, b) {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
real, imag
|
|
1338
|
-
);
|
|
1339
|
-
// Replace these with the actual expected values
|
|
1340
|
-
assert_almost_equal(70.0, real, 0.01); // Example expected real part
|
|
1341
|
-
assert_almost_equal(-8.0, imag, 0.01); // Example expected imaginary part
|
|
1348
|
+
assert_almost_equal(70.0, real, 0.01);
|
|
1349
|
+
assert_almost_equal(-8.0, imag, 0.01);
|
|
1342
1350
|
}
|
|
1343
1351
|
}
|
|
1344
1352
|
|
|
1345
1353
|
#[test]
|
|
1346
|
-
fn
|
|
1354
|
+
fn l2sq_i8() {
|
|
1347
1355
|
let a = &[1, 2, 3];
|
|
1348
1356
|
let b = &[4, 5, 6];
|
|
1349
1357
|
|
|
1350
1358
|
if let Some(result) = SpatialSimilarity::sqeuclidean(a, b) {
|
|
1351
|
-
println!("The result of l2sq_i8 is {:.8}", result);
|
|
1352
1359
|
assert_almost_equal(27.0, result, 0.01);
|
|
1353
1360
|
}
|
|
1354
1361
|
}
|
|
1355
1362
|
|
|
1356
1363
|
#[test]
|
|
1357
|
-
fn
|
|
1364
|
+
fn l2sq_f32() {
|
|
1358
1365
|
let a = &[1.0, 2.0, 3.0];
|
|
1359
1366
|
let b = &[4.0, 5.0, 6.0];
|
|
1360
1367
|
|
|
1361
1368
|
if let Some(result) = SpatialSimilarity::sqeuclidean(a, b) {
|
|
1362
|
-
println!("The result of l2sq_f32 is {:.8}", result);
|
|
1363
1369
|
assert_almost_equal(27.0, result, 0.01);
|
|
1364
1370
|
}
|
|
1365
1371
|
}
|
|
1366
1372
|
|
|
1367
1373
|
#[test]
|
|
1368
|
-
fn
|
|
1374
|
+
fn l2_f32() {
|
|
1369
1375
|
let a: &[f32; 3] = &[1.0, 2.0, 3.0];
|
|
1370
1376
|
let b: &[f32; 3] = &[4.0, 5.0, 6.0];
|
|
1371
1377
|
if let Some(result) = SpatialSimilarity::euclidean(a, b) {
|
|
1372
|
-
println!("The result of l2_f32 is {:.8}", result);
|
|
1373
1378
|
assert_almost_equal(5.2, result, 0.01);
|
|
1374
1379
|
}
|
|
1375
1380
|
}
|
|
1376
1381
|
|
|
1377
1382
|
#[test]
|
|
1378
|
-
fn
|
|
1383
|
+
fn l2_f64() {
|
|
1379
1384
|
let a: &[f64; 3] = &[1.0, 2.0, 3.0];
|
|
1380
1385
|
let b: &[f64; 3] = &[4.0, 5.0, 6.0];
|
|
1381
1386
|
if let Some(result) = SpatialSimilarity::euclidean(a, b) {
|
|
1382
|
-
println!("The result of l2_f64 is {:.8}", result);
|
|
1383
1387
|
assert_almost_equal(5.2, result, 0.01);
|
|
1384
1388
|
}
|
|
1385
1389
|
}
|
|
1386
1390
|
|
|
1387
1391
|
#[test]
|
|
1388
|
-
fn
|
|
1392
|
+
fn l2_f16() {
|
|
1389
1393
|
let a_half: Vec<HalfF16> = vec![1.0, 2.0, 3.0]
|
|
1390
1394
|
.iter()
|
|
1391
1395
|
.map(|&x| HalfF16::from_f32(x))
|
|
@@ -1401,110 +1405,98 @@ mod tests {
|
|
|
1401
1405
|
unsafe { std::slice::from_raw_parts(b_half.as_ptr() as *const f16, b_half.len()) };
|
|
1402
1406
|
|
|
1403
1407
|
if let Some(result) = SpatialSimilarity::euclidean(&a_simsimd, &b_simsimd) {
|
|
1404
|
-
println!("The result of l2_f16 is {:.8}", result);
|
|
1405
1408
|
assert_almost_equal(5.2, result, 0.01);
|
|
1406
1409
|
}
|
|
1407
1410
|
}
|
|
1408
1411
|
|
|
1409
1412
|
#[test]
|
|
1410
|
-
fn
|
|
1413
|
+
fn l2_i8() {
|
|
1411
1414
|
let a = &[1, 2, 3];
|
|
1412
1415
|
let b = &[4, 5, 6];
|
|
1413
1416
|
|
|
1414
1417
|
if let Some(result) = SpatialSimilarity::euclidean(a, b) {
|
|
1415
|
-
println!("The result of l2_i8 is {:.8}", result);
|
|
1416
1418
|
assert_almost_equal(5.2, result, 0.01);
|
|
1417
1419
|
}
|
|
1418
1420
|
}
|
|
1419
1421
|
// Adding new tests for bit-level distances
|
|
1420
1422
|
#[test]
|
|
1421
|
-
fn
|
|
1422
|
-
let a = &[0b01010101, 0b11110000, 0b10101010];
|
|
1423
|
+
fn hamming_u8() {
|
|
1424
|
+
let a = &[0b01010101, 0b11110000, 0b10101010];
|
|
1423
1425
|
let b = &[0b01010101, 0b11110000, 0b10101010];
|
|
1424
1426
|
|
|
1425
1427
|
if let Some(result) = BinarySimilarity::hamming(a, b) {
|
|
1426
|
-
|
|
1427
|
-
assert_almost_equal(0.0, result, 0.01); // Perfect match
|
|
1428
|
+
assert_almost_equal(0.0, result, 0.01);
|
|
1428
1429
|
}
|
|
1429
1430
|
}
|
|
1430
1431
|
|
|
1431
1432
|
#[test]
|
|
1432
|
-
fn
|
|
1433
|
+
fn jaccard_u8() {
|
|
1433
1434
|
// For binary data, treat each byte as a set of bits
|
|
1434
1435
|
let a = &[0b11110000, 0b00001111, 0b10101010];
|
|
1435
1436
|
let b = &[0b11110000, 0b00001111, 0b01010101];
|
|
1436
1437
|
|
|
1437
1438
|
if let Some(result) = BinarySimilarity::jaccard(a, b) {
|
|
1438
|
-
|
|
1439
|
-
assert_almost_equal(0.5, result, 0.01); // Example value
|
|
1439
|
+
assert_almost_equal(0.5, result, 0.01);
|
|
1440
1440
|
}
|
|
1441
1441
|
}
|
|
1442
1442
|
|
|
1443
1443
|
// Adding new tests for probability similarities
|
|
1444
1444
|
#[test]
|
|
1445
|
-
fn
|
|
1445
|
+
fn js_f32() {
|
|
1446
1446
|
let a: &[f32; 3] = &[0.1, 0.9, 0.0];
|
|
1447
1447
|
let b: &[f32; 3] = &[0.2, 0.8, 0.0];
|
|
1448
1448
|
|
|
1449
1449
|
if let Some(result) = ProbabilitySimilarity::jensenshannon(a, b) {
|
|
1450
|
-
|
|
1451
|
-
assert_almost_equal(0.099, result, 0.01); // Example value
|
|
1450
|
+
assert_almost_equal(0.099, result, 0.01);
|
|
1452
1451
|
}
|
|
1453
1452
|
}
|
|
1454
1453
|
|
|
1455
1454
|
#[test]
|
|
1456
|
-
fn
|
|
1455
|
+
fn kl_f32() {
|
|
1457
1456
|
let a: &[f32; 3] = &[0.1, 0.9, 0.0];
|
|
1458
1457
|
let b: &[f32; 3] = &[0.2, 0.8, 0.0];
|
|
1459
1458
|
|
|
1460
1459
|
if let Some(result) = ProbabilitySimilarity::kullbackleibler(a, b) {
|
|
1461
|
-
|
|
1462
|
-
assert_almost_equal(0.036, result, 0.01); // Example value
|
|
1460
|
+
assert_almost_equal(0.036, result, 0.01);
|
|
1463
1461
|
}
|
|
1464
1462
|
}
|
|
1465
1463
|
|
|
1466
1464
|
#[test]
|
|
1467
|
-
fn
|
|
1465
|
+
fn cos_f16_same() {
|
|
1468
1466
|
// Assuming these u16 values represent f16 bit patterns, and they are identical
|
|
1469
1467
|
let a_u16: &[u16] = &[15360, 16384, 17408]; // Corresponding to some f16 values
|
|
1470
1468
|
let b_u16: &[u16] = &[15360, 16384, 17408]; // Same as above for simplicity
|
|
1471
1469
|
|
|
1472
1470
|
// Reinterpret cast from &[u16] to &[f16]
|
|
1473
|
-
// SAFETY: This is safe as long as the representations are guaranteed to be identical,
|
|
1474
|
-
// which they are for transparent structs wrapping the same type.
|
|
1475
1471
|
let a_f16: &[f16] =
|
|
1476
1472
|
unsafe { std::slice::from_raw_parts(a_u16.as_ptr() as *const f16, a_u16.len()) };
|
|
1477
1473
|
let b_f16: &[f16] =
|
|
1478
1474
|
unsafe { std::slice::from_raw_parts(b_u16.as_ptr() as *const f16, b_u16.len()) };
|
|
1479
1475
|
|
|
1480
1476
|
if let Some(result) = SpatialSimilarity::cosine(a_f16, b_f16) {
|
|
1481
|
-
|
|
1482
|
-
assert_almost_equal(0.0, result, 0.01); // Example value, adjust according to actual expected value
|
|
1477
|
+
assert_almost_equal(0.0, result, 0.01);
|
|
1483
1478
|
}
|
|
1484
1479
|
}
|
|
1485
1480
|
|
|
1486
1481
|
#[test]
|
|
1487
|
-
fn
|
|
1482
|
+
fn cos_bf16_same() {
|
|
1488
1483
|
// Assuming these u16 values represent bf16 bit patterns, and they are identical
|
|
1489
1484
|
let a_u16: &[u16] = &[15360, 16384, 17408]; // Corresponding to some bf16 values
|
|
1490
1485
|
let b_u16: &[u16] = &[15360, 16384, 17408]; // Same as above for simplicity
|
|
1491
1486
|
|
|
1492
1487
|
// Reinterpret cast from &[u16] to &[bf16]
|
|
1493
|
-
// SAFETY: This is safe as long as the representations are guaranteed to be identical,
|
|
1494
|
-
// which they are for transparent structs wrapping the same type.
|
|
1495
1488
|
let a_bf16: &[bf16] =
|
|
1496
1489
|
unsafe { std::slice::from_raw_parts(a_u16.as_ptr() as *const bf16, a_u16.len()) };
|
|
1497
1490
|
let b_bf16: &[bf16] =
|
|
1498
1491
|
unsafe { std::slice::from_raw_parts(b_u16.as_ptr() as *const bf16, b_u16.len()) };
|
|
1499
1492
|
|
|
1500
1493
|
if let Some(result) = SpatialSimilarity::cosine(a_bf16, b_bf16) {
|
|
1501
|
-
|
|
1502
|
-
assert_almost_equal(0.0, result, 0.01); // Example value, adjust according to actual expected value
|
|
1494
|
+
assert_almost_equal(0.0, result, 0.01);
|
|
1503
1495
|
}
|
|
1504
1496
|
}
|
|
1505
1497
|
|
|
1506
1498
|
#[test]
|
|
1507
|
-
fn
|
|
1499
|
+
fn cos_f16_interop() {
|
|
1508
1500
|
let a_half: Vec<HalfF16> = vec![1.0, 2.0, 3.0]
|
|
1509
1501
|
.iter()
|
|
1510
1502
|
.map(|&x| HalfF16::from_f32(x))
|
|
@@ -1523,15 +1515,12 @@ mod tests {
|
|
|
1523
1515
|
|
|
1524
1516
|
// Use the reinterpret-casted slices with your SpatialSimilarity implementation
|
|
1525
1517
|
if let Some(result) = SpatialSimilarity::cosine(a_simsimd, b_simsimd) {
|
|
1526
|
-
// Expected value might need adjustment depending on actual cosine functionality
|
|
1527
|
-
// Assuming identical vectors yield cosine similarity of 1.0
|
|
1528
|
-
println!("The result of cos_f16 (interop) is {:.8}", result);
|
|
1529
1518
|
assert_almost_equal(0.025, result, 0.01);
|
|
1530
1519
|
}
|
|
1531
1520
|
}
|
|
1532
1521
|
|
|
1533
1522
|
#[test]
|
|
1534
|
-
fn
|
|
1523
|
+
fn cos_bf16_interop() {
|
|
1535
1524
|
let a_half: Vec<HalfBF16> = vec![1.0, 2.0, 3.0]
|
|
1536
1525
|
.iter()
|
|
1537
1526
|
.map(|&x| HalfBF16::from_f32(x))
|
|
@@ -1550,44 +1539,38 @@ mod tests {
|
|
|
1550
1539
|
|
|
1551
1540
|
// Use the reinterpret-casted slices with your SpatialSimilarity implementation
|
|
1552
1541
|
if let Some(result) = SpatialSimilarity::cosine(a_simsimd, b_simsimd) {
|
|
1553
|
-
// Expected value might need adjustment depending on actual cosine functionality
|
|
1554
|
-
// Assuming identical vectors yield cosine similarity of 1.0
|
|
1555
|
-
println!("The result of cos_bf16 (interop) is {:.8}", result);
|
|
1556
1542
|
assert_almost_equal(0.025, result, 0.01);
|
|
1557
1543
|
}
|
|
1558
1544
|
}
|
|
1559
1545
|
|
|
1560
1546
|
#[test]
|
|
1561
|
-
fn
|
|
1547
|
+
fn intersect_u16() {
|
|
1562
1548
|
{
|
|
1563
1549
|
let a_u16: &[u16] = &[153, 16384, 17408];
|
|
1564
|
-
let b_u16: &[u16] = &[15360, 16384
|
|
1550
|
+
let b_u16: &[u16] = &[7408, 15360, 16384];
|
|
1565
1551
|
|
|
1566
1552
|
if let Some(result) = Sparse::intersect(a_u16, b_u16) {
|
|
1567
|
-
println!("The result of intersect_u16 is {:.8}", result);
|
|
1568
1553
|
assert_almost_equal(1.0, result, 0.0001);
|
|
1569
1554
|
}
|
|
1570
1555
|
}
|
|
1571
1556
|
|
|
1572
1557
|
{
|
|
1573
|
-
let a_u16: &[u16] = &[153, 11638
|
|
1574
|
-
let b_u16: &[u16] = &[15360, 16384
|
|
1558
|
+
let a_u16: &[u16] = &[8, 153, 11638];
|
|
1559
|
+
let b_u16: &[u16] = &[7408, 15360, 16384];
|
|
1575
1560
|
|
|
1576
1561
|
if let Some(result) = Sparse::intersect(a_u16, b_u16) {
|
|
1577
|
-
println!("The result of intersect_u16 is {:.8}", result);
|
|
1578
1562
|
assert_almost_equal(0.0, result, 0.0001);
|
|
1579
1563
|
}
|
|
1580
1564
|
}
|
|
1581
1565
|
}
|
|
1582
1566
|
|
|
1583
1567
|
#[test]
|
|
1584
|
-
fn
|
|
1568
|
+
fn intersect_u32() {
|
|
1585
1569
|
{
|
|
1586
1570
|
let a_u32: &[u32] = &[11, 153];
|
|
1587
1571
|
let b_u32: &[u32] = &[11, 153, 7408, 16384];
|
|
1588
1572
|
|
|
1589
1573
|
if let Some(result) = Sparse::intersect(a_u32, b_u32) {
|
|
1590
|
-
println!("The result of intersect_u32 is {:.8}", result);
|
|
1591
1574
|
assert_almost_equal(2.0, result, 0.0001);
|
|
1592
1575
|
}
|
|
1593
1576
|
}
|
|
@@ -1597,34 +1580,194 @@ mod tests {
|
|
|
1597
1580
|
let b_u32: &[u32] = &[153, 7408, 11638];
|
|
1598
1581
|
|
|
1599
1582
|
if let Some(result) = Sparse::intersect(a_u32, b_u32) {
|
|
1600
|
-
println!("The result of intersect_u32 is {:.8}", result);
|
|
1601
1583
|
assert_almost_equal(3.0, result, 0.0001);
|
|
1602
1584
|
}
|
|
1603
1585
|
}
|
|
1604
1586
|
}
|
|
1605
1587
|
|
|
1588
|
+
/// Reference implementation of set intersection using Rust's standard library
|
|
1589
|
+
fn reference_intersect<T: Ord>(a: &[T], b: &[T]) -> usize {
|
|
1590
|
+
let mut a_iter = a.iter();
|
|
1591
|
+
let mut b_iter = b.iter();
|
|
1592
|
+
let mut a_current = a_iter.next();
|
|
1593
|
+
let mut b_current = b_iter.next();
|
|
1594
|
+
let mut count = 0;
|
|
1595
|
+
|
|
1596
|
+
while let (Some(a_val), Some(b_val)) = (a_current, b_current) {
|
|
1597
|
+
match a_val.cmp(b_val) {
|
|
1598
|
+
core::cmp::Ordering::Less => a_current = a_iter.next(),
|
|
1599
|
+
core::cmp::Ordering::Greater => b_current = b_iter.next(),
|
|
1600
|
+
core::cmp::Ordering::Equal => {
|
|
1601
|
+
count += 1;
|
|
1602
|
+
a_current = a_iter.next();
|
|
1603
|
+
b_current = b_iter.next();
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
count
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/// Generate test arrays with various sizes and patterns for intersection testing
|
|
1611
|
+
/// Includes empty, small, medium, large arrays with different overlap characteristics
|
|
1612
|
+
fn generate_intersection_test_arrays<T>() -> Vec<Vec<T>>
|
|
1613
|
+
where
|
|
1614
|
+
T: core::convert::TryFrom<u32> + Copy,
|
|
1615
|
+
<T as core::convert::TryFrom<u32>>::Error: core::fmt::Debug,
|
|
1616
|
+
{
|
|
1617
|
+
vec![
|
|
1618
|
+
// Empty array
|
|
1619
|
+
vec![],
|
|
1620
|
+
// Single element
|
|
1621
|
+
vec![T::try_from(42).unwrap()],
|
|
1622
|
+
// Very small arrays (< 16 elements) - tests serial fallback
|
|
1623
|
+
vec![
|
|
1624
|
+
T::try_from(1).unwrap(),
|
|
1625
|
+
T::try_from(5).unwrap(),
|
|
1626
|
+
T::try_from(10).unwrap(),
|
|
1627
|
+
],
|
|
1628
|
+
vec![
|
|
1629
|
+
T::try_from(2).unwrap(),
|
|
1630
|
+
T::try_from(4).unwrap(),
|
|
1631
|
+
T::try_from(6).unwrap(),
|
|
1632
|
+
T::try_from(8).unwrap(),
|
|
1633
|
+
T::try_from(10).unwrap(),
|
|
1634
|
+
T::try_from(12).unwrap(),
|
|
1635
|
+
T::try_from(14).unwrap(),
|
|
1636
|
+
],
|
|
1637
|
+
// Small arrays (< 32 elements) - boundary case for Turin
|
|
1638
|
+
(0..14).map(|x| T::try_from(x * 10).unwrap()).collect(),
|
|
1639
|
+
(5..20).map(|x| T::try_from(x * 10).unwrap()).collect(),
|
|
1640
|
+
// Medium arrays (32-64 elements) - tests one or two SIMD iterations
|
|
1641
|
+
(0..40).map(|x| T::try_from(x * 2).unwrap()).collect(),
|
|
1642
|
+
(10..50).map(|x| T::try_from(x * 2).unwrap()).collect(), // 50% overlap with previous
|
|
1643
|
+
(0..45).map(|x| T::try_from(x * 3).unwrap()).collect(), // Different stride
|
|
1644
|
+
// Large arrays (> 64 elements) - tests main SIMD loop
|
|
1645
|
+
(0..100).map(|x| T::try_from(x * 2).unwrap()).collect(),
|
|
1646
|
+
(50..150).map(|x| T::try_from(x * 2).unwrap()).collect(), // 50% overlap
|
|
1647
|
+
(0..100).map(|x| T::try_from(x * 5).unwrap()).collect(), // Sparse overlap
|
|
1648
|
+
(0..150)
|
|
1649
|
+
.filter(|x| x % 7 == 0)
|
|
1650
|
+
.map(|x| T::try_from(x).unwrap())
|
|
1651
|
+
.collect(),
|
|
1652
|
+
// Very large arrays (> 256 elements) - stress test
|
|
1653
|
+
(0..500).map(|x| T::try_from(x * 3).unwrap()).collect(),
|
|
1654
|
+
(100..600).map(|x| T::try_from(x * 3).unwrap()).collect(), // Large overlap
|
|
1655
|
+
(0..600).map(|x| T::try_from(x * 7).unwrap()).collect(), // Minimal overlap
|
|
1656
|
+
// Edge cases: no overlap at all
|
|
1657
|
+
(0..50).map(|x| T::try_from(x * 2).unwrap()).collect(),
|
|
1658
|
+
(1000..1050).map(|x| T::try_from(x * 2).unwrap()).collect(), // Completely disjoint
|
|
1659
|
+
// Dense arrays at boundaries
|
|
1660
|
+
(0..16).map(|x| T::try_from(x).unwrap()).collect(), // Exactly 16 elements
|
|
1661
|
+
(0..32).map(|x| T::try_from(x).unwrap()).collect(), // Exactly 32 elements
|
|
1662
|
+
(0..64).map(|x| T::try_from(x).unwrap()).collect(), // Exactly 64 elements
|
|
1663
|
+
]
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1606
1666
|
#[test]
|
|
1607
|
-
fn
|
|
1667
|
+
fn intersect_u32_comprehensive() {
|
|
1668
|
+
let test_arrays: Vec<Vec<u32>> = generate_intersection_test_arrays();
|
|
1669
|
+
|
|
1670
|
+
for (i, array_a) in test_arrays.iter().enumerate() {
|
|
1671
|
+
for (j, array_b) in test_arrays.iter().enumerate() {
|
|
1672
|
+
let expected = reference_intersect(array_a, array_b);
|
|
1673
|
+
let result =
|
|
1674
|
+
Sparse::intersect(array_a.as_slice(), array_b.as_slice()).unwrap() as usize;
|
|
1675
|
+
|
|
1676
|
+
assert_eq!(
|
|
1677
|
+
expected,
|
|
1678
|
+
result,
|
|
1679
|
+
"Intersection mismatch for arrays[{}] (len={}) and arrays[{}] (len={})",
|
|
1680
|
+
i,
|
|
1681
|
+
array_a.len(),
|
|
1682
|
+
j,
|
|
1683
|
+
array_b.len()
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
#[test]
|
|
1690
|
+
fn intersect_u16_comprehensive() {
|
|
1691
|
+
let test_arrays: Vec<Vec<u16>> = generate_intersection_test_arrays();
|
|
1692
|
+
|
|
1693
|
+
for (i, array_a) in test_arrays.iter().enumerate() {
|
|
1694
|
+
for (j, array_b) in test_arrays.iter().enumerate() {
|
|
1695
|
+
let expected = reference_intersect(array_a, array_b);
|
|
1696
|
+
let result =
|
|
1697
|
+
Sparse::intersect(array_a.as_slice(), array_b.as_slice()).unwrap() as usize;
|
|
1698
|
+
|
|
1699
|
+
assert_eq!(
|
|
1700
|
+
expected,
|
|
1701
|
+
result,
|
|
1702
|
+
"Intersection mismatch for arrays[{}] (len={}) and arrays[{}] (len={})",
|
|
1703
|
+
i,
|
|
1704
|
+
array_a.len(),
|
|
1705
|
+
j,
|
|
1706
|
+
array_b.len()
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
#[test]
|
|
1713
|
+
fn intersect_edge_cases() {
|
|
1714
|
+
// Test empty arrays
|
|
1715
|
+
let empty: &[u32] = &[];
|
|
1716
|
+
let non_empty: &[u32] = &[1, 2, 3];
|
|
1717
|
+
assert_eq!(Sparse::intersect(empty, empty), Some(0.0));
|
|
1718
|
+
assert_eq!(Sparse::intersect(empty, non_empty), Some(0.0));
|
|
1719
|
+
assert_eq!(Sparse::intersect(non_empty, empty), Some(0.0));
|
|
1720
|
+
|
|
1721
|
+
// Test single element matches
|
|
1722
|
+
assert_eq!(Sparse::intersect(&[42u32], &[42u32]), Some(1.0));
|
|
1723
|
+
assert_eq!(Sparse::intersect(&[42u32], &[43u32]), Some(0.0));
|
|
1724
|
+
|
|
1725
|
+
// Test no overlap
|
|
1726
|
+
let a: &[u32] = &[1, 2, 3, 4, 5];
|
|
1727
|
+
let b: &[u32] = &[10, 20, 30, 40, 50];
|
|
1728
|
+
assert_eq!(Sparse::intersect(a, b), Some(0.0));
|
|
1729
|
+
|
|
1730
|
+
// Test complete overlap
|
|
1731
|
+
let c: &[u32] = &[10, 20, 30, 40, 50];
|
|
1732
|
+
assert_eq!(Sparse::intersect(c, c), Some(5.0));
|
|
1733
|
+
|
|
1734
|
+
// Test one element at boundary (exactly at 16, 32, 64 element boundaries)
|
|
1735
|
+
let boundary_16: Vec<u32> = (0..16).collect();
|
|
1736
|
+
let boundary_32: Vec<u32> = (0..32).collect();
|
|
1737
|
+
let boundary_64: Vec<u32> = (0..64).collect();
|
|
1738
|
+
|
|
1739
|
+
assert_eq!(Sparse::intersect(&boundary_16, &boundary_16), Some(16.0));
|
|
1740
|
+
assert_eq!(Sparse::intersect(&boundary_32, &boundary_32), Some(32.0));
|
|
1741
|
+
assert_eq!(Sparse::intersect(&boundary_64, &boundary_64), Some(64.0));
|
|
1742
|
+
|
|
1743
|
+
// Test partial overlap at boundaries
|
|
1744
|
+
let first_half: Vec<u32> = (0..32).collect();
|
|
1745
|
+
let second_half: Vec<u32> = (16..48).collect();
|
|
1746
|
+
assert_eq!(Sparse::intersect(&first_half, &second_half), Some(16.0));
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
#[test]
|
|
1750
|
+
fn f16_arithmetic() {
|
|
1608
1751
|
let a = f16::from_f32(3.5);
|
|
1609
1752
|
let b = f16::from_f32(2.0);
|
|
1610
|
-
|
|
1753
|
+
|
|
1611
1754
|
// Test basic arithmetic
|
|
1612
1755
|
assert!((a + b).to_f32() - 5.5 < 0.01);
|
|
1613
1756
|
assert!((a - b).to_f32() - 1.5 < 0.01);
|
|
1614
1757
|
assert!((a * b).to_f32() - 7.0 < 0.01);
|
|
1615
1758
|
assert!((a / b).to_f32() - 1.75 < 0.01);
|
|
1616
1759
|
assert!((-a).to_f32() + 3.5 < 0.01);
|
|
1617
|
-
|
|
1760
|
+
|
|
1618
1761
|
// Test constants
|
|
1619
1762
|
assert!(f16::ZERO.to_f32() == 0.0);
|
|
1620
1763
|
assert!((f16::ONE.to_f32() - 1.0).abs() < 0.01);
|
|
1621
1764
|
assert!((f16::NEG_ONE.to_f32() + 1.0).abs() < 0.01);
|
|
1622
|
-
|
|
1765
|
+
|
|
1623
1766
|
// Test comparisons
|
|
1624
1767
|
assert!(a > b);
|
|
1625
1768
|
assert!(!(a < b));
|
|
1626
1769
|
assert!(a == a);
|
|
1627
|
-
|
|
1770
|
+
|
|
1628
1771
|
// Test utility methods
|
|
1629
1772
|
assert!((-a).abs().to_f32() - 3.5 < 0.01);
|
|
1630
1773
|
assert!(a.is_finite());
|
|
@@ -1633,31 +1776,46 @@ mod tests {
|
|
|
1633
1776
|
}
|
|
1634
1777
|
|
|
1635
1778
|
#[test]
|
|
1636
|
-
fn
|
|
1779
|
+
fn bf16_arithmetic() {
|
|
1637
1780
|
let a = bf16::from_f32(3.5);
|
|
1638
1781
|
let b = bf16::from_f32(2.0);
|
|
1639
|
-
|
|
1782
|
+
|
|
1640
1783
|
// Test basic arithmetic
|
|
1641
1784
|
assert!((a + b).to_f32() - 5.5 < 0.1);
|
|
1642
1785
|
assert!((a - b).to_f32() - 1.5 < 0.1);
|
|
1643
1786
|
assert!((a * b).to_f32() - 7.0 < 0.1);
|
|
1644
1787
|
assert!((a / b).to_f32() - 1.75 < 0.1);
|
|
1645
1788
|
assert!((-a).to_f32() + 3.5 < 0.1);
|
|
1646
|
-
|
|
1789
|
+
|
|
1647
1790
|
// Test constants
|
|
1648
1791
|
assert!(bf16::ZERO.to_f32() == 0.0);
|
|
1649
1792
|
assert!((bf16::ONE.to_f32() - 1.0).abs() < 0.01);
|
|
1650
1793
|
assert!((bf16::NEG_ONE.to_f32() + 1.0).abs() < 0.01);
|
|
1651
|
-
|
|
1794
|
+
|
|
1652
1795
|
// Test comparisons
|
|
1653
1796
|
assert!(a > b);
|
|
1654
1797
|
assert!(!(a < b));
|
|
1655
1798
|
assert!(a == a);
|
|
1656
|
-
|
|
1799
|
+
|
|
1657
1800
|
// Test utility methods
|
|
1658
1801
|
assert!((-a).abs().to_f32() - 3.5 < 0.1);
|
|
1659
1802
|
assert!(a.is_finite());
|
|
1660
1803
|
assert!(!a.is_nan());
|
|
1661
1804
|
assert!(!a.is_infinite());
|
|
1662
1805
|
}
|
|
1806
|
+
|
|
1807
|
+
#[test]
|
|
1808
|
+
fn bf16_dot() {
|
|
1809
|
+
let brain_a: Vec<bf16> = vec![1.0, 2.0, 3.0, 1.0, 2.0]
|
|
1810
|
+
.iter()
|
|
1811
|
+
.map(|&x| bf16::from_f32(x))
|
|
1812
|
+
.collect();
|
|
1813
|
+
let brain_b: Vec<bf16> = vec![4.0, 5.0, 6.0, 4.0, 5.0]
|
|
1814
|
+
.iter()
|
|
1815
|
+
.map(|&x| bf16::from_f32(x))
|
|
1816
|
+
.collect();
|
|
1817
|
+
if let Some(result) = <bf16 as SpatialSimilarity>::dot(&brain_a, &brain_b) {
|
|
1818
|
+
assert_eq!(46.0, result);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1663
1821
|
}
|