use super::*;
use crate::primitives::Matrix;
#[test]
fn falsify_knn_001_predictions_in_label_range() {
let x = Matrix::from_vec(
6,
2,
vec![0.0, 0.0, 0.5, 0.5, 1.0, 0.0, 5.0, 5.0, 5.5, 5.5, 6.0, 5.0],
)
.expect("valid");
let y = vec![0_usize, 0, 0, 1, 1, 1];
let mut knn = KNearestNeighbors::new(3);
knn.fit(&x, &y).expect("fit");
let preds = knn.predict(&x).expect("predict");
for (i, &p) in preds.iter().enumerate() {
assert!(
p <= 1,
"FALSIFIED KNN-001: prediction[{i}] = {p}, not in {{0, 1}}"
);
}
}
#[test]
fn falsify_knn_002_prediction_count() {
let x = Matrix::from_vec(
6,
2,
vec![0.0, 0.0, 0.5, 0.5, 1.0, 0.0, 5.0, 5.0, 5.5, 5.5, 6.0, 5.0],
)
.expect("valid");
let y = vec![0_usize, 0, 0, 1, 1, 1];
let mut knn = KNearestNeighbors::new(3);
knn.fit(&x, &y).expect("fit");
let x_test = Matrix::from_vec(3, 2, vec![0.2, 0.2, 3.0, 3.0, 5.8, 5.8]).expect("valid");
let preds = knn.predict(&x_test).expect("predict");
assert_eq!(
preds.len(),
3,
"FALSIFIED KNN-002: {} predictions for 3 inputs",
preds.len()
);
}
#[test]
fn falsify_knn_003_separable_data() {
let x = Matrix::from_vec(
6,
2,
vec![
0.0, 0.0, 0.1, 0.1, 0.2, 0.2, 100.0, 100.0, 100.1, 100.1, 100.2, 100.2,
],
)
.expect("valid");
let y = vec![0_usize, 0, 0, 1, 1, 1];
let mut knn = KNearestNeighbors::new(3);
knn.fit(&x, &y).expect("fit");
let preds = knn.predict(&x).expect("predict");
assert_eq!(
preds, y,
"FALSIFIED KNN-003: KNN cannot classify well-separated clusters"
);
}
#[test]
fn falsify_knn_004_deterministic() {
let x = Matrix::from_vec(4, 2, vec![0.0, 0.0, 1.0, 1.0, 5.0, 5.0, 6.0, 6.0]).expect("valid");
let y = vec![0_usize, 0, 1, 1];
let mut knn = KNearestNeighbors::new(1);
knn.fit(&x, &y).expect("fit");
let p1 = knn.predict(&x).expect("predict 1");
let p2 = knn.predict(&x).expect("predict 2");
assert_eq!(
p1, p2,
"FALSIFIED KNN-004: predictions differ on same input"
);
}
#[test]
fn falsify_knn_005_partial_select_ties_stable() {
let x = Matrix::from_vec(
5,
2,
vec![
1.0, 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, -1.0, 9.0, 9.0, ],
)
.expect("valid");
let y = vec![0_usize, 0, 1, 1, 1];
let mut knn = KNearestNeighbors::new(3);
knn.fit(&x, &y).expect("fit");
let query = Matrix::from_vec(1, 2, vec![0.0, 0.0]).expect("valid");
let p1 = knn.predict(&query).expect("predict 1");
let p2 = knn.predict(&query).expect("predict 2");
assert_eq!(
p1, p2,
"FALSIFIED KNN-005: tie-broken prediction not deterministic"
);
assert_eq!(
p1[0], 0,
"FALSIFIED KNN-005: tie-break k-set changed (expected class 0)"
);
}
#[test]
fn falsify_knn_tie_001_majority_vote_smallest_label() {
let knn = KNearestNeighbors::new(2);
let neighbors: Vec<(f32, usize)> = vec![(1.0, 0), (1.0, 2)];
let neighbors_rev: Vec<(f32, usize)> = vec![(1.0, 2), (1.0, 0)];
for _ in 0..1000 {
assert_eq!(
knn.majority_vote(&neighbors),
0,
"FALSIFIED KNN-TIE-001: tie {{0,2}} did not resolve to smallest label 0"
);
assert_eq!(
knn.majority_vote(&neighbors_rev),
0,
"FALSIFIED KNN-TIE-001: reversed tie {{2,0}} did not resolve to smallest label 0"
);
}
}
#[test]
fn falsify_knn_tie_002_weighted_vote_smallest_label() {
let knn = KNearestNeighbors::new(2).with_weights(true);
let neighbors: Vec<(f32, usize)> = vec![(2.0, 0), (2.0, 2)];
let neighbors_rev: Vec<(f32, usize)> = vec![(2.0, 2), (2.0, 0)];
for _ in 0..1000 {
assert_eq!(
knn.weighted_vote(&neighbors),
0,
"FALSIFIED KNN-TIE-002: weighted tie {{0,2}} did not resolve to smallest label 0"
);
assert_eq!(
knn.weighted_vote(&neighbors_rev),
0,
"FALSIFIED KNN-TIE-002: reversed weighted tie {{2,0}} did not resolve to smallest label 0"
);
}
}
#[test]
fn falsify_knn_tie_003_predict_tie_smallest_label() {
let x = Matrix::from_vec(2, 2, vec![1.0, 0.0, -1.0, 0.0]).expect("valid");
let y = vec![0_usize, 2];
let mut knn = KNearestNeighbors::new(2);
knn.fit(&x, &y).expect("fit");
let query = Matrix::from_vec(1, 2, vec![0.0, 0.0]).expect("valid");
for _ in 0..200 {
let pred = knn.predict(&query).expect("predict");
assert_eq!(
pred[0], 0,
"FALSIFIED KNN-TIE-003: 2-way vote tie {{0,2}} did not resolve to smallest label 0"
);
}
}
#[test]
fn falsify_knn_tie_004_three_way_tie_smallest_label() {
let knn = KNearestNeighbors::new(3);
let neighbors: Vec<(f32, usize)> = vec![(1.0, 1), (1.0, 2), (1.0, 0)];
for _ in 0..1000 {
assert_eq!(
knn.majority_vote(&neighbors),
0,
"FALSIFIED KNN-TIE-004: 3-way tie {{0,1,2}} did not resolve to smallest label 0"
);
}
}
mod knn_proptest_falsify {
use super::*;
use proptest::prelude::*;
fn reference_k_set(distances: &[(f32, usize, usize)], k: usize) -> Vec<usize> {
let mut d = distances.to_vec();
d.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap().then(a.1.cmp(&b.1)));
let mut idx: Vec<usize> = d[..k].iter().map(|&(_, i, _)| i).collect();
idx.sort_unstable();
idx
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(200))]
#[test]
fn falsify_knn_005_prop_select_matches_stable_sort(
dists in proptest::collection::vec(0u8..4, 1usize..=16),
k_raw in 1usize..=16,
) {
let n = dists.len();
let k = k_raw.min(n);
let buf: Vec<(f32, usize, usize)> = dists
.iter()
.enumerate()
.map(|(i, &d)| (d as f32, i, i % 3))
.collect();
let want = reference_k_set(&buf, k);
let mut buf_sel = buf.clone();
let chosen = super::super::select_k_nearest(&mut buf_sel, k);
prop_assert_eq!(chosen.len(), k, "select_k_nearest returned wrong count");
let mut got: Vec<usize> = buf_sel[..k].iter().map(|&(_, i, _)| i).collect();
got.sort_unstable();
prop_assert_eq!(
got, want,
"FALSIFIED KNN-005-prop: partial-select k-set != stable-sort k-set (k={})",
k
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(15))]
#[test]
fn falsify_knn_002_prop_prediction_count(
n_train in 6..=12usize,
n_test in 3..=8usize,
seed in 0..200u32,
) {
let mut x_data = Vec::with_capacity(n_train * 2);
let mut y_data = Vec::with_capacity(n_train);
let half = n_train / 2;
for i in 0..half {
let offset = (seed as f32 + i as f32) * 0.01;
x_data.push(0.0 + offset);
x_data.push(0.0 + offset);
y_data.push(0_usize);
}
for i in 0..(n_train - half) {
let offset = (seed as f32 + i as f32) * 0.01;
x_data.push(10.0 + offset);
x_data.push(10.0 + offset);
y_data.push(1_usize);
}
let x = Matrix::from_vec(n_train, 2, x_data).expect("valid");
let mut knn = KNearestNeighbors::new(3.min(half));
knn.fit(&x, &y_data).expect("fit");
let x_test_data: Vec<f32> = (0..n_test * 2)
.map(|i| ((i as f32 + seed as f32) * 0.37).sin() * 5.0 + 5.0)
.collect();
let x_test = Matrix::from_vec(n_test, 2, x_test_data).expect("valid");
let preds = knn.predict(&x_test).expect("predict");
prop_assert_eq!(
preds.len(),
n_test,
"FALSIFIED KNN-002-prop: {} predictions for {} inputs",
preds.len(), n_test
);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(15))]
#[test]
fn falsify_knn_004_prop_deterministic(
seed in 0..200u32,
) {
let x = Matrix::from_vec(
6, 2,
vec![0.0, 0.0, 0.5, 0.5, 1.0, 0.0,
5.0, 5.0, 5.5, 5.5, 6.0, 5.0],
).expect("valid");
let y = vec![0_usize, 0, 0, 1, 1, 1];
let k = ((seed % 3) + 1) as usize;
let mut knn = KNearestNeighbors::new(k);
knn.fit(&x, &y).expect("fit");
let p1 = knn.predict(&x).expect("predict 1");
let p2 = knn.predict(&x).expect("predict 2");
prop_assert_eq!(
p1, p2,
"FALSIFIED KNN-004-prop: predictions differ (k={})",
k
);
}
}
}