#![allow(clippy::disallowed_methods, clippy::float_cmp)]
use trueno::Vector;
const FP_TOLERANCE: f32 = 1e-5;
#[test]
fn smoke_simd_vector_add() {
let size = 10_000;
let a: Vec<f32> = (0..size).map(|i| i as f32 * 0.1).collect();
let b: Vec<f32> = (0..size).map(|i| (size - i) as f32 * 0.1).collect();
let va = Vector::from_slice(&a);
let vb = Vector::from_slice(&b);
let result = va.add(&vb).expect("Vector add failed");
let expected: Vec<f32> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
for (i, (got, want)) in result.as_slice().iter().zip(expected.iter()).enumerate() {
assert!((got - want).abs() < FP_TOLERANCE, "Mismatch at index {i}: got {got}, want {want}");
}
}
#[test]
fn smoke_simd_dot_product() {
let size = 10_000;
let a: Vec<f32> = (0..size).map(|i| (i as f32).sin()).collect();
let b: Vec<f32> = (0..size).map(|i| (i as f32).cos()).collect();
let va = Vector::from_slice(&a);
let vb = Vector::from_slice(&b);
let result = va.dot(&vb).expect("Dot product failed");
let expected: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
assert!(
(result - expected).abs() < FP_TOLERANCE * size as f32,
"Dot product mismatch: got {result}, want {expected}"
);
}
#[test]
fn smoke_simd_vector_norm() {
let size = 10_000;
let data: Vec<f32> = (0..size).map(|i| (i as f32 * 0.01).sin()).collect();
let v = Vector::from_slice(&data);
let result = v.norm_l2().expect("norm_l2 failed");
let expected = data.iter().map(|x| x * x).sum::<f32>().sqrt();
let tolerance = FP_TOLERANCE * (size as f32).sqrt(); assert!(
(result - expected).abs() < tolerance,
"Norm mismatch: got {result}, want {expected}, diff {}, tolerance {}",
(result - expected).abs(),
tolerance
);
}
#[test]
fn smoke_simd_vector_mul() {
let size = 10_000;
let a: Vec<f32> = (0..size).map(|i| i as f32 * 0.01).collect();
let b: Vec<f32> = (0..size).map(|i| (size - i) as f32 * 0.01).collect();
let va = Vector::from_slice(&a);
let vb = Vector::from_slice(&b);
let result = va.mul(&vb).expect("Vector mul failed");
let expected: Vec<f32> = a.iter().zip(b.iter()).map(|(x, y)| x * y).collect();
for (i, (got, want)) in result.as_slice().iter().zip(expected.iter()).enumerate() {
assert!((got - want).abs() < FP_TOLERANCE, "Mismatch at index {i}: got {got}, want {want}");
}
}
#[test]
fn smoke_simd_relu() {
let size = 10_000;
let data: Vec<f32> =
(0..size).map(|i| if i % 2 == 0 { i as f32 } else { -(i as f32) }).collect();
let v = Vector::from_slice(&data);
let result = v.relu().expect("ReLU failed");
for (i, (got, orig)) in result.as_slice().iter().zip(data.iter()).enumerate() {
let want = orig.max(0.0);
assert!(
(got - want).abs() < FP_TOLERANCE,
"ReLU mismatch at index {i}: got {got}, want {want}"
);
}
}
#[test]
fn smoke_simd_softmax() {
let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
let v = Vector::from_slice(&data);
let result = v.softmax().expect("Softmax failed");
let sum: f32 = result.as_slice().iter().sum();
assert!((sum - 1.0).abs() < FP_TOLERANCE, "Softmax sum should be 1.0, got {sum}");
for (i, val) in result.as_slice().iter().enumerate() {
assert!(*val > 0.0, "Softmax value at {i} should be positive: {val}");
}
}
#[cfg(feature = "gpu")]
mod wgpu_tests {
use super::*;
use trueno::backends::gpu::GpuBackend;
#[test]
fn smoke_wgpu_vector_add() {
if !GpuBackend::is_available() {
eprintln!("Skipping WGPU test: no GPU available");
return;
}
let size = 100_000; let a: Vec<f32> = (0..size).map(|i| i as f32 * 0.1).collect();
let b: Vec<f32> = (0..size).map(|i| (size - i) as f32 * 0.1).collect();
let va = Vector::from_slice(&a);
let vb = Vector::from_slice(&b);
let result = va.add(&vb).expect("WGPU vector add failed");
let expected: Vec<f32> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
let mut max_diff = 0.0f32;
for (got, want) in result.as_slice().iter().zip(expected.iter()) {
let diff = (got - want).abs();
if diff > max_diff {
max_diff = diff;
}
}
let wgpu_tolerance = FP_TOLERANCE * 2.0;
assert!(
max_diff < wgpu_tolerance,
"WGPU max diff {max_diff} exceeds tolerance {wgpu_tolerance}"
);
}
#[test]
fn smoke_wgpu_matmul() {
if !GpuBackend::is_available() {
eprintln!("Skipping WGPU matmul test: no GPU available");
return;
}
use trueno::Matrix;
let n = 256;
let a_data: Vec<f32> = (0..n * n).map(|i| (i as f32 * 0.001) % 1.0).collect();
let b_data: Vec<f32> = (0..n * n).map(|i| ((n * n - i) as f32 * 0.001) % 1.0).collect();
let a = Matrix::from_vec(n, n, a_data.clone()).expect("Matrix A creation failed");
let b = Matrix::from_vec(n, n, b_data.clone()).expect("Matrix B creation failed");
let result = a.matmul(&b).expect("WGPU matmul failed");
assert_eq!(result.as_slice().len(), n * n, "Result dimensions wrong");
for i in 0..3 {
for j in 0..3 {
let mut expected = 0.0f32;
for k in 0..n {
expected += a_data[i * n + k] * b_data[k * n + j];
}
let got = result.as_slice()[i * n + j];
let diff = (got - expected).abs();
assert!(diff < FP_TOLERANCE * n as f32, "Matmul[{i}][{j}] diff {diff} too large");
}
}
}
}
#[test]
fn smoke_backend_equivalence() {
let size = 1000;
let a: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).sin()).collect();
let b: Vec<f32> = (0..size).map(|i| (i as f32 * 0.1).cos()).collect();
let scalar_a = Vector::from_slice_with_backend(&a, trueno::Backend::Scalar);
let scalar_b = Vector::from_slice_with_backend(&b, trueno::Backend::Scalar);
let scalar_result = scalar_a.add(&scalar_b).expect("Scalar add failed");
let auto_a = Vector::from_slice(&a);
let auto_b = Vector::from_slice(&b);
let auto_result = auto_a.add(&auto_b).expect("Auto add failed");
for (i, (scalar, auto)) in
scalar_result.as_slice().iter().zip(auto_result.as_slice().iter()).enumerate()
{
assert!(
(scalar - auto).abs() < FP_TOLERANCE,
"Backend equivalence failed at index {i}: scalar={scalar}, auto={auto}"
);
}
}
#[test]
fn smoke_empty_input() {
let empty: Vec<f32> = vec![];
let v = Vector::from_slice(&empty);
assert_eq!(v.len(), 0);
}
#[test]
fn smoke_single_element() {
let single = vec![42.0f32];
let v = Vector::from_slice(&single);
let norm = v.norm_l2().expect("norm_l2 failed");
assert!((norm - 42.0).abs() < FP_TOLERANCE, "Single element norm: {norm}");
}
#[test]
fn smoke_non_aligned_17() {
let size = 17; let a: Vec<f32> = (0..size).map(|i| i as f32).collect();
let b: Vec<f32> = (0..size).map(|i| (size - i) as f32).collect();
let va = Vector::from_slice(&a);
let vb = Vector::from_slice(&b);
let result = va.add(&vb).expect("Non-aligned add failed");
for (i, val) in result.as_slice().iter().enumerate() {
assert!((val - 17.0).abs() < FP_TOLERANCE, "Non-aligned result at {i}: {val}");
}
}
#[test]
fn smoke_nan_propagation() {
let data = vec![1.0f32, f32::NAN, 3.0];
let v = Vector::from_slice(&data);
let result = v.add(&v).expect("NaN add failed");
assert!(result.as_slice()[1].is_nan(), "NaN should propagate");
}
#[test]
fn smoke_infinity_handling() {
let data = vec![1.0f32, f32::INFINITY, -f32::INFINITY];
let v = Vector::from_slice(&data);
let result = v.mul(&v).expect("Infinity mul failed");
assert!(result.as_slice()[1].is_infinite(), "Infinity should persist");
}
#[test]
fn smoke_performance_baseline() {
let start = std::time::Instant::now();
let size = 100_000;
let iterations = 10;
for _ in 0..iterations {
let a: Vec<f32> = (0..size).map(|i| i as f32 * 0.01).collect();
let b: Vec<f32> = (0..size).map(|i| (size - i) as f32 * 0.01).collect();
let va = Vector::from_slice(&a);
let vb = Vector::from_slice(&b);
let _ = va.add(&vb).expect("Add failed");
let _ = va.mul(&vb).expect("Mul failed");
let _ = va.dot(&vb).expect("Dot failed");
}
let elapsed = start.elapsed();
let max_duration = std::time::Duration::from_secs(120);
assert!(elapsed < max_duration, "Smoke test took {:?}, exceeds 2 minute limit", elapsed);
println!("Smoke test completed in {:?}", elapsed);
}