himada-dispatch 0.1.0

Adaptive SIMD dispatch for Himada — auto-selects fastest kernel at runtime
use crate::kernels::*;
use proptest::collection::vec;
use proptest::prelude::*;

fn any_f64() -> impl Strategy<Value = f64> {
    -1e3_f64..1e3_f64
}

fn any_f32() -> impl Strategy<Value = f32> {
    -1e2_f32..1e2_f32
}

fn close_f64(a: f64, b: f64) -> bool {
    let tol = 1e-12 * (1.0 + a.abs().max(b.abs()));
    (a - b).abs() < tol
}

proptest! {
    #[test]
    fn dot_f64_commutative(a in vec(any_f64(), 1usize..100), b in vec(any_f64(), 1usize..100)) {
        let len = a.len().min(b.len());
        let ab = dot_scalar(&a[..len], &b[..len]);
        let ba = dot_scalar(&b[..len], &a[..len]);
        assert!(close_f64(ab, ba), "commutativity: {} != {}", ab, ba);
    }

    #[test]
    fn dot_f64_variants_agree(a in vec(any_f64(), 1usize..100), b in vec(any_f64(), 1usize..100)) {
        let len = a.len().min(b.len());
        let expected = dot_scalar(&a[..len], &b[..len]);
        let neon = dot_neon(&a[..len], &b[..len]);
        assert!(close_f64(expected, neon), "scalar={} != neon={}", expected, neon);
    }

    #[test]
    fn dot_f32_variants_agree(a in vec(any_f32(), 1usize..50), b in vec(any_f32(), 1usize..50)) {
        let len = a.len().min(b.len());
        let expected = dot_f32_scalar(&a[..len], &b[..len]);
        let neon = dot_f32_neon(&a[..len], &b[..len]);
        let f32_tol = 1e-3 * (1.0 + expected.abs().max(neon.abs()));
        assert!((expected - neon).abs() < f32_tol, "scalar={} != neon={}", expected, neon);
    }
}

proptest! {
    #[test]
    fn reduce_sum_additive(v in vec(any_f64(), 2usize..100)) {
        let mid = v.len() / 2;
        let total = reduce_sum_scalar(&v);
        let left = reduce_sum_scalar(&v[..mid]);
        let right = reduce_sum_scalar(&v[mid..]);
        assert!(close_f64(total, left + right),
                "reduce_sum not additive: total={} left={} right={}", total, left, right);
    }
}

proptest! {
    #[test]
    fn reduce_max_properties(v in vec(any_f64(), 1usize..100)) {
        if v.is_empty() { return Ok(()); }
        let mx = reduce_max_scalar(&v);
        assert!(v.iter().all(|&x| x <= mx + 1e-12),
                "reduce_max {} not >= all elements", mx);
        assert!(v.iter().any(|&x| close_f64(x, mx)),
                "reduce_max {} not found in vector", mx);
    }

    #[test]
    fn abs_max_noneg(v in vec(any_f64(), 1usize..100)) {
        if v.is_empty() { return Ok(()); }
        let am = abs_max_f64_scalar(&v);
        assert!(am >= 0.0, "abs_max {} is negative", am);
        assert!(v.iter().all(|&x| x.abs() <= am + 1e-12),
                "abs_max {} less than |x| for some element", am);
    }
}

proptest! {
    #[test]
    fn argmax_index(v in vec(any_f64(), 1usize..100)) {
        if v.is_empty() { return Ok(()); }
        let idx = argmax_f64_scalar(&v);
        assert!(idx < v.len(), "argmax {} out of bounds", idx);
        let mx = reduce_max_scalar(&v);
        assert!(close_f64(v[idx], mx), "argmax value {} != max {}", v[idx], mx);
    }
}

proptest! {
    #[test]
    fn softmax_sums_to_one(v in vec(any_f64(), 1usize..50)) {
        if v.is_empty() { return Ok(()); }
        let mut out = vec![0.0; v.len()];
        softmax_scalar(&v, &mut out);
        let sum: f64 = out.iter().sum();
        assert!((sum - 1.0).abs() < 1e-10, "softmax sum={} != 1", sum);
        assert!(out.iter().all(|&x| x >= 0.0), "softmax output < 0");
    }
}

