use ann_search_rs::prelude::AnnSearchFloat;
use faer::{Mat, MatRef};
use super::graph_structures::{adjacency_to_laplacian, get_knn_graph_adj};
use crate::ml::clustering::k_means::*;
use crate::prelude::*;
pub fn spectral_clustering<T>(
similarities: &MatRef<T>,
k_neighbours: usize,
n_clusters: usize,
kmeans_params: Option<KMeansParamsWrappers>,
seed: usize,
) -> Result<Vec<usize>, BixverseErrors>
where
T: BixverseFloat + AnnSearchFloat,
{
let adjacency = get_knn_graph_adj(similarities, k_neighbours);
let laplacian = adjacency_to_laplacian(&adjacency.as_ref(), true);
let eigendecomp = laplacian
.self_adjoint_eigen(faer::Side::Lower)
.map_err(|_| BixverseErrors::FaerEigenError)?;
let eigenvectors = eigendecomp.U();
let mut features = Mat::zeros(similarities.nrows(), n_clusters);
for j in 0..n_clusters {
for i in 0..similarities.nrows() {
features[(i, j)] = eigenvectors[(i, j)];
}
}
for i in 0..features.nrows() {
let norm: T = (0..n_clusters)
.map(|j| features[(i, j)].powi(2))
.fold(T::zero(), |acc, x| acc + x)
.sqrt();
if norm > T::from_f64(1e-10).unwrap() {
for j in 0..n_clusters {
features[(i, j)] /= norm;
}
}
}
let (_, assignments) = k_means_clusters(
features.as_ref(),
"euclidean",
n_clusters,
kmeans_params,
seed,
false,
)?;
Ok(assignments)
}
#[cfg(test)]
mod tests {
use super::*;
use faer::Mat;
#[test]
fn test_spectral_clustering_block_diagonal() {
let mut sim: Mat<f64> = Mat::zeros(4, 4);
sim[(0, 0)] = 1.0;
sim[(0, 1)] = 0.9;
sim[(1, 0)] = 0.9;
sim[(1, 1)] = 1.0;
sim[(2, 2)] = 1.0;
sim[(2, 3)] = 0.9;
sim[(3, 2)] = 0.9;
sim[(3, 3)] = 1.0;
sim[(0, 2)] = 0.1;
sim[(2, 0)] = 0.1;
let labels = spectral_clustering(&sim.as_ref(), 1, 2, None, 42).unwrap();
assert_eq!(labels.len(), 4);
assert_eq!(labels[0], labels[1]); assert_eq!(labels[2], labels[3]); assert_ne!(labels[0], labels[2]); }
}