rust_igraph/algorithms/properties/
mixing_ratios.rs1#![allow(
13 clippy::cast_possible_truncation,
14 clippy::cast_precision_loss,
15 clippy::many_single_char_names,
16 clippy::needless_range_loop,
17 clippy::similar_names,
18 clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23pub fn degree_assortativity_proxy(graph: &Graph) -> IgraphResult<f64> {
40 let n = graph.vcount() as usize;
41 let m = graph.ecount();
42 if m < 2 {
43 return Ok(0.0);
44 }
45
46 let mut degrees = Vec::with_capacity(n);
47 for v in 0..n {
48 degrees.push(graph.degree(v as u32)?);
49 }
50
51 let mut sum_x = 0.0_f64;
52 let mut sum_y = 0.0_f64;
53 let mut sum_xx = 0.0_f64;
54 let mut sum_yy = 0.0_f64;
55 let mut sum_xy = 0.0_f64;
56 let mut edge_count = 0_u64;
57
58 for v in 0..n {
59 let neighbors = graph.neighbors(v as u32)?;
60 for &u in &neighbors {
61 let ui = u as usize;
62 if !graph.is_directed() && ui <= v {
63 continue;
64 }
65 let dx = degrees[v] as f64;
66 let dy = degrees[ui] as f64;
67 sum_x += dx;
68 sum_y += dy;
69 sum_xx += dx * dx;
70 sum_yy += dy * dy;
71 sum_xy += dx * dy;
72 edge_count += 1;
73 }
74 }
75
76 if edge_count < 2 {
77 return Ok(0.0);
78 }
79
80 let mf = edge_count as f64;
81 let mean_x = sum_x / mf;
82 let mean_y = sum_y / mf;
83 let var_x = sum_xx / mf - mean_x * mean_x;
84 let var_y = sum_yy / mf - mean_y * mean_y;
85
86 if var_x < 1e-30 || var_y < 1e-30 {
87 return Ok(0.0);
88 }
89
90 let cov = sum_xy / mf - mean_x * mean_y;
91 Ok(cov / (var_x.sqrt() * var_y.sqrt()))
92}
93
94pub fn rich_club_density(graph: &Graph) -> IgraphResult<f64> {
113 let n = graph.vcount() as usize;
114 if n < 2 {
115 return Ok(0.0);
116 }
117
118 let mut degrees = Vec::with_capacity(n);
119 for v in 0..n {
120 degrees.push(graph.degree(v as u32)?);
121 }
122
123 let mut sorted_degs: Vec<(usize, usize)> = degrees.iter().copied().enumerate().collect();
124 sorted_degs.sort_unstable_by(|a, b| b.1.cmp(&a.1));
125
126 let k = (n / 4).max(2).min(n);
127 let rich_set: Vec<usize> = sorted_degs[..k].iter().map(|&(v, _)| v).collect();
128
129 if rich_set.len() < 2 {
130 return Ok(0.0);
131 }
132
133 let mut in_rich = vec![false; n];
134 for &v in &rich_set {
135 in_rich[v] = true;
136 }
137
138 let mut edges_in_rich = 0_u64;
139 for &v in &rich_set {
140 let neighbors = graph.neighbors(v as u32)?;
141 for &u in &neighbors {
142 let ui = u as usize;
143 if in_rich[ui] && (graph.is_directed() || ui > v) {
144 edges_in_rich += 1;
145 }
146 }
147 }
148
149 let rs = rich_set.len();
150 let max_edges = if graph.is_directed() {
151 rs * (rs - 1)
152 } else {
153 rs * (rs - 1) / 2
154 };
155
156 if max_edges == 0 {
157 return Ok(0.0);
158 }
159
160 Ok(edges_in_rich as f64 / max_edges as f64)
161}
162
163pub fn degree_mixing_entropy(graph: &Graph) -> IgraphResult<f64> {
180 let n = graph.vcount() as usize;
181 let m = graph.ecount();
182 if m < 2 {
183 return Ok(0.0);
184 }
185
186 let mut degrees = Vec::with_capacity(n);
187 for v in 0..n {
188 degrees.push(graph.degree(v as u32)?);
189 }
190
191 let mut pair_counts = std::collections::HashMap::new();
192 let mut edge_count = 0_u64;
193
194 for v in 0..n {
195 let neighbors = graph.neighbors(v as u32)?;
196 for &u in &neighbors {
197 let ui = u as usize;
198 if !graph.is_directed() && ui <= v {
199 continue;
200 }
201 let (da, db) = if degrees[v] <= degrees[ui] {
202 (degrees[v], degrees[ui])
203 } else {
204 (degrees[ui], degrees[v])
205 };
206 *pair_counts.entry((da, db)).or_insert(0_u64) += 1;
207 edge_count += 1;
208 }
209 }
210
211 if edge_count < 2 {
212 return Ok(0.0);
213 }
214
215 let mf = edge_count as f64;
216 let log_m = mf.log2();
217 if log_m < 1e-30 {
218 return Ok(0.0);
219 }
220
221 let mut entropy = 0.0_f64;
222 for &count in pair_counts.values() {
223 let p = count as f64 / mf;
224 if p > 0.0 {
225 entropy -= p * p.log2();
226 }
227 }
228
229 Ok(entropy / log_m)
230}
231
232pub fn hub_dominance_ratio(graph: &Graph) -> IgraphResult<f64> {
250 let n = graph.vcount() as usize;
251 let m = graph.ecount();
252 if m == 0 || n == 0 {
253 return Ok(0.0);
254 }
255
256 let mut max_deg = 0_usize;
257 for v in 0..n {
258 let d = graph.degree(v as u32)?;
259 if d > max_deg {
260 max_deg = d;
261 }
262 }
263
264 Ok(max_deg as f64 / m as f64)
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 fn single_edge() -> Graph {
272 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
273 }
274
275 fn path3() -> Graph {
276 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
277 }
278
279 fn k3() -> Graph {
280 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
281 }
282
283 fn k4() -> Graph {
284 Graph::from_edges(
285 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
286 false,
287 Some(4),
288 )
289 .unwrap()
290 }
291
292 fn cycle4() -> Graph {
293 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
294 }
295
296 fn star5() -> Graph {
297 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
298 }
299
300 fn paw() -> Graph {
301 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
302 }
303
304 #[test]
307 fn dap_empty() {
308 let g = Graph::with_vertices(0);
309 assert!(degree_assortativity_proxy(&g).unwrap().abs() < 1e-10);
310 }
311
312 #[test]
313 fn dap_single() {
314 let g = Graph::with_vertices(1);
315 assert!(degree_assortativity_proxy(&g).unwrap().abs() < 1e-10);
316 }
317
318 #[test]
319 fn dap_single_edge() {
320 assert!(degree_assortativity_proxy(&single_edge()).unwrap().abs() < 1e-10);
322 }
323
324 #[test]
325 fn dap_k3() {
326 assert!(degree_assortativity_proxy(&k3()).unwrap().abs() < 1e-10);
328 }
329
330 #[test]
331 fn dap_k4() {
332 assert!(degree_assortativity_proxy(&k4()).unwrap().abs() < 1e-10);
333 }
334
335 #[test]
336 fn dap_cycle4() {
337 assert!(degree_assortativity_proxy(&cycle4()).unwrap().abs() < 1e-10);
338 }
339
340 #[test]
341 fn dap_star5() {
342 let r = degree_assortativity_proxy(&star5()).unwrap();
344 assert!(r.abs() < 1e-10);
345 }
346
347 #[test]
348 fn dap_in_range() {
349 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
350 let r = degree_assortativity_proxy(g).unwrap();
351 assert!(r >= -1.0 - 1e-10);
352 assert!(r <= 1.0 + 1e-10);
353 }
354 }
355
356 #[test]
359 fn rcd_empty() {
360 let g = Graph::with_vertices(0);
361 assert!(rich_club_density(&g).unwrap().abs() < 1e-10);
362 }
363
364 #[test]
365 fn rcd_single() {
366 let g = Graph::with_vertices(1);
367 assert!(rich_club_density(&g).unwrap().abs() < 1e-10);
368 }
369
370 #[test]
371 fn rcd_k4() {
372 assert!((rich_club_density(&k4()).unwrap() - 1.0).abs() < 1e-10);
375 }
376
377 #[test]
378 fn rcd_star5() {
379 assert!((rich_club_density(&star5()).unwrap() - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn rcd_in_01() {
387 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
388 let r = rich_club_density(g).unwrap();
389 assert!(r >= -1e-10);
390 assert!(r <= 1.0 + 1e-10);
391 }
392 }
393
394 #[test]
397 fn dme_empty() {
398 let g = Graph::with_vertices(0);
399 assert!(degree_mixing_entropy(&g).unwrap().abs() < 1e-10);
400 }
401
402 #[test]
403 fn dme_single() {
404 let g = Graph::with_vertices(1);
405 assert!(degree_mixing_entropy(&g).unwrap().abs() < 1e-10);
406 }
407
408 #[test]
409 fn dme_single_edge() {
410 assert!(degree_mixing_entropy(&single_edge()).unwrap().abs() < 1e-10);
411 }
412
413 #[test]
414 fn dme_k3() {
415 assert!(degree_mixing_entropy(&k3()).unwrap().abs() < 1e-10);
417 }
418
419 #[test]
420 fn dme_k4() {
421 assert!(degree_mixing_entropy(&k4()).unwrap().abs() < 1e-10);
423 }
424
425 #[test]
426 fn dme_cycle4() {
427 assert!(degree_mixing_entropy(&cycle4()).unwrap().abs() < 1e-10);
429 }
430
431 #[test]
432 fn dme_star5() {
433 assert!(degree_mixing_entropy(&star5()).unwrap().abs() < 1e-10);
435 }
436
437 #[test]
438 fn dme_paw() {
439 let r = degree_mixing_entropy(&paw()).unwrap();
445 assert!(r > 0.0);
446 assert!(r <= 1.0);
447 }
448
449 #[test]
450 fn dme_in_01() {
451 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
452 let r = degree_mixing_entropy(g).unwrap();
453 assert!(r >= -1e-10);
454 assert!(r <= 1.0 + 1e-10);
455 }
456 }
457
458 #[test]
461 fn hdr_empty() {
462 let g = Graph::with_vertices(0);
463 assert!(hub_dominance_ratio(&g).unwrap().abs() < 1e-10);
464 }
465
466 #[test]
467 fn hdr_single() {
468 let g = Graph::with_vertices(1);
469 assert!(hub_dominance_ratio(&g).unwrap().abs() < 1e-10);
470 }
471
472 #[test]
473 fn hdr_single_edge() {
474 assert!((hub_dominance_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
476 }
477
478 #[test]
479 fn hdr_k3() {
480 assert!((hub_dominance_ratio(&k3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
482 }
483
484 #[test]
485 fn hdr_k4() {
486 assert!((hub_dominance_ratio(&k4()).unwrap() - 0.5).abs() < 1e-10);
488 }
489
490 #[test]
491 fn hdr_star5() {
492 assert!((hub_dominance_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
494 }
495
496 #[test]
497 fn hdr_cycle4() {
498 assert!((hub_dominance_ratio(&cycle4()).unwrap() - 0.5).abs() < 1e-10);
500 }
501
502 #[test]
503 fn hdr_path3() {
504 assert!((hub_dominance_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
506 }
507
508 #[test]
509 fn hdr_positive() {
510 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
511 assert!(hub_dominance_ratio(g).unwrap() > 0.0);
512 }
513 }
514
515 #[test]
518 fn regular_zero_assortativity() {
519 assert!(degree_assortativity_proxy(&k3()).unwrap().abs() < 1e-10);
520 assert!(degree_assortativity_proxy(&k4()).unwrap().abs() < 1e-10);
521 assert!(degree_assortativity_proxy(&cycle4()).unwrap().abs() < 1e-10);
522 }
523
524 #[test]
525 fn regular_zero_entropy() {
526 assert!(degree_mixing_entropy(&k3()).unwrap().abs() < 1e-10);
527 assert!(degree_mixing_entropy(&k4()).unwrap().abs() < 1e-10);
528 assert!(degree_mixing_entropy(&cycle4()).unwrap().abs() < 1e-10);
529 }
530
531 #[test]
532 fn star_max_hub_dominance() {
533 assert!((hub_dominance_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
534 }
535}