proptest! {
    #[test]
    fn euclidean_nonnegative(a in vec(any_f64(), 1usize..100), b in vec(any_f64(), 1usize..100)) {
        let len = a.len().min(b.len());
        let d = euclidean_distance_f64_scalar(&a[..len], &b[..len]);
        assert!(d >= 0.0, "euclidean distance < 0: {}", d);
    }

    #[test]
    fn euclidean_triangle_inequality(
        a in vec(any_f64(), 5usize..50),
        b in vec(any_f64(), 5usize..50),
        c in vec(any_f64(), 5usize..50)
    ) {
        let len = a.len().min(b.len()).min(c.len());
        let ab = euclidean_distance_f64_scalar(&a[..len], &b[..len]);
        let bc = euclidean_distance_f64_scalar(&b[..len], &c[..len]);
        let ac = euclidean_distance_f64_scalar(&a[..len], &c[..len]);
        assert!(ac <= ab + bc + 1e-9,
                "triangle inequality: {} > {} + {}", ac, ab, bc);
    }

    #[test]
    fn euclidean_zero_for_equal(a in vec(any_f64(), 1usize..50)) {
        let d = euclidean_distance_f64_scalar(&a, &a);
        assert!(d < 1e-12, "identical vectors distance={}", d);
    }

    #[test]
    fn euclidean_simd_agrees(
        a in vec(any_f64(), 1usize..100),
        b in vec(any_f64(), 1usize..100)
    ) {
        let len = a.len().min(b.len());
        let expected = euclidean_distance_f64_scalar(&a[..len], &b[..len]);
        let neon = euclidean_distance_f64_neon(&a[..len], &b[..len]);
        assert!(close_f64(expected, neon),
                "euclidean scalar={} != neon={}", expected, neon);
    }
}

proptest! {
    #[test]
    fn cosine_range(a in vec(any_f64(), 2usize..50), b in vec(any_f64(), 2usize..50)) {
        let len = a.len().min(b.len());
        let c = cosine_similarity_f64_scalar(&a[..len], &b[..len]);
        assert!(c >= -1.0 - 1e-9 && c <= 1.0 + 1e-9,
                "cosine {} outside [-1,1]", c);
    }

    #[test]
    fn cosine_parallel_is_one(a in vec(any_f64(), 2usize..50)) {
        let c = cosine_similarity_f64_scalar(&a, &a);
        assert!(close_f64(c, 1.0), "self cosine {} != 1", c);
    }
}

proptest! {
    #[test]
    fn hadamard_agrees(
        a in vec(any_f64(), 1usize..100),
        b in vec(any_f64(), 1usize..100)
    ) {
        let len = a.len().min(b.len());
        let mut c_scalar = vec![0.0; len];
        let mut c_neon = vec![0.0; len];
        hadamard_product_f64_scalar(&a[..len], &b[..len], &mut c_scalar);
        hadamard_product_f64_neon(&a[..len], &b[..len], &mut c_neon);
        for i in 0..len {
            assert!(close_f64(c_scalar[i], c_neon[i]),
                    "hadamard[{}] scalar={} neon={}", i, c_scalar[i], c_neon[i]);
        }
    }

    #[test]
    fn add_scalar_correct(
        a in vec(any_f64(), 1usize..100),
        b in vec(any_f64(), 1usize..100)
    ) {
        let len = a.len().min(b.len());
        let mut c = vec![0.0; len];
        add_f64_scalar(&a[..len], &b[..len], &mut c);
        for i in 0..len {
            assert!(close_f64(c[i], a[i] + b[i]),
                    "add[{}] got {} expected {}", i, c[i], a[i] + b[i]);
        }
    }

    #[test]
    fn sub_scalar_correct(
        a in vec(any_f64(), 1usize..100),
        b in vec(any_f64(), 1usize..100)
    ) {
        let len = a.len().min(b.len());
        let mut c = vec![0.0; len];
        sub_f64_scalar(&a[..len], &b[..len], &mut c);
        for i in 0..len {
            assert!(close_f64(c[i], a[i] - b[i]),
                    "sub[{}] got {} expected {}", i, c[i], a[i] - b[i]);
        }
    }

    #[test]
    fn mul_scalar_correct(
        a in vec(any_f64(), 1usize..100),
        b in vec(any_f64(), 1usize..100)
    ) {
        let len = a.len().min(b.len());
        let mut c = vec![0.0; len];
        mul_f64_scalar(&a[..len], &b[..len], &mut c);
        for i in 0..len {
            assert!(close_f64(c[i], a[i] * b[i]),
                    "mul[{}] got {} expected {}", i, c[i], a[i] * b[i]);
        }
    }
}

proptest! {
    #[test]
    fn negate_double_negation(v in vec(any_f64(), 1usize..100)) {
        if v.is_empty() { return Ok(()); }
        let mut tmp = vec![0.0; v.len()];
        let mut back = vec![0.0; v.len()];
        negate_f64_scalar(&v, &mut tmp);
        negate_f64_scalar(&tmp, &mut back);
        for i in 0..v.len() {
            assert!(close_f64(back[i], v[i]),
                    "negate[{}] expected {} got {}", i, v[i], back[i]);
        }
    }

    #[test]
    fn negate_simd_agrees(v in vec(any_f64(), 1usize..100)) {
        if v.is_empty() { return Ok(()); }
        let mut scalar = vec![0.0; v.len()];
        let mut neon = vec![0.0; v.len()];
        negate_f64_scalar(&v, &mut scalar);
        negate_f64_neon(&v, &mut neon);
        for i in 0..v.len() {
            assert!(close_f64(scalar[i], neon[i]),
                    "negate[{}] scalar={} neon={}", i, scalar[i], neon[i]);
        }
    }
}

