Skip to main content

abd_clam/core/
tree.rs

1//! A `Tree` represents a hierarchy of "similar" instances from a metric-`Space`.
2
3use core::marker::PhantomData;
4
5use std::path::Path;
6
7use distances::Number;
8
9use crate::{Cluster, Dataset, Instance, PartitionCriterion};
10
11/// A `Tree` represents a hierarchy of `Cluster`s, i.e. "similar" instances
12/// from a metric-`Space`.
13///
14/// The `Tree` has other implementation blocks spread across the crate. These
15/// are used for specific functionality for the concrete `Cluster` types we provide.
16///
17/// # Type Parameters
18///
19/// - `T`: The type of the instances in the `Tree`.
20/// - `U`: The type of the distance values between instances.
21/// - `D`: The type of the `Dataset` from which the `Tree` is built.
22#[derive(Debug)]
23pub struct Tree<I: Instance, U: Number, D: Dataset<I, U>, C: Cluster<U>> {
24    /// The dataset from which the tree is built.
25    pub(crate) data: D,
26    /// The root `Cluster` of the tree.
27    pub(crate) root: C,
28    /// The depth of the tree.
29    pub(crate) depth: usize,
30    /// To satisfy the `Instance` trait bound.
31    _i: PhantomData<I>,
32    /// To satisfy the `Number` trait bound.
33    _u: PhantomData<U>,
34}
35
36impl<I: Instance, U: Number, D: Dataset<I, U>, C: Cluster<U>> Tree<I, U, D, C> {
37    /// Constructs a new `Tree` for a given dataset. Importantly, this does not
38    /// partition the tree.
39    ///
40    /// # Arguments
41    /// dataset: The dataset from which the tree will be built
42    pub fn new(data: D, seed: Option<u64>) -> Self {
43        let root = C::new_root(&data, seed);
44        let depth = root.max_leaf_depth();
45        Self {
46            data,
47            root,
48            depth,
49            _i: PhantomData,
50            _u: PhantomData,
51        }
52    }
53
54    /// Recursively partitions the root `Cluster` using the given criteria.
55    ///
56    /// # Arguments
57    ///
58    /// * `criteria`: the criteria used to decide when to partition a `Cluster`.
59    ///
60    /// # Returns
61    ///
62    /// The `Tree` after partitioning.
63    #[must_use]
64    pub fn partition<P: PartitionCriterion<U>>(mut self, criteria: &P, seed: Option<u64>) -> Self {
65        self.root = self.root.partition(&mut self.data, criteria, seed);
66        self.depth = self.root.max_leaf_depth();
67        self
68    }
69
70    /// Returns the `Cluster` with the given `offset` and `cardinality`.
71    ///
72    /// # Arguments
73    ///
74    /// * `offset`: The offset of the `Cluster` to return.
75    /// * `cardinality`: The cardinality of the `Cluster` to return.
76    ///
77    /// # Returns
78    ///
79    /// The `Cluster` with the given `offset` and `cardinality` if it exists.
80    /// Otherwise, `None`.
81    pub fn get_cluster(&self, offset: usize, cardinality: usize) -> Option<&C> {
82        self.root.descend_to(offset, cardinality)
83    }
84
85    /// Returns a reference to the data used to build the `Tree`.
86    pub const fn data(&self) -> &D {
87        &self.data
88    }
89
90    /// The cardinality of the `Tree`, i.e. the number of instances in the data.
91    pub fn cardinality(&self) -> usize {
92        self.root.cardinality()
93    }
94
95    /// The radius of the root of the `Tree`.
96    pub fn radius(&self) -> U {
97        self.root.radius()
98    }
99
100    /// The root `Cluster` of the `Tree`.
101    pub const fn root(&self) -> &C {
102        &self.root
103    }
104
105    /// The depth of the `Tree`.
106    pub const fn depth(&self) -> usize {
107        self.depth
108    }
109
110    /// Saves a tree to a given location
111    ///
112    /// The path given will point to a newly created folder which will
113    /// store all necessary data for tree reconstruction.
114    ///
115    /// The directory structure looks like the following:
116    ///
117    /// ```text
118    /// /user/given/path/
119    ///    |- dataset      <-- The serialized dataset.
120    ///    |- clusters     <-- Clusters are serialized to a single file.
121    /// ```
122    ///
123    /// # Arguments
124    ///
125    /// * `path` - The path to save the tree to.
126    ///
127    /// # Errors
128    ///
129    /// * If `path` does not exist.
130    /// * If `path` cannot be written to.
131    /// * If there are any serialization errors with the dataset.
132    pub fn save(&self, path: &Path) -> Result<(), String> {
133        if !path.exists() {
134            return Err("Given path does not exist".to_string());
135        }
136
137        let dataset_path = path.join("dataset");
138        self.data.save(&dataset_path)?;
139
140        let cluster_path = path.join("clusters");
141        self.root.save(&cluster_path)?;
142
143        Ok(())
144    }
145
146    /// Reconstructs a `Tree` from a directory `path` with associated metric `metric`. Returns the
147    /// reconstructed tree.
148    ///
149    /// # Arguments
150    ///
151    /// * `path` - The path to load the tree from.
152    /// * `metric` - The metric to use for the tree.
153    /// * `is_expensive` - Whether or not the metric is expensive to compute.
154    ///
155    /// # Returns
156    ///
157    /// The reconstructed tree.
158    ///
159    /// # Errors
160    ///
161    /// * If `path` does not exist.
162    /// * If `path` does not contain a valid tree. See `save` for more information
163    /// on the directory structure.
164    /// * If the `path` cannot be read from.
165    /// * If there are any deserialization errors with the dataset.
166    /// * If there are any deserialization errors with the clusters.
167    pub fn load(path: &Path, metric: fn(&I, &I) -> U, is_expensive: bool) -> Result<Self, String> {
168        if !path.exists() {
169            return Err("Given path does not exist".to_string());
170        }
171
172        // Aliases to relevant paths
173        let cluster_path = path.join("clusters");
174        let dataset_path = path.join("dataset");
175
176        if !(cluster_path.exists() && dataset_path.exists()) {
177            return Err("Saved tree is malformed".to_string());
178        }
179
180        let data = D::load(&dataset_path, metric, is_expensive)?;
181        let root = C::load(&cluster_path)?;
182
183        Ok(Self {
184            data,
185            depth: root.max_leaf_depth(),
186            root,
187            _i: PhantomData,
188            _u: PhantomData,
189        })
190    }
191}