#[derive(Debug, Clone)]
pub struct DecisionGraph {
pub rho: Vec<f64>,
pub delta: Vec<f64>,
}
impl DecisionGraph {
pub fn new(rho: Vec<f64>, delta: Vec<f64>) -> Self {
Self { rho, delta }
}
pub fn gamma(&self) -> Vec<f64> {
self.rho
.iter()
.zip(self.delta.iter())
.map(|(r, d)| r * d)
.collect()
}
pub fn optimal_n_centers(&self) -> usize {
let mut gamma = self.gamma();
if gamma.len() < 3 {
return 1;
}
gamma.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
let drops: Vec<f64> = gamma.windows(2).map(|w| w[0] - w[1]).collect();
drops
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i + 1)
.unwrap_or(2)
}
pub fn top_k_centers(&self, k: usize) -> Vec<usize> {
let gamma = self.gamma();
let mut indexed: Vec<(f64, usize)> = gamma.iter().copied().enumerate().map(|(i, g)| (g, i)).collect();
indexed.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
indexed.iter().take(k).map(|(_, i)| *i).collect()
}
pub fn normalized_gamma(&self) -> Vec<f64> {
let gamma = self.gamma();
let max_g = gamma.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
if max_g <= 0.0 {
return vec![0.0; gamma.len()];
}
gamma.iter().map(|&g| g / max_g).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamma_computation() {
let rho = vec![1.0, 2.0, 3.0];
let delta = vec![4.0, 5.0, 6.0];
let dg = DecisionGraph::new(rho, delta);
let gamma = dg.gamma();
assert_eq!(gamma, vec![4.0, 10.0, 18.0]);
}
#[test]
fn test_optimal_n_centers_two_clusters() {
let rho = vec![10.0, 9.0, 1.0, 1.0, 1.0, 1.0];
let delta = vec![10.0, 9.0, 0.1, 0.1, 0.1, 0.1];
let dg = DecisionGraph::new(rho, delta);
let n = dg.optimal_n_centers();
assert_eq!(n, 2, "should identify 2 cluster centers");
}
#[test]
fn test_optimal_n_centers_small() {
let rho = vec![1.0, 2.0];
let delta = vec![1.0, 2.0];
let dg = DecisionGraph::new(rho, delta);
assert_eq!(dg.optimal_n_centers(), 1);
}
#[test]
fn test_top_k_centers() {
let rho = vec![1.0, 5.0, 3.0, 2.0];
let delta = vec![1.0, 5.0, 3.0, 2.0];
let dg = DecisionGraph::new(rho, delta);
let top2 = dg.top_k_centers(2);
assert_eq!(top2[0], 1);
assert_eq!(top2[1], 2);
}
#[test]
fn test_normalized_gamma() {
let rho = vec![1.0, 2.0, 0.0];
let delta = vec![2.0, 3.0, 0.0];
let dg = DecisionGraph::new(rho, delta);
let ng = dg.normalized_gamma();
assert!((ng[0] - 2.0 / 6.0).abs() < 1e-10);
assert!((ng[1] - 1.0).abs() < 1e-10);
assert!((ng[2] - 0.0).abs() < 1e-10);
}
}