rust_igraph/algorithms/properties/
walk_diversity.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 walk_entropy(graph: &Graph) -> IgraphResult<f64> {
41 let n = graph.vcount() as usize;
42 let mut sum_d: u64 = 0;
43 let mut degrees = Vec::with_capacity(n);
44
45 for v in 0..n {
46 let d = graph.degree(v as u32)?;
47 degrees.push(d);
48 sum_d = sum_d.saturating_add(d as u64);
49 }
50
51 if sum_d == 0 {
52 return Ok(0.0);
53 }
54
55 let two_m: f64 = sum_d as f64;
56 let mut h: f64 = 0.0;
57
58 for &d in °rees {
59 if d > 0 {
60 let p: f64 = d as f64 / two_m;
61 h -= p * p.ln();
62 }
63 }
64
65 Ok(h)
66}
67
68pub fn walk_regularity(graph: &Graph) -> IgraphResult<f64> {
87 let n = graph.vcount() as usize;
88 if n < 2 {
89 return Ok(1.0);
90 }
91
92 let mut sum_d: f64 = 0.0;
93 let mut sum_d2: f64 = 0.0;
94
95 for v in 0..n {
96 let d: f64 = graph.degree(v as u32)? as f64;
97 sum_d += d;
98 sum_d2 += d * d;
99 }
100
101 if sum_d < 1e-15 {
102 return Ok(1.0);
103 }
104
105 let mean: f64 = sum_d / n as f64;
106 let variance: f64 = sum_d2 / n as f64 - mean * mean;
107
108 if variance < 1e-15 {
109 return Ok(1.0);
110 }
111
112 let cv: f64 = variance.sqrt() / mean;
113 Ok((1.0 - cv).max(0.0))
114}
115
116pub fn degree_laplacian_energy(graph: &Graph) -> IgraphResult<f64> {
135 let n = graph.vcount() as usize;
136 if n < 2 {
137 return Ok(0.0);
138 }
139
140 let mut sum_d: f64 = 0.0;
141 let mut d_max: f64 = 0.0;
142 let mut degrees = Vec::with_capacity(n);
143
144 for v in 0..n {
145 let d: f64 = graph.degree(v as u32)? as f64;
146 degrees.push(d);
147 sum_d += d;
148 if d > d_max {
149 d_max = d;
150 }
151 }
152
153 if sum_d < 1e-15 || d_max < 1e-15 {
154 return Ok(0.0);
155 }
156
157 let mean: f64 = sum_d / n as f64;
158 let mut sum_abs_dev: f64 = 0.0;
159
160 for &d in °rees {
161 sum_abs_dev += (d - mean).abs();
162 }
163
164 Ok(sum_abs_dev / (n as f64 * d_max))
165}
166
167pub fn avg_neighbor_connectivity(graph: &Graph) -> IgraphResult<f64> {
187 let n = graph.vcount() as usize;
188 if n == 0 {
189 return Ok(0.0);
190 }
191
192 let mut degrees = Vec::with_capacity(n);
193 for v in 0..n {
194 degrees.push(graph.degree(v as u32)?);
195 }
196
197 let mut sum_ratio: f64 = 0.0;
198 let mut count = 0_u32;
199
200 for v in 0..n {
201 let dv = degrees[v];
202 if dv == 0 {
203 continue;
204 }
205 let neighbors = graph.neighbors(v as u32)?;
206 let mut neighbor_deg_sum: u64 = 0;
207 for &u in &neighbors {
208 neighbor_deg_sum = neighbor_deg_sum.saturating_add(degrees[u as usize] as u64);
209 }
210 sum_ratio += neighbor_deg_sum as f64 / dv as f64;
211 count += 1;
212 }
213
214 if count == 0 {
215 return Ok(0.0);
216 }
217
218 Ok(sum_ratio / f64::from(count))
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn single_edge() -> Graph {
226 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
227 }
228
229 fn path3() -> Graph {
230 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
231 }
232
233 fn k3() -> Graph {
234 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
235 }
236
237 fn k4() -> Graph {
238 Graph::from_edges(
239 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
240 false,
241 Some(4),
242 )
243 .unwrap()
244 }
245
246 fn cycle4() -> Graph {
247 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
248 }
249
250 fn star5() -> Graph {
251 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
252 }
253
254 fn paw() -> Graph {
255 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
256 }
257
258 #[test]
261 fn we_empty() {
262 let g = Graph::with_vertices(0);
263 assert!(walk_entropy(&g).unwrap().abs() < 1e-10);
264 }
265
266 #[test]
267 fn we_isolated() {
268 let g = Graph::with_vertices(5);
269 assert!(walk_entropy(&g).unwrap().abs() < 1e-10);
270 }
271
272 #[test]
273 fn we_single_edge() {
274 assert!((walk_entropy(&single_edge()).unwrap() - 2.0_f64.ln()).abs() < 1e-10);
276 }
277
278 #[test]
279 fn we_k3() {
280 assert!((walk_entropy(&k3()).unwrap() - 3.0_f64.ln()).abs() < 1e-10);
282 }
283
284 #[test]
285 fn we_k4() {
286 assert!((walk_entropy(&k4()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
288 }
289
290 #[test]
291 fn we_cycle4() {
292 assert!((walk_entropy(&cycle4()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
294 }
295
296 #[test]
297 fn we_path3() {
298 let expected: f64 = 1.5 * 2.0_f64.ln();
304 assert!((walk_entropy(&path3()).unwrap() - expected).abs() < 1e-10);
305 }
306
307 #[test]
308 fn we_star5() {
309 let expected: f64 = 2.0 * 2.0_f64.ln();
315 assert!((walk_entropy(&star5()).unwrap() - expected).abs() < 1e-10);
316 }
317
318 #[test]
319 fn we_paw() {
320 let p: [f64; 4] = [0.25, 0.25, 0.375, 0.125];
324 let expected: f64 = -p.iter().map(|&pi| pi * pi.ln()).sum::<f64>();
325 assert!((walk_entropy(&paw()).unwrap() - expected).abs() < 1e-10);
326 }
327
328 #[test]
329 fn we_nonneg() {
330 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
331 assert!(walk_entropy(g).unwrap() >= -1e-10);
332 }
333 }
334
335 #[test]
338 fn wr_empty() {
339 let g = Graph::with_vertices(0);
340 assert!((walk_regularity(&g).unwrap() - 1.0).abs() < 1e-10);
341 }
342
343 #[test]
344 fn wr_single() {
345 let g = Graph::with_vertices(1);
346 assert!((walk_regularity(&g).unwrap() - 1.0).abs() < 1e-10);
347 }
348
349 #[test]
350 fn wr_isolated() {
351 let g = Graph::with_vertices(5);
352 assert!((walk_regularity(&g).unwrap() - 1.0).abs() < 1e-10);
353 }
354
355 #[test]
356 fn wr_regular() {
357 assert!((walk_regularity(&k3()).unwrap() - 1.0).abs() < 1e-10);
359 assert!((walk_regularity(&k4()).unwrap() - 1.0).abs() < 1e-10);
360 assert!((walk_regularity(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
361 }
362
363 #[test]
364 fn wr_single_edge() {
365 assert!((walk_regularity(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
367 }
368
369 #[test]
370 fn wr_path3() {
371 let cv: f64 = (2.0_f64 / 9.0).sqrt() / (4.0 / 3.0);
375 let expected: f64 = 1.0 - cv;
376 assert!((walk_regularity(&path3()).unwrap() - expected).abs() < 1e-10);
377 }
378
379 #[test]
380 fn wr_star5() {
381 assert!((walk_regularity(&star5()).unwrap() - 0.25).abs() < 1e-10);
386 }
387
388 #[test]
389 fn wr_paw() {
390 let cv: f64 = 0.5_f64.sqrt() / 2.0;
395 let expected: f64 = 1.0 - cv;
396 assert!((walk_regularity(&paw()).unwrap() - expected).abs() < 1e-10);
397 }
398
399 #[test]
400 fn wr_in_01() {
401 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
402 let w = walk_regularity(g).unwrap();
403 assert!(w >= -1e-10);
404 assert!(w <= 1.0 + 1e-10);
405 }
406 }
407
408 #[test]
411 fn dle_empty() {
412 let g = Graph::with_vertices(0);
413 assert!(degree_laplacian_energy(&g).unwrap().abs() < 1e-10);
414 }
415
416 #[test]
417 fn dle_single() {
418 let g = Graph::with_vertices(1);
419 assert!(degree_laplacian_energy(&g).unwrap().abs() < 1e-10);
420 }
421
422 #[test]
423 fn dle_isolated() {
424 let g = Graph::with_vertices(5);
425 assert!(degree_laplacian_energy(&g).unwrap().abs() < 1e-10);
426 }
427
428 #[test]
429 fn dle_regular() {
430 assert!(degree_laplacian_energy(&k3()).unwrap().abs() < 1e-10);
432 assert!(degree_laplacian_energy(&k4()).unwrap().abs() < 1e-10);
433 assert!(degree_laplacian_energy(&cycle4()).unwrap().abs() < 1e-10);
434 }
435
436 #[test]
437 fn dle_single_edge() {
438 assert!(degree_laplacian_energy(&single_edge()).unwrap().abs() < 1e-10);
440 }
441
442 #[test]
443 fn dle_path3() {
444 let expected: f64 = 2.0 / 9.0;
448 assert!((degree_laplacian_energy(&path3()).unwrap() - expected).abs() < 1e-10);
449 }
450
451 #[test]
452 fn dle_star5() {
453 assert!((degree_laplacian_energy(&star5()).unwrap() - 0.24).abs() < 1e-10);
457 }
458
459 #[test]
460 fn dle_paw() {
461 let expected: f64 = 1.0 / 6.0;
465 assert!((degree_laplacian_energy(&paw()).unwrap() - expected).abs() < 1e-10);
466 }
467
468 #[test]
469 fn dle_nonneg() {
470 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
471 assert!(degree_laplacian_energy(g).unwrap() >= -1e-10);
472 }
473 }
474
475 #[test]
478 fn anc_empty() {
479 let g = Graph::with_vertices(0);
480 assert!(avg_neighbor_connectivity(&g).unwrap().abs() < 1e-10);
481 }
482
483 #[test]
484 fn anc_isolated() {
485 let g = Graph::with_vertices(5);
486 assert!(avg_neighbor_connectivity(&g).unwrap().abs() < 1e-10);
487 }
488
489 #[test]
490 fn anc_single_edge() {
491 assert!((avg_neighbor_connectivity(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
493 }
494
495 #[test]
496 fn anc_k3() {
497 assert!((avg_neighbor_connectivity(&k3()).unwrap() - 2.0).abs() < 1e-10);
500 }
501
502 #[test]
503 fn anc_k4() {
504 assert!((avg_neighbor_connectivity(&k4()).unwrap() - 3.0).abs() < 1e-10);
507 }
508
509 #[test]
510 fn anc_cycle4() {
511 assert!((avg_neighbor_connectivity(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
514 }
515
516 #[test]
517 fn anc_path3() {
518 let expected: f64 = 5.0 / 3.0;
523 assert!((avg_neighbor_connectivity(&path3()).unwrap() - expected).abs() < 1e-10);
524 }
525
526 #[test]
527 fn anc_star5() {
528 let expected: f64 = 17.0 / 5.0;
532 assert!((avg_neighbor_connectivity(&star5()).unwrap() - expected).abs() < 1e-10);
533 }
534
535 #[test]
536 fn anc_paw() {
537 let v2_ratio: f64 = 5.0 / 3.0;
544 let expected: f64 = (2.5 + 2.5 + v2_ratio + 3.0) / 4.0;
545 assert!((avg_neighbor_connectivity(&paw()).unwrap() - expected).abs() < 1e-10);
546 }
547
548 #[test]
549 fn anc_positive() {
550 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
551 assert!(avg_neighbor_connectivity(g).unwrap() > 0.0);
552 }
553 }
554
555 #[test]
558 fn regular_entropy_maximal() {
559 let h_k3 = walk_entropy(&k3()).unwrap();
561 assert!((h_k3 - 3.0_f64.ln()).abs() < 1e-10);
562
563 let h_k4 = walk_entropy(&k4()).unwrap();
564 assert!((h_k4 - 4.0_f64.ln()).abs() < 1e-10);
565 }
566
567 #[test]
568 fn regular_dle_zero() {
569 assert!(degree_laplacian_energy(&k3()).unwrap().abs() < 1e-10);
570 assert!(degree_laplacian_energy(&k4()).unwrap().abs() < 1e-10);
571 assert!(degree_laplacian_energy(&cycle4()).unwrap().abs() < 1e-10);
572 }
573
574 #[test]
575 fn regular_wr_one() {
576 assert!((walk_regularity(&k3()).unwrap() - 1.0).abs() < 1e-10);
577 assert!((walk_regularity(&k4()).unwrap() - 1.0).abs() < 1e-10);
578 assert!((walk_regularity(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
579 }
580
581 #[test]
582 fn regular_anc_equals_degree() {
583 assert!((avg_neighbor_connectivity(&k3()).unwrap() - 2.0).abs() < 1e-10);
585 assert!((avg_neighbor_connectivity(&k4()).unwrap() - 3.0).abs() < 1e-10);
586 assert!((avg_neighbor_connectivity(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
587 }
588}