use faer::{Mat, MatRef};
use rand::prelude::*;
use crate::graph::graph_structures::{adjacency_to_laplacian, get_knn_graph_adj};
use crate::prelude::*;
fn kmeans<T>(data: &MatRef<T>, k: usize, max_iters: usize, seed: usize) -> Vec<usize>
where
T: BixverseFloat,
{
let n = data.nrows();
let d = data.ncols();
let mut labels = vec![0; n];
let mut centroids = Mat::zeros(k, d);
let mut rng = StdRng::seed_from_u64(seed as u64);
centroids
.as_mut()
.row_mut(0)
.copy_from(data.row(rng.random_range(0..n)));
for i in 1..k {
let mut distances = vec![T::infinity(); n];
for j in 0..n {
for c in 0..i {
let dist: T = (0..d)
.map(|dim| (data[(j, dim)] - centroids[(c, dim)]).powi(2))
.fold(T::zero(), |acc, x| acc + x);
distances[j] = distances[j].min(dist);
}
}
let idx = distances
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
.unwrap()
.0;
centroids.as_mut().row_mut(i).copy_from(data.row(idx));
}
for _ in 0..max_iters {
for i in 0..n {
let mut min_dist = T::infinity();
for j in 0..k {
let dist: T = (0..d)
.map(|dim| (data[(i, dim)] - centroids[(j, dim)]).powi(2))
.fold(T::zero(), |acc, x| acc + x);
if dist < min_dist {
min_dist = dist;
labels[i] = j;
}
}
}
let mut counts = vec![0; k];
let mut new_centroids = Mat::zeros(k, d);
for i in 0..n {
counts[labels[i]] += 1;
for j in 0..d {
new_centroids[(labels[i], j)] += data[(i, j)];
}
}
for i in 0..k {
if counts[i] > 0 {
for j in 0..d {
new_centroids[(i, j)] /= T::from_usize(counts[i]).unwrap();
}
}
}
centroids = new_centroids;
}
labels
}
pub fn spectral_clustering<T>(
similarities: &MatRef<T>,
k_neighbours: usize,
n_clusters: usize,
max_iters: usize,
seed: usize,
) -> Vec<usize>
where
T: BixverseFloat,
{
let adjacency = get_knn_graph_adj(similarities, k_neighbours);
let laplacian = adjacency_to_laplacian(&adjacency.as_ref(), true);
let eigendecomp = laplacian.eigen().unwrap();
let eigenvalues = eigendecomp.S().column_vector();
let eigenvectors = eigendecomp.U();
let mut indices: Vec<usize> = (0..eigenvalues.nrows()).collect();
indices.sort_by(|&a, &b| eigenvalues[a].re.partial_cmp(&eigenvalues[b].re).unwrap());
let mut features = Mat::zeros(similarities.nrows(), n_clusters);
for i in 0..similarities.nrows() {
for j in 0..n_clusters {
features[(i, j)] = eigenvectors[(i, indices[j])].re
}
}
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;
}
}
}
kmeans(&features.as_ref(), n_clusters, max_iters, seed)
}
#[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, 100, 42);
assert_eq!(labels.len(), 4);
assert_eq!(labels[0], labels[1]); assert_eq!(labels[2], labels[3]); assert_ne!(labels[0], labels[2]); }
}