use core::marker::PhantomData;
use std::path::Path;
use distances::Number;
use crate::{utils, Cluster, Dataset, Instance, PartitionCriteria};
#[derive(Debug)]
pub struct Tree<I: Instance, U: Number, D: Dataset<I, U>> {
pub(crate) data: D,
pub(crate) root: Cluster<U>,
pub(crate) depth: usize,
_i: PhantomData<I>,
}
impl<I: Instance, U: Number, D: Dataset<I, U>> Tree<I, U, D> {
pub fn new(data: D, seed: Option<u64>) -> Self {
let root = Cluster::new_root(&data, seed);
let depth = root.max_leaf_depth();
Self {
data,
root,
depth,
_i: PhantomData,
}
}
#[must_use]
pub fn partition(mut self, criteria: &PartitionCriteria<U>) -> Self {
self.root = self.root.partition(&mut self.data, criteria);
self.depth = self.root.max_leaf_depth();
self
}
#[must_use]
pub fn with_ratios(mut self, normalize: bool) -> Self {
self.root = self.root.set_child_parent_ratios([1.0; 6]);
if normalize {
let all_ratios = self
.root
.subtree()
.iter()
.map(|c| {
c.ratios()
.unwrap_or_else(|| unreachable!("We just set the ratios above."))
})
.collect::<Vec<_>>();
let all_ratios = utils::rows_to_cols(&all_ratios);
let means: [f64; 6] = utils::calc_row_means(&all_ratios);
let sds: [f64; 6] = utils::calc_row_sds(&all_ratios);
self.root.set_normalized_ratios(means, sds);
}
self
}
pub fn get_cluster(&self, offset: usize, cardinality: usize) -> Option<&Cluster<U>> {
self.root.descend_to(offset, cardinality)
}
pub const fn data(&self) -> &D {
&self.data
}
pub const fn cardinality(&self) -> usize {
self.root.cardinality()
}
pub const fn radius(&self) -> U {
self.root.radius()
}
pub const fn root(&self) -> &Cluster<U> {
&self.root
}
pub const fn depth(&self) -> usize {
self.depth
}
pub fn save(&self, path: &Path) -> Result<(), String> {
if !path.exists() {
return Err("Given path does not exist".to_string());
}
let dataset_path = path.join("dataset");
self.data.save(&dataset_path)?;
let cluster_path = path.join("clusters");
self.root.save(&cluster_path)?;
Ok(())
}
pub fn load(path: &Path, metric: fn(&I, &I) -> U, is_expensive: bool) -> Result<Self, String> {
if !path.exists() {
return Err("Given path does not exist".to_string());
}
let cluster_path = path.join("clusters");
let dataset_path = path.join("dataset");
if !(cluster_path.exists() && dataset_path.exists()) {
return Err("Saved tree is malformed".to_string());
}
let data = D::load(&dataset_path, metric, is_expensive)?;
let root = Cluster::<U>::load(&cluster_path)?;
Ok(Self {
data,
depth: root.max_leaf_depth(),
root,
_i: PhantomData,
})
}
}