1use crate::graph::traits::{GraphBase, GraphQuery};
7use crate::graph::Graph;
8use crate::node::NodeIndex;
9use nalgebra::{DMatrix, DVector};
10
11pub struct AdjacencyMatrix {
13 matrix: DMatrix<f64>,
14 node_indices: Vec<NodeIndex>,
15}
16
17impl AdjacencyMatrix {
18 pub fn from_graph<T, E>(graph: &Graph<T, E>) -> Self
20 where
21 E: Clone + Into<f64>,
22 {
23 let n = graph.node_count();
24 let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
25 let index_to_pos: std::collections::HashMap<usize, usize> = node_indices
26 .iter()
27 .enumerate()
28 .map(|(i, ni)| (ni.index(), i))
29 .collect();
30
31 let mut matrix = DMatrix::zeros(n, n);
32
33 for edge in graph.edges() {
34 if let Ok((src, tgt)) = graph.edge_endpoints(edge.index()) {
35 if let (Some(&i), Some(&j)) = (
36 index_to_pos.get(&src.index()),
37 index_to_pos.get(&tgt.index()),
38 ) {
39 let weight: f64 = edge.data().clone().into();
40 matrix[(i, j)] = weight;
41 }
42 }
43 }
44
45 Self {
46 matrix,
47 node_indices,
48 }
49 }
50
51 pub fn as_matrix(&self) -> &DMatrix<f64> {
53 &self.matrix
54 }
55
56 pub fn node_indices(&self) -> &[NodeIndex] {
58 &self.node_indices
59 }
60
61 pub fn size(&self) -> usize {
63 self.matrix.nrows()
64 }
65
66 pub fn node_position(&self, node: NodeIndex) -> Option<usize> {
68 self.node_indices
69 .iter()
70 .position(|&ni| ni.index() == node.index())
71 }
72
73 pub fn node_from_position(&self, pos: usize) -> Option<NodeIndex> {
75 self.node_indices.get(pos).copied()
76 }
77}
78
79pub struct LaplacianMatrix {
83 matrix: DMatrix<f64>,
84 node_indices: Vec<NodeIndex>,
85}
86
87impl LaplacianMatrix {
88 pub fn from_graph<T>(graph: &Graph<T, f64>) -> Self {
90 let n = graph.node_count();
91 let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
92 let index_to_pos: std::collections::HashMap<usize, usize> = node_indices
93 .iter()
94 .enumerate()
95 .map(|(i, ni)| (ni.index(), i))
96 .collect();
97
98 let mut matrix = DMatrix::zeros(n, n);
99
100 for edge in graph.edges() {
102 if let Ok((src, tgt)) = graph.edge_endpoints(edge.index()) {
103 if let (Some(&i), Some(&j)) = (
104 index_to_pos.get(&src.index()),
105 index_to_pos.get(&tgt.index()),
106 ) {
107 let weight = *edge.data();
108 matrix[(i, j)] = -weight;
110 matrix[(i, i)] += weight;
112 matrix[(j, j)] += weight;
113 }
114 }
115 }
116
117 Self {
118 matrix,
119 node_indices,
120 }
121 }
122
123 pub fn as_matrix(&self) -> &DMatrix<f64> {
125 &self.matrix
126 }
127
128 pub fn node_indices(&self) -> &[NodeIndex] {
130 &self.node_indices
131 }
132}
133
134pub fn fiedler_vector<T>(graph: &Graph<T, f64>) -> Option<DVector<f64>>
138where
139 T: Clone,
140{
141 let laplacian = LaplacianMatrix::from_graph(graph);
142 let matrix = laplacian.as_matrix();
143
144 let n = matrix.nrows();
147 if n < 2 {
148 return None;
149 }
150
151 let mut v = DVector::from_fn(n, |i, _| (i as f64 * 0.1).sin());
153 v.normalize_mut();
154
155 let sum: f64 = v.sum();
157 let ones = DVector::from_element(n, 1.0);
158 v -= &ones * (sum / n as f64);
159 v.normalize_mut();
160
161 for _ in 0..100 {
163 let mut w = matrix * &v;
164
165 let sum: f64 = w.sum();
167 w -= &ones * (sum / n as f64);
168
169 let norm = w.norm();
170 if norm < 1e-10 {
171 break;
172 }
173 v = w / norm;
174 }
175
176 Some(v)
177}
178
179pub fn spectral_radius<T>(graph: &Graph<T, f64>) -> f64
181where
182 T: Clone,
183{
184 let adjacency = AdjacencyMatrix::from_graph(graph);
185 let matrix = adjacency.as_matrix();
186
187 let n = matrix.nrows();
189 if n == 0 {
190 return 0.0;
191 }
192
193 let mut v = DVector::from_element(n, 1.0);
194 let mut eigenvalue = 0.0;
195
196 for _ in 0..100 {
197 let w = matrix * &v;
198 let new_eigenvalue = w.norm();
199 if (new_eigenvalue - eigenvalue).abs() < 1e-10 {
200 break;
201 }
202 eigenvalue = new_eigenvalue;
203 v = w / eigenvalue;
204 }
205
206 eigenvalue
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::graph::builders::GraphBuilder;
213
214 #[test]
215 fn test_adjacency_matrix() {
216 let graph = GraphBuilder::directed()
217 .with_nodes(vec!["A", "B", "C"])
218 .with_edges(vec![(0, 1, 1.0), (1, 2, 2.0), (2, 0, 3.0)])
219 .build()
220 .unwrap();
221
222 let adj = AdjacencyMatrix::from_graph(&graph);
223 assert_eq!(adj.size(), 3);
224
225 assert_eq!(adj.matrix[(0, 1)], 1.0);
227 assert_eq!(adj.matrix[(1, 2)], 2.0);
228 assert_eq!(adj.matrix[(2, 0)], 3.0);
229 }
230
231 #[test]
232 fn test_laplacian_matrix() {
233 let graph = GraphBuilder::directed()
235 .with_nodes(vec!["A", "B", "C"])
236 .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
237 .build()
238 .unwrap();
239
240 let lap = LaplacianMatrix::from_graph(&graph);
241
242 assert_eq!(lap.matrix[(0, 0)], 1.0); assert_eq!(lap.matrix[(1, 1)], 2.0); assert_eq!(lap.matrix[(2, 2)], 1.0); assert_eq!(lap.matrix[(0, 1)], -1.0);
249 assert_eq!(lap.matrix[(1, 2)], -1.0);
250 }
251
252 #[test]
253 fn test_spectral_radius() {
254 let graph = GraphBuilder::directed()
256 .with_nodes(vec!["A", "B", "C"])
257 .with_edges(vec![
258 (0, 1, 1.0),
259 (1, 0, 1.0), (1, 2, 1.0),
261 (2, 1, 1.0), (2, 0, 1.0),
263 (0, 2, 1.0), ])
265 .build()
266 .unwrap();
267
268 let radius = spectral_radius(&graph);
269 assert!((radius - 2.0).abs() < 0.5);
271 }
272}