use std::hash::Hash;
use std::hash::Hasher;
use bitvec::prelude::*;
use super::PartitionCriteria;
use crate::dataset::Dataset;
use crate::number::Number;
use crate::utils::helpers;
pub type Ratios = [f64; 6];
#[derive(Debug)]
pub(crate) struct Cluster<T: Number, U: Number, D: Dataset<T, U>> {
_t: std::marker::PhantomData<T>,
_d: std::marker::PhantomData<D>,
cardinality: usize,
history: BitVec,
arg_center: usize,
arg_radius: usize,
radius: U,
#[allow(dead_code)]
lfd: f64,
#[allow(dead_code)]
ratios: Option<Ratios>,
seed: Option<u64>,
#[allow(clippy::type_complexity)]
children: Option<([(usize, Box<Cluster<T, U, D>>); 2], U)>,
index: Index,
}
#[derive(Debug)]
enum Index {
Indices(Vec<usize>),
Offset(usize),
Empty,
}
impl<T: Number, U: Number, D: Dataset<T, U>> PartialEq for Cluster<T, U, D> {
fn eq(&self, other: &Self) -> bool {
self.history == other.history
}
}
impl<T: Number, U: Number, D: Dataset<T, U>> Eq for Cluster<T, U, D> {}
impl<T: Number, U: Number, D: Dataset<T, U>> PartialOrd for Cluster<T, U, D> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.depth() == other.depth() {
self.history.partial_cmp(&other.history)
} else {
self.depth().partial_cmp(&other.depth())
}
}
}
impl<T: Number, U: Number, D: Dataset<T, U>> Ord for Cluster<T, U, D> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
impl<T: Number, U: Number, D: Dataset<T, U>> Hash for Cluster<T, U, D> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
}
impl<T: Number, U: Number, D: Dataset<T, U>> std::fmt::Display for Cluster<T, U, D> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl<T: Number, U: Number, D: Dataset<T, U>> Cluster<T, U, D> {
pub fn new_root(data: &D, indices: &[usize], seed: Option<u64>) -> Self {
let name = bitvec![1];
Cluster::new(data, indices, name, seed)
}
pub fn new(data: &D, indices: &[usize], history: BitVec, seed: Option<u64>) -> Self {
let cardinality = indices.len();
let arg_samples = if cardinality < 100 {
indices.to_vec()
} else {
let n = ((indices.len() as f64).sqrt()) as usize;
data.choose_unique(n, indices, seed)
};
let arg_center = data.median(&arg_samples);
let center_distances = data.one_to_many(arg_center, indices);
let (arg_radius, radius) = helpers::arg_max(¢er_distances);
let arg_radius = indices[arg_radius];
Cluster {
_t: Default::default(),
_d: Default::default(),
cardinality,
history,
arg_center,
arg_radius,
radius,
lfd: helpers::compute_lfd(radius, ¢er_distances),
ratios: None,
seed,
children: None,
index: Index::Indices(indices.to_vec()),
}
}
fn partition_once(&self, data: &D) -> ([(usize, Vec<usize>, BitVec); 2], U) {
let indices = match &self.index {
Index::Indices(indices) => indices,
_ => panic!("`build` can only be called once per cluster."),
};
let left_pole = self.arg_radius();
let left_distances = data.one_to_many(left_pole, indices);
let (arg_right, polar_distance) = helpers::arg_max(&left_distances);
let right_pole = indices[arg_right];
let right_distances = data.one_to_many(right_pole, indices);
let (left, right) = indices
.iter()
.zip(left_distances.into_iter())
.zip(right_distances.into_iter())
.filter(|&((&i, _), _)| i != left_pole && i != right_pole)
.partition::<Vec<_>, _>(|&((_, l), r)| l <= r);
let left_indices = left
.into_iter()
.map(|((&i, _), _)| i)
.chain([left_pole].into_iter())
.collect::<Vec<_>>();
let right_indices = right
.into_iter()
.map(|((&i, _), _)| i)
.chain([right_pole].into_iter())
.collect::<Vec<_>>();
let (left_pole, left_indices, right_pole, right_indices) = if left_indices.len() < right_indices.len() {
(right_pole, right_indices, left_pole, left_indices)
} else {
(left_pole, left_indices, right_pole, right_indices)
};
let left_name = {
let mut name = self.history.clone();
name.push(false);
name
};
let right_name = {
let mut name = self.history.clone();
name.push(true);
name
};
(
[
(left_pole, left_indices, left_name),
(right_pole, right_indices, right_name),
],
polar_distance,
)
}
pub fn partition(mut self, data: &D, criteria: &PartitionCriteria<T, U, D>, recursive: bool) -> Self {
if criteria.check(&self) {
let ([(left_pole, left_indices, left_name), (right_pole, right_indices, right_name)], polar_distance) =
self.partition_once(data);
let (left, right) = (
Cluster::new(data, &left_indices, left_name, self.seed),
Cluster::new(data, &right_indices, right_name, self.seed),
);
let (left, right) = if recursive {
(
left.partition(data, criteria, recursive),
right.partition(data, criteria, recursive),
)
} else {
(left, right)
};
self.children = Some((
[(left_pole, Box::new(left)), (right_pole, Box::new(right))],
polar_distance,
));
self.index = Index::Empty;
}
self
}
pub fn par_partition(mut self, data: &D, criteria: &PartitionCriteria<T, U, D>, recursive: bool) -> Self {
if criteria.check(&self) {
let ([(left_pole, left_indices, left_name), (right_pole, right_indices, right_name)], polar_distance) =
self.partition_once(data);
let (left, right) = rayon::join(
|| Cluster::new(data, &left_indices, left_name, self.seed),
|| Cluster::new(data, &right_indices, right_name, self.seed),
);
let (left, right) = if recursive {
rayon::join(
|| left.par_partition(data, criteria, recursive),
|| right.par_partition(data, criteria, recursive),
)
} else {
(left, right)
};
self.children = Some((
[(left_pole, Box::new(left)), (right_pole, Box::new(right))],
polar_distance,
));
self.index = Index::Empty;
}
self
}
#[allow(unused_mut, unused_variables, dead_code)]
pub fn with_ratios(mut self, normalized: bool) -> Self {
todo!()
}
#[inline(always)]
#[allow(dead_code)]
fn next_ema(&self, ratio: f64, parent_ema: f64) -> f64 {
let alpha = 2. / 11.;
alpha * ratio + (1. - alpha) * parent_ema
}
#[allow(unused_mut, unused_variables, dead_code)]
fn set_child_parent_ratios(mut self, parent_ratios: Ratios) -> Self {
todo!()
}
#[allow(unused_mut, unused_variables, dead_code)]
fn set_normalized_ratios(&mut self, means: Ratios, sds: Ratios) {
todo!()
}
pub fn cardinality(&self) -> usize {
self.cardinality
}
pub fn indices<'a>(&'a self, data: &'a D) -> &[usize] {
match &self.index {
Index::Indices(indices) => indices,
Index::Offset(o) => {
let start = *o;
&data.indices()[start..start + self.cardinality]
}
Index::Empty => panic!("Cannot call indices from parent clusters"),
}
}
pub fn leaf_indices(&self) -> Vec<usize> {
match &self.index {
Index::Empty => match &self.children {
Some(([(_, left), (_, right)], _)) => left
.leaf_indices()
.iter()
.chain(right.leaf_indices().iter())
.copied()
.collect(),
None => panic!("Structural invariant invalidated. Node with no contents and no children"),
},
Index::Indices(indices) => indices.clone(),
Index::Offset(_) => {
panic!("Cannot get leaf indices once tree has been reordered!");
}
}
}
#[allow(dead_code)]
pub fn history(&self) -> Vec<bool> {
self.history.iter().map(|v| *v).collect()
}
pub fn name(&self) -> String {
let d = self.history.len();
let padding = if d % 4 == 0 { 0 } else { 4 - d % 4 };
let bin_name = (0..padding)
.map(|_| "0")
.chain(self.history.iter().map(|b| if *b { "1" } else { "0" }))
.collect::<Vec<_>>();
bin_name
.chunks_exact(4)
.map(|s| {
let [a, b, c, d] = [s[0], s[1], s[2], s[3]];
let s = format!("{a}{b}{c}{d}");
let s = u8::from_str_radix(&s, 2).unwrap();
format!("{s:01x}")
})
.collect::<Vec<_>>()
.join("")
}
#[allow(dead_code)]
pub fn is_root(&self) -> bool {
self.depth() == 0
}
pub fn depth(&self) -> usize {
self.history.len() - 1
}
pub fn arg_center(&self) -> usize {
self.arg_center
}
pub fn arg_radius(&self) -> usize {
self.arg_radius
}
pub fn radius(&self) -> U {
self.radius
}
pub fn is_singleton(&self) -> bool {
self.radius() == U::zero()
}
#[allow(dead_code)]
pub fn lfd(&self) -> f64 {
self.lfd
}
#[allow(dead_code)]
pub fn polar_distance(&self) -> Option<U> {
self.children.as_ref().map(|(_, lr)| *lr)
}
#[allow(dead_code)]
pub fn ratios(&self) -> Ratios {
self.ratios
.expect("Please call `with_ratios` before using this method.")
}
pub fn children(&self) -> Option<[&Self; 2]> {
self.children
.as_ref()
.map(|([(_, left), (_, right)], _)| [left.as_ref(), right.as_ref()])
}
pub fn is_leaf(&self) -> bool {
matches!(&self.index, Index::Indices(_))
}
#[allow(dead_code)]
pub fn is_ancestor_of(&self, other: &Self) -> bool {
self.depth() < other.depth() && self.history.iter().zip(other.history.iter()).all(|(l, r)| *l == *r)
}
#[allow(dead_code)]
pub fn is_descendant_of(&self, other: &Self) -> bool {
other.is_ancestor_of(self)
}
pub fn subtree(&self) -> Vec<&Self> {
let subtree = vec![self];
match &self.children {
Some(([(_, left), (_, right)], _)) => subtree
.into_iter()
.chain(left.subtree().into_iter())
.chain(right.subtree().into_iter())
.collect(),
None => subtree,
}
}
#[allow(dead_code)]
pub fn num_descendants(&self) -> usize {
self.subtree().len() - 1
}
pub fn max_leaf_depth(&self) -> usize {
self.subtree().into_iter().map(|c| c.depth()).max().unwrap()
}
#[allow(dead_code)]
pub fn distance_to_indexed_instance(&self, data: &D, index: usize) -> U {
data.one_to_one(index, self.arg_center())
}
pub fn distance_to_instance(&self, data: &D, instance: &[T]) -> U {
data.query_to_one(instance, self.arg_center())
}
#[allow(dead_code)]
pub fn distance_to_other(&self, data: &D, other: &Self) -> U {
self.distance_to_indexed_instance(data, other.arg_center())
}
pub fn overlapping_children(&self, data: &D, query: &[T], radius: U) -> Vec<&Self> {
let (l, left, r, right, lr) = match &self.children {
None => panic!("Can only be called on non-leaf clusters."),
Some(([(l, left), (r, right)], lr)) => (*l, left.as_ref(), *r, right.as_ref(), *lr),
};
let ql = data.query_to_one(query, l);
let qr = data.query_to_one(query, r);
let swap = ql < qr;
let (ql, qr) = if swap { (qr, ql) } else { (ql, qr) };
if (ql + qr) * (ql - qr) <= U::from(2).unwrap() * lr * radius {
vec![left, right]
} else if swap {
vec![left]
} else {
vec![right]
}
}
#[allow(dead_code)]
pub fn depth_first_reorder(&mut self, data: &D) {
if self.depth() != 0 {
panic!("Cannot call this method except from the root.")
}
self.dfr(data, 0);
}
pub fn dfr(&mut self, data: &D, offset: usize) {
self.index = Index::Offset(offset);
self.arg_center = data.get_reordered_index(self.arg_center);
self.arg_radius = data.get_reordered_index(self.arg_radius);
if let Some(([(_, left), (_, right)], _)) = self.children.as_mut() {
left.dfr(data, offset);
right.dfr(data, offset + left.cardinality);
}
}
}
#[cfg(test)]
mod tests {
use crate::cluster::Tree;
use crate::dataset::{Dataset, VecVec};
use crate::distances;
use super::*;
#[test]
fn test_cluster() {
let data = vec![vec![0., 0., 0.], vec![1., 1., 1.], vec![2., 2., 2.], vec![3., 3., 3.]];
let metric = distances::f32::euclidean;
let name = "test".to_string();
let data = VecVec::new(data, metric, name, false);
let indices = data.indices().to_vec();
let partition_criteria = PartitionCriteria::new(true).with_max_depth(3).with_min_cardinality(1);
let cluster = Cluster::new_root(&data, &indices, Some(42)).partition(&data, &partition_criteria, true);
assert_eq!(cluster.depth(), 0);
assert_eq!(cluster.cardinality(), 4);
assert_eq!(cluster.num_descendants(), 6);
assert!(cluster.radius() > 0.);
assert_eq!(format!("{cluster}"), "1");
let [left, right] = cluster.children().unwrap();
assert_eq!(format!("{left}"), "2");
assert_eq!(format!("{right}"), "3");
for child in [left, right] {
assert_eq!(child.depth(), 1);
assert_eq!(child.cardinality(), 2);
assert_eq!(child.num_descendants(), 2);
}
}
#[test]
fn test_leaf_indices() {
let data = vec![
vec![10.],
vec![1.],
vec![-5.],
vec![8.],
vec![3.],
vec![2.],
vec![0.5],
vec![0.],
];
let metric = distances::f32::euclidean;
let name = "test".to_string();
let data = VecVec::new(data, metric, name, false);
let partition_criteria = PartitionCriteria::new(true).with_max_depth(3).with_min_cardinality(1);
let tree = Tree::new(data, Some(42)).partition(&partition_criteria, true);
let mut leaf_indices = tree.root().leaf_indices();
leaf_indices.sort();
assert_eq!(leaf_indices, tree.data().indices());
}
mod reordering {
use super::*;
#[test]
fn test_end_to_end_reordering() {
let data = vec![
vec![10.],
vec![1.],
vec![-5.],
vec![8.],
vec![3.],
vec![2.],
vec![0.5],
vec![0.],
];
let metric = distances::f32::euclidean;
let name = "test".to_string();
let data = VecVec::new(data, metric, name, false);
let partition_criteria = PartitionCriteria::new(true).with_max_depth(3).with_min_cardinality(1);
let tree = Tree::new(data, Some(42))
.partition(&partition_criteria, true)
.depth_first_reorder();
assert_eq!(tree.data().cardinality(), tree.indices().len());
assert_eq!((0..tree.cardinality()).collect::<Vec<usize>>(), tree.indices());
}
#[test]
fn test_tree_transformation_before_after_reordering() {
let data = vec![
vec![10.],
vec![1.],
vec![-5.],
vec![8.],
vec![3.],
vec![2.],
vec![0.5],
vec![0.],
];
let metric = distances::f32::euclidean;
let name = "test".to_string();
let data = VecVec::new(data, metric, name, false);
let partition_criteria = PartitionCriteria::new(true).with_max_depth(3).with_min_cardinality(1);
let tree = Tree::new(data, Some(42)).partition(&partition_criteria, true);
assert!(matches!(tree.root().index, Index::Empty));
let tree = tree.depth_first_reorder();
assert!(matches!(tree.root().index, Index::Offset(0)));
assert_eq!((0..tree.cardinality()).collect::<Vec<usize>>(), tree.indices());
}
}
}