rust_igraph/algorithms/properties/
neighborhood_density.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_precision_loss,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::similar_names,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn avg_neighbor_degree_ratio(graph: &Graph) -> IgraphResult<f64> {
38 let n = graph.vcount() as usize;
39 if n == 0 {
40 return Ok(0.0);
41 }
42
43 let mut degrees = Vec::with_capacity(n);
44 for v in 0..n {
45 degrees.push(graph.degree(v as u32)?);
46 }
47
48 let mut sum = 0.0_f64;
49 let mut count = 0_usize;
50
51 for v in 0..n {
52 let dv = degrees[v];
53 if dv == 0 {
54 continue;
55 }
56
57 let neighbors = graph.neighbors(v as u32)?;
58 let mut neighbor_deg_sum = 0_usize;
59 for &u in &neighbors {
60 neighbor_deg_sum += degrees[u as usize];
61 }
62
63 let avg_neighbor_deg = neighbor_deg_sum as f64 / dv as f64;
64 sum += avg_neighbor_deg / dv as f64;
65 count += 1;
66 }
67
68 if count == 0 {
69 return Ok(0.0);
70 }
71
72 Ok(sum / count as f64)
73}
74
75pub fn hub_ratio(graph: &Graph) -> IgraphResult<f64> {
90 let n = graph.vcount() as usize;
91 if n == 0 {
92 return Ok(0.0);
93 }
94
95 let mut sum_deg = 0_usize;
96 let mut degrees = Vec::with_capacity(n);
97 for v in 0..n {
98 let d = graph.degree(v as u32)?;
99 degrees.push(d);
100 sum_deg += d;
101 }
102
103 let avg_deg = sum_deg as f64 / n as f64;
104
105 let hub_count = degrees
106 .iter()
107 .filter(|&&d| d as f64 > avg_deg + 1e-15)
108 .count();
109
110 Ok(hub_count as f64 / n as f64)
111}
112
113pub fn leaf_to_hub_ratio(graph: &Graph) -> IgraphResult<f64> {
129 let n = graph.vcount() as usize;
130 if n == 0 {
131 return Ok(0.0);
132 }
133
134 let mut sum_deg = 0_usize;
135 let mut degrees = Vec::with_capacity(n);
136 for v in 0..n {
137 let d = graph.degree(v as u32)?;
138 degrees.push(d);
139 sum_deg += d;
140 }
141
142 let avg_deg = sum_deg as f64 / n as f64;
143
144 let leaf_count = degrees.iter().filter(|&&d| d == 1).count();
145 let hub_count = degrees
146 .iter()
147 .filter(|&&d| d as f64 > avg_deg + 1e-15)
148 .count();
149
150 if hub_count == 0 || leaf_count == 0 {
151 return Ok(0.0);
152 }
153
154 Ok(leaf_count as f64 / hub_count as f64)
155}
156
157pub fn freeman_degree_centralization(graph: &Graph) -> IgraphResult<f64> {
176 let n = graph.vcount() as usize;
177 if n < 3 {
178 return Ok(0.0);
179 }
180
181 let mut max_deg = 0_usize;
182 let mut degrees = Vec::with_capacity(n);
183 for v in 0..n {
184 let d = graph.degree(v as u32)?;
185 degrees.push(d);
186 if d > max_deg {
187 max_deg = d;
188 }
189 }
190
191 let mut deviation_sum = 0_usize;
192 for &d in °rees {
193 deviation_sum += max_deg - d;
194 }
195
196 let denom = (n - 1) * (n - 2);
197 if denom == 0 {
198 return Ok(0.0);
199 }
200
201 Ok(deviation_sum as f64 / denom as f64)
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 fn single_edge() -> Graph {
209 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
210 }
211
212 fn path3() -> Graph {
213 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
214 }
215
216 fn k3() -> Graph {
217 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
218 }
219
220 fn k4() -> Graph {
221 Graph::from_edges(
222 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
223 false,
224 Some(4),
225 )
226 .unwrap()
227 }
228
229 fn cycle4() -> Graph {
230 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
231 }
232
233 fn star5() -> Graph {
234 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
235 }
236
237 fn paw() -> Graph {
238 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
239 }
240
241 #[test]
244 fn andr_empty() {
245 let g = Graph::with_vertices(0);
246 assert!(avg_neighbor_degree_ratio(&g).unwrap().abs() < 1e-10);
247 }
248
249 #[test]
250 fn andr_isolated() {
251 let g = Graph::with_vertices(5);
252 assert!(avg_neighbor_degree_ratio(&g).unwrap().abs() < 1e-10);
253 }
254
255 #[test]
256 fn andr_single_edge() {
257 assert!((avg_neighbor_degree_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
259 }
260
261 #[test]
262 fn andr_k3() {
263 assert!((avg_neighbor_degree_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
265 }
266
267 #[test]
268 fn andr_k4() {
269 assert!((avg_neighbor_degree_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
271 }
272
273 #[test]
274 fn andr_cycle4() {
275 assert!((avg_neighbor_degree_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
277 }
278
279 #[test]
280 fn andr_star5() {
281 assert!((avg_neighbor_degree_ratio(&star5()).unwrap() - 3.25).abs() < 1e-10);
285 }
286
287 #[test]
290 fn hr_empty() {
291 let g = Graph::with_vertices(0);
292 assert!(hub_ratio(&g).unwrap().abs() < 1e-10);
293 }
294
295 #[test]
296 fn hr_isolated() {
297 let g = Graph::with_vertices(5);
298 assert!(hub_ratio(&g).unwrap().abs() < 1e-10);
299 }
300
301 #[test]
302 fn hr_k3() {
303 assert!(hub_ratio(&k3()).unwrap().abs() < 1e-10);
305 }
306
307 #[test]
308 fn hr_k4() {
309 assert!(hub_ratio(&k4()).unwrap().abs() < 1e-10);
311 }
312
313 #[test]
314 fn hr_cycle4() {
315 assert!(hub_ratio(&cycle4()).unwrap().abs() < 1e-10);
317 }
318
319 #[test]
320 fn hr_star5() {
321 assert!((hub_ratio(&star5()).unwrap() - 0.2).abs() < 1e-10);
324 }
325
326 #[test]
327 fn hr_path3() {
328 assert!((hub_ratio(&path3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
331 }
332
333 #[test]
334 fn hr_paw() {
335 assert!((hub_ratio(&paw()).unwrap() - 0.25).abs() < 1e-10);
338 }
339
340 #[test]
343 fn lthr_empty() {
344 let g = Graph::with_vertices(0);
345 assert!(leaf_to_hub_ratio(&g).unwrap().abs() < 1e-10);
346 }
347
348 #[test]
349 fn lthr_isolated() {
350 let g = Graph::with_vertices(5);
351 assert!(leaf_to_hub_ratio(&g).unwrap().abs() < 1e-10);
352 }
353
354 #[test]
355 fn lthr_k3() {
356 assert!(leaf_to_hub_ratio(&k3()).unwrap().abs() < 1e-10);
358 }
359
360 #[test]
361 fn lthr_star5() {
362 assert!((leaf_to_hub_ratio(&star5()).unwrap() - 4.0).abs() < 1e-10);
364 }
365
366 #[test]
367 fn lthr_path3() {
368 assert!((leaf_to_hub_ratio(&path3()).unwrap() - 2.0).abs() < 1e-10);
371 }
372
373 #[test]
374 fn lthr_paw() {
375 assert!((leaf_to_hub_ratio(&paw()).unwrap() - 1.0).abs() < 1e-10);
378 }
379
380 #[test]
381 fn lthr_k4() {
382 assert!(leaf_to_hub_ratio(&k4()).unwrap().abs() < 1e-10);
384 }
385
386 #[test]
387 fn lthr_cycle4() {
388 assert!(leaf_to_hub_ratio(&cycle4()).unwrap().abs() < 1e-10);
390 }
391
392 #[test]
395 fn fdc_empty() {
396 let g = Graph::with_vertices(0);
397 assert!(freeman_degree_centralization(&g).unwrap().abs() < 1e-10);
398 }
399
400 #[test]
401 fn fdc_single() {
402 let g = Graph::with_vertices(1);
403 assert!(freeman_degree_centralization(&g).unwrap().abs() < 1e-10);
404 }
405
406 #[test]
407 fn fdc_two() {
408 let g = Graph::with_vertices(2);
409 assert!(freeman_degree_centralization(&g).unwrap().abs() < 1e-10);
410 }
411
412 #[test]
413 fn fdc_k3() {
414 assert!(freeman_degree_centralization(&k3()).unwrap().abs() < 1e-10);
416 }
417
418 #[test]
419 fn fdc_k4() {
420 assert!(freeman_degree_centralization(&k4()).unwrap().abs() < 1e-10);
422 }
423
424 #[test]
425 fn fdc_cycle4() {
426 assert!(freeman_degree_centralization(&cycle4()).unwrap().abs() < 1e-10);
428 }
429
430 #[test]
431 fn fdc_star5() {
432 assert!((freeman_degree_centralization(&star5()).unwrap() - 1.0).abs() < 1e-10);
434 }
435
436 #[test]
437 fn fdc_path3() {
438 assert!((freeman_degree_centralization(&path3()).unwrap() - 1.0).abs() < 1e-10);
440 }
441
442 #[test]
443 fn fdc_paw() {
444 assert!((freeman_degree_centralization(&paw()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
446 }
447
448 #[test]
451 fn andr_nonneg() {
452 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
453 assert!(avg_neighbor_degree_ratio(g).unwrap() >= -1e-10);
454 }
455 }
456
457 #[test]
458 fn hr_in_01() {
459 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
460 let r = hub_ratio(g).unwrap();
461 assert!(r >= -1e-10);
462 assert!(r <= 1.0 + 1e-10);
463 }
464 }
465
466 #[test]
467 fn lthr_nonneg() {
468 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
469 assert!(leaf_to_hub_ratio(g).unwrap() >= -1e-10);
470 }
471 }
472
473 #[test]
474 fn fdc_in_01() {
475 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
476 let r = freeman_degree_centralization(g).unwrap();
477 assert!(r >= -1e-10);
478 assert!(r <= 1.0 + 1e-10);
479 }
480 }
481
482 #[test]
483 fn regular_graphs_zero_centralization() {
484 assert!(freeman_degree_centralization(&k3()).unwrap().abs() < 1e-10);
485 assert!(freeman_degree_centralization(&k4()).unwrap().abs() < 1e-10);
486 assert!(freeman_degree_centralization(&cycle4()).unwrap().abs() < 1e-10);
487 }
488
489 #[test]
490 fn regular_graphs_unit_ratio() {
491 assert!((avg_neighbor_degree_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
493 assert!((avg_neighbor_degree_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
494 assert!((avg_neighbor_degree_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
495 }
496}