use aprender::preprocessing::PCA;
use aprender::primitives::Matrix;
use aprender::traits::Transformer;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[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
);
}
#[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();
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");
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
);
}
#[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();
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]
);
}
}
#[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
);
}
}
}
}