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}