rust_igraph/algorithms/properties/
hierarchy_ratios.rs1#![allow(
13 clippy::cast_lossless,
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::similar_names,
19 clippy::too_many_lines
20)]
21
22use crate::core::{Graph, IgraphResult};
23
24pub fn degree_hierarchy(graph: &Graph) -> IgraphResult<f64> {
43 let n = graph.vcount() as usize;
44 if n < 2 {
45 return Ok(0.0);
46 }
47
48 let mut degrees = Vec::with_capacity(n);
49 let mut sum = 0_u64;
50 for v in 0..n {
51 let d = graph.degree(v as u32)?;
52 degrees.push(d);
53 sum += d as u64;
54 }
55
56 if sum == 0 {
57 return Ok(0.0);
58 }
59
60 degrees.sort_unstable();
61
62 let mut weighted_sum = 0_u64;
64 for (i, &d) in degrees.iter().enumerate() {
65 weighted_sum += (i as u64 + 1) * d as u64;
66 }
67
68 let gini = (2.0 * weighted_sum as f64) / (n as f64 * sum as f64) - (n as f64 + 1.0) / n as f64;
69 Ok(gini.clamp(0.0, 1.0))
70}
71
72pub fn layer_ratio(graph: &Graph) -> IgraphResult<f64> {
95 let n = graph.vcount() as usize;
96 if n < 2 {
97 return Ok(0.0);
98 }
99
100 let mut max_deg = 0_usize;
102 let mut hub = 0_usize;
103 for v in 0..n {
104 let d = graph.degree(v as u32)?;
105 if d > max_deg {
106 max_deg = d;
107 hub = v;
108 }
109 }
110
111 if max_deg == 0 {
112 return Ok(0.0);
113 }
114
115 let mut dist = vec![u32::MAX; n];
117 dist[hub] = 0;
118 let mut queue = std::collections::VecDeque::new();
119 queue.push_back(hub);
120 let mut visit_count = 1_usize;
121 let mut depth_sum = 0_u64;
122
123 while let Some(v) = queue.pop_front() {
124 let nbrs = graph.neighbors(v as u32)?;
125 for &u in &nbrs {
126 let ui = u as usize;
127 if dist[ui] == u32::MAX {
128 dist[ui] = dist[v] + 1;
129 depth_sum += dist[ui] as u64;
130 visit_count += 1;
131 queue.push_back(ui);
132 }
133 }
134 }
135
136 if visit_count < n {
137 return Ok(0.0);
138 }
139
140 let avg_depth = depth_sum as f64 / (n - 1) as f64;
141 let max_depth = (n - 1) as f64;
142
143 Ok(avg_depth / max_depth)
144}
145
146pub fn dominance_ratio(graph: &Graph) -> IgraphResult<f64> {
166 let n = graph.vcount() as usize;
167 if n < 2 {
168 return Ok(0.0);
169 }
170
171 let m = graph.ecount();
172 if m == 0 {
173 return Ok(0.0);
174 }
175
176 let mut nbr_sets: Vec<Vec<bool>> = Vec::with_capacity(n);
178 for v in 0..n {
179 let mut set = vec![false; n];
180 let nbrs = graph.neighbors(v as u32)?;
181 for &u in &nbrs {
182 set[u as usize] = true;
183 }
184 set[v] = true; nbr_sets.push(set);
186 }
187
188 let mut dominance_count = 0_u64;
189 let directed_pairs = (n * (n - 1)) as u64;
190
191 for u in 0..n {
192 for v in 0..n {
193 if u == v {
194 continue;
195 }
196 let mut dominates = true;
198 let nbrs_v = graph.neighbors(v as u32)?;
199 for &w in &nbrs_v {
200 if !nbr_sets[u][w as usize] {
201 dominates = false;
202 break;
203 }
204 }
205 if dominates {
206 dominance_count += 1;
207 }
208 }
209 }
210
211 Ok(dominance_count as f64 / directed_pairs as f64)
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 fn empty() -> Graph {
219 Graph::with_vertices(0)
220 }
221
222 fn single() -> Graph {
223 Graph::with_vertices(1)
224 }
225
226 fn single_edge() -> Graph {
227 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
228 }
229
230 fn path3() -> Graph {
231 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
232 }
233
234 fn path4() -> Graph {
235 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
236 }
237
238 fn k3() -> Graph {
239 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
240 }
241
242 fn k4() -> Graph {
243 Graph::from_edges(
244 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
245 false,
246 Some(4),
247 )
248 .unwrap()
249 }
250
251 fn cycle4() -> Graph {
252 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
253 }
254
255 fn star5() -> Graph {
256 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
257 }
258
259 fn paw() -> Graph {
260 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
261 }
262
263 #[test]
266 fn dh_empty() {
267 assert!(degree_hierarchy(&empty()).unwrap().abs() < 1e-10);
268 }
269
270 #[test]
271 fn dh_single() {
272 assert!(degree_hierarchy(&single()).unwrap().abs() < 1e-10);
273 }
274
275 #[test]
276 fn dh_k3() {
277 assert!(degree_hierarchy(&k3()).unwrap().abs() < 1e-10);
279 }
280
281 #[test]
282 fn dh_k4() {
283 assert!(degree_hierarchy(&k4()).unwrap().abs() < 1e-10);
284 }
285
286 #[test]
287 fn dh_cycle4() {
288 assert!(degree_hierarchy(&cycle4()).unwrap().abs() < 1e-10);
289 }
290
291 #[test]
292 fn dh_star5() {
293 let r = degree_hierarchy(&star5()).unwrap();
295 assert!(r > 0.1);
296 }
297
298 #[test]
299 fn dh_in_01() {
300 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
301 let r = degree_hierarchy(g).unwrap();
302 assert!(r >= -1e-10);
303 assert!(r <= 1.0 + 1e-10);
304 }
305 }
306
307 #[test]
310 fn lr_empty() {
311 assert!(layer_ratio(&empty()).unwrap().abs() < 1e-10);
312 }
313
314 #[test]
315 fn lr_single() {
316 assert!(layer_ratio(&single()).unwrap().abs() < 1e-10);
317 }
318
319 #[test]
320 fn lr_star5() {
321 let r = layer_ratio(&star5()).unwrap();
323 assert!((r - 0.25).abs() < 1e-10);
324 }
325
326 #[test]
327 fn lr_path4() {
328 let r = layer_ratio(&path4()).unwrap();
332 assert!(r > 0.3 && r < 0.6);
333 }
334
335 #[test]
336 fn lr_k4() {
337 let r = layer_ratio(&k4()).unwrap();
339 assert!((r - 1.0 / 3.0).abs() < 1e-10);
340 }
341
342 #[test]
343 fn lr_in_01() {
344 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
345 let r = layer_ratio(g).unwrap();
346 assert!(r >= -1e-10);
347 assert!(r <= 1.0 + 1e-10);
348 }
349 }
350
351 #[test]
354 fn dr_empty() {
355 assert!(dominance_ratio(&empty()).unwrap().abs() < 1e-10);
356 }
357
358 #[test]
359 fn dr_single() {
360 assert!(dominance_ratio(&single()).unwrap().abs() < 1e-10);
361 }
362
363 #[test]
364 fn dr_single_edge() {
365 assert!((dominance_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
370 }
371
372 #[test]
373 fn dr_k3() {
374 assert!((dominance_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
378 }
379
380 #[test]
381 fn dr_k4() {
382 assert!((dominance_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn dr_star5() {
387 let r = dominance_ratio(&star5()).unwrap();
394 assert!((r - 16.0 / 20.0).abs() < 1e-10);
395 }
396
397 #[test]
398 fn dr_in_01() {
399 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
400 let r = dominance_ratio(g).unwrap();
401 assert!(r >= -1e-10);
402 assert!(r <= 1.0 + 1e-10);
403 }
404 }
405
406 #[test]
409 fn regular_zero_hierarchy() {
410 assert!(degree_hierarchy(&k3()).unwrap().abs() < 1e-10);
411 assert!(degree_hierarchy(&k4()).unwrap().abs() < 1e-10);
412 assert!(degree_hierarchy(&cycle4()).unwrap().abs() < 1e-10);
413 }
414
415 #[test]
416 fn complete_full_dominance() {
417 assert!((dominance_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
418 assert!((dominance_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
419 }
420
421 #[test]
422 fn star_hierarchy_measures() {
423 let dh = degree_hierarchy(&star5()).unwrap();
425 let lr = layer_ratio(&star5()).unwrap();
426 assert!(dh > 0.3);
427 assert!(lr < 0.5);
428 }
429}