sim-metrics 0.1.0

Implementation of chemical similarity metrics
Documentation
use crate::measures::euclidean;
use ndarray::arr2;
use petal_clustering::{Fit, Optics};
use petal_neighbors::distance::Euclidean;
use rand::seq::SliceRandom;

#[derive(Clone)]
pub struct Bubble {
    objects: Vec<Vec<u8>>,
}

impl Bubble {
    pub fn n(&self) -> usize {
        self.objects.len()
    }

    pub fn d(&self) -> usize {
        self.objects[0].len()
    }

    pub fn rep(&self) -> Vec<f64> {
        self.objects
            .iter()
            .fold(vec![0.0; self.d()], |acc, i| {
                acc.iter()
                    .zip(i.iter())
                    .map(|(a, b)| a + *b as f64)
                    .collect()
            })
            .iter()
            .map(|x| x / self.n() as f64)
            .collect()
    }

    pub fn extent(&self) -> f64 {
        (self.objects.iter().fold(0.0, |acc, i| {
            acc + self.objects.iter().fold(0.0, |inner_acc, j| {
                inner_acc
                    + i.iter()
                        .zip(j.iter())
                        .map(|(a, b)| a - b)
                        .fold(0, |squaresum, x| squaresum + (x ^ 2)) as f64
            })
        }) / (self.n() * (self.n() - 1)) as f64)
            .sqrt()
    }

    pub fn nn_dist(&self, k: usize) -> f64 {
        (k as f64 / self.n() as f64).powf(1.0 / (self.d() as f64))
    }
}

pub fn compute_bubbles(x: Vec<Vec<u8>>, k: usize) -> Vec<Bubble> {
    let sample_objects: Vec<Vec<u8>> = x
        .choose_multiple(&mut rand::thread_rng(), k)
        .cloned()
        .collect();

    x.iter()
        .fold(vec![Bubble { objects: vec![] }; k], |mut acc, a| {
            acc[sample_objects
                .iter()
                .map(|x| euclidean(x, a))
                .enumerate()
                .min_by(|(_, a), (_, b)| a.total_cmp(b))
                .unwrap()
                .0]
                .objects
                .push(a.clone());
            acc
        })
}

pub fn cluster(x: Vec<Vec<u8>>, k: usize, tolerance: f64, min_pts: usize) -> Vec<Vec<Vec<u8>>> {
    println!("== Computing Bubbles");
    let bubbles: Vec<Bubble> = compute_bubbles(x.clone(), k)
        .iter()
        .filter(|x| !x.objects.is_empty())
        .cloned()
        .collect();
    println!("== Finding representatives");
    let rep = arr2(
        bubbles
            .iter()
            .map(|x| {
                x.rep().try_into().unwrap()
            })
            .collect::<Vec<[f64; 64]>>()
            .as_slice(),
    );
    println!("== Clustering");
    let clustering = Optics::new(tolerance, min_pts, Euclidean::default()).fit(&rep, None);

    clustering
        .0
        .values()
        .map(|i| i.iter().flat_map(|j| bubbles[*j].objects.clone()).collect())
        .collect()
}