rust_igraph/algorithms/properties/
irregularity.rs1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23pub fn albertson_index(graph: &Graph) -> IgraphResult<f64> {
40 let n = graph.vcount();
41 if n == 0 {
42 return Ok(0.0);
43 }
44
45 let mut alb = 0.0_f64;
46
47 for (u, v) in graph.edges() {
48 if u == v {
49 continue;
50 }
51 let du = graph.degree(u)? as f64;
52 let dv = graph.degree(v)? as f64;
53 alb += (du - dv).abs();
54 }
55
56 Ok(alb)
57}
58
59pub fn sigma_index(graph: &Graph) -> IgraphResult<f64> {
77 let n = graph.vcount();
78 if n == 0 {
79 return Ok(0.0);
80 }
81
82 let mut sigma = 0.0_f64;
83
84 for (u, v) in graph.edges() {
85 if u == v {
86 continue;
87 }
88 let du = graph.degree(u)? as f64;
89 let dv = graph.degree(v)? as f64;
90 let diff = du - dv;
91 sigma += diff * diff;
92 }
93
94 Ok(sigma)
95}
96
97pub fn total_irregularity(graph: &Graph) -> IgraphResult<f64> {
116 let n = graph.vcount();
117 if n < 2 {
118 return Ok(0.0);
119 }
120
121 let mut degrees = Vec::with_capacity(n as usize);
122 for v in 0..n {
123 degrees.push(graph.degree(v)? as f64);
124 }
125
126 let mut total = 0.0_f64;
127 for i in 0..degrees.len() {
128 for j in (i + 1)..degrees.len() {
129 total += (degrees[i] - degrees[j]).abs();
130 }
131 }
132
133 Ok(total / 2.0)
134}
135
136pub fn degree_variance(graph: &Graph) -> IgraphResult<f64> {
153 let n = graph.vcount();
154 if n == 0 {
155 return Ok(0.0);
156 }
157
158 let nf = f64::from(n);
159 let m = graph.ecount() as f64;
160 let mean_deg = 2.0 * m / nf;
161
162 let mut var = 0.0_f64;
163 for v in 0..n {
164 let d = graph.degree(v)? as f64;
165 let diff = d - mean_deg;
166 var += diff * diff;
167 }
168
169 Ok(var / nf)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn single_edge() -> Graph {
177 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
178 }
179
180 fn path3() -> Graph {
181 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
182 }
183
184 fn path4() -> Graph {
185 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
186 }
187
188 fn k3() -> Graph {
189 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
190 }
191
192 fn k4() -> Graph {
193 Graph::from_edges(
194 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
195 false,
196 Some(4),
197 )
198 .unwrap()
199 }
200
201 fn cycle4() -> Graph {
202 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
203 }
204
205 fn cycle5() -> Graph {
206 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
207 }
208
209 fn star5() -> Graph {
210 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
211 }
212
213 fn paw() -> Graph {
214 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
216 }
217
218 fn diamond() -> Graph {
219 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap()
221 }
222
223 #[test]
226 fn alb_empty() {
227 let g = Graph::with_vertices(0);
228 assert!((albertson_index(&g).unwrap()).abs() < 1e-10);
229 }
230
231 #[test]
232 fn alb_single_vertex() {
233 let g = Graph::with_vertices(1);
234 assert!((albertson_index(&g).unwrap()).abs() < 1e-10);
235 }
236
237 #[test]
238 fn alb_no_edges() {
239 let g = Graph::with_vertices(3);
240 assert!((albertson_index(&g).unwrap()).abs() < 1e-10);
241 }
242
243 #[test]
244 fn alb_single_edge() {
245 assert!((albertson_index(&single_edge()).unwrap()).abs() < 1e-10);
247 }
248
249 #[test]
250 fn alb_path3() {
251 assert!((albertson_index(&path3()).unwrap() - 2.0).abs() < 1e-10);
253 }
254
255 #[test]
256 fn alb_path4() {
257 assert!((albertson_index(&path4()).unwrap() - 2.0).abs() < 1e-10);
259 }
260
261 #[test]
262 fn alb_k3() {
263 assert!((albertson_index(&k3()).unwrap()).abs() < 1e-10);
265 }
266
267 #[test]
268 fn alb_k4() {
269 assert!((albertson_index(&k4()).unwrap()).abs() < 1e-10);
270 }
271
272 #[test]
273 fn alb_cycle4() {
274 assert!((albertson_index(&cycle4()).unwrap()).abs() < 1e-10);
275 }
276
277 #[test]
278 fn alb_cycle5() {
279 assert!((albertson_index(&cycle5()).unwrap()).abs() < 1e-10);
280 }
281
282 #[test]
283 fn alb_star5() {
284 assert!((albertson_index(&star5()).unwrap() - 12.0).abs() < 1e-10);
286 }
287
288 #[test]
289 fn alb_paw() {
290 assert!((albertson_index(&paw()).unwrap() - 4.0).abs() < 1e-10);
294 }
295
296 #[test]
297 fn alb_diamond() {
298 assert!((albertson_index(&diamond()).unwrap() - 4.0).abs() < 1e-10);
301 }
302
303 #[test]
304 fn alb_nonnegative() {
305 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
306 assert!(albertson_index(g).unwrap() >= -1e-10);
307 }
308 }
309
310 #[test]
311 fn alb_zero_iff_regular() {
312 for g in &[k3(), k4(), cycle4(), cycle5()] {
313 assert!((albertson_index(g).unwrap()).abs() < 1e-10);
314 }
315 for g in &[path3(), star5(), paw(), diamond()] {
316 assert!(albertson_index(g).unwrap() > 1e-10);
317 }
318 }
319
320 #[test]
323 fn sig_empty() {
324 let g = Graph::with_vertices(0);
325 assert!((sigma_index(&g).unwrap()).abs() < 1e-10);
326 }
327
328 #[test]
329 fn sig_single_vertex() {
330 let g = Graph::with_vertices(1);
331 assert!((sigma_index(&g).unwrap()).abs() < 1e-10);
332 }
333
334 #[test]
335 fn sig_single_edge() {
336 assert!((sigma_index(&single_edge()).unwrap()).abs() < 1e-10);
337 }
338
339 #[test]
340 fn sig_path3() {
341 assert!((sigma_index(&path3()).unwrap() - 2.0).abs() < 1e-10);
343 }
344
345 #[test]
346 fn sig_path4() {
347 assert!((sigma_index(&path4()).unwrap() - 2.0).abs() < 1e-10);
349 }
350
351 #[test]
352 fn sig_k3() {
353 assert!((sigma_index(&k3()).unwrap()).abs() < 1e-10);
354 }
355
356 #[test]
357 fn sig_k4() {
358 assert!((sigma_index(&k4()).unwrap()).abs() < 1e-10);
359 }
360
361 #[test]
362 fn sig_cycle4() {
363 assert!((sigma_index(&cycle4()).unwrap()).abs() < 1e-10);
364 }
365
366 #[test]
367 fn sig_star5() {
368 assert!((sigma_index(&star5()).unwrap() - 36.0).abs() < 1e-10);
370 }
371
372 #[test]
373 fn sig_paw() {
374 assert!((sigma_index(&paw()).unwrap() - 6.0).abs() < 1e-10);
377 }
378
379 #[test]
380 fn sig_diamond() {
381 assert!((sigma_index(&diamond()).unwrap() - 4.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn sig_zero_iff_regular() {
387 for g in &[k3(), k4(), cycle4(), cycle5()] {
388 assert!((sigma_index(g).unwrap()).abs() < 1e-10);
389 }
390 for g in &[path3(), star5(), paw()] {
391 assert!(sigma_index(g).unwrap() > 1e-10);
392 }
393 }
394
395 #[test]
396 fn sig_geq_alb_squared_over_m() {
397 for g in &[
399 single_edge(),
400 path3(),
401 path4(),
402 k3(),
403 star5(),
404 paw(),
405 diamond(),
406 ] {
407 let sig = sigma_index(g).unwrap();
408 let alb = albertson_index(g).unwrap();
409 let m = g.ecount() as f64;
410 if m > 0.0 {
411 assert!(sig >= alb * alb / m - 1e-8);
412 }
413 }
414 }
415
416 #[test]
419 fn irrt_empty() {
420 let g = Graph::with_vertices(0);
421 assert!((total_irregularity(&g).unwrap()).abs() < 1e-10);
422 }
423
424 #[test]
425 fn irrt_single_vertex() {
426 let g = Graph::with_vertices(1);
427 assert!((total_irregularity(&g).unwrap()).abs() < 1e-10);
428 }
429
430 #[test]
431 fn irrt_single_edge() {
432 assert!((total_irregularity(&single_edge()).unwrap()).abs() < 1e-10);
434 }
435
436 #[test]
437 fn irrt_path3() {
438 assert!((total_irregularity(&path3()).unwrap() - 1.0).abs() < 1e-10);
442 }
443
444 #[test]
445 fn irrt_path4() {
446 assert!((total_irregularity(&path4()).unwrap() - 2.0).abs() < 1e-10);
450 }
451
452 #[test]
453 fn irrt_k3() {
454 assert!((total_irregularity(&k3()).unwrap()).abs() < 1e-10);
455 }
456
457 #[test]
458 fn irrt_k4() {
459 assert!((total_irregularity(&k4()).unwrap()).abs() < 1e-10);
460 }
461
462 #[test]
463 fn irrt_cycle4() {
464 assert!((total_irregularity(&cycle4()).unwrap()).abs() < 1e-10);
465 }
466
467 #[test]
468 fn irrt_star5() {
469 assert!((total_irregularity(&star5()).unwrap() - 6.0).abs() < 1e-10);
473 }
474
475 #[test]
476 fn irrt_paw() {
477 assert!((total_irregularity(&paw()).unwrap() - 3.0).abs() < 1e-10);
481 }
482
483 #[test]
484 fn irrt_diamond() {
485 assert!((total_irregularity(&diamond()).unwrap() - 2.0).abs() < 1e-10);
489 }
490
491 #[test]
492 fn irrt_zero_iff_regular() {
493 for g in &[k3(), k4(), cycle4(), cycle5()] {
494 assert!((total_irregularity(g).unwrap()).abs() < 1e-10);
495 }
496 for g in &[path3(), star5(), paw()] {
497 assert!(total_irregularity(g).unwrap() > 1e-10);
498 }
499 }
500
501 #[test]
502 fn irrt_geq_alb() {
503 for g in &[
506 single_edge(),
507 path3(),
508 path4(),
509 k3(),
510 star5(),
511 paw(),
512 diamond(),
513 ] {
514 let irrt = total_irregularity(g).unwrap();
515 let alb = albertson_index(g).unwrap();
516 assert!(irrt >= alb / 2.0 - 1e-8);
517 }
518 }
519
520 #[test]
521 fn irrt_with_isolated() {
522 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
526 assert!((total_irregularity(&g).unwrap() - 1.0).abs() < 1e-10);
527 }
528
529 #[test]
532 fn dv_empty() {
533 let g = Graph::with_vertices(0);
534 assert!((degree_variance(&g).unwrap()).abs() < 1e-10);
535 }
536
537 #[test]
538 fn dv_single_vertex() {
539 let g = Graph::with_vertices(1);
540 assert!((degree_variance(&g).unwrap()).abs() < 1e-10);
541 }
542
543 #[test]
544 fn dv_no_edges() {
545 let g = Graph::with_vertices(3);
546 assert!((degree_variance(&g).unwrap()).abs() < 1e-10);
547 }
548
549 #[test]
550 fn dv_single_edge() {
551 assert!((degree_variance(&single_edge()).unwrap()).abs() < 1e-10);
553 }
554
555 #[test]
556 fn dv_k3() {
557 assert!((degree_variance(&k3()).unwrap()).abs() < 1e-10);
558 }
559
560 #[test]
561 fn dv_k4() {
562 assert!((degree_variance(&k4()).unwrap()).abs() < 1e-10);
563 }
564
565 #[test]
566 fn dv_cycle4() {
567 assert!((degree_variance(&cycle4()).unwrap()).abs() < 1e-10);
568 }
569
570 #[test]
571 fn dv_path3() {
572 assert!((degree_variance(&path3()).unwrap() - 2.0 / 9.0).abs() < 1e-10);
576 }
577
578 #[test]
579 fn dv_star5() {
580 assert!((degree_variance(&star5()).unwrap() - 1.44).abs() < 1e-10);
583 }
584
585 #[test]
586 fn dv_paw() {
587 assert!((degree_variance(&paw()).unwrap() - 0.5).abs() < 1e-10);
590 }
591
592 #[test]
593 fn dv_zero_iff_regular() {
594 for g in &[k3(), k4(), cycle4(), cycle5()] {
595 assert!((degree_variance(g).unwrap()).abs() < 1e-10);
596 }
597 for g in &[path3(), star5(), paw()] {
598 assert!(degree_variance(g).unwrap() > 1e-10);
599 }
600 }
601
602 #[test]
603 fn dv_with_isolated() {
604 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
608 assert!((degree_variance(&g).unwrap() - 2.0 / 9.0).abs() < 1e-10);
609 }
610
611 #[test]
614 fn all_nonneg_for_connected() {
615 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
616 assert!(albertson_index(g).unwrap() >= -1e-10);
617 assert!(sigma_index(g).unwrap() >= -1e-10);
618 assert!(total_irregularity(g).unwrap() >= -1e-10);
619 assert!(degree_variance(g).unwrap() >= -1e-10);
620 }
621 }
622
623 #[test]
624 fn all_zero_for_regular() {
625 for g in &[k3(), k4(), cycle4(), cycle5()] {
626 assert!((albertson_index(g).unwrap()).abs() < 1e-10);
627 assert!((sigma_index(g).unwrap()).abs() < 1e-10);
628 assert!((total_irregularity(g).unwrap()).abs() < 1e-10);
629 assert!((degree_variance(g).unwrap()).abs() < 1e-10);
630 }
631 }
632
633 #[test]
634 fn sigma_geq_albertson() {
635 for g in &[
641 single_edge(),
642 path3(),
643 k3(),
644 k4(),
645 star5(),
646 paw(),
647 diamond(),
648 ] {
649 let sig = sigma_index(g).unwrap();
650 let alb = albertson_index(g).unwrap();
651 assert!(sig >= alb - 1e-8);
652 }
653 }
654}