#[cfg(not(feature = "blas"))]
use linfa_linalg::{
eigh::*,
lobpcg::{self, LobpcgResult, Order as TruncatedOrder},
};
use ndarray::{Array1, Array2};
#[cfg(feature = "blas")]
use ndarray_linalg::{eigh::EighInto, lobpcg, lobpcg::LobpcgResult, Scalar, TruncatedOrder, UPLO};
use ndarray_rand::{rand_distr::Uniform, RandomExt};
use linfa::dataset::{WithLapack, WithoutLapack};
use linfa::{traits::Transformer, Float};
use linfa_kernel::Kernel;
use rand::{prelude::SmallRng, SeedableRng};
use super::hyperparams::DiffusionMapValidParams;
#[derive(Debug, Clone, PartialEq)]
pub struct DiffusionMap<F> {
embedding: Array2<F>,
eigvals: Array1<F>,
}
impl<'a, F: Float> Transformer<&'a Kernel<F>, DiffusionMap<F>> for DiffusionMapValidParams {
fn transform(&self, kernel: &'a Kernel<F>) -> DiffusionMap<F> {
let (embedding, eigvals) =
compute_diffusion_map(kernel, self.steps(), 0.0, self.embedding_size(), None);
DiffusionMap { embedding, eigvals }
}
}
impl<F: Float> DiffusionMap<F> {
pub fn estimate_clusters(&self) -> usize {
let mean = self.eigvals.sum() / F::cast(self.eigvals.len());
self.eigvals.iter().filter(|x| *x > &mean).count() + 1
}
pub fn eigvals(&self) -> &Array1<F> {
&self.eigvals
}
pub fn embedding(&self) -> &Array2<F> {
&self.embedding
}
}
#[allow(unused)]
fn compute_diffusion_map<F: Float>(
kernel: &Kernel<F>,
steps: usize,
alpha: f32,
embedding_size: usize,
guess: Option<Array2<F>>,
) -> (Array2<F>, Array1<F>) {
assert!(embedding_size < kernel.size());
let d = kernel.sum().mapv(|x| x.recip());
let (vals, vecs) = if kernel.size() < 5 * embedding_size + 1 {
let mut matrix = kernel.dot(&Array2::from_diag(&d).view());
matrix
.columns_mut()
.into_iter()
.zip(d.iter())
.for_each(|(mut a, b)| a *= *b);
let matrix = matrix.with_lapack();
#[cfg(feature = "blas")]
let (vals, vecs) = {
let (vals, vecs) = matrix.eigh_into(UPLO::Lower).unwrap();
(
vals.slice_move(s![..; -1]).mapv(Scalar::from_real),
vecs.slice_move(s![.., ..; -1]),
)
};
#[cfg(not(feature = "blas"))]
let (vals, vecs) = matrix.eigh_into().unwrap().sort_eig_desc();
(
vals.slice_move(s![1..=embedding_size]),
vecs.slice_move(s![.., 1..=embedding_size]),
)
} else {
let d2 = d.mapv(|x| x.powf(F::cast(0.5 + alpha)));
let x = guess
.unwrap_or_else(|| {
Array2::random_using(
(kernel.size(), embedding_size + 1),
Uniform::new(0.0f64, 1.0),
&mut SmallRng::seed_from_u64(31),
)
.mapv(F::cast)
})
.with_lapack();
let result = lobpcg::lobpcg(
|y| {
let mut y = y.to_owned().without_lapack();
y.rows_mut()
.into_iter()
.zip(d2.iter())
.for_each(|(mut a, b)| a *= *b);
let mut y = kernel.dot(&y.view());
y.rows_mut()
.into_iter()
.zip(d2.iter())
.for_each(|(mut a, b)| a *= *b);
y.with_lapack()
},
x,
|_| {},
None,
1e-7,
200,
TruncatedOrder::Largest,
);
let (vals, vecs) = match result {
#[cfg(feature = "blas")]
LobpcgResult::Ok(vals, vecs, _) | LobpcgResult::Err(vals, vecs, _, _) => (vals, vecs),
#[cfg(not(feature = "blas"))]
LobpcgResult::Ok(lobpcg) | LobpcgResult::Err((_, Some(lobpcg))) => {
(lobpcg.eigvals, lobpcg.eigvecs)
}
_ => panic!("Eigendecomposition failed!"),
};
(vals.slice_move(s![1..]), vecs.slice_move(s![.., 1..]))
};
let (vals, mut vecs): (Array1<F>, _) = (vals.without_lapack(), vecs.without_lapack());
let d = d.mapv(|x| x.sqrt());
for (mut col, val) in vecs.rows_mut().into_iter().zip(d.iter()) {
col *= *val;
}
let steps = F::cast(steps);
for (mut vec, val) in vecs.columns_mut().into_iter().zip(vals.iter()) {
vec *= val.powf(steps);
}
(vecs, vals)
}