proptest! {
    #[test]
    fn clamp_bounds(
        a in vec(any_f64(), 1usize..100),
        lo in any_f64(),
        hi in any_f64()
    ) {
        let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
        let mut c = vec![0.0; a.len()];
        clamp_f64_scalar(&a, lo, hi, &mut c);
        for i in 0..a.len() {
            assert!(c[i] >= lo - 1e-12, "clamp[{}] {} < lo {}", i, c[i], lo);
            assert!(c[i] <= hi + 1e-12, "clamp[{}] {} > hi {}", i, c[i], hi);
        }
    }

    #[test]
    fn clamp_simd_agrees(
        a in vec(any_f64(), 1usize..100),
        lo in any_f64(),
        hi in any_f64()
    ) {
        let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
        let mut c_scalar = vec![0.0; a.len()];
        let mut c_neon = vec![0.0; a.len()];
        clamp_f64_scalar(&a, lo, hi, &mut c_scalar);
        clamp_f64_neon(&a, lo, hi, &mut c_neon);
        for i in 0..a.len() {
            assert!(close_f64(c_scalar[i], c_neon[i]),
                    "clamp[{}] scalar={} neon={}", i, c_scalar[i], c_neon[i]);
        }
    }
}

proptest! {
    #[test]
    fn memchr_correctness(data in vec(any::<u8>(), 0usize..100)) {
        if data.is_empty() { return Ok(()); }
        let byte = data[0];
        let result = memchr_scalar(byte, &data);
        assert!(result.is_some(), "memchr failed to find byte {}", byte);
        assert_eq!(data[result.unwrap()], byte,
                "memchr returned wrong index {}", result.unwrap());
    }

    #[test]
    fn memchr_not_found(data in vec(any::<u8>(), 1usize..50)) {
        let byte = if data.is_empty() || data[0] == 255 { 0 } else { 255 };
        if data.contains(&byte) { return Ok(()); }
        let result = memchr_scalar(byte, &data);
        assert!(result.is_none(), "memchr found absent byte at {:?}", result);
    }

    #[test]
    fn memchr_simd_agrees(data in vec(any::<u8>(), 0usize..100), byte in any::<u8>()) {
        let expected = memchr_scalar(byte, &data);
        let neon = memchr_neon(byte, &data);
        assert_eq!(expected, neon, "memchr scalar={:?} != neon={:?}", expected, neon);
    }
}

proptest! {
    #[test]
    fn matmul_identity(n in 1usize..10) {
        let size = n;
        let mut a = vec![0.0; size * size];
        for i in 0..size { a[i * size + i] = 1.0; }
        let b = vec![1.0; size * size];
        let mut c = vec![0.0; size * size];
        matmul_scalar(&a, &b, &mut c, size);
        for i in 0..size {
            for j in 0..size {
                let expected = b[i * size + j];
                assert!(close_f64(c[i * size + j], expected),
                        "matmul identity[{}][{}] expected {} got {}",
                        i, j, expected, c[i * size + j]);
            }
        }
    }

    #[test]
    fn matmul_simd_agrees(
        a in vec(any_f64(), 1usize..36),
        b in vec(any_f64(), 1usize..36),
        n in 1usize..6
    ) {
        let size = n;
        if a.len() < size * size || b.len() < size * size { return Ok(()); }
        let mut c_scalar = vec![0.0; size * size];
        let mut c_neon = vec![0.0; size * size];
        matmul_scalar(&a[..size*size], &b[..size*size], &mut c_scalar, size);
        matmul_neon(&a[..size*size], &b[..size*size], &mut c_neon, size);
        for i in 0..size*size {
            assert!(close_f64(c_scalar[i], c_neon[i]),
                    "matmul[{}] scalar={} neon={}", i, c_scalar[i], c_neon[i]);
        }
    }
}

proptest! {
    #[test]
    fn dot_clamp_reduce_agrees(
        a in vec(any_f64(), 1usize..100),
        b in vec(any_f64(), 1usize..100)
    ) {
        let len = a.len().min(b.len());
        let expected = dot_clamp_reduce_scalar(&a[..len], &b[..len]);
        let neon = dot_clamp_reduce_neon(&a[..len], &b[..len]);
        assert!(close_f64(expected, neon),
                "fused scalar={} != neon={}", expected, neon);
    }
}

#[test]
fn proptest_module_active() {
    assert!(true);
}