use core::marker::PhantomData;
use std::{
fs::{create_dir, File},
io::{Read, Write},
path::Path,
};
use super::{
SerializedCluster,
_cluster::{Children, SerializedChildren},
};
use crate::{utils, Cluster, Dataset, Instance, PartitionCriteria};
use distances::Number;
use serde::{Deserialize, Serialize};
#[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 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 cluster_path = path.join("clusters");
create_dir(&cluster_path).map_err(|e| e.to_string())?;
let childinfo_path = path.join("childinfo");
create_dir(&childinfo_path).map_err(|e| e.to_string())?;
let dataset_dir = path.join("dataset");
create_dir(&dataset_dir).map_err(|e| e.to_string())?;
let mut leaves: Vec<String> = vec![];
let mut stack = vec![&self.root];
while let Some(cur) = stack.pop() {
let filename: String = cur.name();
match cur.children() {
Some([left, right]) => {
stack.push(left);
stack.push(right);
}
None => {
leaves.push(filename.clone());
}
}
let (serialized, children) = SerializedCluster::from_cluster(cur);
let node_path = cluster_path.join(&filename);
serialize_to_file(&node_path, &serialized)?;
if let Some(childinfo) = children {
let info_path = childinfo_path.join(&filename);
serialize_to_file(&info_path, &childinfo)?;
}
}
let saved_dataset_path = dataset_dir.join("data");
self.data.save(&saved_dataset_path)?;
let leaf_data_path = path.join("leaves.json");
serialize_to_file(&leaf_data_path, &leaves)?;
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 childinfo_path = path.join("childinfo");
let dataset_dir = path.join("dataset").join("data");
if !(cluster_path.exists() && childinfo_path.exists() && dataset_dir.exists()) {
return Err("Save directory is malformed".to_string());
}
let dataset = D::load(&dataset_dir, metric, is_expensive)?;
let root = recover_serialized_cluster(&cluster_path.join("1"))?;
let leaf_names: Vec<String> = deserialize_from_file(&path.join("leaves.json"), &mut Vec::new())?;
let mut boxed_root = Box::new(root);
for leaf in leaf_names {
let mut cur = &mut boxed_root;
let leaf_history = Cluster::<U>::name_to_history(&leaf);
for step in 1..leaf_history.len() {
let go_right = leaf_history[step];
if cur.children.is_none() {
let mut left_history = leaf_history[0..step].to_vec();
left_history.push(false);
let mut right_history = leaf_history[0..step].to_vec();
right_history.push(true);
let left_name = Cluster::<U>::history_to_name(&left_history);
let right_name = Cluster::<U>::history_to_name(&right_history);
let left: Cluster<U> = recover_serialized_cluster(&cluster_path.join(left_name))?;
let right: Cluster<U> = recover_serialized_cluster(&cluster_path.join(right_name))?;
let parent_name = Cluster::<U>::history_to_name(&leaf_history[0..step]);
let childinfo = recover_serialized_childinfo(&childinfo_path.join(&parent_name))?;
cur.children = Some(Children {
left: Box::new(left),
right: Box::new(right),
arg_l: childinfo.arg_l,
arg_r: childinfo.arg_r,
polar_distance: <U as Number>::from_le_bytes(&childinfo.polar_distance_bytes),
});
}
let children = cur
.children
.as_mut()
.unwrap_or_else(|| unreachable!("We have already checked if `children` is None."));
if go_right {
cur = &mut children.right;
} else {
cur = &mut children.left;
}
}
}
let root = *boxed_root;
Ok(Self {
data: dataset,
depth: root.max_leaf_depth(),
root,
_i: PhantomData,
})
}
}
fn serialize_to_file<S: Serialize>(path: &Path, object: &S) -> Result<(), String> {
let mut file = File::create(path).map_err(|e| e.to_string())?;
let object = postcard::to_allocvec(object).map_err(|e| e.to_string())?;
file.write_all(&object).map_err(|e| e.to_string())
}
fn deserialize_from_file<'a, D: Deserialize<'a>>(path: &Path, buffer: &'a mut Vec<u8>) -> Result<D, String> {
let mut handle = File::open(path).map_err(|e| e.to_string())?;
handle.read_to_end(buffer).map_err(|e| e.to_string())?;
postcard::from_bytes(buffer).map_err(|e| e.to_string())
}
fn recover_serialized_cluster<U: Number>(path: &Path) -> Result<Cluster<U>, String> {
let cluster: SerializedCluster = deserialize_from_file(path, &mut Vec::new())?;
Ok(cluster.into_partial_cluster())
}
fn recover_serialized_childinfo(path: &Path) -> Result<SerializedChildren, String> {
deserialize_from_file(path, &mut Vec::new())
}