rust_igraph/algorithms/properties/
degree_inequality.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::too_many_lines
16)]
17
18use crate::core::{Graph, IgraphResult};
19
20pub fn degree_herfindahl(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 total = 0_u64;
44 let mut degrees = Vec::with_capacity(n);
45 for v in 0..n {
46 let d = graph.degree(v as u32)?;
47 degrees.push(d);
48 total = total.saturating_add(d as u64);
49 }
50
51 if total == 0 {
52 return Ok(0.0);
53 }
54
55 let total_f = total as f64;
56 let mut hhi = 0.0_f64;
57 for &d in °rees {
58 let share = d as f64 / total_f;
59 hhi += share * share;
60 }
61
62 Ok(hhi)
63}
64
65pub fn degree_theil(graph: &Graph) -> IgraphResult<f64> {
84 let n = graph.vcount() as usize;
85 if n == 0 {
86 return Ok(0.0);
87 }
88
89 let mut total = 0_u64;
90 let mut degrees = Vec::with_capacity(n);
91 for v in 0..n {
92 let d = graph.degree(v as u32)?;
93 degrees.push(d);
94 total = total.saturating_add(d as u64);
95 }
96
97 if total == 0 {
98 return Ok(0.0);
99 }
100
101 let mean = total as f64 / n as f64;
102 let mut theil = 0.0_f64;
103 for &d in °rees {
104 if d == 0 {
105 continue;
106 }
107 let ratio = d as f64 / mean;
108 theil += ratio * ratio.ln();
109 }
110
111 Ok(theil / n as f64)
112}
113
114pub fn degree_palma(graph: &Graph) -> IgraphResult<f64> {
136 let n = graph.vcount() as usize;
137 if n < 2 {
138 return Ok(0.0);
139 }
140
141 let mut degrees = Vec::with_capacity(n);
142 for v in 0..n {
143 degrees.push(graph.degree(v as u32)?);
144 }
145 degrees.sort_unstable();
146
147 let bottom_count = n * 40 / 100;
148 let top_start = n.saturating_sub(n * 10 / 100);
149
150 let bottom_sum: u64 = degrees[..bottom_count].iter().map(|&d| d as u64).sum();
151 let top_sum: u64 = degrees[top_start..].iter().map(|&d| d as u64).sum();
152
153 if bottom_sum == 0 {
154 if top_sum == 0 {
155 return Ok(0.0);
156 }
157 return Ok(f64::INFINITY);
158 }
159
160 Ok(top_sum as f64 / bottom_sum as f64)
161}
162
163pub fn degree_hoover(graph: &Graph) -> IgraphResult<f64> {
182 let n = graph.vcount() as usize;
183 if n == 0 {
184 return Ok(0.0);
185 }
186
187 let mut total = 0_u64;
188 let mut degrees = Vec::with_capacity(n);
189 for v in 0..n {
190 let d = graph.degree(v as u32)?;
191 degrees.push(d);
192 total = total.saturating_add(d as u64);
193 }
194
195 if total == 0 {
196 return Ok(0.0);
197 }
198
199 let mean = total as f64 / n as f64;
200 let mut abs_dev_sum = 0.0_f64;
201 for &d in °rees {
202 abs_dev_sum += (d as f64 - mean).abs();
203 }
204
205 Ok(abs_dev_sum / (2.0 * total as f64))
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 fn single_edge() -> Graph {
213 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
214 }
215
216 fn path3() -> Graph {
217 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
218 }
219
220 fn k3() -> Graph {
221 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
222 }
223
224 fn k4() -> Graph {
225 Graph::from_edges(
226 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
227 false,
228 Some(4),
229 )
230 .unwrap()
231 }
232
233 fn cycle4() -> Graph {
234 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
235 }
236
237 fn star5() -> Graph {
238 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
239 }
240
241 fn paw() -> Graph {
242 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
243 }
244
245 #[test]
248 fn hhi_empty() {
249 let g = Graph::with_vertices(0);
250 assert!(degree_herfindahl(&g).unwrap().abs() < 1e-10);
251 }
252
253 #[test]
254 fn hhi_isolated() {
255 let g = Graph::with_vertices(5);
256 assert!(degree_herfindahl(&g).unwrap().abs() < 1e-10);
257 }
258
259 #[test]
260 fn hhi_regular() {
261 assert!((degree_herfindahl(&k3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
263 assert!((degree_herfindahl(&k4()).unwrap() - 1.0 / 4.0).abs() < 1e-10);
264 assert!((degree_herfindahl(&cycle4()).unwrap() - 1.0 / 4.0).abs() < 1e-10);
265 }
266
267 #[test]
268 fn hhi_single_edge() {
269 assert!((degree_herfindahl(&single_edge()).unwrap() - 0.5).abs() < 1e-10);
271 }
272
273 #[test]
274 fn hhi_star5() {
275 assert!((degree_herfindahl(&star5()).unwrap() - 0.3125).abs() < 1e-10);
278 }
279
280 #[test]
281 fn hhi_path3() {
282 assert!((degree_herfindahl(&path3()).unwrap() - 3.0 / 8.0).abs() < 1e-10);
285 }
286
287 #[test]
288 fn hhi_paw() {
289 assert!((degree_herfindahl(&paw()).unwrap() - 9.0 / 32.0).abs() < 1e-10);
292 }
293
294 #[test]
297 fn theil_empty() {
298 let g = Graph::with_vertices(0);
299 assert!(degree_theil(&g).unwrap().abs() < 1e-10);
300 }
301
302 #[test]
303 fn theil_isolated() {
304 let g = Graph::with_vertices(5);
305 assert!(degree_theil(&g).unwrap().abs() < 1e-10);
306 }
307
308 #[test]
309 fn theil_regular() {
310 assert!(degree_theil(&k3()).unwrap().abs() < 1e-10);
312 assert!(degree_theil(&k4()).unwrap().abs() < 1e-10);
313 assert!(degree_theil(&cycle4()).unwrap().abs() < 1e-10);
314 }
315
316 #[test]
317 fn theil_single_edge() {
318 assert!(degree_theil(&single_edge()).unwrap().abs() < 1e-10);
320 }
321
322 #[test]
323 fn theil_star5() {
324 let mean = 1.6_f64;
329 let r0 = 4.0 / mean;
330 let r1 = 1.0 / mean;
331 let expected = (r0 * r0.ln() + 4.0 * r1 * r1.ln()) / 5.0;
332 assert!((degree_theil(&star5()).unwrap() - expected).abs() < 1e-10);
333 }
334
335 #[test]
336 fn theil_path3() {
337 let mean: f64 = 4.0 / 3.0;
343 let r_end: f64 = 1.0 / mean;
344 let r_mid: f64 = 2.0 / mean;
345 let expected = (2.0 * r_end * r_end.ln() + r_mid * r_mid.ln()) / 3.0;
346 assert!((degree_theil(&path3()).unwrap() - expected).abs() < 1e-10);
347 }
348
349 #[test]
350 fn theil_paw() {
351 let mean = 2.0_f64;
353 let vals: [f64; 4] = [2.0, 2.0, 3.0, 1.0];
354 let mut sum = 0.0_f64;
355 for &d in &vals {
356 let r: f64 = d / mean;
357 sum += r * r.ln();
358 }
359 let expected = sum / 4.0;
360 assert!((degree_theil(&paw()).unwrap() - expected).abs() < 1e-10);
361 }
362
363 #[test]
366 fn palma_empty() {
367 let g = Graph::with_vertices(0);
368 assert!(degree_palma(&g).unwrap().abs() < 1e-10);
369 }
370
371 #[test]
372 fn palma_single() {
373 let g = Graph::with_vertices(1);
374 assert!(degree_palma(&g).unwrap().abs() < 1e-10);
375 }
376
377 #[test]
378 fn palma_single_edge() {
379 let p = degree_palma(&single_edge()).unwrap();
381 assert!(p.abs() < 1e-10);
382 }
383
384 #[test]
385 fn palma_k3() {
386 assert!(degree_palma(&k3()).unwrap().abs() < 1e-10);
389 }
390
391 #[test]
392 fn palma_star5() {
393 assert!(degree_palma(&star5()).unwrap().abs() < 1e-10);
398 }
399
400 #[test]
401 fn palma_10vertices() {
402 let mut edges = Vec::new();
407 for i in 1..10_u32 {
408 edges.push((0, i));
409 }
410 let g = Graph::from_edges(&edges, false, Some(10)).unwrap();
411 assert!((degree_palma(&g).unwrap() - 2.25).abs() < 1e-10);
412 }
413
414 #[test]
415 fn palma_20vertices_regular() {
416 let mut edges = Vec::new();
421 for i in 0..20_u32 {
422 edges.push((i, (i + 1) % 20));
423 }
424 let g = Graph::from_edges(&edges, false, Some(20)).unwrap();
425 assert!((degree_palma(&g).unwrap() - 0.25).abs() < 1e-10);
426 }
427
428 #[test]
431 fn hoover_empty() {
432 let g = Graph::with_vertices(0);
433 assert!(degree_hoover(&g).unwrap().abs() < 1e-10);
434 }
435
436 #[test]
437 fn hoover_isolated() {
438 let g = Graph::with_vertices(5);
439 assert!(degree_hoover(&g).unwrap().abs() < 1e-10);
440 }
441
442 #[test]
443 fn hoover_regular() {
444 assert!(degree_hoover(&k3()).unwrap().abs() < 1e-10);
446 assert!(degree_hoover(&k4()).unwrap().abs() < 1e-10);
447 assert!(degree_hoover(&cycle4()).unwrap().abs() < 1e-10);
448 }
449
450 #[test]
451 fn hoover_single_edge() {
452 assert!(degree_hoover(&single_edge()).unwrap().abs() < 1e-10);
454 }
455
456 #[test]
457 fn hoover_star5() {
458 assert!((degree_hoover(&star5()).unwrap() - 0.3).abs() < 1e-10);
463 }
464
465 #[test]
466 fn hoover_path3() {
467 assert!((degree_hoover(&path3()).unwrap() - 1.0 / 6.0).abs() < 1e-10);
472 }
473
474 #[test]
475 fn hoover_paw() {
476 assert!((degree_hoover(&paw()).unwrap() - 0.125).abs() < 1e-10);
480 }
481
482 #[test]
485 fn herfindahl_bounds() {
486 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
488 let n = f64::from(g.vcount());
489 let h = degree_herfindahl(g).unwrap();
490 assert!(h >= 1.0 / n - 1e-10);
491 assert!(h <= 1.0 + 1e-10);
492 }
493 }
494
495 #[test]
496 fn theil_nonnegative() {
497 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
498 assert!(degree_theil(g).unwrap() >= -1e-10);
499 }
500 }
501
502 #[test]
503 fn hoover_in_01() {
504 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
505 let h = degree_hoover(g).unwrap();
506 assert!(h >= -1e-10);
507 assert!(h <= 1.0 + 1e-10);
508 }
509 }
510
511 #[test]
512 fn palma_nonnegative() {
513 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
514 assert!(degree_palma(g).unwrap() >= -1e-10);
515 }
516 }
517
518 #[test]
519 fn regular_all_zero_inequality() {
520 for g in &[k3(), k4(), cycle4()] {
521 assert!(degree_theil(g).unwrap().abs() < 1e-10);
522 assert!(degree_hoover(g).unwrap().abs() < 1e-10);
523 }
524 }
525}