1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// 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
);
}
}
}