use super::index::uniform_index;
use crate::rng::SplitMix64;
#[must_use]
pub fn bootstrap_indices(n: usize, b: usize, rng: &mut SplitMix64) -> Vec<Vec<usize>> {
(0..b)
.map(|_| {
if n == 0 {
Vec::new()
} else {
(0..n).map(|_| uniform_index(rng, n)).collect()
}
})
.collect()
}
#[must_use]
pub fn permutation(n: usize, rng: &mut SplitMix64) -> Vec<usize> {
let mut idx: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let j = uniform_index(rng, i + 1);
idx.swap(i, j);
}
idx
}
#[must_use]
pub fn kfold_indices(n: usize, k: usize, rng: &mut SplitMix64) -> Vec<(Vec<usize>, Vec<usize>)> {
if k == 0 {
return Vec::new();
}
let perm = permutation(n, rng);
(0..k)
.map(|fold| {
let mut train = Vec::new();
let mut test = Vec::new();
for (pos, &obs) in perm.iter().enumerate() {
if pos % k == fold {
test.push(obs);
} else {
train.push(obs);
}
}
(train, test)
})
.collect()
}
#[cfg(kani)]
mod verification {
use super::{SplitMix64, bootstrap_indices, kfold_indices, permutation};
#[kani::proof]
#[kani::unwind(8)]
fn resampling_permutation_is_bijection() {
const N: usize = 4;
let state: u64 = kani::any();
let mut rng = SplitMix64::new(state);
let perm = permutation(N, &mut rng);
assert!(perm.len() == N, "permutation length changed from {N}");
let mut seen = [false; N];
for &v in &perm {
assert!(v < N, "permutation produced an out-of-range index {v}");
assert!(!seen[v], "permutation repeated index {v}");
seen[v] = true;
}
assert!(seen.iter().all(|&s| s), "permutation dropped an index");
}
#[kani::proof]
#[kani::unwind(8)]
fn resampling_kfold_test_sets_partition() {
const N: usize = 4;
let state: u64 = kani::any();
let mut rng = SplitMix64::new(state);
let folds = kfold_indices(N, 2, &mut rng);
let mut seen = [0u8; N];
for (_, test) in &folds {
for &obs in test {
assert!(obs < N, "k-fold test index {obs} escaped 0..N");
seen[obs] += 1;
}
}
assert!(
seen.iter().all(|&c| c == 1),
"each observation must appear in exactly one test fold"
);
}
#[kani::proof]
fn resampling_kfold_zero_k_is_empty() {
let n: usize = kani::any();
let state: u64 = kani::any();
let mut rng = SplitMix64::new(state);
let folds = kfold_indices(n, 0, &mut rng);
assert!(folds.is_empty(), "k == 0 must yield no folds");
}
#[kani::proof]
#[kani::unwind(6)]
fn resampling_bootstrap_indices_in_bounds() {
const N: usize = 3;
const B: usize = 2;
let state: u64 = kani::any();
let mut rng = SplitMix64::new(state);
let draws = bootstrap_indices(N, B, &mut rng);
assert!(draws.len() == B, "expected B resamples");
for resample in &draws {
assert!(resample.len() == N, "each resample must hold N indices");
for &i in resample {
assert!(i < N, "bootstrap index {i} escaped 0..N");
}
}
}
}