use faer::{Mat, MatRef};
use rayon::iter::*;
use crate::prelude::*;
use crate::utils::matrix_utils::*;
const PAR_MAP_MIN_ELEMS: usize = 1 << 16;
#[derive(Debug, Default)]
pub enum RbfType {
#[default]
Gaussian,
Bump,
InverseQuadratic,
}
pub fn parse_rbf_types(s: &str) -> Option<RbfType> {
match s.to_lowercase().as_str() {
"gaussian" => Some(RbfType::Gaussian),
"bump" => Some(RbfType::Bump),
"inverse_quadratic" => Some(RbfType::InverseQuadratic),
_ => None,
}
}
fn map_mat_par<T, F>(mat: MatRef<T>, f: F) -> Mat<T>
where
T: BixverseFloat,
F: Fn(T) -> T + Sync,
{
let mut out = Mat::<T>::zeros(mat.nrows(), mat.ncols());
let fill = |j: usize, col: faer::ColMut<T>| {
for (o, &x) in col.iter_mut().zip(mat.col(j).iter()) {
*o = f(x);
}
};
if mat.nrows() * mat.ncols() >= PAR_MAP_MIN_ELEMS {
out.par_col_iter_mut()
.enumerate()
.for_each(|(j, col)| fill(j, col));
} else {
for j in 0..mat.ncols() {
fill(j, out.col_mut(j));
}
}
out
}
pub fn rbf_gaussian<T>(dist: &[T], epsilon: &T) -> Vec<T>
where
T: BixverseFloat,
{
dist.par_iter()
.map(|x| T::exp(-((*x * *epsilon).powi(2))))
.collect()
}
pub fn rbf_gaussian_mat<T>(dist: MatRef<T>, epsilon: &T) -> Mat<T>
where
T: BixverseFloat,
{
map_mat_par(dist, |x| T::exp(-((x * *epsilon).powi(2))))
}
pub fn rbf_bump<T>(dist: &[T], epsilon: &T) -> Vec<T>
where
T: BixverseFloat,
{
dist.par_iter()
.map(|x| {
if *x < (T::one() / *epsilon) {
T::exp(-(T::one() / (T::one() - (*epsilon * *x).powi(2))) + T::one())
} else {
T::zero()
}
})
.collect()
}
pub fn rbf_bump_mat<T>(dist: MatRef<T>, epsilon: &T) -> Mat<T>
where
T: BixverseFloat,
{
map_mat_par(dist, |x| {
if x < (T::one() / *epsilon) {
T::exp(-(T::one() / (T::one() - (*epsilon * x).powi(2))) + T::one())
} else {
T::zero()
}
})
}
pub fn rbf_inverse_quadratic<T>(dist: &[T], epsilon: &T) -> Vec<T>
where
T: BixverseFloat,
{
dist.par_iter()
.map(|x| T::one() / (T::one() + (*epsilon * *x).powi(2)))
.collect()
}
pub fn rbf_inverse_quadratic_mat<T>(dist: MatRef<T>, epsilon: &T) -> Mat<T>
where
T: BixverseFloat,
{
map_mat_par(dist, |x| T::one() / (T::one() + (*epsilon * x).powi(2)))
}
pub fn rbf_iterate_epsilons<T>(
dist: &[T],
epsilons: &[T],
n: usize,
shift: usize,
rbf_type: &str,
) -> Mat<T>
where
T: BixverseFloat,
{
let rbf_fun = parse_rbf_types(rbf_type).unwrap_or_default();
let k_res: Vec<Vec<T>> = epsilons
.par_iter()
.map(|epsilon| {
let affinity_adj: Vec<T> = match rbf_fun {
RbfType::Gaussian => dist
.iter()
.map(|x| T::exp(-((*x * *epsilon).powi(2))))
.collect(),
RbfType::Bump => dist
.iter()
.map(|x| {
if *x < (T::one() / *epsilon) {
T::exp(-(T::one() / (T::one() - (*epsilon * *x).powi(2))) + T::one())
} else {
T::zero()
}
})
.collect(),
RbfType::InverseQuadratic => dist
.iter()
.map(|x| T::one() / (T::one() + (*epsilon * *x).powi(2)))
.collect(),
};
let affinity_adj_mat = upper_triangle_to_sym_faer(&affinity_adj, shift, n);
affinity_adj_mat
.col_iter()
.map(|col| col.iter().fold(T::zero(), |acc, &v| acc + v))
.collect()
})
.collect();
nested_vector_to_faer_mat(k_res, true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_rbf_types() {
assert!(matches!(
parse_rbf_types("gaussian"),
Some(RbfType::Gaussian)
));
assert!(matches!(parse_rbf_types("BUMP"), Some(RbfType::Bump)));
assert!(matches!(
parse_rbf_types("inverse_quadratic"),
Some(RbfType::InverseQuadratic)
));
assert!(parse_rbf_types("unknown").is_none());
}
#[test]
fn test_rbf_gaussian() {
let dist: Vec<f64> = vec![0.0, 1.0];
let eps = 1.0;
let res = rbf_gaussian(&dist, &eps);
assert!((res[0] - 1.0).abs() < 1e-6);
assert!((res[1] - std::f64::consts::E.powi(-1)).abs() < 1e-6);
}
#[test]
fn test_rbf_inverse_quadratic() {
let dist: Vec<f64> = vec![0.0, 1.0, 2.0];
let eps = 1.0;
let res = rbf_inverse_quadratic(&dist, &eps);
assert!((res[0] - 1.0).abs() < 1e-6);
assert!((res[1] - 0.5).abs() < 1e-6);
assert!((res[2] - 0.2).abs() < 1e-6);
}
#[test]
fn test_rbf_bump() {
let dist: Vec<f64> = vec![0.0, 2.0];
let eps = 1.0;
let res = rbf_bump(&dist, &eps);
assert!((res[0] - 1.0).abs() < 1e-6);
assert!((res[1] - 0.0).abs() < 1e-6);
}
}