pub use trueno::matrix::Matrix;
pub use trueno::vector::Vector;
pub use trueno::{select_best_available_backend, Backend};
#[must_use]
pub fn kahan_sum(values: &[f64]) -> f64 {
let mut sum = 0.0;
let mut c = 0.0; for &x in values {
let y = x - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
#[must_use]
pub fn kahan_sum_f32(values: &[f32]) -> f32 {
let mut sum = 0.0_f32;
let mut c = 0.0_f32;
for &x in values {
let y = x - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
pub fn dot_f32(a: &[f32], b: &[f32]) -> Result<f32, String> {
if a.len() != b.len() {
return Err(format!(
"Vector length mismatch: {} vs {}",
a.len(),
b.len()
));
}
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.dot(&vb).map_err(|e| format!("Dot product failed: {e}"))
}
pub fn dot(a: &[f64], b: &[f64]) -> Result<f64, String> {
if a.len() != b.len() {
return Err(format!(
"Vector length mismatch: {} vs {}",
a.len(),
b.len()
));
}
let a_f32: Vec<f32> = a.iter().map(|&x| x as f32).collect();
let b_f32: Vec<f32> = b.iter().map(|&x| x as f32).collect();
dot_f32(&a_f32, &b_f32).map(f64::from)
}
#[must_use]
pub fn mean(values: &[f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
kahan_sum(values) / values.len() as f64
}
#[must_use]
pub fn variance(values: &[f64]) -> f64 {
if values.len() < 2 {
return 0.0;
}
let n = values.len() as f64;
let m = mean(values);
let squared_diffs: Vec<f64> = values.iter().map(|&x| (x - m).powi(2)).collect();
kahan_sum(&squared_diffs) / (n - 1.0)
}
#[must_use]
pub fn std_dev(values: &[f64]) -> f64 {
variance(values).sqrt()
}
#[must_use]
pub fn best_backend() -> Backend {
select_best_available_backend()
}
pub fn add_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>, String> {
if a.len() != b.len() {
return Err(format!(
"Vector length mismatch: {} vs {}",
a.len(),
b.len()
));
}
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.add(&vb)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Add failed: {e}"))
}
pub fn mul_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>, String> {
if a.len() != b.len() {
return Err(format!(
"Vector length mismatch: {} vs {}",
a.len(),
b.len()
));
}
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.mul(&vb)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Mul failed: {e}"))
}
#[must_use]
pub fn scale_f32(a: &[f32], scalar: f32) -> Vec<f32> {
let va = Vector::from_slice(a);
match va.scale(scalar) {
Ok(result) => result.as_slice().to_vec(),
Err(_) => a.iter().map(|&x| x * scalar).collect(),
}
}
pub fn sub_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>, String> {
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.sub(&vb)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Sub failed: {e}"))
}
pub fn div_f32(a: &[f32], b: &[f32]) -> Result<Vec<f32>, String> {
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.div(&vb)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Div failed: {e}"))
}
pub fn fma_f32(a: &[f32], b: &[f32], c: &[f32]) -> Result<Vec<f32>, String> {
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
let vc = Vector::from_slice(c);
va.fma(&vb, &vc)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("FMA failed: {e}"))
}
pub fn sum_f32(a: &[f32]) -> Result<f32, String> {
Vector::from_slice(a)
.sum()
.map_err(|e| format!("Sum failed: {e}"))
}
pub fn max_f32(a: &[f32]) -> Result<f32, String> {
Vector::from_slice(a)
.max()
.map_err(|e| format!("Max failed: {e}"))
}
pub fn min_f32(a: &[f32]) -> Result<f32, String> {
Vector::from_slice(a)
.min()
.map_err(|e| format!("Min failed: {e}"))
}
pub fn argmax_f32(a: &[f32]) -> Result<usize, String> {
Vector::from_slice(a)
.argmax()
.map_err(|e| format!("Argmax failed: {e}"))
}
pub fn argmin_f32(a: &[f32]) -> Result<usize, String> {
Vector::from_slice(a)
.argmin()
.map_err(|e| format!("Argmin failed: {e}"))
}
pub fn norm_l2_f32(a: &[f32]) -> Result<f32, String> {
Vector::from_slice(a)
.norm_l2()
.map_err(|e| format!("L2 norm failed: {e}"))
}
pub fn norm_l1_f32(a: &[f32]) -> Result<f32, String> {
Vector::from_slice(a)
.norm_l1()
.map_err(|e| format!("L1 norm failed: {e}"))
}
pub fn norm_linf_f32(a: &[f32]) -> Result<f32, String> {
Vector::from_slice(a)
.norm_linf()
.map_err(|e| format!("Linf norm failed: {e}"))
}
pub fn abs_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.abs()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Abs failed: {e}"))
}
pub fn sqrt_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.sqrt()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Sqrt failed: {e}"))
}
pub fn recip_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.recip()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Recip failed: {e}"))
}
pub fn pow_f32(a: &[f32], n: f32) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.pow(n)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Pow failed: {e}"))
}
pub fn clamp_f32(a: &[f32], min_val: f32, max_val: f32) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.clamp(min_val, max_val)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Clamp failed: {e}"))
}
pub fn neg_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.neg()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Neg failed: {e}"))
}
pub fn exp_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.exp()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Exp failed: {e}"))
}
pub fn ln_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.ln()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Ln failed: {e}"))
}
pub fn sin_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.sin()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Sin failed: {e}"))
}
pub fn cos_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.cos()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Cos failed: {e}"))
}
pub fn tanh_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.tanh()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Tanh failed: {e}"))
}
pub fn softmax_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.softmax()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Softmax failed: {e}"))
}
pub fn relu_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.relu()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("ReLU failed: {e}"))
}
pub fn sigmoid_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.sigmoid()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Sigmoid failed: {e}"))
}
pub fn zscore_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.zscore()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Z-score failed: {e}"))
}
pub fn minmax_normalize_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.minmax_normalize()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("MinMax normalize failed: {e}"))
}
pub fn normalize_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.normalize()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Normalize failed: {e}"))
}
pub fn covariance_f32(a: &[f32], b: &[f32]) -> Result<f32, String> {
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.covariance(&vb)
.map_err(|e| format!("Covariance failed: {e}"))
}
pub fn correlation_f32(a: &[f32], b: &[f32]) -> Result<f32, String> {
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.correlation(&vb)
.map_err(|e| format!("Correlation failed: {e}"))
}
pub fn lerp_f32(a: &[f32], b: &[f32], t: f32) -> Result<Vec<f32>, String> {
let va = Vector::from_slice(a);
let vb = Vector::from_slice(b);
va.lerp(&vb, t)
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Lerp failed: {e}"))
}
pub fn floor_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.floor()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Floor failed: {e}"))
}
pub fn ceil_f32(a: &[f32]) -> Result<Vec<f32>, String> {
Vector::from_slice(a)
.ceil()
.map(|v| v.as_slice().to_vec())
.map_err(|e| format!("Ceil failed: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kahan_sum_basic() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((kahan_sum(&values) - 15.0).abs() < 1e-10);
}
#[test]
fn test_kahan_sum_empty() {
let values: Vec<f64> = vec![];
assert!((kahan_sum(&values) - 0.0).abs() < 1e-10);
}
#[test]
fn test_kahan_sum_cancellation() {
let values = vec![1e8, 1.0, 1.0, 1.0, -1e8];
let result = kahan_sum(&values);
assert!(
(result - 3.0).abs() < 1e-10,
"Kahan sum failed: got {result}, expected 3.0"
);
let small: Vec<f64> = vec![0.1; 10];
let result = kahan_sum(&small);
assert!(
(result - 1.0).abs() < 1e-14,
"Kahan sum of 0.1s failed: got {result}, expected 1.0"
);
}
#[test]
fn test_kahan_sum_many_small_values() {
let values: Vec<f64> = vec![1e-10; 1_000_000];
let result = kahan_sum(&values);
let expected = 1e-10 * 1_000_000.0;
assert!(
(result - expected).abs() < 1e-10,
"Kahan sum error: got {result}, expected {expected}"
);
}
#[test]
fn test_dot_basic() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let result = dot(&a, &b).expect("dot should succeed");
assert!((result - 32.0).abs() < 0.01); }
#[test]
fn test_dot_length_mismatch() {
let a = vec![1.0, 2.0];
let b = vec![1.0, 2.0, 3.0];
assert!(dot(&a, &b).is_err());
}
#[test]
fn test_dot_empty() {
let a: Vec<f64> = vec![];
let b: Vec<f64> = vec![];
let result = dot(&a, &b).expect("dot should succeed");
assert!((result - 0.0).abs() < 1e-10);
}
#[test]
fn test_mean_basic() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((mean(&values) - 3.0).abs() < 1e-10);
}
#[test]
fn test_mean_empty() {
let values: Vec<f64> = vec![];
assert!((mean(&values) - 0.0).abs() < 1e-10);
}
#[test]
fn test_variance_basic() {
let values = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let var = variance(&values);
assert!((var - 4.571_428_571_428_571).abs() < 1e-10);
}
#[test]
fn test_variance_single() {
let values = vec![5.0];
assert!((variance(&values) - 0.0).abs() < 1e-10);
}
#[test]
fn test_std_dev_basic() {
let values = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let std = std_dev(&values);
assert!((std - 2.138_089_935_299_395).abs() < 1e-10);
}
#[test]
fn test_add_f32_basic() {
let a = vec![1.0_f32, 2.0, 3.0];
let b = vec![4.0_f32, 5.0, 6.0];
let result = add_f32(&a, &b).expect("add should succeed");
assert_eq!(result.len(), 3);
assert!((result[0] - 5.0).abs() < 1e-5);
assert!((result[1] - 7.0).abs() < 1e-5);
assert!((result[2] - 9.0).abs() < 1e-5);
}
#[test]
fn test_mul_f32_basic() {
let a = vec![1.0_f32, 2.0, 3.0];
let b = vec![4.0_f32, 5.0, 6.0];
let result = mul_f32(&a, &b).expect("mul should succeed");
assert!((result[0] - 4.0).abs() < 1e-5);
assert!((result[1] - 10.0).abs() < 1e-5);
assert!((result[2] - 18.0).abs() < 1e-5);
}
#[test]
fn test_scale_f32_basic() {
let a = vec![1.0_f32, 2.0, 3.0];
let result = scale_f32(&a, 2.0);
assert!((result[0] - 2.0).abs() < 1e-5);
assert!((result[1] - 4.0).abs() < 1e-5);
assert!((result[2] - 6.0).abs() < 1e-5);
}
#[test]
fn test_sub_f32_basic() {
let a = vec![5.0_f32, 7.0, 9.0];
let b = vec![1.0_f32, 2.0, 3.0];
let result = sub_f32(&a, &b).expect("sub should succeed");
assert!((result[0] - 4.0).abs() < 1e-5);
assert!((result[1] - 5.0).abs() < 1e-5);
assert!((result[2] - 6.0).abs() < 1e-5);
}
#[test]
fn test_div_f32_basic() {
let a = vec![10.0_f32, 20.0, 30.0];
let b = vec![2.0_f32, 5.0, 6.0];
let result = div_f32(&a, &b).expect("div should succeed");
assert!((result[0] - 5.0).abs() < 1e-5);
assert!((result[1] - 4.0).abs() < 1e-5);
assert!((result[2] - 5.0).abs() < 1e-5);
}
#[test]
fn test_sum_f32() {
let a = vec![1.0_f32, 2.0, 3.0, 4.0];
let result = sum_f32(&a).expect("sum should succeed");
assert!((result - 10.0).abs() < 1e-4);
}
#[test]
fn test_max_min_f32() {
let a = vec![3.0_f32, 1.0, 4.0, 1.0, 5.0];
assert!((max_f32(&a).unwrap() - 5.0).abs() < 1e-5);
assert!((min_f32(&a).unwrap() - 1.0).abs() < 1e-5);
}
#[test]
fn test_argmax_argmin_f32() {
let a = vec![3.0_f32, 1.0, 4.0, 1.0, 5.0];
assert_eq!(argmax_f32(&a).unwrap(), 4);
assert_eq!(argmin_f32(&a).unwrap(), 1);
}
#[test]
fn test_norm_l2_f32() {
let a = vec![3.0_f32, 4.0]; let result = norm_l2_f32(&a).unwrap();
assert!((result - 5.0).abs() < 1e-4);
}
#[test]
fn test_norm_l1_f32() {
let a = vec![-3.0_f32, 4.0]; let result = norm_l1_f32(&a).unwrap();
assert!((result - 7.0).abs() < 1e-4);
}
#[test]
fn test_abs_f32() {
let a = vec![-1.0_f32, 2.0, -3.0];
let result = abs_f32(&a).unwrap();
assert!((result[0] - 1.0).abs() < 1e-5);
assert!((result[2] - 3.0).abs() < 1e-5);
}
#[test]
fn test_sqrt_f32() {
let a = vec![4.0_f32, 9.0, 16.0];
let result = sqrt_f32(&a).unwrap();
assert!((result[0] - 2.0).abs() < 1e-4);
assert!((result[1] - 3.0).abs() < 1e-4);
assert!((result[2] - 4.0).abs() < 1e-4);
}
#[test]
fn test_neg_f32() {
let a = vec![1.0_f32, -2.0, 3.0];
let result = neg_f32(&a).unwrap();
assert!((result[0] - (-1.0)).abs() < 1e-5);
assert!((result[1] - 2.0).abs() < 1e-5);
}
#[test]
fn test_clamp_f32() {
let a = vec![-1.0_f32, 0.5, 2.0];
let result = clamp_f32(&a, 0.0, 1.0).unwrap();
assert!((result[0] - 0.0).abs() < 1e-5);
assert!((result[1] - 0.5).abs() < 1e-5);
assert!((result[2] - 1.0).abs() < 1e-5);
}
#[test]
fn test_relu_f32() {
let a = vec![-2.0_f32, -1.0, 0.0, 1.0, 2.0];
let result = relu_f32(&a).unwrap();
assert!((result[0] - 0.0).abs() < 1e-5);
assert!((result[1] - 0.0).abs() < 1e-5);
assert!((result[3] - 1.0).abs() < 1e-5);
assert!((result[4] - 2.0).abs() < 1e-5);
}
#[test]
fn test_sigmoid_f32() {
let a = vec![0.0_f32];
let result = sigmoid_f32(&a).unwrap();
assert!((result[0] - 0.5).abs() < 1e-4); }
#[test]
fn test_softmax_f32_sums_to_one() {
let a = vec![1.0_f32, 2.0, 3.0];
let result = softmax_f32(&a).unwrap();
let total: f32 = result.iter().sum();
assert!((total - 1.0).abs() < 1e-4);
}
#[test]
fn test_exp_f32() {
let a = vec![0.0_f32, 1.0];
let result = exp_f32(&a).unwrap();
assert!((result[0] - 1.0).abs() < 1e-4); assert!((result[1] - std::f32::consts::E).abs() < 1e-3);
}
#[test]
fn test_normalize_f32() {
let a = vec![3.0_f32, 4.0]; let result = normalize_f32(&a).unwrap();
assert!((result[0] - 0.6).abs() < 1e-4);
assert!((result[1] - 0.8).abs() < 1e-4);
}
#[test]
fn test_floor_ceil_f32() {
let a = vec![1.3_f32, 2.7, -0.5];
let floored = floor_f32(&a).unwrap();
assert!((floored[0] - 1.0).abs() < 1e-5);
assert!((floored[1] - 2.0).abs() < 1e-5);
let ceiled = ceil_f32(&a).unwrap();
assert!((ceiled[0] - 2.0).abs() < 1e-5);
assert!((ceiled[1] - 3.0).abs() < 1e-5);
}
#[test]
fn test_best_backend_available() {
let backend = best_backend();
assert!(matches!(
backend,
Backend::Scalar
| Backend::SSE2
| Backend::AVX
| Backend::AVX2
| Backend::AVX512
| Backend::NEON
| Backend::WasmSIMD
| Backend::GPU
| Backend::Auto
));
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
#[test]
fn prop_kahan_sum_commutative(a in prop::collection::vec(-1e10..1e10, 0..100)) {
let sum1 = kahan_sum(&a);
#[allow(clippy::redundant_clone)]
let mut reversed = a.clone();
reversed.reverse();
let sum2 = kahan_sum(&reversed);
prop_assert!((sum1 - sum2).abs() < 1e-6 * sum1.abs().max(1.0));
}
#[test]
fn prop_mean_bounds(a in prop::collection::vec(-100.0..100.0f64, 1..100)) {
let m = mean(&a);
let min = a.iter().copied().fold(f64::INFINITY, f64::min);
let max = a.iter().copied().fold(f64::NEG_INFINITY, f64::max);
prop_assert!(m >= min && m <= max);
}
#[test]
fn prop_variance_non_negative(a in prop::collection::vec(-100.0..100.0f64, 2..100)) {
let var = variance(&a);
prop_assert!(var >= 0.0);
}
#[test]
fn prop_scale_by_one_identity(a in prop::collection::vec(-100.0..100.0f32, 1..50)) {
let result = scale_f32(&a, 1.0);
for (x, y) in a.iter().zip(result.iter()) {
prop_assert!((x - y).abs() < 1e-5);
}
}
}
}
#[cfg(test)]
mod backend_equivalence_tests {
use super::*;
fn scalar_kahan_sum(values: &[f64]) -> f64 {
let mut sum = 0.0_f64;
let mut c = 0.0_f64;
for &x in values {
let y = x - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum
}
fn scalar_dot(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
fn scalar_add(a: &[f32], b: &[f32]) -> Vec<f32> {
a.iter().zip(b.iter()).map(|(x, y)| x + y).collect()
}
fn scalar_mul(a: &[f32], b: &[f32]) -> Vec<f32> {
a.iter().zip(b.iter()).map(|(x, y)| x * y).collect()
}
fn scalar_scale(a: &[f32], s: f32) -> Vec<f32> {
a.iter().map(|x| x * s).collect()
}
#[test]
fn test_kahan_sum_matches_scalar_baseline() {
let values: Vec<f64> = (0..1000).map(|i| f64::from(i) * 0.001).collect();
let simd_result = kahan_sum(&values);
let scalar_result = scalar_kahan_sum(&values);
assert!(
(simd_result - scalar_result).abs() < 1e-12,
"SIMD kahan_sum ({simd_result}) != scalar ({scalar_result})"
);
}
#[test]
fn test_dot_matches_scalar_baseline() {
let a: Vec<f32> = (0..256).map(|i| (i as f32) * 0.1).collect();
let b: Vec<f32> = (0..256).map(|i| (i as f32) * 0.2).collect();
let simd_result = dot_f32(&a, &b).unwrap();
let scalar_result = scalar_dot(&a, &b);
let rel_error = (simd_result - scalar_result).abs() / scalar_result.abs().max(1.0);
assert!(
rel_error < 1e-5,
"SIMD dot ({simd_result}) != scalar ({scalar_result}), rel_error={rel_error}"
);
}
#[test]
fn test_add_matches_scalar_baseline() {
let a: Vec<f32> = (0..128).map(|i| (i as f32) * 0.5).collect();
let b: Vec<f32> = (0..128).map(|i| (i as f32) * 0.3).collect();
let simd_result = add_f32(&a, &b).unwrap();
let scalar_result = scalar_add(&a, &b);
for (i, (s, r)) in simd_result.iter().zip(scalar_result.iter()).enumerate() {
assert!(
(s - r).abs() < 1e-6,
"add mismatch at index {i}: SIMD={s}, scalar={r}"
);
}
}
#[test]
fn test_mul_matches_scalar_baseline() {
let a: Vec<f32> = (0..128).map(|i| (i as f32) * 0.5).collect();
let b: Vec<f32> = (0..128).map(|i| (i as f32) * 0.3).collect();
let simd_result = mul_f32(&a, &b).unwrap();
let scalar_result = scalar_mul(&a, &b);
for (i, (s, r)) in simd_result.iter().zip(scalar_result.iter()).enumerate() {
assert!(
(s - r).abs() < 1e-5,
"mul mismatch at index {i}: SIMD={s}, scalar={r}"
);
}
}
#[test]
fn test_scale_matches_scalar_baseline() {
let a: Vec<f32> = (0..128).map(|i| (i as f32) * 0.7).collect();
let scalar = 2.5_f32;
let simd_result = scale_f32(&a, scalar);
let scalar_result = scalar_scale(&a, scalar);
for (i, (s, r)) in simd_result.iter().zip(scalar_result.iter()).enumerate() {
assert!(
(s - r).abs() < 1e-5,
"scale mismatch at index {i}: SIMD={s}, scalar={r}"
);
}
}
#[test]
fn test_mean_variance_consistency() {
let uniform: Vec<f64> = vec![42.0; 100];
assert!((mean(&uniform) - 42.0).abs() < 1e-10);
assert!(variance(&uniform).abs() < 1e-10);
let seq: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!((mean(&seq) - 3.0).abs() < 1e-10);
assert!((variance(&seq) - 2.5).abs() < 1e-10);
}
#[test]
fn test_backend_reports_valid_type() {
let backend = best_backend();
eprintln!("Running with backend: {backend:?}");
assert!(matches!(
backend,
Backend::Scalar
| Backend::SSE2
| Backend::AVX
| Backend::AVX2
| Backend::AVX512
| Backend::NEON
| Backend::WasmSIMD
| Backend::GPU
| Backend::Auto
));
}
}