aprender-core 0.30.0

Next-generation machine learning library in pure Rust
// CONTRACT: pca-v1.yaml
// HASH: sha256:b3c4d5e6f7a8b901
// Generated by: pv probar --binding
// DO NOT EDIT — regenerate with `pv probar --binding`

use aprender::preprocessing::PCA;
use aprender::primitives::Matrix;
use aprender::traits::Transformer;
use proptest::prelude::*;

proptest! {
    #![proptest_config(ProptestConfig::with_cases(256))]

    /// FALSIFY-PCA-001: Dimensionality reduction (shape invariant)
    /// Formal: transform(X)_{n×d} with k components → (n, k) output
    #[test]
    fn prop_dimensionality_reduction(
        n in 10usize..30,
        data in proptest::collection::vec(-100.0f32..100.0, 30 * 4)
    ) {
        let d = 4usize;
        let k = 2usize;
        let total = n * d;
        let vals: Vec<f32> = data.iter().copied().cycle().take(total).collect();
        let x = Matrix::from_vec(n, d, vals).expect("valid matrix dimensions");

        let mut pca = PCA::new(k);
        pca.fit(&x).expect("fit succeeds");
        let transformed = pca.transform(&x).expect("transform succeeds");

        let (out_rows, out_cols) = transformed.shape();
        prop_assert!(
            out_rows == n,
            "expected {} rows, got {}",
            n, out_rows
        );
        prop_assert!(
            out_cols == k,
            "expected {} cols, got {}",
            k, out_cols
        );
    }

    /// FALSIFY-PCA-002: Explained variance bounded (bound)
    /// Formal: ∀i ratio_i ∈ [0, 1] ∧ |Σ ratio_i - 1.0| < ε
    #[test]
    fn prop_explained_variance_bounded(
        n in 10usize..30,
        data in proptest::collection::vec(-100.0f32..100.0, 30 * 4)
    ) {
        let d = 4usize;
        let total = n * d;
        let vals: Vec<f32> = data.iter().copied().cycle().take(total).collect();

        // Skip if all data is constant (total variance = 0)
        let all_constant = (0..d).all(|j| {
            let first = vals[j];
            (0..n).all(|i| (vals[i * d + j] - first).abs() < 1e-10)
        });
        prop_assume!(!all_constant);

        let x = Matrix::from_vec(n, d, vals).expect("valid matrix dimensions");

        // Fit with full d components to capture all variance
        let mut pca = PCA::new(d);
        pca.fit(&x).expect("fit succeeds");

        let ratios = pca.explained_variance_ratio().expect("ratios available after fit");

        prop_assert!(
            ratios.len() == d,
            "expected {} ratios, got {}",
            d, ratios.len()
        );

        for (i, &r) in ratios.iter().enumerate() {
            prop_assert!(
                r >= -1e-6 && r <= 1.0 + 1e-6,
                "ratio[{}] = {}, expected in [0, 1]",
                i, r
            );
        }

        let sum: f32 = ratios.iter().sum();
        prop_assert!(
            (sum - 1.0).abs() < 1e-3,
            "sum of ratios = {}, expected ~1.0",
            sum
        );
    }

    /// FALSIFY-PCA-003: Variance ordering (monotonicity)
    /// Formal: ratio_i >= ratio_{i+1} for all i
    #[test]
    fn prop_variance_ordering(
        n in 10usize..30,
        data in proptest::collection::vec(-100.0f32..100.0, 30 * 4)
    ) {
        let d = 4usize;
        let total = n * d;
        let vals: Vec<f32> = data.iter().copied().cycle().take(total).collect();

        // Skip constant data
        let all_constant = (0..d).all(|j| {
            let first = vals[j];
            (0..n).all(|i| (vals[i * d + j] - first).abs() < 1e-10)
        });
        prop_assume!(!all_constant);

        let x = Matrix::from_vec(n, d, vals).expect("valid matrix dimensions");

        let mut pca = PCA::new(d);
        pca.fit(&x).expect("fit succeeds");

        let ratios = pca.explained_variance_ratio().expect("ratios available after fit");

        for i in 0..ratios.len() - 1 {
            prop_assert!(
                ratios[i] >= ratios[i + 1] - 1e-6,
                "ratio[{}] = {} < ratio[{}] = {}, expected non-increasing",
                i, ratios[i], i + 1, ratios[i + 1]
            );
        }
    }

    /// FALSIFY-PCA-004: Deterministic transform (equivalence)
    /// Formal: ||transform(X) - transform(X)||_∞ < ε
    #[test]
    fn prop_deterministic_transform(
        n in 10usize..30,
        data in proptest::collection::vec(-100.0f32..100.0, 30 * 4)
    ) {
        let d = 4usize;
        let k = 2usize;
        let total = n * d;
        let vals: Vec<f32> = data.iter().copied().cycle().take(total).collect();
        let x = Matrix::from_vec(n, d, vals).expect("valid matrix dimensions");

        let mut pca = PCA::new(k);
        pca.fit(&x).expect("fit succeeds");
        let t1 = pca.transform(&x).expect("first transform succeeds");
        let t2 = pca.transform(&x).expect("second transform succeeds");

        let (rows, cols) = t1.shape();
        for i in 0..rows {
            for j in 0..cols {
                let v1 = t1.get(i, j);
                let v2 = t2.get(i, j);
                let diff = (v1 - v2).abs();
                prop_assert!(
                    diff < 1e-6,
                    "non-deterministic at [{}, {}]: first={}, second={}, diff={}",
                    i, j, v1, v2, diff
                );
            }
        }
    }
}