use super::*;
use crate::diarization::embedding::SpeakerEmbedding;
#[test]
fn test_clustering_algorithm_default() {
let alg = ClusteringAlgorithm::default();
assert_eq!(alg, ClusteringAlgorithm::Spectral);
}
#[test]
fn test_clustering_config_default() {
let config = ClusteringConfig::default();
assert_eq!(config.algorithm, ClusteringAlgorithm::Spectral);
assert!(config.use_cosine_distance);
assert_eq!(config.max_iterations, 100);
}
#[test]
fn test_clustering_config_for_realtime() {
let config = ClusteringConfig::for_realtime();
assert_eq!(config.algorithm, ClusteringAlgorithm::KMeans);
assert_eq!(config.max_iterations, 50);
}
#[test]
fn test_clustering_config_for_accuracy() {
let config = ClusteringConfig::for_accuracy();
assert_eq!(config.algorithm, ClusteringAlgorithm::Spectral);
assert_eq!(config.max_iterations, 200);
}
#[test]
fn test_clustering_config_with_algorithm() {
let config = ClusteringConfig::default().with_algorithm(ClusteringAlgorithm::KMeans);
assert_eq!(config.algorithm, ClusteringAlgorithm::KMeans);
}
#[test]
fn test_clustering_config_with_distance_threshold() {
let config = ClusteringConfig::default().with_distance_threshold(0.7);
assert!((config.distance_threshold - 0.7).abs() < f32::EPSILON);
}
#[test]
fn test_speaker_cluster_new() {
let centroid = SpeakerEmbedding::new(vec![0.1; 256], 0);
let cluster = SpeakerCluster::new(0, vec![0, 1, 2], centroid);
assert_eq!(cluster.id(), 0);
assert_eq!(cluster.size(), 3);
assert_eq!(cluster.member_indices().len(), 3);
}
#[test]
fn test_speaker_cluster_with_cohesion() {
let centroid = SpeakerEmbedding::new(vec![0.1; 256], 0);
let cluster = SpeakerCluster::new(0, vec![0], centroid).with_cohesion(0.5);
assert!((cluster.cohesion() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_clustering_result_new() {
let centroid = SpeakerEmbedding::new(vec![0.1; 256], 0);
let clusters = vec![SpeakerCluster::new(0, vec![0, 1], centroid)];
let result = ClusteringResult::new(vec![0, 0], clusters);
assert_eq!(result.num_clusters(), 1);
assert_eq!(result.labels().len(), 2);
}
#[test]
fn test_clustering_result_with_silhouette() {
let result = ClusteringResult::new(vec![], vec![]).with_silhouette_score(0.8);
assert!((result.silhouette_score() - 0.8).abs() < f32::EPSILON);
}
#[test]
fn test_clustering_result_cluster_centroids() {
let centroid1 = SpeakerEmbedding::new(vec![0.1; 256], 0);
let centroid2 = SpeakerEmbedding::new(vec![0.2; 256], 1);
let clusters = vec![
SpeakerCluster::new(0, vec![0], centroid1),
SpeakerCluster::new(1, vec![1], centroid2),
];
let result = ClusteringResult::new(vec![0, 1], clusters);
let centroids = result.cluster_centroids();
assert_eq!(centroids.len(), 2);
}
#[test]
fn test_spectral_clustering_new() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
assert!(clustering.config().use_cosine_distance);
}
#[test]
fn test_spectral_clustering_empty() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let result = clustering.cluster(&[], None, 1);
assert!(result.is_ok());
let result = result.expect("should succeed");
assert_eq!(result.num_clusters(), 0);
}
#[test]
fn test_spectral_clustering_single() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let embeddings = vec![SpeakerEmbedding::new(vec![0.1; 256], 0)];
let result = clustering.cluster(&embeddings, None, 1);
assert!(result.is_ok());
let result = result.expect("should succeed");
assert_eq!(result.num_clusters(), 1);
assert_eq!(result.labels(), &[0]);
}
#[test]
fn test_spectral_clustering_two_distinct() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let emb1 = SpeakerEmbedding::new(vec![1.0; 256], 0);
let emb2 = SpeakerEmbedding::new(vec![-1.0; 256], 1);
let result = clustering.cluster(&[emb1, emb2], Some(2), 1);
assert!(result.is_ok());
let result = result.expect("should succeed");
assert!(result.num_clusters() >= 1);
}
#[test]
fn test_spectral_clustering_similar_embeddings() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let emb1 = SpeakerEmbedding::new(vec![0.9; 256], 0);
let emb2 = SpeakerEmbedding::new(vec![0.95; 256], 0);
let emb3 = SpeakerEmbedding::new(vec![0.92; 256], 0);
let result = clustering.cluster(&[emb1, emb2, emb3], Some(2), 1);
assert!(result.is_ok());
let result = result.expect("should succeed");
let labels = result.labels();
assert!(labels[0] == labels[1] && labels[1] == labels[2]);
}
#[test]
fn test_spectral_clustering_respects_min_clusters() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let embeddings: Vec<SpeakerEmbedding> = (0..5)
.map(|i| SpeakerEmbedding::new(vec![i as f32 * 0.1; 256], 0))
.collect();
let result = clustering.cluster(&embeddings, Some(5), 2);
assert!(result.is_ok());
let result = result.expect("should succeed");
assert!(result.num_clusters() >= 1); }
#[test]
fn test_spectral_clustering_respects_max_clusters() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let embeddings: Vec<SpeakerEmbedding> = (0..10)
.map(|i| SpeakerEmbedding::new(vec![i as f32 * 0.5; 256], 0))
.collect();
let result = clustering.cluster(&embeddings, Some(3), 1);
assert!(result.is_ok());
let result = result.expect("should succeed");
assert!(result.num_clusters() <= 3);
}
#[test]
fn test_build_affinity_matrix() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let emb1 = SpeakerEmbedding::new(vec![1.0, 0.0], 0);
let emb2 = SpeakerEmbedding::new(vec![1.0, 0.0], 0);
let affinity = clustering.build_affinity_matrix(&[emb1, emb2]);
assert_eq!(affinity.len(), 2);
assert_eq!(affinity[0].len(), 2);
assert!(affinity[0][1] > 0.9);
}
#[test]
fn test_compute_cluster_cohesion() {
let clustering = SpectralClustering::new(ClusteringConfig::default());
let centroid = SpeakerEmbedding::new(vec![1.0, 0.0], 0);
let members = vec![
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
];
let cohesion = clustering.compute_cluster_cohesion(&members, ¢roid);
assert!(cohesion < 0.1);
}
#[test]
fn test_compute_silhouette_single_embedding_returns_zero() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![SpeakerEmbedding::new(vec![1.0, 0.0, 0.0], 0)];
let labels = vec![0];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.abs() < f32::EPSILON,
"single embedding should yield silhouette = 0.0, got {}",
score
);
}
#[test]
fn test_compute_silhouette_empty_embeddings_returns_zero() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings: Vec<SpeakerEmbedding> = Vec::new();
let labels: Vec<usize> = Vec::new();
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.abs() < f32::EPSILON,
"empty embeddings should yield silhouette = 0.0, got {}",
score
);
}
#[test]
fn test_compute_silhouette_single_cluster_returns_zero() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.9, 0.1, 0.0], 0),
SpeakerEmbedding::new(vec![0.8, 0.2, 0.0], 0),
];
let labels = vec![0, 0, 0];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.abs() < f32::EPSILON,
"single cluster should yield silhouette = 0.0, got {}",
score
);
}
#[test]
fn test_compute_silhouette_two_clusters_cosine() {
let config = ClusteringConfig {
use_cosine_distance: true,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.9, 0.1, 0.0], 0),
SpeakerEmbedding::new(vec![-1.0, 0.0, 0.0], 1),
SpeakerEmbedding::new(vec![-0.9, -0.1, 0.0], 1),
];
let labels = vec![0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"silhouette should be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_euclidean_distance() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.1, 0.1], 0),
SpeakerEmbedding::new(vec![10.0, 10.0], 1),
SpeakerEmbedding::new(vec![10.1, 10.1], 1),
];
let labels = vec![0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"silhouette should be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_zero_vectors() {
let config = ClusteringConfig {
use_cosine_distance: true,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 0.0, 0.0], 1),
];
let labels = vec![0, 0, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"silhouette should be finite for zero vectors, got {}",
score
);
}
#[test]
fn test_compute_silhouette_lone_point_in_cluster() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.9], 1),
];
let labels = vec![0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"silhouette should be finite for lone cluster, got {}",
score
);
}
#[test]
fn test_compute_silhouette_three_clusters() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.9, 0.1, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.9, 0.1], 1),
SpeakerEmbedding::new(vec![0.0, 0.0, 1.0], 2),
SpeakerEmbedding::new(vec![0.1, 0.0, 0.9], 2),
];
let labels = vec![0, 0, 1, 1, 2, 2];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"silhouette should be finite for 3 clusters, got {}",
score
);
}
#[test]
fn test_cluster_pipeline_exercises_silhouette_euclidean() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.1, 0.0], 0),
SpeakerEmbedding::new(vec![10.0, 0.0], 1),
SpeakerEmbedding::new(vec![10.1, 0.0], 1),
];
let result = clustering
.cluster(&embeddings, Some(2), 1)
.expect("cluster should succeed");
assert!(
result.silhouette_score().is_finite(),
"silhouette from cluster() should be finite"
);
}
#[test]
fn test_cluster_pipeline_exercises_silhouette_cosine_many_embeddings() {
let config = ClusteringConfig {
use_cosine_distance: true,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let mut embeddings = Vec::new();
for i in 0..4 {
let mut v = vec![0.0f32; 8];
v[0] = 1.0;
v[1] = i as f32 * 0.05;
embeddings.push(SpeakerEmbedding::new(v, 0));
}
for i in 0..4 {
let mut v = vec![0.0f32; 8];
v[0] = -1.0;
v[1] = i as f32 * 0.05;
embeddings.push(SpeakerEmbedding::new(v, 1));
}
let result = clustering
.cluster(&embeddings, Some(2), 1)
.expect("cluster should succeed");
assert!(result.silhouette_score().is_finite());
assert!(result.num_clusters() >= 1);
}
#[test]
fn test_build_affinity_matrix_euclidean() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let emb1 = SpeakerEmbedding::new(vec![0.0, 0.0], 0);
let emb2 = SpeakerEmbedding::new(vec![0.0, 0.0], 0);
let emb3 = SpeakerEmbedding::new(vec![10.0, 10.0], 1);
let affinity = clustering.build_affinity_matrix(&[emb1, emb2, emb3]);
assert_eq!(affinity.len(), 3);
assert!(
affinity[0][1] > 0.9,
"identical embeddings should have high Euclidean affinity, got {}",
affinity[0][1]
);
assert!(
affinity[0][2] < affinity[0][1],
"distant embeddings should have lower affinity"
);
}
#[test]
fn test_compute_cluster_cohesion_euclidean() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let centroid = SpeakerEmbedding::new(vec![5.0, 5.0], 0);
let members = vec![
SpeakerEmbedding::new(vec![5.0, 5.0], 0),
SpeakerEmbedding::new(vec![5.1, 5.1], 0),
];
let cohesion = clustering.compute_cluster_cohesion(&members, ¢roid);
assert!(
cohesion < 1.0,
"near-identical embeddings should have low Euclidean cohesion, got {}",
cohesion
);
}
#[test]
fn test_compute_cluster_cohesion_empty_members() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let centroid = SpeakerEmbedding::new(vec![1.0, 0.0], 0);
let members: Vec<SpeakerEmbedding> = Vec::new();
let cohesion = clustering.compute_cluster_cohesion(&members, ¢roid);
assert!(
cohesion.abs() < f32::EPSILON,
"empty members should yield cohesion = 0.0, got {}",
cohesion
);
}
#[test]
fn test_estimate_num_clusters_small_n() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let emb1 = SpeakerEmbedding::new(vec![1.0, 0.0], 0);
let emb2 = SpeakerEmbedding::new(vec![0.0, 1.0], 1);
let affinity = clustering.build_affinity_matrix(&[emb1, emb2]);
let num = clustering.estimate_num_clusters(&affinity, None, 3);
assert_eq!(num, 2, "with n=2 and min_clusters=3, should return 2");
}
#[test]
fn test_spectral_cluster_num_clusters_ge_n() {
let config = ClusteringConfig {
distance_threshold: 1.0, ..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0], 1),
];
let result = clustering
.cluster(&embeddings, Some(2), 2)
.expect("should succeed");
assert_eq!(result.labels().len(), 2);
}
#[test]
fn test_compute_silhouette_euclidean_intra_cluster() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.1, 0.1, 0.0], 0),
SpeakerEmbedding::new(vec![100.0, 100.0, 100.0], 1),
SpeakerEmbedding::new(vec![100.1, 100.1, 100.0], 1),
];
let labels = vec![0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"euclidean intra-cluster silhouette must be finite, got {}",
score
);
assert!(
(-1.0..=1.0).contains(&score),
"silhouette must be in [-1, 1], got {}",
score
);
}
#[test]
fn test_compute_silhouette_single_member_cluster_a_equals_zero() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0, 0.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.9, 0.1, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.8, 0.2, 0.0], 1),
];
let labels = vec![0, 1, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"single-member cluster silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_euclidean_single_member_cluster() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0), SpeakerEmbedding::new(vec![5.0, 5.0], 1),
SpeakerEmbedding::new(vec![5.1, 5.0], 1),
];
let labels = vec![0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"euclidean single-member silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_a_max_b_zero_branch() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 1.0], 0),
SpeakerEmbedding::new(vec![1.0, 1.0], 0),
SpeakerEmbedding::new(vec![1.0, 1.0], 1),
SpeakerEmbedding::new(vec![1.0, 1.0], 1),
];
let labels = vec![0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"identical points silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_four_clusters() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.9, 0.1, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0, 0.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.9, 0.1, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.0, 1.0, 0.0], 2),
SpeakerEmbedding::new(vec![0.0, 0.0, 0.9, 0.1], 2),
SpeakerEmbedding::new(vec![0.0, 0.0, 0.0, 1.0], 3),
SpeakerEmbedding::new(vec![0.1, 0.0, 0.0, 0.9], 3),
];
let labels = vec![0, 0, 1, 1, 2, 2, 3, 3];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"four-cluster silhouette must be finite, got {}",
score
);
assert!(
(-1.0..=1.0).contains(&score),
"silhouette must be in [-1, 1], got {}",
score
);
}
#[test]
fn test_compute_silhouette_euclidean_four_clusters() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.1, 0.0], 0),
SpeakerEmbedding::new(vec![10.0, 0.0], 1),
SpeakerEmbedding::new(vec![10.1, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 10.0], 2),
SpeakerEmbedding::new(vec![0.0, 10.1], 2),
SpeakerEmbedding::new(vec![10.0, 10.0], 3),
SpeakerEmbedding::new(vec![10.1, 10.1], 3),
];
let labels = vec![0, 0, 1, 1, 2, 2, 3, 3];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"euclidean four-cluster silhouette must be finite, got {}",
score
);
}
#[test]
fn test_cluster_pipeline_silhouette_two_distinct_clusters() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let mut embeddings = Vec::new();
for i in 0..5 {
let mut v = vec![0.0f32; 16];
v[0] = 1.0;
v[1] = i as f32 * 0.02;
embeddings.push(SpeakerEmbedding::new(v, 0));
}
for i in 0..5 {
let mut v = vec![0.0f32; 16];
v[0] = -1.0;
v[1] = i as f32 * 0.02;
embeddings.push(SpeakerEmbedding::new(v, 1));
}
let result = clustering
.cluster(&embeddings, Some(2), 1)
.expect("clustering should succeed");
assert!(
result.silhouette_score().is_finite(),
"silhouette from pipeline must be finite"
);
assert!(result.num_clusters() >= 1);
}
#[test]
fn test_compute_silhouette_uneven_cluster_sizes() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.95, 0.05, 0.0], 0),
SpeakerEmbedding::new(vec![0.9, 0.1, 0.0], 0),
SpeakerEmbedding::new(vec![0.92, 0.08, 0.0], 0),
SpeakerEmbedding::new(vec![0.97, 0.03, 0.0], 0),
SpeakerEmbedding::new(vec![-1.0, 0.0, 0.0], 1),
];
let labels = vec![0, 0, 0, 0, 0, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"uneven cluster silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_non_contiguous_labels() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.9, 0.1], 0),
SpeakerEmbedding::new(vec![-1.0, 0.0], 5),
SpeakerEmbedding::new(vec![-0.9, -0.1], 5),
];
let labels = vec![0, 0, 5, 5];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"non-contiguous labels silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_minimum_two_embeddings_two_clusters() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![-1.0, 0.0], 1),
];
let labels = vec![0, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"two-point two-cluster silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_euclidean_zero_distance_all_points() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![5.0, 5.0], 0),
SpeakerEmbedding::new(vec![5.0, 5.0], 0),
SpeakerEmbedding::new(vec![5.0, 5.0], 1),
SpeakerEmbedding::new(vec![5.0, 5.0], 1),
];
let labels = vec![0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"zero-distance silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_all_singletons_cosine() {
let config = ClusteringConfig::default();
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.0, 1.0], 2),
];
let labels = vec![0, 1, 2];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"all-singletons cosine silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_all_singletons_euclidean() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![10.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 10.0], 2),
];
let labels = vec![0, 1, 2];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"all-singletons euclidean silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_euclidean_mixed_cluster_sizes() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.1, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 0.1, 0.0], 0),
SpeakerEmbedding::new(vec![50.0, 50.0, 50.0], 1), ];
let labels = vec![0, 0, 0, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"mixed sizes euclidean silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_negative_coefficient() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![5.0, 5.0], 0), SpeakerEmbedding::new(vec![5.0, 6.0], 1),
SpeakerEmbedding::new(vec![5.0, 7.0], 1),
];
let labels = vec![0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"negative coefficient silhouette must be finite, got {}",
score
);
assert!(
(-1.0..=1.0).contains(&score),
"silhouette must be in [-1, 1], got {}",
score
);
}
#[test]
fn test_compute_silhouette_cosine_three_clusters_b_iteration() {
let config = ClusteringConfig {
use_cosine_distance: true,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.98, 0.02, 0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0, 0.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.02, 0.98, 0.0, 0.0], 1),
SpeakerEmbedding::new(vec![0.0, 0.0, 1.0, 0.0], 2),
SpeakerEmbedding::new(vec![0.0, 0.02, 0.98, 0.0], 2),
];
let labels = vec![0, 0, 1, 1, 2, 2];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"3-cluster cosine b-iteration silhouette must be finite, got {}",
score
);
}
#[test]
fn test_cluster_pipeline_euclidean_three_groups() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.1, 0.1], 0),
SpeakerEmbedding::new(vec![50.0, 0.0], 1),
SpeakerEmbedding::new(vec![50.1, 0.1], 1),
SpeakerEmbedding::new(vec![0.0, 50.0], 2),
SpeakerEmbedding::new(vec![0.1, 50.1], 2),
];
let result = clustering
.cluster(&embeddings, Some(3), 1)
.expect("clustering should succeed");
assert!(result.silhouette_score().is_finite());
assert!(result.num_clusters() >= 1);
}
#[test]
fn test_compute_silhouette_two_points_same_cluster_cosine() {
let config = ClusteringConfig {
use_cosine_distance: true,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.8, 0.2], 0),
SpeakerEmbedding::new(vec![-1.0, 0.0], 1),
];
let labels = vec![0, 0, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"two-in-cluster cosine silhouette must be finite, got {}",
score
);
}
#[test]
fn test_compute_silhouette_euclidean_nontrivial_coefficient() {
let config = ClusteringConfig {
use_cosine_distance: false,
..ClusteringConfig::default()
};
let clustering = SpectralClustering::new(config);
let embeddings = vec![
SpeakerEmbedding::new(vec![0.0, 0.0], 0),
SpeakerEmbedding::new(vec![1.0, 0.0], 0),
SpeakerEmbedding::new(vec![0.0, 1.0], 0),
SpeakerEmbedding::new(vec![20.0, 20.0], 1),
SpeakerEmbedding::new(vec![21.0, 20.0], 1),
];
let labels = vec![0, 0, 0, 1, 1];
let score = clustering.compute_silhouette(&embeddings, &labels);
assert!(
score.is_finite(),
"nontrivial euclidean silhouette must be finite, got {}",
score
);
assert!(
(-1.0..=1.0).contains(&score),
"silhouette must be in [-1, 1], got {}",
score
);
}
#[test]
fn test_clustering_result_clusters_accessor() {
let c0 = SpeakerCluster::new(0, vec![0, 1], SpeakerEmbedding::new(vec![1.0], 0));
let c1 = SpeakerCluster::new(1, vec![2], SpeakerEmbedding::new(vec![2.0], 1));
let result = ClusteringResult::new(vec![0, 0, 1], vec![c0, c1]).with_silhouette_score(0.5);
let clusters = result.clusters();
assert_eq!(clusters.len(), 2);
assert_eq!(result.num_clusters(), 2);
assert!((result.silhouette_score() - 0.5).abs() < f32::EPSILON);
}