aprender-core 0.31.2

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

use aprender::decomposition::ICA;
use aprender::primitives::Matrix;
use proptest::prelude::*;

/// Strategy: generate a random matrix with n rows (20..40) and d=4 columns,
/// plus a seed for ICA reproducibility.
fn ica_data_strategy() -> impl Strategy<Value = (Matrix<f32>, usize, u64)> {
    (20usize..=40, 0u64..1000)
        .prop_flat_map(|(n, seed)| {
            let d = 4usize;
            let data = proptest::collection::vec(-10.0f32..10.0f32, n * d);
            (data, Just(n), Just(d), Just(seed))
        })
        .prop_map(|(data, n, d, seed)| {
            let x = Matrix::from_vec(n, d, data).expect("valid matrix dimensions");
            (x, n, seed)
        })
}

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

    /// FALSIFY-ICA-001: Output shape.
    /// Generate random matrix (n=20..40, d=4). Fit ICA with k=2.
    /// Transform. Assert output shape = (n, 2).
    #[test]
    fn prop_output_shape(
        (x, n, seed) in ica_data_strategy(),
    ) {
        let k = 2usize;
        let mut ica = ICA::new(k)
            .with_max_iter(100)
            .with_random_state(seed);

        // ICA may fail to converge on some random data — skip gracefully.
        if ica.fit(&x).is_err() {
            return Ok(());
        }

        let result = ica.transform(&x);
        if let Ok(output) = result {
            let (out_rows, out_cols) = output.shape();
            prop_assert!(
                out_rows == n,
                "expected {} rows, got {}", n, out_rows
            );
            prop_assert!(
                out_cols == k,
                "expected {} cols, got {}", k, out_cols
            );
        }
        // Transform failure after successful fit is also acceptable for
        // edge-case random data — we do not assert on it.
    }

    /// FALSIFY-ICA-002: Transform deterministic.
    /// Fit ICA, then transform the same data twice. Assert outputs are
    /// identical within epsilon.
    #[test]
    fn prop_transform_deterministic(
        (x, _n, seed) in ica_data_strategy(),
    ) {
        let mut ica = ICA::new(2)
            .with_max_iter(100)
            .with_random_state(seed);

        if ica.fit(&x).is_err() {
            return Ok(());
        }

        let out1 = match ica.transform(&x) {
            Ok(m) => m,
            Err(_) => return Ok(()),
        };
        let out2 = match ica.transform(&x) {
            Ok(m) => m,
            Err(_) => return Ok(()),
        };

        let (r1, c1) = out1.shape();
        let (r2, c2) = out2.shape();
        prop_assert!(
            r1 == r2 && c1 == c2,
            "shape mismatch: ({}, {}) vs ({}, {})", r1, c1, r2, c2
        );

        let slice1 = out1.as_slice();
        let slice2 = out2.as_slice();
        for i in 0..slice1.len() {
            let diff = (slice1[i] - slice2[i]).abs();
            prop_assert!(
                diff < 1e-6,
                "element {} differs: {} vs {} (diff={})", i, slice1[i], slice2[i], diff
            );
        }
    }

    /// FALSIFY-ICA-003: Finite output.
    /// Fit and transform random data. Assert all output values are finite
    /// (not NaN, not Inf). Use prop_assume! to skip if fit fails.
    #[test]
    fn prop_finite_output(
        (x, _n, seed) in ica_data_strategy(),
    ) {
        let mut ica = ICA::new(2)
            .with_max_iter(100)
            .with_tolerance(1e-4)
            .with_random_state(seed);

        let fit_ok = ica.fit(&x).is_ok();
        prop_assume!(fit_ok, "fit did not converge — skipping");

        let output = match ica.transform(&x) {
            Ok(m) => m,
            Err(_) => return Ok(()),
        };

        let data = output.as_slice();
        for (i, &val) in data.iter().enumerate() {
            prop_assert!(
                val.is_finite(),
                "element {} is not finite: {}", i, val
            );
        }
    }
}