rust_igraph/algorithms/eigen/
adjacency.rs1use super::symmetric::{EigenDecomposition, EigenWhich, eigen_matrix_symmetric};
21use crate::core::error::{IgraphError, IgraphResult};
22use crate::core::graph::Graph;
23
24pub fn eigen_adjacency(
56 graph: &Graph,
57 nev: usize,
58 which: EigenWhich,
59) -> IgraphResult<EigenDecomposition> {
60 let n = graph.vcount() as usize;
61 if n == 0 {
62 return Err(IgraphError::InvalidArgument(
63 "eigen_adjacency: graph has zero vertices".into(),
64 ));
65 }
66
67 let directed = graph.is_directed();
68 let ecount = graph.ecount();
69
70 let ecount_u32 = u32::try_from(ecount).unwrap_or(u32::MAX);
72 let mut edges: Vec<(usize, usize)> = Vec::with_capacity(ecount);
73 for eid in 0..ecount_u32 {
74 if let Ok((u, v)) = graph.edge(eid) {
75 edges.push((u as usize, v as usize));
76 }
77 }
78
79 let matvec = |x: &[f64], y: &mut [f64]| {
80 for val in y.iter_mut() {
81 *val = 0.0;
82 }
83 for &(u, v) in &edges {
84 if directed {
85 y[u] += x[v];
87 y[v] += x[u];
88 } else {
89 y[u] += x[v];
91 y[v] += x[u];
92 }
93 }
94 if directed {
95 for val in y.iter_mut() {
96 *val *= 0.5;
97 }
98 }
99 };
100
101 eigen_matrix_symmetric(n, matvec, nev, which)
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::core::graph::Graph;
108
109 #[test]
110 fn k3_largest_eigenvalue() {
111 let mut g = Graph::with_vertices(3);
112 g.add_edge(0, 1).unwrap();
113 g.add_edge(0, 2).unwrap();
114 g.add_edge(1, 2).unwrap();
115
116 let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
117 assert_eq!(result.eigenvalues.len(), 1);
118 assert!(
119 (result.eigenvalues[0] - 2.0).abs() < 1e-4,
120 "K3 largest eigenvalue should be 2.0, got {}",
121 result.eigenvalues[0]
122 );
123 }
124
125 #[test]
126 fn path_graph_eigenvalues() {
127 let mut g = Graph::with_vertices(3);
129 g.add_edge(0, 1).unwrap();
130 g.add_edge(1, 2).unwrap();
131
132 let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
133 let expected = std::f64::consts::SQRT_2;
134 assert!(
135 (result.eigenvalues[0] - expected).abs() < 1e-4,
136 "P3 largest eigenvalue should be sqrt(2)={expected}, got {}",
137 result.eigenvalues[0]
138 );
139 }
140
141 #[test]
142 fn isolated_vertices() {
143 let g = Graph::with_vertices(5);
144 let result = eigen_adjacency(&g, 3, EigenWhich::LargestAlgebraic).unwrap();
145 for &ev in &result.eigenvalues {
146 assert!(
147 ev.abs() < 1e-6,
148 "isolated graph eigenvalue should be 0.0, got {ev}"
149 );
150 }
151 }
152
153 #[test]
154 fn empty_graph_error() {
155 let g = Graph::with_vertices(0);
156 assert!(eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).is_err());
157 }
158
159 #[test]
160 fn star_graph_eigenvalues() {
161 let mut g = Graph::with_vertices(4);
163 g.add_edge(0, 1).unwrap();
164 g.add_edge(0, 2).unwrap();
165 g.add_edge(0, 3).unwrap();
166
167 let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
168 let expected = 3.0_f64.sqrt();
169 assert!(
170 (result.eigenvalues[0] - expected).abs() < 1e-4,
171 "S4 largest eigenvalue should be sqrt(3)={expected}, got {}",
172 result.eigenvalues[0]
173 );
174 }
175
176 #[test]
177 fn cycle_graph_eigenvalues() {
178 let mut g = Graph::with_vertices(4);
180 g.add_edge(0, 1).unwrap();
181 g.add_edge(1, 2).unwrap();
182 g.add_edge(2, 3).unwrap();
183 g.add_edge(3, 0).unwrap();
184
185 let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
186 assert!(
187 (result.eigenvalues[0] - 2.0).abs() < 1e-4,
188 "C4 largest eigenvalue should be 2.0, got {}",
189 result.eigenvalues[0]
190 );
191 }
192}