use clap::Args;
#[derive(Debug, Clone, Args)]
pub struct ClusteringSettings {
#[arg(long = "cluster-min-records", value_name = "NUM", default_value_t = 2)]
pub min_records: usize,
#[arg(long = "cluster-max-gap", value_name = "BASES", default_value_t = 20)]
pub max_gap: usize,
#[arg(
long = "cluster-min-density",
value_name = "NUM",
default_value_t = 0.2
)]
pub min_density: f64,
}
impl Default for ClusteringSettings {
fn default() -> Self {
Self {
min_records: 2,
max_gap: 20,
min_density: 0.2,
}
}
}
impl ClusteringSettings {
pub fn is_valid_cluster(&self, complexity: f64, span: f64, count: usize) -> bool {
if count < 1.max(self.min_records) {
return false;
}
complexity / span >= self.min_density
}
}
#[allow(clippy::cast_precision_loss)]
pub fn variant_complexity(ref_len: usize, alt_len: usize) -> f64 {
match (ref_len, alt_len) {
(1, 1) => 1.0,
(n, m) => 1.0 + ((n.max(m) - 1) as f64).log2(),
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn snp_complexity_is_one() {
assert_eq!(variant_complexity(1, 1), 1.0);
}
#[test]
fn insertion_complexity() {
assert_eq!(variant_complexity(1, 2), 1.0);
assert_eq!(variant_complexity(1, 3), 2.0);
assert_eq!(variant_complexity(1, 5), 3.0);
}
#[test]
fn deletion_complexity_mirrors_insertion() {
assert_eq!(variant_complexity(2, 1), variant_complexity(1, 2));
assert_eq!(variant_complexity(3, 1), variant_complexity(1, 3));
}
#[test]
fn valid_cluster_at_density_threshold() {
let s = ClusteringSettings {
min_records: 2,
max_gap: 20,
min_density: 0.2,
};
assert!(s.is_valid_cluster(2.0, 10.0, 2));
}
#[test]
fn cluster_rejected_below_density_threshold() {
let s = ClusteringSettings {
min_records: 2,
max_gap: 20,
min_density: 0.2,
};
assert!(!s.is_valid_cluster(1.9, 10.0, 2));
}
#[test]
fn cluster_rejected_below_min_records() {
let s = ClusteringSettings {
min_records: 3,
max_gap: 20,
min_density: 0.0,
};
assert!(!s.is_valid_cluster(10.0, 5.0, 2));
assert!(s.is_valid_cluster(10.0, 5.0, 3));
}
}