use super::kmeans::Rng;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubsampleOptions {
pub n_peaks: usize,
pub per_channel: bool,
pub seed: u64,
}
pub fn subsample_peaks(samples: &[u64], channels: &[usize], opts: &SubsampleOptions) -> Vec<usize> {
assert_eq!(samples.len(), channels.len(), "one channel per peak");
let mut rng = Rng(opts.seed);
let mut chosen = if opts.per_channel {
let mut by: std::collections::BTreeMap<usize, Vec<usize>> = std::collections::BTreeMap::new();
for (i, &c) in channels.iter().enumerate() {
by.entry(c).or_default().push(i);
}
by.into_values().flat_map(|group| uniform(group, opts.n_peaks, &mut rng)).collect()
} else {
uniform((0..samples.len()).collect(), opts.n_peaks, &mut rng)
};
chosen.sort_unstable_by_key(|&i| (samples[i], i));
chosen
}
fn uniform(mut pool: Vec<usize>, n: usize, rng: &mut Rng) -> Vec<usize> {
if n >= pool.len() {
return pool;
}
for k in 0..n {
let j = k + (rng.next_u64() % (pool.len() - k) as u64) as usize;
pool.swap(k, j);
}
pool.truncate(n);
pool
}
#[cfg(test)]
mod tests {
use super::*;
fn peaks() -> (Vec<u64>, Vec<usize>) {
((0..1000).map(|i| (i * 7919 % 1000) as u64).collect(), (0..1000).map(|i| i % 4).collect())
}
#[test]
fn takes_n_sorted_by_sample_and_reproducible() {
let (samples, channels) = peaks();
let opts = SubsampleOptions { n_peaks: 100, per_channel: false, seed: 1 };
let a = subsample_peaks(&samples, &channels, &opts);
assert_eq!(a.len(), 100);
assert!(a.windows(2).all(|w| samples[w[0]] <= samples[w[1]]), "sorted by sample");
let mut unique = a.clone();
unique.dedup();
assert_eq!(unique.len(), 100, "without replacement");
assert_eq!(a, subsample_peaks(&samples, &channels, &opts), "same seed, same subset");
assert_ne!(a, subsample_peaks(&samples, &channels, &SubsampleOptions { seed: 2, ..opts }));
}
#[test]
fn per_channel_caps_each_channel() {
let (samples, mut channels) = peaks();
channels[0] = 9; let opts = SubsampleOptions { n_peaks: 30, per_channel: true, seed: 1 };
let c = subsample_peaks(&samples, &channels, &opts);
for ch in 0..4 {
assert_eq!(c.iter().filter(|&&i| channels[i] == ch).count(), 30);
}
assert!(c.contains(&0));
}
#[test]
fn asking_for_more_than_there_are_keeps_all() {
let (samples, channels) = peaks();
let all = subsample_peaks(&samples, &channels, &SubsampleOptions { n_peaks: 5000, per_channel: false, seed: 0 });
assert_eq!(all.len(), 1000);
}
#[test]
fn every_peak_is_equally_likely() {
let samples: Vec<u64> = (0..20).collect();
let channels = vec![0usize; 20];
let mut hits = [0u32; 20];
for seed in 0..4000 {
for i in subsample_peaks(&samples, &channels, &SubsampleOptions { n_peaks: 5, per_channel: false, seed }) {
hits[i] += 1;
}
}
assert!(hits.iter().all(|&h| (850..=1150).contains(&h)), "{hits:?}");
}
}