pub mod distance;
#[macro_use]
extern crate derivative;
const EPSILON: f64 = 1e-9;
#[derive(Clone, Copy, Debug)]
struct IndexFloat {
index: usize,
value: f64,
}
fn partial_quicksort(data: &mut [IndexFloat], first: usize, last: usize, part: usize) {
if first >= last {
return;
}
data.swap(first, (first + last) / 2);
let pivot = data[first].value;
let mut lower = first + 1;
let mut upper = last;
while lower <= upper {
while lower <= last && data[lower].value < pivot {
lower += 1;
}
while pivot < data[upper].value {
upper -= 1;
}
if lower < upper {
data.swap(lower, upper);
upper -= 1;
}
lower += 1;
}
data.swap(first, upper);
if upper > 0 && first < (upper - 1) {
partial_quicksort(data, first, upper - 1, part);
}
if upper >= part {
return;
}
if (upper + 1) < last {
partial_quicksort(data, upper + 1, last, part);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ObjectType {
Normal,
Support,
Outlier,
}
#[derive(Debug)]
pub struct DistanceGraph {
n: usize,
kmax: usize,
neighbors: Vec<Vec<usize>>,
distances: Vec<Vec<f64>>,
}
impl DistanceGraph {
pub fn build<'a, V, F>(data: &'a [V], distfunc: F) -> DistanceGraph
where
F: Fn(&V, &V) -> f64,
{
let n = data.len();
match n {
0 => {
return DistanceGraph {
n: 0,
kmax: 0,
neighbors: vec![],
distances: vec![],
};
}
1 => {
return DistanceGraph {
n: 1,
kmax: 0,
neighbors: vec![vec![]],
distances: vec![vec![]],
};
}
_ => {}
}
let mut kmax: usize = (n as f64).sqrt() as usize + 10;
if kmax >= n {
kmax = n - 1;
}
let mut neighbors = Vec::<Vec<usize>>::with_capacity(n);
let mut distances = Vec::<Vec<f64>>::with_capacity(n);
let mut vals = Vec::with_capacity(n - 1);
for i in 0..n {
vals.clear();
for j in 0..n {
if j == i {
continue;
}
vals.push(IndexFloat {
index: j,
value: distfunc(&data[i], &data[j]),
});
}
partial_quicksort(&mut vals, 0, n - 2, kmax);
let mut indexes = Vec::with_capacity(kmax);
let mut values = Vec::with_capacity(kmax);
for x in vals.iter().take(kmax) {
indexes.push(x.index);
values.push(x.value);
}
neighbors.push(indexes);
distances.push(values);
}
DistanceGraph {
n,
kmax,
neighbors,
distances,
}
}
pub fn neighbors(&self, id: usize) -> impl Iterator<Item = (usize, f64)> + '_ {
self.neighbors[id]
.iter()
.copied()
.zip(self.distances[id].iter().copied())
}
pub fn find_supporting_objects(
&self,
mut knn: usize,
mut thd: f64,
) -> ClusterSupportingObjects {
match self.n {
0 => {
return ClusterSupportingObjects {
neighbors: vec![],
weights: vec![],
cso_count: 0,
obtypes: vec![],
fuzzyships: vec![],
fuzzyships2: vec![],
};
}
1 => {
return ClusterSupportingObjects {
neighbors: vec![vec![]],
weights: vec![vec![]],
cso_count: 1,
obtypes: vec![ObjectType::Support],
fuzzyships: vec![vec![1.0]],
fuzzyships2: vec![vec![1.0]],
};
}
_ => {}
}
if knn > self.kmax {
knn = self.kmax;
}
let mut neighbors = Vec::<Vec<usize>>::with_capacity(self.n);
let mut weights = Vec::<Vec<f64>>::with_capacity(self.n);
let mut density = Vec::<f64>::with_capacity(self.n);
for i in 0..self.n {
let dists = &self.distances[i];
let mut k = knn;
let d = dists[knn - 1];
k += dists[knn..self.kmax].iter().filter(|&x| *x == d).count();
neighbors.push(self.neighbors[i].iter().take(k).copied().collect());
let mut sum = ((k * (k + 1)) / 2) as f64;
weights.push((0..k).map(|j| (k - j) as f64 / sum).collect());
sum = dists.iter().take(k).sum();
density.push((sum + EPSILON).recip());
}
let mut sum = 0.0;
let mut sum2 = 0.0;
for d in &density {
sum += d;
sum2 += d * d;
}
sum /= self.n as f64;
thd = sum + thd * (sum2 / (self.n as f64) - sum * sum).sqrt();
let mut obtypes = vec![ObjectType::Normal; self.n];
let mut cso_count = 0;
for i in 0..self.n {
let k = neighbors[i].len();
let mut fmax = 0.0;
let mut fmin = density[i] / density[self.neighbors[i][0]];
for j in 1..k {
let d = density[i] / density[self.neighbors[i][j]];
if d > fmax {
fmax = d;
}
if d < fmin {
fmin = d;
}
if obtypes[self.neighbors[i][j]] != ObjectType::Normal {
fmin = 0.0;
}
}
if fmin >= 1.0 {
cso_count += 1;
obtypes[i] = ObjectType::Support;
} else if fmax <= 1.0 && density[i] < thd {
obtypes[i] = ObjectType::Outlier;
}
}
let mut k = 0;
let fuzzyships = obtypes
.iter()
.map(|obtype| match obtype {
ObjectType::Support => {
let mut fuzzy = vec![0.0; cso_count + 1];
fuzzy[k] = 1.0;
k += 1;
fuzzy
}
ObjectType::Outlier => {
let mut fuzzy = vec![0.0; cso_count + 1];
fuzzy[cso_count] = 1.0;
fuzzy
}
_ => {
let frac = ((cso_count + 1) as f64).recip();
vec![frac; cso_count + 1]
}
})
.collect::<Vec<Vec<f64>>>();
let fuzzyships2 = fuzzyships.clone();
ClusterSupportingObjects {
neighbors,
weights,
cso_count,
obtypes,
fuzzyships,
fuzzyships2,
}
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct ClusterSupportingObjects {
neighbors: Vec<Vec<usize>>,
weights: Vec<Vec<f64>>,
cso_count: usize,
obtypes: Vec<ObjectType>,
fuzzyships: Vec<Vec<f64>>,
#[derivative(Debug = "ignore")]
fuzzyships2: Vec<Vec<f64>>,
}
impl ClusterSupportingObjects {
pub fn count(&self) -> usize {
self.cso_count
}
pub fn object_type(&self, id: usize) -> ObjectType {
self.obtypes[id]
}
pub fn approximate_fuzzy_memberships(mut self, steps: usize, epsilon: f64) -> Self {
for _ in 0..steps {
std::mem::swap(&mut self.fuzzyships, &mut self.fuzzyships2);
if self.step(ObjectType::Normal) < epsilon {
break;
}
}
self.step(ObjectType::Support);
self
}
pub fn assign_outliers(mut self) -> Self {
self.step(ObjectType::Outlier);
self
}
fn step(&mut self, obtype: ObjectType) -> f64 {
let fuzzy2 = &self.fuzzyships2;
let mut dev = 0.0;
for (i, fuzzy) in self.fuzzyships.iter_mut().enumerate() {
if self.obtypes[i] != obtype {
continue;
}
let ids = &self.neighbors[i];
let wt = &self.weights[i];
let mut sum = 0.0;
for (j, fv) in fuzzy.iter_mut().enumerate() {
let value = ids
.iter()
.enumerate()
.map(|(k, &id)| wt[k] * fuzzy2[id][j])
.sum();
*fv = value;
sum += value;
let d = value - fuzzy2[i][j];
dev += d * d;
}
for value in fuzzy.iter_mut() {
*value /= sum;
}
}
dev
}
pub fn make_clusters(&self, thd: f64) -> (Vec<Vec<usize>>, Vec<usize>) {
let mut vals = self
.fuzzyships
.iter()
.enumerate()
.map(|(index, fuzzy)| IndexFloat {
index,
value: fuzzy.iter().fold(0.0, |value, &fs| {
if fs > EPSILON {
value - fs * fs.ln()
} else {
value
}
}),
})
.collect::<Vec<IndexFloat>>();
vals.sort_unstable_by(|a, b| a.value.partial_cmp(&b.value).unwrap());
let mut clusters = Vec::with_capacity(self.cso_count + 1);
for _ in 0..=self.cso_count {
clusters.push(Vec::new());
}
if !(0.0..=1.0).contains(&thd) {
for id in vals.iter().map(|x| x.index) {
let (imax, _) = self.fuzzyships[id]
.iter()
.enumerate()
.max_by(|(_, &a), (_, &b)| a.partial_cmp(&b).unwrap())
.unwrap();
clusters[imax].push(id);
}
} else {
for id in vals.iter().map(|x| x.index) {
let mut assigned = false;
for (j, cluster) in clusters.iter_mut().enumerate() {
if self.fuzzyships[id][j] > thd {
cluster.push(id);
assigned = true;
}
}
if !assigned {
clusters[self.cso_count].push(id);
}
}
}
let outliers = clusters.pop().unwrap();
clusters.retain(|v| !v.is_empty());
(clusters, outliers)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn partial_sort_in_order() {
let mut v = vec![
IndexFloat {
index: 0,
value: 0.0,
},
IndexFloat {
index: 1,
value: 1.0,
},
IndexFloat {
index: 2,
value: 2.0,
},
IndexFloat {
index: 3,
value: 3.0,
},
IndexFloat {
index: 4,
value: 4.0,
},
];
let last = v.len() - 1;
partial_quicksort(&mut v, 0, last, 2);
for i in 0..2 {
assert!(
v[i].value < v[i + 1].value,
"Expected v[{}] ({}) to be less than v[{}] ({})",
i,
v[i].value,
i + 1,
v[i + 1].value
);
}
}
#[test]
pub fn partial_sort_in_reverse() {
let mut v = vec![
IndexFloat {
index: 0,
value: 4.0,
},
IndexFloat {
index: 1,
value: 3.0,
},
IndexFloat {
index: 2,
value: 2.0,
},
IndexFloat {
index: 3,
value: 1.0,
},
IndexFloat {
index: 4,
value: 0.0,
},
];
let last = v.len() - 1;
partial_quicksort(&mut v, 0, last, 2);
for i in 0..2 {
assert!(
v[i].value < v[i + 1].value,
"Expected v[{}] ({}) to be less than v[{}] ({})",
i,
v[i].value,
i + 1,
v[i + 1].value
);
}
}
#[test]
fn zero_objects() {
let data: Vec<f64> = vec![];
let (clusters, outliers) = DistanceGraph::build(&data, |a, b| (a - b).abs())
.find_supporting_objects(3, -1.0)
.approximate_fuzzy_memberships(2, 1e-6)
.make_clusters(-1.0);
assert!(clusters.is_empty());
assert!(outliers.is_empty());
}
#[test]
fn one_object() {
let data: Vec<f64> = vec![0.0];
let (clusters, outliers) = DistanceGraph::build(&data, |a, b| (a - b).abs())
.find_supporting_objects(3, -1.0)
.approximate_fuzzy_memberships(2, 1e-6)
.make_clusters(-1.0);
assert_eq!(clusters.as_slice(), &[[0]]);
assert!(outliers.is_empty());
}
}