rust_igraph/algorithms/properties/
algebraic_connectivity.rs1#![allow(
25 clippy::cast_possible_truncation,
26 clippy::cast_precision_loss,
27 clippy::many_single_char_names,
28 clippy::needless_range_loop,
29 clippy::similar_names,
30 clippy::too_many_lines
31)]
32
33use crate::core::{Graph, IgraphError, IgraphResult};
34
35fn dense_laplacian(graph: &Graph) -> Vec<f64> {
37 let n = graph.vcount() as usize;
38 let mut lap = vec![0.0_f64; n * n];
39 for (u, v) in graph.edges() {
40 let ui = u as usize;
41 let vi = v as usize;
42 lap[ui * n + vi] -= 1.0;
43 lap[vi * n + ui] -= 1.0;
44 lap[ui * n + ui] += 1.0;
45 lap[vi * n + vi] += 1.0;
46 }
47 lap
48}
49
50fn jacobi_eigen_ascending(mat: &mut [f64], n: usize) -> (Vec<f64>, Vec<Vec<f64>>) {
53 if n == 0 {
54 return (Vec::new(), Vec::new());
55 }
56 if n == 1 {
57 return (vec![mat[0]], vec![vec![1.0]]);
58 }
59
60 let mut v = vec![0.0_f64; n * n];
61 for i in 0..n {
62 v[i * n + i] = 1.0;
63 }
64
65 let max_sweeps = 100;
66 for _ in 0..max_sweeps {
67 let mut max_off = 0.0_f64;
68 let mut p = 0;
69 let mut q = 1;
70 for i in 0..n {
71 for j in (i + 1)..n {
72 let val = mat[i * n + j].abs();
73 if val > max_off {
74 max_off = val;
75 p = i;
76 q = j;
77 }
78 }
79 }
80
81 if max_off < 1e-14 {
82 break;
83 }
84
85 let app = mat[p * n + p];
86 let aqq = mat[q * n + q];
87 let apq = mat[p * n + q];
88
89 let (cos, sin) = if (app - aqq).abs() < 1e-300 {
90 let s = std::f64::consts::FRAC_1_SQRT_2;
91 (s, s)
92 } else {
93 let tau = (aqq - app) / (2.0 * apq);
94 let t = if tau >= 0.0 {
95 1.0 / (tau + (1.0 + tau * tau).sqrt())
96 } else {
97 -1.0 / (-tau + (1.0 + tau * tau).sqrt())
98 };
99 let c = 1.0 / (1.0 + t * t).sqrt();
100 (c, t * c)
101 };
102
103 for i in 0..n {
104 if i == p || i == q {
105 continue;
106 }
107 let aip = mat[i * n + p];
108 let aiq = mat[i * n + q];
109 mat[i * n + p] = cos * aip - sin * aiq;
110 mat[p * n + i] = mat[i * n + p];
111 mat[i * n + q] = sin * aip + cos * aiq;
112 mat[q * n + i] = mat[i * n + q];
113 }
114
115 let new_pp = cos * cos * app - 2.0 * sin * cos * apq + sin * sin * aqq;
116 let new_qq = sin * sin * app + 2.0 * sin * cos * apq + cos * cos * aqq;
117 mat[p * n + p] = new_pp;
118 mat[q * n + q] = new_qq;
119 mat[p * n + q] = 0.0;
120 mat[q * n + p] = 0.0;
121
122 for i in 0..n {
123 let vip = v[i * n + p];
124 let viq = v[i * n + q];
125 v[i * n + p] = cos * vip - sin * viq;
126 v[i * n + q] = sin * vip + cos * viq;
127 }
128 }
129
130 let eigenvalues: Vec<f64> = (0..n).map(|i| mat[i * n + i]).collect();
131 let mut indices: Vec<usize> = (0..n).collect();
132 indices.sort_by(|&a, &b| {
133 eigenvalues[a]
134 .partial_cmp(&eigenvalues[b])
135 .unwrap_or(std::cmp::Ordering::Equal)
136 });
137
138 let sorted_vals: Vec<f64> = indices.iter().map(|&i| eigenvalues[i]).collect();
139 let sorted_vecs: Vec<Vec<f64>> = indices
140 .iter()
141 .map(|&idx| {
142 let mut col = vec![0.0_f64; n];
143 for i in 0..n {
144 col[i] = v[i * n + idx];
145 }
146 col
147 })
148 .collect();
149
150 (sorted_vals, sorted_vecs)
151}
152
153fn laplacian_spectrum_internal(graph: &Graph) -> (Vec<f64>, Vec<Vec<f64>>) {
155 let n = graph.vcount() as usize;
156 if n == 0 {
157 return (Vec::new(), Vec::new());
158 }
159 let mut lap = dense_laplacian(graph);
160 jacobi_eigen_ascending(&mut lap, n)
161}
162
163pub fn algebraic_connectivity(graph: &Graph) -> IgraphResult<f64> {
181 if graph.is_directed() {
182 return Err(IgraphError::InvalidArgument(
183 "algebraic_connectivity is defined for undirected graphs only".into(),
184 ));
185 }
186 let n = graph.vcount() as usize;
187 if n <= 1 {
188 return Ok(0.0);
189 }
190
191 let (vals, _) = laplacian_spectrum_internal(graph);
192 Ok(vals[1].max(0.0))
193}
194
195pub fn fiedler_vector(graph: &Graph) -> IgraphResult<Vec<f64>> {
214 if graph.is_directed() {
215 return Err(IgraphError::InvalidArgument(
216 "fiedler_vector is defined for undirected graphs only".into(),
217 ));
218 }
219 let n = graph.vcount() as usize;
220 if n <= 1 {
221 return Ok(vec![0.0; n]);
222 }
223
224 let (_, vecs) = laplacian_spectrum_internal(graph);
225 Ok(vecs[1].clone())
226}
227
228pub fn spectral_bisection(graph: &Graph) -> IgraphResult<Vec<u32>> {
250 let fv = fiedler_vector(graph)?;
251 Ok(fv.iter().map(|&v| u32::from(v < 0.0)).collect())
252}
253
254pub fn laplacian_spectrum(graph: &Graph) -> IgraphResult<Vec<f64>> {
271 if graph.is_directed() {
272 return Err(IgraphError::InvalidArgument(
273 "laplacian_spectrum is defined for undirected graphs only".into(),
274 ));
275 }
276 let (vals, _) = laplacian_spectrum_internal(graph);
277 Ok(vals)
278}
279
280pub fn spanning_tree_count(graph: &Graph) -> IgraphResult<f64> {
306 if graph.is_directed() {
307 return Err(IgraphError::InvalidArgument(
308 "spanning_tree_count is defined for undirected graphs only".into(),
309 ));
310 }
311 let n = graph.vcount() as usize;
312 if n <= 1 {
313 return Ok(if n == 1 { 1.0 } else { 0.0 });
314 }
315
316 let (vals, _) = laplacian_spectrum_internal(graph);
317
318 let eps = 1e-10;
319 let mut product = 1.0_f64;
320 let mut nonzero_count = 0_usize;
321
322 for &lam in &vals[1..] {
323 if lam.abs() > eps {
324 product *= lam;
325 nonzero_count += 1;
326 }
327 }
328
329 if nonzero_count < n - 1 {
330 return Ok(0.0);
331 }
332
333 Ok(product / n as f64)
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 fn path3() -> Graph {
341 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
342 }
343
344 fn path4() -> Graph {
345 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
346 }
347
348 fn k3() -> Graph {
349 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
350 }
351
352 fn k4() -> Graph {
353 Graph::from_edges(
354 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
355 false,
356 Some(4),
357 )
358 .unwrap()
359 }
360
361 fn cycle4() -> Graph {
362 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
363 }
364
365 fn star4() -> Graph {
366 Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, Some(4)).unwrap()
367 }
368
369 #[test]
372 fn ac_single() {
373 let g = Graph::with_vertices(1);
374 let a = algebraic_connectivity(&g).unwrap();
375 assert!(a.abs() < 1e-10);
376 }
377
378 #[test]
379 fn ac_disconnected() {
380 let g = Graph::with_vertices(3);
381 let a = algebraic_connectivity(&g).unwrap();
382 assert!(a.abs() < 0.01);
383 }
384
385 #[test]
386 fn ac_k3() {
387 let g = k3();
388 let a = algebraic_connectivity(&g).unwrap();
389 assert!((a - 3.0).abs() < 0.1);
391 }
392
393 #[test]
394 fn ac_k4() {
395 let g = k4();
396 let a = algebraic_connectivity(&g).unwrap();
397 assert!((a - 4.0).abs() < 0.1);
399 }
400
401 #[test]
402 fn ac_path() {
403 let g = path4();
404 let a = algebraic_connectivity(&g).unwrap();
405 assert!((a - (2.0 - std::f64::consts::SQRT_2)).abs() < 0.1);
407 }
408
409 #[test]
410 fn ac_cycle() {
411 let g = cycle4();
412 let a = algebraic_connectivity(&g).unwrap();
413 assert!((a - 2.0).abs() < 0.1);
415 }
416
417 #[test]
418 fn ac_nonneg() {
419 let g = star4();
420 let a = algebraic_connectivity(&g).unwrap();
421 assert!(a >= -1e-10);
422 }
423
424 #[test]
425 fn ac_directed_error() {
426 let g = Graph::from_edges(&[(0, 1)], true, Some(2)).unwrap();
427 assert!(algebraic_connectivity(&g).is_err());
428 }
429
430 #[test]
433 fn fv_length() {
434 let g = path4();
435 let fv = fiedler_vector(&g).unwrap();
436 assert_eq!(fv.len(), 4);
437 }
438
439 #[test]
440 fn fv_orthogonal_to_ones() {
441 let g = k3();
443 let fv = fiedler_vector(&g).unwrap();
444 let dot: f64 = fv.iter().sum();
445 assert!(dot.abs() < 0.1);
446 }
447
448 #[test]
449 fn fv_single() {
450 let g = Graph::with_vertices(1);
451 let fv = fiedler_vector(&g).unwrap();
452 assert_eq!(fv.len(), 1);
453 assert!(fv[0].abs() < 1e-10);
454 }
455
456 #[test]
459 fn sb_two_parts() {
460 let g = path4();
461 let parts = spectral_bisection(&g).unwrap();
462 assert_eq!(parts.len(), 4);
463 assert!(parts.contains(&0));
464 assert!(parts.contains(&1));
465 }
466
467 #[test]
468 fn sb_values_01() {
469 let g = k3();
470 let parts = spectral_bisection(&g).unwrap();
471 for &p in &parts {
472 assert!(p == 0 || p == 1);
473 }
474 }
475
476 #[test]
479 fn ls_empty() {
480 let g = Graph::with_vertices(0);
481 let spec = laplacian_spectrum(&g).unwrap();
482 assert!(spec.is_empty());
483 }
484
485 #[test]
486 fn ls_k3() {
487 let g = k3();
488 let spec = laplacian_spectrum(&g).unwrap();
489 assert_eq!(spec.len(), 3);
490 assert!(spec[0].abs() < 0.01); assert!((spec[1] - 3.0).abs() < 0.1);
492 assert!((spec[2] - 3.0).abs() < 0.1);
493 }
494
495 #[test]
496 fn ls_ascending() {
497 let g = star4();
498 let spec = laplacian_spectrum(&g).unwrap();
499 for i in 1..spec.len() {
500 assert!(spec[i] >= spec[i - 1] - 1e-10);
501 }
502 }
503
504 #[test]
505 fn ls_first_is_zero() {
506 let g = cycle4();
507 let spec = laplacian_spectrum(&g).unwrap();
508 assert!(spec[0].abs() < 0.01);
509 }
510
511 #[test]
512 fn ls_disconnected_has_two_zeros() {
513 let g = Graph::with_vertices(2);
515 let spec = laplacian_spectrum(&g).unwrap();
516 assert!(spec[0].abs() < 0.01);
517 assert!(spec[1].abs() < 0.01);
518 }
519
520 #[test]
523 fn stc_single() {
524 let g = Graph::with_vertices(1);
525 let c = spanning_tree_count(&g).unwrap();
526 assert!((c - 1.0).abs() < 0.1);
527 }
528
529 #[test]
530 fn stc_edge() {
531 let g = Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap();
532 let c = spanning_tree_count(&g).unwrap();
533 assert!((c - 1.0).abs() < 0.1);
534 }
535
536 #[test]
537 fn stc_k3() {
538 let g = k3();
539 let c = spanning_tree_count(&g).unwrap();
540 assert!((c - 3.0).abs() < 0.5);
541 }
542
543 #[test]
544 fn stc_k4() {
545 let g = k4();
547 let c = spanning_tree_count(&g).unwrap();
548 assert!((c - 16.0).abs() < 1.0);
549 }
550
551 #[test]
552 fn stc_cycle4() {
553 let g = cycle4();
555 let c = spanning_tree_count(&g).unwrap();
556 assert!((c - 4.0).abs() < 0.5);
557 }
558
559 #[test]
560 fn stc_path() {
561 let g = path3();
563 let c = spanning_tree_count(&g).unwrap();
564 assert!((c - 1.0).abs() < 0.1);
565 }
566
567 #[test]
568 fn stc_disconnected() {
569 let g = Graph::with_vertices(3);
570 let c = spanning_tree_count(&g).unwrap();
571 assert!(c.abs() < 0.1);
572 }
573
574 #[test]
575 fn stc_directed_error() {
576 let g = Graph::from_edges(&[(0, 1)], true, Some(2)).unwrap();
577 assert!(spanning_tree_count(&g).is_err());
578 }
579
580 #[test]
583 fn ac_equals_lambda2() {
584 let g = star4();
585 let a = algebraic_connectivity(&g).unwrap();
586 let spec = laplacian_spectrum(&g).unwrap();
587 assert!((a - spec[1]).abs() < 0.01);
588 }
589
590 #[test]
591 fn complete_graph_ac_is_n() {
592 for n in 3_u32..=5 {
594 let mut edges = Vec::new();
595 for u in 0..n {
596 for v in (u + 1)..n {
597 edges.push((u, v));
598 }
599 }
600 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
601 let a = algebraic_connectivity(&g).unwrap();
602 assert!(
603 (a - f64::from(n)).abs() < 0.5,
604 "K_{n}: a(G) = {a}, expected {n}"
605 );
606 }
607 }
608}