ascii_dag/layout/generic/
metrics.rs1use alloc::vec::Vec;
28use core::hash::Hash;
29
30use super::impact::compute_ancestors_fn;
31use super::impact::compute_descendants_fn;
32use crate::cycles::generic::roots::find_roots_fn;
33
34#[derive(Debug, Clone)]
56pub struct GraphMetrics {
57 node_count: usize,
58 edge_count: usize,
59 root_count: usize,
60 leaf_count: usize,
61 max_depth: usize,
62 max_descendants: usize,
63 total_dependencies: usize,
64}
65
66impl GraphMetrics {
67 pub fn compute<Id, F>(items: &[Id], get_dependencies: F) -> Self
86 where
87 Id: Clone + Eq + Hash,
88 F: Fn(&Id) -> Vec<Id> + Clone,
89 {
90 let node_count = items.len();
91
92 let mut edge_count = 0;
94 let mut total_dependencies = 0;
95 for item in items {
96 let deps = get_dependencies(item);
97 let dep_count = deps.len();
98 edge_count += dep_count;
99 total_dependencies += dep_count;
100 }
101
102 let roots = find_roots_fn(items, get_dependencies.clone());
104 let root_count = roots.len();
105
106 let mut leaf_count = 0;
107 for candidate in items {
108 let mut is_leaf = true;
109 for item in items {
110 if get_dependencies(item).contains(candidate) {
111 is_leaf = false;
112 break;
113 }
114 }
115 if is_leaf {
116 leaf_count += 1;
117 }
118 }
119
120 let mut max_depth = 0;
122 for item in items {
123 let ancestors = compute_ancestors_fn(items, item, get_dependencies.clone());
124 max_depth = max_depth.max(ancestors.len());
125 }
126
127 let mut max_descendants = 0;
129 for item in items {
130 let descendants = compute_descendants_fn(items, item, get_dependencies.clone());
131 max_descendants = max_descendants.max(descendants.len());
132 }
133
134 Self {
135 node_count,
136 edge_count,
137 root_count,
138 leaf_count,
139 max_depth,
140 max_descendants,
141 total_dependencies,
142 }
143 }
144
145 pub fn node_count(&self) -> usize {
147 self.node_count
148 }
149
150 pub fn edge_count(&self) -> usize {
152 self.edge_count
153 }
154
155 pub fn root_count(&self) -> usize {
157 self.root_count
158 }
159
160 pub fn leaf_count(&self) -> usize {
162 self.leaf_count
163 }
164
165 pub fn max_depth(&self) -> usize {
167 self.max_depth
168 }
169
170 pub fn max_descendants(&self) -> usize {
174 self.max_descendants
175 }
176
177 pub fn avg_dependencies(&self) -> f64 {
179 if self.node_count == 0 {
180 0.0
181 } else {
182 self.total_dependencies as f64 / self.node_count as f64
183 }
184 }
185
186 pub fn density(&self) -> f64 {
190 if self.node_count <= 1 {
191 0.0
192 } else {
193 let max_possible_edges = self.node_count * (self.node_count - 1);
194 self.edge_count as f64 / max_possible_edges as f64
195 }
196 }
197
198 pub fn is_tree(&self) -> bool {
200 self.root_count == 1 && self.edge_count == self.node_count.saturating_sub(1)
201 }
202
203 pub fn is_forest(&self) -> bool {
205 self.edge_count == self.node_count.saturating_sub(self.root_count)
206 }
207
208 pub fn is_sparse(&self) -> bool {
210 self.density() < 0.1
211 }
212
213 pub fn is_dense(&self) -> bool {
215 self.density() > 0.5
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_simple_chain() {
225 let get_deps = |&id: &usize| match id {
226 1 => vec![],
227 2 => vec![1],
228 3 => vec![2],
229 _ => vec![],
230 };
231
232 let items = [1, 2, 3];
233 let metrics = GraphMetrics::compute(&items, get_deps);
234
235 assert_eq!(metrics.node_count(), 3);
236 assert_eq!(metrics.edge_count(), 2);
237 assert_eq!(metrics.root_count(), 1);
238 assert_eq!(metrics.leaf_count(), 1);
239 assert_eq!(metrics.max_depth(), 2);
240 assert!(metrics.is_tree());
241 }
242
243 #[test]
244 fn test_diamond() {
245 let get_deps = |&id: &usize| match id {
246 1 => vec![],
247 2 => vec![1],
248 3 => vec![1],
249 4 => vec![2, 3],
250 _ => vec![],
251 };
252
253 let items = [1, 2, 3, 4];
254 let metrics = GraphMetrics::compute(&items, get_deps);
255
256 assert_eq!(metrics.node_count(), 4);
257 assert_eq!(metrics.edge_count(), 4);
258 assert_eq!(metrics.root_count(), 1);
259 assert_eq!(metrics.leaf_count(), 1);
260 assert!(metrics.max_depth() >= 2);
264 assert_eq!(metrics.max_descendants(), 3);
265 assert!(!metrics.is_tree()); }
267
268 #[test]
269 fn test_multiple_roots() {
270 let get_deps = |&id: &usize| match id {
271 1 => vec![],
272 2 => vec![],
273 3 => vec![1, 2],
274 _ => vec![],
275 };
276
277 let items = [1, 2, 3];
278 let metrics = GraphMetrics::compute(&items, get_deps);
279
280 assert_eq!(metrics.root_count(), 2);
281 assert_eq!(metrics.leaf_count(), 1);
282 assert!(!metrics.is_tree()); assert_eq!(metrics.edge_count(), 2);
285 }
286
287 #[test]
288 fn test_empty_graph() {
289 let get_deps = |_: &usize| vec![];
290 let items: [usize; 0] = [];
291 let metrics = GraphMetrics::compute(&items, get_deps);
292
293 assert_eq!(metrics.node_count(), 0);
294 assert_eq!(metrics.edge_count(), 0);
295 assert_eq!(metrics.avg_dependencies(), 0.0);
296 }
297
298 #[test]
299 fn test_isolated_nodes() {
300 let get_deps = |_: &usize| vec![];
301 let items = [1, 2, 3, 4, 5];
302 let metrics = GraphMetrics::compute(&items, get_deps);
303
304 assert_eq!(metrics.node_count(), 5);
305 assert_eq!(metrics.edge_count(), 0);
306 assert_eq!(metrics.root_count(), 5);
307 assert_eq!(metrics.leaf_count(), 5);
308 assert_eq!(metrics.density(), 0.0);
309 assert!(metrics.is_sparse());
310 }
311
312 #[test]
313 fn test_avg_dependencies() {
314 let get_deps = |&id: &usize| match id {
315 1 => vec![],
316 2 => vec![1],
317 3 => vec![1, 2],
318 _ => vec![],
319 };
320
321 let items = [1, 2, 3];
322 let metrics = GraphMetrics::compute(&items, get_deps);
323
324 assert_eq!(metrics.avg_dependencies(), 1.0);
326 }
327}