use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
mod simd_equivalence_tests {
use proptest::prelude::*;
const EPSILON: f64 = 1e-6;
fn cosine_similarity_scalar(v1: &[f64], v2: &[f64]) -> f64 {
if v1.len() != v2.len() || v1.is_empty() {
return 0.0;
}
let mut dot_product = 0.0;
let mut norm1 = 0.0;
let mut norm2 = 0.0;
for i in 0..v1.len() {
dot_product += v1[i] * v2[i];
norm1 += v1[i] * v1[i];
norm2 += v2[i] * v2[i];
}
if norm1 > 0.0 && norm2 > 0.0 {
dot_product / (norm1.sqrt() * norm2.sqrt())
} else {
0.0
}
}
fn entropy_scalar(probabilities: &[f64]) -> f64 {
let mut entropy = 0.0;
for &p in probabilities {
if p > 0.0 {
entropy -= p * p.log2();
}
}
entropy
}
#[cfg(feature = "simd")]
fn cosine_similarity_simd(v1: &[f64], v2: &[f64]) -> f64 {
use trueno::Vector;
if v1.len() != v2.len() || v1.is_empty() {
return 0.0;
}
let v1_f32: Vec<f32> = v1.iter().map(|&x| x as f32).collect();
let v2_f32: Vec<f32> = v2.iter().map(|&x| x as f32).collect();
let vec1 = Vector::from_slice(&v1_f32);
let vec2 = Vector::from_slice(&v2_f32);
let dot = match vec1.dot(&vec2) {
Ok(d) => d as f64,
Err(_) => return 0.0,
};
let norm1 = match vec1.norm_l2() {
Ok(n) => n as f64,
Err(_) => return 0.0,
};
let norm2 = match vec2.norm_l2() {
Ok(n) => n as f64,
Err(_) => return 0.0,
};
if norm1 > 0.0 && norm2 > 0.0 {
dot / (norm1 * norm2)
} else {
0.0
}
}
#[cfg(feature = "simd")]
fn entropy_simd(probabilities: &[f64]) -> f64 {
use trueno::Vector;
if probabilities.is_empty() {
return 0.0;
}
let probs_f32: Vec<f32> = probabilities
.iter()
.map(|&p| if p > 0.0 { p as f32 } else { 1.0 })
.collect();
let probs_vec = Vector::from_slice(&probs_f32);
let log2_vec = match probs_vec.log2() {
Ok(v) => v,
Err(_) => return entropy_scalar(probabilities),
};
let p_log_p = match probs_vec.mul(&log2_vec) {
Ok(v) => v,
Err(_) => return entropy_scalar(probabilities),
};
match p_log_p.sum() {
Ok(sum) => -(sum as f64),
Err(_) => entropy_scalar(probabilities),
}
}
fn probability_distribution(size: usize) -> impl Strategy<Value = Vec<f64>> {
prop::collection::vec(1.0f64..100.0, size..=size).prop_map(|v| {
let sum: f64 = v.iter().sum();
v.iter().map(|&x| x / sum).collect()
})
}
fn non_zero_vector(size: usize) -> impl Strategy<Value = Vec<f64>> {
prop::collection::vec(-100.0f64..100.0, size..=size)
.prop_filter("at least one non-zero element", |v| {
v.iter().any(|&x| x.abs() > 1e-10)
})
}
proptest! {
#[test]
fn cosine_similarity_symmetric(
v1 in non_zero_vector(100),
v2 in non_zero_vector(100)
) {
let sim_12 = cosine_similarity_scalar(&v1, &v2);
let sim_21 = cosine_similarity_scalar(&v2, &v1);
prop_assert!((sim_12 - sim_21).abs() < EPSILON,
"Symmetry violated: {} vs {}", sim_12, sim_21);
}
#[test]
fn cosine_similarity_identical(v in non_zero_vector(100)) {
let sim = cosine_similarity_scalar(&v, &v);
prop_assert!((sim - 1.0).abs() < EPSILON,
"Self-similarity should be 1.0, got {}", sim);
}
#[test]
fn cosine_similarity_bounded(
v1 in non_zero_vector(100),
v2 in non_zero_vector(100)
) {
let sim = cosine_similarity_scalar(&v1, &v2);
prop_assert!((-1.0 - EPSILON..=1.0 + EPSILON).contains(&sim),
"Similarity out of bounds: {}", sim);
}
#[test]
fn entropy_non_negative(probs in probability_distribution(50)) {
let entropy = entropy_scalar(&probs);
prop_assert!(entropy >= -EPSILON,
"Entropy should be non-negative, got {}", entropy);
}
#[test]
fn entropy_maximum_for_uniform(size in 10usize..100) {
let uniform: Vec<f64> = vec![1.0 / size as f64; size];
let max_entropy = (size as f64).log2();
let computed = entropy_scalar(&uniform);
prop_assert!((computed - max_entropy).abs() < EPSILON,
"Uniform entropy should be log2({}), got {}", size, computed);
}
}
#[test]
#[cfg(feature = "simd")]
fn simd_cosine_similarity_matches_scalar() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config::with_cases(1000));
runner
.run(&(non_zero_vector(256), non_zero_vector(256)), |(v1, v2)| {
let scalar_result = cosine_similarity_scalar(&v1, &v2);
let simd_result = cosine_similarity_simd(&v1, &v2);
let diff = (scalar_result - simd_result).abs();
prop_assert!(
diff < EPSILON,
"SIMD/scalar mismatch: scalar={}, simd={}, diff={}",
scalar_result,
simd_result,
diff
);
Ok(())
})
.expect("SIMD cosine similarity must match scalar within epsilon");
}
#[test]
#[cfg(feature = "simd")]
fn simd_entropy_matches_scalar() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config::with_cases(1000));
runner
.run(&probability_distribution(256), |probs| {
let scalar_result = entropy_scalar(&probs);
let simd_result = entropy_simd(&probs);
let diff = (scalar_result - simd_result).abs();
prop_assert!(
diff < EPSILON,
"SIMD/scalar mismatch: scalar={}, simd={}, diff={}",
scalar_result,
simd_result,
diff
);
Ok(())
})
.expect("SIMD entropy must match scalar within epsilon");
}
#[test]
#[cfg(feature = "simd")]
fn simd_handles_various_sizes() {
let sizes = [1, 3, 5, 7, 15, 17, 31, 33, 63, 65, 127, 129, 255, 257];
for &size in &sizes {
let v1: Vec<f64> = (0..size).map(|i| (i as f64).sin()).collect();
let v2: Vec<f64> = (0..size).map(|i| (i as f64).cos()).collect();
let scalar = cosine_similarity_scalar(&v1, &v2);
let simd = cosine_similarity_simd(&v1, &v2);
assert!(
(scalar - simd).abs() < EPSILON,
"Cosine size {} mismatch: scalar={}, simd={}",
size,
scalar,
simd
);
let raw: Vec<f64> = (0..size).map(|i| (i as f64 + 1.0)).collect();
let sum: f64 = raw.iter().sum();
let probs: Vec<f64> = raw.iter().map(|&x| x / sum).collect();
let scalar_entropy = entropy_scalar(&probs);
let simd_entropy = entropy_simd(&probs);
assert!(
(scalar_entropy - simd_entropy).abs() < EPSILON,
"Entropy size {} mismatch: scalar={}, simd={}",
size,
scalar_entropy,
simd_entropy
);
}
}
#[test]
fn edge_cases() {
assert_eq!(cosine_similarity_scalar(&[], &[]), 0.0);
assert!((cosine_similarity_scalar(&[1.0], &[1.0]) - 1.0).abs() < EPSILON);
assert_eq!(cosine_similarity_scalar(&[0.0, 0.0], &[1.0, 1.0]), 0.0);
let orth = cosine_similarity_scalar(&[1.0, 0.0], &[0.0, 1.0]);
assert!(
orth.abs() < EPSILON,
"Orthogonal vectors should have ~0 similarity"
);
let opp = cosine_similarity_scalar(&[1.0, 1.0], &[-1.0, -1.0]);
assert!(
(opp + 1.0).abs() < EPSILON,
"Opposite vectors should have -1 similarity"
);
}
#[test]
fn baseline_performance_scalar() {
let size = 10000;
let v1: Vec<f64> = (0..size).map(|i| (i as f64).sin()).collect();
let v2: Vec<f64> = (0..size).map(|i| (i as f64).cos()).collect();
let result = cosine_similarity_scalar(&v1, &v2);
assert!(result.is_finite(), "Result should be finite");
}
}